Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Numeric Enigma Machine
#1
I have come across Lockheed Martin's problem from the 2018 CodeQuest, a numeric Enigma machine:

   


I am wondering what the best way to code the rotors and their mappings is, considering that "rotation" needs to be applied- similar to how an odometer works.

My first supposition was using lists/arrays:

rotor1 = [1, 3, 6, 0, 5, 4, 8, 7, 9, 2]
rotor2 = [0, 3, 5, 2, 6, 9, 1, 4, 8, 7]
rotor3 = [5, 9, 1, 7, 3, 8, 0, 2, 4, 6]
rotor4 = [1, 6, 5, 2, 9, 0, 7, 4, 3, 8]
In this setup, the index value represents the input side of the rotor and the value represents the output side of the rotor. So, if you want to input 1 into rotor 1, you would get an output of 3. This works okay, if the rotors are all in position 0, as depicted. However, once rotation comes into play, this doesn't seem to work anymore.

For instance, if I use rotors 1,2,3 all initially set to their 0 position (as depicted), and if I want to encrypt the message 1234567890. I know, as a test case, that the output should be 4805344463. I do get the first digit encrypted correctly, to 4. However, then the rightmost rotor (rotor 3 in this case) must "shift down one position." I used slicing to shift the arrays:

shifted_rotor = rotor[1:] + rotor[:1]
I have also tried going in the other direction:

shifted_rotor = rotor[-1:] + rotor[:-1]
However, after applying the aforementioned shift, in either direction, and then attempting to encrypt the next digit in the message sequence, which would be 2, I don't get 8, as I should get.

This has lead me to believe that, perhaps, using the rotor config/mappings as I have laid them out won't work.

Is anyone familiar with this problem? Can anyone offer me any help or advice on this?
Reply
#2
When you shift the array, you are changing how the inputs map to the identical outputs. But I think in your problem thats not how rotors work.

As an example the first line on R1 maps input 0 to output 1. With your shift, you would now map input 1 to output 1. But presumably the output also moves, so that line (moved down 1) would now map input 1 to output 2.

Given that, I would probably model the rotors as an addition mod 10. The first rotor line would then be [+1]. When the rotor is in position 0, input 1 becomes output 2. When the rotor is in position 7, input 8 becomes output 9. When the rotor is in position 8, input 9 becomes output 0.
Reply
#3
I am not sure I understood that correctly, but I am going to try something and see how it works out.


(Mar-27-2024, 10:52 PM)bowlofred Wrote: When you shift the array, you are changing how the inputs map to the identical outputs. But I think in your problem thats not how rotors work.

As an example the first line on R1 maps input 0 to output 1. With your shift, you would now map input 1 to output 1. But presumably the output also moves, so that line (moved down 1) would now map input 1 to output 2.

Given that, I would probably model the rotors as an addition mod 10. The first rotor line would then be [+1]. When the rotor is in position 0, input 1 becomes output 2. When the rotor is in position 7, input 8 becomes output 9. When the rotor is in position 8, input 9 becomes output 0.
Reply
#4
I have revisited all of my previous tinkering and I have tried a new codebase, which seems to be progress.

def enigma(starting_rotors, starting_positions, message):
    # Define the rotors and reflector
    rotors = {
        1: [1, 3, 6, 0, 5, 4, 8, 7, 9, 2],
        2: [0, 3, 5, 2, 6, 9, 1, 4, 8, 7],
        3: [5, 9, 1, 7, 3, 8, 0, 2, 4, 6],
        4: [1, 6, 5, 2, 9, 0, 7, 4, 3, 8]
    }
    reflector = [3, 6, 8, 0, 5, 4, 1, 9, 2, 7]

    selected_rotors = [rotors[i] for i in starting_rotors]
    rotor_positions = starting_positions[:] # Copy to avoid modifying the original
    encrypted_message = ""

    def rotate_rotors():
        # Increment the rightmost rotor's position
        rotor_positions[-1] += 1

        # Check for rotation carry-over
        for i in reversed(range(1, len(rotor_positions))):
            if rotor_positions[i] > 9:
                rotor_positions[i] = 0
                rotor_positions[i - 1] += 1

    for digit in message:
        digit = int(digit)

        # Forward pass
        for i, rotor in enumerate(selected_rotors):
            adjusted_input = (digit + rotor_positions[i]) % 10
            digit = rotor[adjusted_input]

        # Reflection
        digit = reflector[digit]

        # # Reverse pass
        # for i in reversed(range(len(selected_rotors))):
        #     rotor = selected_rotors[i]
        #     position = rotor_positions[i]
        #     # Find the digit mapping
        #     digit = (rotor.index((digit - position) % 10) + position) % 10

        # Reverse pass
        for i in reversed(range(len(selected_rotors))):
            rotor = selected_rotors[i]
            digit = rotor.index(digit)  # Find the index directly, reflecting the true reverse pass logic

        encrypted_message += str(digit)
        rotate_rotors()

    return encrypted_message

