Book cipher

A Book cipher is a very unique method of a encipher. Here's how's it done:

  • You have a book / a document or a article (something full of text, the more pages of text the better).
  • You have a message to convey (a secret, of some sort)
  • You simply-put read trough the text and the secret message (may come from input, or reading it from 2 separate files) - split it into words (so, separate spaces, commas and [dots]) so, ',' , '.' and spaces is the only requirement, how is not really that hugely important and you keep the count of how many words there are in the text, now input the secret message (note, the secret's words must be in the text, so if you secret was "My Secret" then, the words "My" and "Secrets" must both exist in the text.) and output the position of the inputted secret message. (E.g if the word "My" was nearly at the first page, maybe the 20th Word in the text, the program should print out '20'. Same with the word "Secrets", if that maybe were later, (let say the 93th word in the text) then your program should print out '93'.

Note the data type of input and output:

output numbers: Integers.

Input numbers: Integers

( Excluding if the secret actually contains a number, then it does not need to be treated as a int. Can be a plus if it does but is not necessary.)

Mini Example:

document.txt - file that contains text.

Secret: "My Secret"

(in the text, this is the 20th and the 93th word) (Note, this is only a made up secret, it is not from a real file, or a real input) a more better example is below.

Program input:

You enter "My Secret"

Program output:



And, again - you enter those numbers(**Integers**):

20 93

Program outputs:



this is just to show how input and outputs are related to each other.

For reference (if needed) You have a Python3 implementation available at my GitHub page, to see a book cipher in action here: GitHub - Book cipher in Py3

  • Why is this challenge interesting?

I personally think this is a educational (and interesting) challenge ( one might also exercise, because of how simple it might seem to make, but really took myself - literally years to even know how to implement this correctly)

Interesting article to get some background of what Cicada3301 is (not my site) - https://www.clevcode.org/cicada-3301/

Wikipedia: Cicada3301

I created this challenge both to, see other peoples methods of solving this (you are free to use any programming language!) and also - how long it would take others (For me, really I think it took more than 4 years actually - even in Python3. It looks simple but, for me - really not)

  • A motivating fact: There are still so little info (especially on example codes) on the internet(at least by the time writing this challenge) about just, book cipher implementations


I would highly suggest making dedicated functions for this challenge

  • (instead of writing all code in the main() function - but it's totally fine to have it all in main!)


Here's how the program should read, process, and output the result:

First, take the text (the book/document, with the lots of text, (not the secret)) and:

Note: The text can either be entered or read from a file. You choose this.

  1. read it (From a file, or enter it as input)
  2. split it into words (by, I.e detecting '.', spaces(' '), and commas ',') (Or if you already have split the input & are ready to move on to step 3, do that :) )
  3. count the number of words.

Repeat this process with the Secret input part.

So, the input secret part should be:

  • read it (from, again a file or enter it as input)
  • split it (i.e if your input was "My Secret" - split it into words like so: "My" "Secret")

My Python3 implementation only separate spaces.

The Key sequence - this is the nth words your text contains, e.g the 93th word in above example "Secrets".

The winner will be chosen by how short the code is. (So, the shortest code = win)

Example Input and Output

example file used 'document1.txt'in this section is available at the GitHub page. as well as the Python3 file used in the example below.

The output of your program should match the output of the Python3 program.


python3 bookcipher.py

input text: a house with a Bob inside


you entered these words: ['a', 'house', 'with', 'a', 'Bob', 'inside']







Input again: (decrypting)

input key-sequence sep. With spaces: 2 3 5 2 0 30







  • 1
    \$\begingroup\$ I changed the tag code-challenge to code-golf, since you say the shortest code wins. \$\endgroup\$
    – xnor
    Feb 16, 2021 at 21:21
  • 9
    \$\begingroup\$ Welcome to code golf! You seem to have put a lot of effort into writing this, but you have varied from our sites norms in quite a few places. For now, I would recommend moving this to the sandbox, since I think addressing each of them will take a long time. Good luck. \$\endgroup\$ Feb 16, 2021 at 21:22
  • 3
    \$\begingroup\$ @WilliamMartens Don’t worry about it, we’re a nice enough community that we’ll offer feedback even without people asking for it ;) \$\endgroup\$ Feb 16, 2021 at 22:12
  • 2
    \$\begingroup\$ So the question from Jonathan is one of the unclear parts of what you initially wrote. However, in general this seems like a fine idea. Some of the other things included: what if a number appears in the source text, what about non-ASCII characters, and is the extra I/O text you have necessary? This list probably isn't complete, but now that you have some answers you can't delete the question, so try your best to address them as we bring them up. For now, I think I will vote to close, but don't worry - if it gets closed, we'll reopen it when it is ready. \$\endgroup\$ Feb 17, 2021 at 0:05
  • 3
    \$\begingroup\$ I'm also voting to close since answers are already making different assumptions on what the challenge allows. I like the challenge though, I hope things get clarified enough so I can post my solution! In addition to the doubts stated previously by other people I would add: do we need to split both the book and the input ourselves or can we take a list of words for one or both of them? When splitting, the challenge seems to require splitting on ,,.,` `, but the reference program uses python's string.split(), which splits on whitespace... which one should we do? \$\endgroup\$
    – Leo
    Feb 17, 2021 at 2:44

