Checklist OAI 2024 Stage II – Final · Task 1
Ciphers
Polish title: Szyfry
Decipher Polish text lines encoded with a homophonic picture cipher (three emoji per character), using statistics of unencrypted lines from the same library.
The task
A group of pre-school children encrypted some lines of books by covering letters with stickers. They prepared one bag per distinct character in the library (n bags) and 3n different pictures; each bag received the stickers of exactly three pictures, possibly printed in different numbers of copies. For every character in a line chosen for encryption, a random sticker was drawn from that character's bag. All characters, including spaces, were encrypted, except the newline character. Not all lines were processed.
The contestant writes decipher_corpus(clear_corpus, ciphered_corpus), which receives a list of unencrypted lines and a list of encrypted lines and returns the list of decrypted lines. The three pictures assigned to a character need not be equally likely.
The provided data are clear_lines.txt (unencrypted lines), ciphered_lines.txt (30,000 encrypted lines for development) and ciphered_lines_ground_truth.txt (their plaintext). The notebook suggests gensim's Word2Vec (imported as Letter2Vec) as a possibly useful tool.
Abridged and translated by SOTA from the official Polish materials. The official statement has the exact rules, and it wins wherever this summary differs.
In English
This task was published in Polish. SOTA translated it into English on 16 September 2026. Only the words changed in the notebooks: markdown, code comments, messages and printed output. The code, file names and paths are the original's, so a translated notebook runs with the original data.
Read the task notebook in English
Encryption