# Example usage
starting_rotors = [1, 2, 3]  # Rotors to use
starting_positions = [0, 0, 0]  # Initial positions of the rotors
message = "1234567890"  # The message to encrypt

# Encrypt the message
encrypted_message = enigma(starting_rotors, starting_positions, message)
print(f"Encrypted message: {encrypted_message}")
I haven't got something quite right, though. The first two digits are correctly encrypted, but after that all other digits are encrypted incorrectly. The expected output is: 4805344463

The output I am getting is: 4444626524
Reply
#5
Think of it this way. Imagine you have a very simplified rotor:
[0, 1, 2, 3, 4]

This is five straight lines. 0->0, 1->1 etc...

What happens when the rotor is moved one click down? Nothing at all. The 0->0 line is now the 1->1 line, and the previous 4->4 line becomes the new 0->0 line. This "null" rotor doesn't change anything when it is moved.

What happens in your program? You advance the input, but not the output. If your rotor is in postion 1, then you run:
            adjusted_input = (digit + rotor_positions[i]) % 10
            digit = rotor[adjusted_input]
If we pass in digit=0 to this, we get adjusted_input = 1 and then the new digit becomes 1. This is incorrect.



You might consider testing your program with a set of null rotors (each is list(range(9))), and not using the reflector. If your code is correct, you should get the input string back without encryption.
Reply
#6
And that's just on the forward pass. You're not applying any rotation at all on the reverse pass.
Reply
#7
The reverse pass isn't supposed to have rotation.

The rotors get set to their initial position. The first digit in the sequence is encrypted (forward, reflector, backward). Then, rotation happens. The second digit is encrypted (forward, reflector, backward)...rotation...encryption of the third digit, and so on.

