Discord

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.

  • NLP
  • Unsupervised decipherment
  • Polish original · English translation

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 794 words and 6 code cells

Encryption

image.png

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 nn bags: one for each unique character in the whole library. The boys drew 3n3n 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:

score=14i=142max(accuracyi0.5,0)\mathrm{score} = \frac{1}{4} \sum_{i=1}^4 2 \cdot \max(\mathrm{accuracy}_i - 0.5, 0)

Constraints

  • Running all of your code with the FINAL_EVALUATION_MODE flag set to True should 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.txt and ciphered_lines_ground_truth.txt from cipher.zip (Google Drive).
You submit
This notebook only, with decipher_corpus returning 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.

Details

Year
2024, Krzyżowa, Poland
Round
Stage II – Final · Task 1
Language
Polish; English translation by SOTA
License
Not stated by the source