[Math][Python] Simple combinatorics: soldering headphone wires; vehicle license plate; forgotten password

Soldering headphone wires

This is a real story: I tried to repair my headphone's cable, soldering 3 wires with minijack together. But which is left? Right? I couldn't even find ground wire. I asked myself, how many ways there are to solder 3 wires to 3-pin minijack? I could try them all and pick the combination that sounds best.

With Python's itertools module this is just:

import itertools

wires=["red", "green", "blue"]

for i in itertools.permutations(wires):
        print i

('red', 'green', 'blue')
('red', 'blue', 'green')
('green', 'red', 'blue')
('green', 'blue', 'red')
('blue', 'red', 'green')
('blue', 'green', 'red')

(Just 6 ways.)

What if there are 4 wires?

import itertools

wires=["red", "green", "blue", "yellow"]

for i in itertools.permutations(wires):
        print i


('red', 'green', 'blue', 'yellow')
('red', 'green', 'yellow', 'blue')
('red', 'blue', 'green', 'yellow')
('red', 'blue', 'yellow', 'green')
('red', 'yellow', 'green', 'blue')
('red', 'yellow', 'blue', 'green')
('green', 'red', 'blue', 'yellow')
('green', 'red', 'yellow', 'blue')
('green', 'blue', 'red', 'yellow')
('green', 'blue', 'yellow', 'red')
('green', 'yellow', 'red', 'blue')
('green', 'yellow', 'blue', 'red')
('blue', 'red', 'green', 'yellow')
('blue', 'red', 'yellow', 'green')
('blue', 'green', 'red', 'yellow')
('blue', 'green', 'yellow', 'red')
('blue', 'yellow', 'red', 'green')
('blue', 'yellow', 'green', 'red')
('yellow', 'red', 'green', 'blue')
('yellow', 'red', 'blue', 'green')
('yellow', 'green', 'red', 'blue')
('yellow', 'green', 'blue', 'red')
('yellow', 'blue', 'red', 'green')
('yellow', 'blue', 'green', 'red')

(24 ways.)

This is what is called "permutation" in combinatorics.

Vehicle license plate

There was a hit and run. And you're police officer. And this is what your only witness says about 4-digit license plate of the guilty vehicle:

There was 13: 1 and 3 together, I'm sure, but not sure where, 13 as first 2 digits, last 2 digits or in the middle.
And there was also 6, or maybe 8, or maybe even 9, not sure which, but one of them.

Combinatorics textbooks are abound with exercises like this: can you enumerate all possible 4-digit numbers constrained in this way?

import itertools

part1_list=["13"]
part2_list=["6", "8", "9"]
part3_list=["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]

for i in itertools.product(part1_list, part2_list, part3_list):
    for j in itertools.permutations(i):
        print "".join(j)


1360
1306
6130
6013

...

9139
9913
9139
9913
( 180 numbers )

This is something you can query registered vehicle database with...

Forgotten password

You forgot a password, but this is what you remember: there was a name of your parent, or wife, or one of children. Also, someone's year of birth. And one punctuation character, which are so recommended in passwords. Can you enumerate all possible passwords?

import itertools

part1_list=["jake", "melissa", "oliver", "emily"]
part2_list=["1987", "1954", "1963"]
part3_list=["!","@","#","$","%","&","*","-","=","_","+",".",","]

for part1 in part1_list:
    for part2 in part2_list:
        for part3 in part3_list:
            l=[part1, part2, part3]
            for i in list(itertools.permutations(l)):
                print "".join(i)


jake1987!
jake!1987
1987jake!
1987!jake
!jake1987

...

1963emily,
1963,emily
,emily1963
,1963emily

( 936 of them in total )

But nested for's are not aesthetically pleasing. They can be replaced with "cartesian product" operation:

import itertools

part1_list=["jake", "melissa", "oliver", "emily"]
part2_list=["1987", "1954", "1963"]
part3_list=["!","@","#","$","%","&","*","-","=","_","+",".",","]

for l in itertools.product(part1_list, part2_list, part3_list):
    for i in list(itertools.permutations(l)):
        print "".join(i)


And this is a way to memorize it: the length of the final result equals to lengths of all input lists multiplied with each other (like "product").

import itertools

part1_list=["jake", "melissa", "oliver", "emily"] # 4 elements
part2_list=["1987", "1954", "1963"] # 3 elements
part3_list=["!","@","#","$","%","&","*","-","=","_","+",".",","] # 13 elements

for l in itertools.product(part1_list, part2_list, part3_list):
    print l

('jake', '1987', '!')
('jake', '1987', '@')
('jake', '1987', '#')
('jake', '1987', '$')
('jake', '1987', '%')
('jake', '1987', '&')
('jake', '1987', '*')

...

('emily', '1963', '*')
('emily', '1963', '-')
('emily', '1963', '=')
('emily', '1963', '_')
('emily', '1963', '+')
('emily', '1963', '.')
('emily', '1963', ',')

4*3*13=156, and this is a size of a list, to be permuted...

Now the new problem: some Latin characters may be uppercased, some are lowercased. I'll add another "cartesian product" operation to alter a final string in all possible ways:

import itertools, string

part1_list=["jake", "melissa", "oliver", "emily"]
part2_list=["1987", "1954", "1963"]
part3_list=["!","@","#","$","%","&","*","-","=","_","+",".",","]

for l in itertools.product(part1_list, part2_list, part3_list):
    for i in list(itertools.permutations(l)):
        s="".join(i)
        t=[]
        for char in s:
            if char.isalpha():
                t.append([string.lower(char), string.upper(char)])
            else:
                t.append([char])
        for q in itertools.product(*t):
            print "".join(q)


JAke1987!
JAkE1987!
JAKe1987!
JAKE1987!
jake!1987
jakE!1987
jaKe!1987
jaKE!1987

...

,1963eMIly
,1963eMIlY
,1963eMILy
,1963eMILY
,1963Emily
,1963EmilY
,1963EmiLy

( 56160 passwords )

Now leetspeak. This is somewhat popular only among youngsters, but still, this is what people of all age groups do: replacing "o" with "0" in passwords, "e" with "3", etc. Let's add this as well:

import itertools, string

part1_list=["jake", "melissa", "oliver", "emily"]
part2_list=["1987", "1954", "1963"]
part3_list=["!","@","#","$","%","&","*","-","=","_","+",".",","]

for l in itertools.product(part1_list, part2_list, part3_list):
    for i in list(itertools.permutations(l)):
        s="".join(i)
        t=[]
        for char in s:
            if char.isalpha():
                to_be_appended=[string.lower(char), string.upper(char)]
                if char.lower()=='e':
                    to_be_appended.append('3')
                elif char.lower()=='i':
                    to_be_appended.append('1')
                elif char.lower()=='o':
                    to_be_appended.append('0')
                t.append(to_be_appended)
            else:
                t.append([char])
        for q in itertools.product(*t):
            print "".join(q)


jake1987!
jakE1987!
jak31987!
jaKe1987!
jaKE1987!
jaK31987!

...

,1963EM1lY
,1963EM1Ly
,1963EM1LY
,19633mily
,19633milY
,19633miLy

( 140400 passwords )

Obviously, you can't try all 140400 passwords on Facebook, Twitter or any other well-protected internet service. But this is a peace of cake to brute-force them all on password protected RAR-archive or feed them all to John the Ripper, HashCat, etc.

All the files: https://github.com/DennisYurichev/yurichev.com/tree/master/blog/comb.


→ [list of blog posts]

Please drop me email about any bug(s) and suggestion(s): dennis(@)yurichev.com.