(Mar-28-2024, 06:07 AM)bowlofred Wrote: And that's just on the forward pass. You're not applying any rotation at all on the reverse pass.
Reply
#8
Okay, you made an interesting point (if it's a valid one) and I decided to try your test method:

# Define the rotors and reflector
rotors = {
    0: list(range(10)),
    1: list(range(10)),
    2: list(range(10)),
    3: list(range(10)),
    4: list(range(10))
}
reflector = [3, 6, 8, 0, 5, 4, 1, 9, 2, 7]


def apply_rotor(input_digit, rotor, position):
    # Adjust input based on the rotor's position
    adjusted_input = (input_digit - position) % 10
    # Apply the mapping
    output_digit = rotor[adjusted_input]
    return output_digit

def encrypt_digit(digit, rotor_positions, selected_rotors):
    # Convert digit from string to integer
    digit = int(digit)

    # Forward pass through the selected rotors
    for i in range(len(selected_rotors)):
        digit = apply_rotor(digit, selected_rotors[i], rotor_positions[i])
        print(f"Forward - Digit after rotor {i} : {digit}")

    # Reverse pass through the selected rotors
    for i in reversed(range(len(selected_rotors))):
        # For reverse path, adjust input based on the rotor's position before applying the mapping
        adjusted_input = (digit + rotor_positions[i]) % 10
        digit = selected_rotors[i].index(adjusted_input)
        print(f"Reverse - Digit after rotor {i} : {digit}")

    # Convert digit back to string for output
    return str(digit)

def rotate_rotors(rotor_positions):
    # Rotate the rightmost rotor after encrypting each digit
    rotor_positions[-1] += 1
    print("Rotating rotors...")
    print("Rotor positions after rotation:", rotor_positions)
    for i in reversed(range(len(rotor_positions) - 1)):
        if rotor_positions[i + 1] > 9:  # Check if the right rotor completed a revolution
            rotor_positions[i + 1] = 0  # Reset the right rotor
            rotor_positions[i] += 1  # Rotate the next rotor to the left
            print("Rotor positions after reset:", rotor_positions)

def encrypt_sequence(sequence, selected_rotors_indices, starting_positions):
    # Extract the selected rotors based on indices provided
    selected_rotors = [rotors[i] for i in selected_rotors_indices]
    rotor_positions = starting_positions[:]  # Copy to avoid modifying the original starting positions
    encrypted_sequence = ""

    for digit in sequence:
        # Encrypt each digit in the sequence
        encrypted_digit = encrypt_digit(digit, rotor_positions, selected_rotors)
        encrypted_sequence += encrypted_digit
        # Rotate rotors after encrypting each digit
        rotate_rotors(rotor_positions)

    return encrypted_sequence


sequence = "1234567890"  # The sequence to be encrypted
selected_rotors_indices = [0, 1, 2]  # Indices of the rotors selected for the encryption
starting_positions = [0, 0, 0]  # Starting positions of the selected rotors

# Encrypt the sequence
encrypted_sequence = encrypt_sequence(sequence, selected_rotors_indices, starting_positions)

print(f"Encrypted sequence: {encrypted_sequence}")
With the code changes I have implemented, using your debugging strategy, I have been able to get the same output from the input, albeit with a slight quirk:

Output:
Forward - Digit after rotor 0 : 1 Forward - Digit after rotor 1 : 1 Forward - Digit after rotor 2 : 1 Reverse - Digit after rotor 2 : 1 Reverse - Digit after rotor 1 : 1 Reverse - Digit after rotor 0 : 1 Rotating rotors... Rotor positions after rotation: [0, 0, 1] Forward - Digit after rotor 0 : 2 Forward - Digit after rotor 1 : 2 Forward - Digit after rotor 2 : 1 Reverse - Digit after rotor 2 : 2 Reverse - Digit after rotor 1 : 2 Reverse - Digit after rotor 0 : 2 Rotating rotors... Rotor positions after rotation: [0, 0, 2] Forward - Digit after rotor 0 : 3 Forward - Digit after rotor 1 : 3 Forward - Digit after rotor 2 : 1 Reverse - Digit after rotor 2 : 3 Reverse - Digit after rotor 1 : 3 Reverse - Digit after rotor 0 : 3 Rotating rotors... Rotor positions after rotation: [0, 0, 3] Forward - Digit after rotor 0 : 4 Forward - Digit after rotor 1 : 4 Forward - Digit after rotor 2 : 1 Reverse - Digit after rotor 2 : 4 Reverse - Digit after rotor 1 : 4 Reverse - Digit after rotor 0 : 4 Rotating rotors... Rotor positions after rotation: [0, 0, 4] Forward - Digit after rotor 0 : 5 Forward - Digit after rotor 1 : 5 Forward - Digit after rotor 2 : 1 Reverse - Digit after rotor 2 : 5 Reverse - Digit after rotor 1 : 5 Reverse - Digit after rotor 0 : 5 Rotating rotors... Rotor positions after rotation: [0, 0, 5] Forward - Digit after rotor 0 : 6 Forward - Digit after rotor 1 : 6 Forward - Digit after rotor 2 : 1 Reverse - Digit after rotor 2 : 6 Reverse - Digit after rotor 1 : 6 Reverse - Digit after rotor 0 : 6 Rotating rotors... Rotor positions after rotation: [0, 0, 6] Forward - Digit after rotor 0 : 7 Forward - Digit after rotor 1 : 7 Forward - Digit after rotor 2 : 1 Reverse - Digit after rotor 2 : 7 Reverse - Digit after rotor 1 : 7 Reverse - Digit after rotor 0 : 7 Rotating rotors... Rotor positions after rotation: [0, 0, 7] Forward - Digit after rotor 0 : 8 Forward - Digit after rotor 1 : 8 Forward - Digit after rotor 2 : 1 Reverse - Digit after rotor 2 : 8 Reverse - Digit after rotor 1 : 8 Reverse - Digit after rotor 0 : 8 Rotating rotors... Rotor positions after rotation: [0, 0, 8] Forward - Digit after rotor 0 : 9 Forward - Digit after rotor 1 : 9 Forward - Digit after rotor 2 : 1 Reverse - Digit after rotor 2 : 9 Reverse - Digit after rotor 1 : 9 Reverse - Digit after rotor 0 : 9 Rotating rotors... Rotor positions after rotation: [0, 0, 9] Forward - Digit after rotor 0 : 0 Forward - Digit after rotor 1 : 0 Forward - Digit after rotor 2 : 1 Reverse - Digit after rotor 2 : 0 Reverse - Digit after rotor 1 : 0 Reverse - Digit after rotor 0 : 0 Rotating rotors... Rotor positions after rotation: [0, 0, 10] Rotor positions after reset: [0, 1, 0] Encrypted sequence: 1234567890
For some reason, after the input passes through "rotor 2" on the forward pass, it always comes out as 1. Yet, that is always resolved and I am getting the correct output. Still, I am wondering why that occurs and if it is a cause for further debugging.



(Mar-28-2024, 04:44 AM)bowlofred Wrote: Think of it this way. Imagine you have a very simplified rotor:
[0, 1, 2, 3, 4]

This is five straight lines. 0->0, 1->1 etc...

What happens when the rotor is moved one click down? Nothing at all. The 0->0 line is now the 1->1 line, and the previous 4->4 line becomes the new 0->0 line. This "null" rotor doesn't change anything when it is moved.

What happens in your program? You advance the input, but not the output. If your rotor is in postion 1, then you run:
            adjusted_input = (digit + rotor_positions[i]) % 10
            digit = rotor[adjusted_input]
If we pass in digit=0 to this, we get adjusted_input = 1 and then the new digit becomes 1. This is incorrect.



You might consider testing your program with a set of null rotors (each is list(range(9))), and not using the reflector. If your code is correct, you should get the input string back without encryption.
Reply
#9
As an update to my previous reply, there seems to be an issue with
apply_rotor(input_digit, rotor, position):
Output:
1 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0 adjusted_input 1 Forward - Digit after rotor 0 : 1 1 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0 adjusted_input 1 Forward - Digit after rotor 1 : 1 1 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0 adjusted_input 1 Forward - Digit after rotor 2 : 1 Reverse - Digit after rotor 2 : 1 Reverse - Digit after rotor 1 : 1 Reverse - Digit after rotor 0 : 1 Rotating rotors... Rotor positions after rotation: [0, 0, 1] 2 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0 adjusted_input 2 Forward - Digit after rotor 0 : 2 2 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0 adjusted_input 2 Forward - Digit after rotor 1 : 2 2 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 1 adjusted_input 1 Forward - Digit after rotor 2 : 1 Reverse - Digit after rotor 2 : 2 Reverse - Digit after rotor 1 : 2 Reverse - Digit after rotor 0 : 2 Rotating rotors... Rotor positions after rotation: [0, 0, 2] 3 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0 adjusted_input 3 Forward - Digit after rotor 0 : 3 3 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0 adjusted_input 3 Forward - Digit after rotor 1 : 3 3 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 2 adjusted_input 1 Forward - Digit after rotor 2 : 1 Reverse - Digit after rotor 2 : 3 Reverse - Digit after rotor 1 : 3 Reverse - Digit after rotor 0 : 3 Rotating rotors... Rotor positions after rotation: [0, 0, 3] 4 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0 adjusted_input 4 Forward - Digit after rotor 0 : 4 4 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0 adjusted_input 4 Forward - Digit after rotor 1 : 4 4 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 3 adjusted_input 1 Forward - Digit after rotor 2 : 1 Reverse - Digit after rotor 2 : 4 Reverse - Digit after rotor 1 : 4 Reverse - Digit after rotor 0 : 4 Rotating rotors... Rotor positions after rotation: [0, 0, 4] 5 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0 adjusted_input 5 Forward - Digit after rotor 0 : 5 5 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0 adjusted_input 5 Forward - Digit after rotor 1 : 5 5 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 4 adjusted_input 1 Forward - Digit after rotor 2 : 1 Reverse - Digit after rotor 2 : 5 Reverse - Digit after rotor 1 : 5 Reverse - Digit after rotor 0 : 5 Rotating rotors... Rotor positions after rotation: [0, 0, 5] 6 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0 adjusted_input 6 Forward - Digit after rotor 0 : 6 6 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0 adjusted_input 6 Forward - Digit after rotor 1 : 6 6 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 5 adjusted_input 1 Forward - Digit after rotor 2 : 1 Reverse - Digit after rotor 2 : 6 Reverse - Digit after rotor 1 : 6 Reverse - Digit after rotor 0 : 6 Rotating rotors... Rotor positions after rotation: [0, 0, 6] 7 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0 adjusted_input 7 Forward - Digit after rotor 0 : 7 7 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0 adjusted_input 7 Forward - Digit after rotor 1 : 7 7 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 6 adjusted_input 1 Forward - Digit after rotor 2 : 1 Reverse - Digit after rotor 2 : 7 Reverse - Digit after rotor 1 : 7 Reverse - Digit after rotor 0 : 7 Rotating rotors... Rotor positions after rotation: [0, 0, 7] 8 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0 adjusted_input 8 Forward - Digit after rotor 0 : 8 8 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0 adjusted_input 8 Forward - Digit after rotor 1 : 8 8 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 7 adjusted_input 1 Forward - Digit after rotor 2 : 1 Reverse - Digit after rotor 2 : 8 Reverse - Digit after rotor 1 : 8 Reverse - Digit after rotor 0 : 8 Rotating rotors... Rotor positions after rotation: [0, 0, 8] 9 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0 adjusted_input 9 Forward - Digit after rotor 0 : 9 9 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0 adjusted_input 9 Forward - Digit after rotor 1 : 9 9 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 8 adjusted_input 1 Forward - Digit after rotor 2 : 1 Reverse - Digit after rotor 2 : 9 Reverse - Digit after rotor 1 : 9 Reverse - Digit after rotor 0 : 9 Rotating rotors... Rotor positions after rotation: [0, 0, 9] 0 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0 adjusted_input 0 Forward - Digit after rotor 0 : 0 0 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0 adjusted_input 0 Forward - Digit after rotor 1 : 0 0 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 9 adjusted_input 1 Forward - Digit after rotor 2 : 1 Reverse - Digit after rotor 2 : 0 Reverse - Digit after rotor 1 : 0 Reverse - Digit after rotor 0 : 0 Rotating rotors... Rotor positions after rotation: [0, 0, 10] Rotor positions after reset: [0, 1, 0] Encrypted sequence: 1234567890
Reply
#10
In the end, I realized that the issue is how I was configuring my rotors. I adjusted the configuration to something that was more appropriate and enabled the whole rotor (input, output, wiring) to rotate and that resolved the biggest of obstacles with this project. I just had to work through a few smaller issues, but ended up getting the whole thing coded properly.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Enigma Decoding Problem krisarmstrong 4 747 Dec-14-2023, 10:42 AM
Last Post: Larz60+
Question Numeric Anagrams - Count Occurances monty024 2 1,516 Nov-13-2021, 05:05 PM
Last Post: monty024
  How to get datetime from numeric format field klllmmm 3 2,009 Nov-06-2021, 03:26 PM
Last Post: snippsat
  Extract continuous numeric characters from a string in Python Robotguy 2 2,656 Jan-16-2021, 12:44 AM
Last Post: snippsat
  How to calculate column mean and row skip non numeric and na Mekala 5 4,965 May-06-2020, 10:52 AM
Last Post: anbu23
  Alpha numeric element list search rhubarbpieguy 1 1,795 Apr-01-2020, 12:41 PM
Last Post: pyzyx3qwerty
  convert a character to numeric and back Skaperen 2 2,116 Jan-28-2020, 09:32 PM
Last Post: Skaperen
  are numeric types passed by value or reference? rudihammad 4 2,634 Nov-19-2019, 06:25 AM
Last Post: rudihammad
  'Age' categorical (years -months -days ) to numeric Smiling29 4 2,951 Oct-17-2019, 05:26 PM
Last Post: Smiling29
  how to do a numeric sort Skaperen 11 4,965 Jul-12-2019, 09:50 AM
Last Post: Skaperen

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020