Introduction
For centuries, people have tried to encrypt texts so that outsiders could not easily understand them. In this task you will have to decipher a text produced by a group of preschoolers who decided to encrypt part of the contents of their parents' library by covering the letters in selected lines with stickers.
Task
The children began by preparing bags: one for each unique character in the whole library. The boys drew different pictures, and the girls printed stickers with these pictures: each picture was printed in some unknown and possibly different number of copies. There are, however, so many stickers that they will not run out during the game. Next, into each of the prepared bags the children poured the stickers corresponding to exactly three pictures. Altogether, every sticker ended up in one of the bags.
During the game, the children decided to encrypt some of the lines in the books. For each character they came across, they drew a random sticker from the bag belonging to that character and stuck it in the character's place. Children being children, they got bored, so not all lines of the books were processed in this way. Unfortunately, the pictures cannot be peeled off in any way, but perhaps, by analysing the arrangements of letters in the texts that were not covered, we will be able to reconstruct the text of the covered lines.
Write a function decipher_corpus(clear_corpus, ciphered_corpus) that takes two arguments:
clear_corpus: a list containing unencrypted strings lines that the children did not touch. For example:
clear_corpus = ["a tajną . u rzymian niewolnika , który ukradł , ",
"środka : tam wszystko jasno oświetlone . * i widzę ",
"się do góry , utracił własność * dążącą do centru ",
"chciałabym widzieć tego malca tylko na chwilę . słyszałem , ",
"mości notę , prosząc o uwolnienie z tej usługi , "]
ciphered_corpus: a list of encrypted lines lines covered with stickers. For example:
ciphered_corpus = ["🥥🖕👼🥥🔀🛠👡🐌💸🐌📩🖕🧇🤿🧰🦆🥨🥥🦨🐌🚕🔻🥅🐘🚒🗣🛠🔻🚄🥥🤳🥺🐌🚄👼👲🐤🧰🤳📩🚄🔒🥥🧵🐈🖕🥺🖕",
"🥤🔒🚒🚔🚄🦫🐌🦒🐌👼🦫🦆🖕🐘🦓🤿💣🦓🚷👱🧔🖕💐🥥🚭🚕🚒🐌🙉🥤🐘🥨🗿👼🗣🚒🦨🔶🤳💑🖕📈🐌🔻🖕🖊🔻🚔🐅👽🐌",
"🦓🔻👽🐌🧵🚒🤳🚥🐛🐤💁🖕🖈🤳🔖👼🧇🦫💯🕰👂🤳🐘👂🥥🚭🛠🚒🚚🛰🤳🧚🐌🧵👠🦔🕶🦈👡🐌🚓🙉🐌🦈🗿🦨🚷🐤🧍🐌"]
This function should decipher the list of lines ciphered_corpus, written in the picture cipher, using the other, unencrypted lines clear_corpus. The function should return a list of deciphered lines.
Evaluation
For this task you can receive a score between 0 and 1 point. Your function decipher_corpus(clear_corpus, ciphered_corpus) will be tested on 4 test cases: independent ciphers. The scoring criterion for a given cipher will be the percentage of correctly deciphered characters in the text (the number of correctly deciphered characters divided by the number of all characters in the text). A model achieving a classification accuracy below 50% will receive 0 points.
The final score will be the mean of the scores for all tests and will be computed according to the formula:
Constraints
-
Running all of your code with the
FINAL_EVALUATION_MODEflag set toTrueshould take no longer than 5 minutes on Google Colab with a GPU. -
Your solution will be tested without internet access.
Data
The data available to you in this task are:
clear_lines.txt- a file containing lines from books that the children did not touch - unencrypted text;ciphered_lines.txt- a file containing 30000 lines from books, encrypted in the way described above; this is the set on which you can evaluate your solution;ciphered_lines_ground_truth.txt- a file containing the deciphered lines from that file.
Remarks and hints
- All characters were encrypted, including spaces, except for the newline character (there are none in any of the sets).
- The three pictures assigned to a letter do not necessarily have equal probability of being stuck in place of that letter, i.e. some pictures may have been used more often than the others in the triple.
- This may be useful:
from gensim.models import Word2Vec as Letter2Vec
Submission files
Only this notebook.
Evaluation
Remember that during grading the FINAL_EVALUATION_MODE flag will be set to True. Using the validation_script.py script, you will be able to make sure that your solution will run correctly during grading.
Starter code
FINAL_EVALUATION_MODE = False # We will change this value to True
from gensim.models import Word2Vec as Letter2Vec
if not FINAL_EVALUATION_MODE:
! gdown https://drive.google.com/uc?id=1smu8e_muUU2YDPQvZ5oT2VW46tg6zP5F
# ! gdown https://drive.google.com/uc?id=1uPacHqw6qEkECBIBvertRib469SHOMc_
# ! gdown https://drive.google.com/uc?id=1VAGP-8s6yhlubtvJF0hHgy30H5NmUjn3
! unzip -o cipher.zip
Loading the data
######################### DO NOT CHANGE THIS CELL ##########################
clear_file_path = "clear_lines.txt"
ciphered_file_path = "ciphered_lines.txt"
solutions_file_path = "ciphered_lines_ground_truth.txt"
corpus_clear = [line.strip().lower() for line in open(clear_file_path)]
corpus_ciphered = [line.strip().lower() for line in open(ciphered_file_path)]
corpus_ground_truth = [line.rstrip('\n').lower() for line in open(solutions_file_path)]
Your solution
This is the only section in which you need to do anything.
def decipher_corpus(clear_corpus, ciphered_corpus):
# TODO: Implement a function that deciphers the texts from `ciphered_corpus` and returns the deciphered texts
deciphered = ciphered_corpus
return deciphered
Evaluation
During the grading of the solution, your function decipher_corpus will be called in a way similar to the one below. Different, test data will be used, however.
Before submitting, make sure that, after setting the flag FINAL_EVALUATION_MODE = True, the whole notebook runs from start to finish (the Run All command) without errors and without user intervention. You can also run the validation script validation_script.py, which executes the whole notebook and scores the decipher_corpus function defined in it.
######################### DO NOT CHANGE THIS CELL ##########################
def accuracy_metric(original_lines, deciphered_lines):
original_str = "".join(original_lines)
deciphered_str = "".join(deciphered_lines)
assert len(original_str) == len(deciphered_str)
good_char = sum(int(a == b) for a, b in zip(original_str, deciphered_str))
return good_char / len(original_str)
if not FINAL_EVALUATION_MODE:
deciphered_file = decipher_corpus(corpus_clear, corpus_ciphered)
accuracy = accuracy_metric(corpus_ground_truth, deciphered_file)
score = 2 * max(accuracy - 0.5, 0.0)
print(f"Accuracy: {accuracy}")
print(f'Your score is {score} pts')
Translated by SOTA. The Polish original is the official version and wins wherever the two differ. The texts to decipher are Polish book lines, so the example lines in the statement stay in Polish. If you organise this olympiad and would like the translation removed, email [email protected] and we will take it down.
At a glance
- You get
clear_lines.txt,ciphered_lines.txtandciphered_lines_ground_truth.txtfromcipher.zip(Google Drive).- You submit
- This notebook only, with
decipher_corpusreturning the decrypted lines. - Scoring
- Per cipher, accuracy = fraction of correctly decrypted characters. Tested on 4 independent ciphers; score = (1/4) Σ 2·max(
accuracy_i− 0.5, 0), between 0 and 1 point. - Rules
- Running the whole notebook with FINAL_EVALUATION_MODE = True must take at most 5 minutes on Google Colab with a GPU.
- Tested without Internet access.
- Format
- Stage II final contest (five hours) held during the final scientific camp in Krzyżowa, 15–21 June 2024; about 30 top Stage I participants took part.