5 Answers 5


R, 112 bytes


Try it online!

Ungolfed code

book_cypher=function(x,t){          # define book_cyper function with args:
                                    # x=either the message (if it's text) or the code (if it's a vector of integers)
                                    # t=the text of the book
  w=function(x){                    # we need to split-up strings into words in 3 places, so we 
                                    # define the helper function 'w' to do this:
    el(                             #   get the first 'el'ement of
       strsplit(x,"[^a-zA-Z]"))     #   splitting the argument on any character except a-z and A-Z
                                    #   (note that with the clarified challenge rules we can also do this with [., ]
                                    #   to split only on dots, commas & spaces)
  }                                 #
                                    # now the main function body:       
  if(is.integer(x)){                # if the agument is of type 'integer' we need to decode:
    Reduce(paste,w(t)[x])           #   split the text using helper function 'w', 
                                    #   and select the elements at indices given by the argument
                                    #   ('Reduce(paste())' is just a golfy way of joining the elements
                                    #   back together into a space-separated string)
  } else {                          # otherwise, we need to encode:
    match(w(x),w(t)))               #   split the message and the text using helper function 'w',
                                    #   and use 'match' to find the indices of the elements of the split text
                                    #   that match each element of the split message.
  • \$\begingroup\$ @Domic van Essen Really good! My gosh.. I did not know there were this fast people.. My gosh this took me YEARS to even know how to do.. I'm deeply impressed! \$\endgroup\$ Feb 16, 2021 at 21:41
  • \$\begingroup\$ @Domic Van Essen Do you think you could make the same code, but readable(not, 1 line - like, normal code?) Really I am impressed! I just thought, it would be so so useful to learn this and I think others would appreciate it as well (the challenge (this one) is currently over (and, since it is not accepting answers) I just thought, if you have time - to do that :) \$\endgroup\$ Feb 19, 2021 at 20:08
  • \$\begingroup\$ @WilliamMartens - No problem, here you are. \$\endgroup\$ Feb 19, 2021 at 21:19
  • \$\begingroup\$ @WilliamMartens - By the way: (1) This was a really nice challenge idea, so I think you should try to make the suggested clarifications to it so that it'll get re-opened again. Don't be disheartened that it's been closed for now: if you fix the points that were raised in the comments, it'll get opened up again and I'm sure you'll get more answers. (2) I don't think you should mark this answer as 'accepted': both because it could discourage other answers (once re-opened), and also because the challenge clearly stated 'shortest code wins', and it isn't the shortest! \$\endgroup\$ Feb 19, 2021 at 21:23
  • \$\begingroup\$ @Domic Of course I will re-create it, and improve it! Thanks a lot for explaining! What should I do with the accepted answ? Un accept it..? or - make a edit to the post; explaining ? thanks again; You're all (even you who haven't yet posted your solutions) are all great! \$\endgroup\$ Feb 26, 2021 at 7:51

Jelly, 36 (14?) bytes

36 bytes if we must write a program/function which encodes if given a string and decodes if given a list of integers:

“. , ”yḲ¹Ƈ

A full program accepting two arguments:

  • the message string or list of integers; and
  • the book text.

which prints either a list of integers (when given a message string) or the decrypted message (if given a list of integers).

Try it online!

14 bytes if we only need to write an encoder:

“. , ”yḲ¹Ƈ)iⱮ/

A dyadic Link accepting a list [message, book_text] which yields a list of integers.

Try it online!


Husk, 32 bytes

wm(?c' √

Try it online!

If input is a string of letters/words: encodes to string of integers:
input: "this is my secret message"+book, output: "1 5 7 12 9"

If input is a string of integers: decodes to string of words:
input: "1 5 7 12 9"+book, output "this is my secret message" (try it)

Encoder (15 bytes) and Decoder (15 bytes) would total 30 bytes as separate programs (with 9 bytes of shared code).

  • 1
    \$\begingroup\$ Nice. I'm not sure we may assume the secret message would never be composed of words that only contain digits (which would, unfortunately, trigger your decode logic), let's see what the clarified specification says. \$\endgroup\$ Feb 17, 2021 at 19:47
  • 1
    \$\begingroup\$ @JonathanAllan - Yes, I realised that this was a limitation, although if the secret message can be only digits it'll be difficult to know whether it's already encoded or not. Unfortunately, Husk is strongly-typed, so the option of selecting encode/decode based on the type of input (text string vs list of integers) isn't possible at all (I think): the extra bytes of the all-in-one program compared to the separate encoder/decoder ones almost all come from converting numeric input/output into strings of integers... \$\endgroup\$ Feb 17, 2021 at 20:45
  • 1
    \$\begingroup\$ Husk does have nondeterministic typing so it might be possible to construct a function which works with either [TChr] or [TNum] as one of the two arguments, although I couldn't see any obvious ways to employ the overloaded commands to do so. (This is effectively what I did in my Jelly submission where ⁸ŒṘ€FḊm3$⁼¤ is getting a string representation of each element in the list of integers or characters, flattening the result, removing the first element, taking every third element and checking if that is equal to the original list; but similar things look much tougher to implement in Husk!) \$\endgroup\$ Feb 17, 2021 at 22:35
  • 2
    \$\begingroup\$ @JonathanAllan I get your idea, but I also can't see how the Husk 'inference' would be able to cope with this... but I'll see if any other Huskers in the chat room have an idea if it can be done... \$\endgroup\$ Feb 17, 2021 at 22:42

J, 23 bytes

(i.~cut@rplc&', . ')~;:

Try it online!

This takes the document text as the left argument and the string of words as the right argument.

The main verb:

  • ;: Chops the secret string into boxed words
  • rplc&', . ' Replaces commas and periods with space.
  • cut Cuts the remaining text on spaces into a boxed list of words.
  • i.~ Finds the index of each input word within that list.
  • \$\begingroup\$ This is extremely, Impressive... I did not know there were more than this but.. My gosh.. I am registered at the right place after all! :o this was something else, really I am impressed, @Jonah! \$\endgroup\$ Feb 16, 2021 at 22:45
  • 1
    \$\begingroup\$ @Jonah there is no requirement to remove line feeds so I think you may assume there are none or that they are part of words (like ! would be). The spec does say we must split the message (not take as a list of words) - probably only costs you a byte or two. Also, I've asked if we need to write a multi-purpose encoder/decoder because the question might be for that (given the "input again (decrypting)" section), I see that Dominic did so in their answer. \$\endgroup\$ Feb 16, 2021 at 23:46
  • 2
    \$\begingroup\$ @JonathanAllan Thanks, you're right on both counts. I broke exactly even on the line feed savings and the bytes for chopping up the secret string. \$\endgroup\$
    – Jonah
    Feb 16, 2021 at 23:56

C (gcc), 154 147 bytes

Thanks to ceilingcat for the -7.

I'm assuming from the challenge that I can have a pre-split array of secret words and that the indexes must be 1-based.

Takes an array of the word list and a string which contains one or more 1-based indexes or words, and returns the inverse of each word/index. These may be mixed: for example, if my test returns 5 10, then my 10 returns 5 test. The words are case-sensitive.

How it works:

f(char **a, char *s) {
  int i,t,v;
  char *u;

    t=NULL; // initialize the tokenizer
    t=strtok(t?NULL:u=strdup(s),"., "); // tokenize the input on first call
    printf(v?"%s ":"%d ",v?:i+1)) // print either string or index
        v=(i=atoi(t))?a[i-1]:(i=0); // integer or string?
        !v*a[i]&&strcmp(t,a[i]); // if string, search for match
i,t,v;f(a,s)int*a;{for(t=0;t=strtok(t?0:strdup(s),"., ");printf(v?"%s ":"%d ",v?:i+1))for(v=(i=atoi(t))?a[i-1]:(i=0);!v*a[i]&&strcmp(t,a[i]);i++);}

Try it online!


Not the answer you're looking for? Browse other questions tagged or ask your own question.