Using combinatorics in Python to list 4-digit passcodes - python

I came across this interesting article about why using 3 unique numbers for a 4-digit passcode is the most secure: (LINK)
The math involved is pretty straightforward - if you have to guess a phone's 4-digit passcode based on the smudges left on the screen, then:
4 smudges indicates that there are 4 unique numbers in the passcode. Since each of them must be used at least once, then we have 4! = 24 possible passcodes.
With 3 distinct numbers, the passcode becomes a little more secure. Since there are three smudges, one number is repeated - but we don't know which one. So accounting for multiplicity, we get (4!/2!) x 3 = 36 possible passcodes.
Similarly, with 2 distinct numbers, we get 14 possible passcodes.
My question is, is there any way I can "prove" the above in Python? A way to justify that 3 numbers gives the most secure passcode, with Python code, probably something that lists out all possible passcodes if you give it some numbers? I was thinking about using itertools, with itertools.permutations as a starting point, but then I found that Python has a combinatorics module, which may be a far more elegant way. Would someone be kind enough to show me how to use it? I'm reading the documentation right now but some syntax is escaping me.

There is no combinatorics module in the standard distribution, but this is easy to do regardless. For example,
def guess(smudged_numbers):
from itertools import product
num_smudges = len(smudged_numbers)
for raw in product(smudged_numbers, repeat=4):
if len(set(raw)) == num_smudges:
yield raw
count = 0
for nums in guess([1, 8]):
print nums
count += 1
print "total", count
That prints:
(1, 1, 1, 8)
(1, 1, 8, 1)
(1, 1, 8, 8)
(1, 8, 1, 1)
(1, 8, 1, 8)
(1, 8, 8, 1)
(1, 8, 8, 8)
(8, 1, 1, 1)
(8, 1, 1, 8)
(8, 1, 8, 1)
(8, 1, 8, 8)
(8, 8, 1, 1)
(8, 8, 1, 8)
(8, 8, 8, 1)
total 14
The search space is very small (len(num_smudges)**4, which as at most 4**4 = 256), so no point to doing anything fancier ;-)
How it works: it generates all possible (product) 4-tuples (repeat=4) containing the passed-in sequence of smudged numbers. So for [1, 8], it generates all 2**4 = len(smudged_numbers)**4 = 16 possibilities for a 4-tuple containing nothing but 1's and 8's.
Converting a raw possibility to a set then tells us how many (len) different numbers appear in the raw 4-tuple. We only want those containing all the smudged numbers. That's all there is to it. In the [1, 8] case, this step only weeds out 2 of the 16 raw 4-tuples: (1, 1, 1, 1) and (8, 8, 8, 8).

My try with the permutations method in the itertools module.
I have added the shuffle method from the random module to generate more random tries from normal crackers. (To try your luck you would never go serially would you?!) But, if you want the serial tries method, you can just remove the shuffle(codes_to_try) line.
from itertools import combinations, permutations
from random import randint, shuffle
def crack_the_code(smudges, actual_code):
""" Takes a list of digit strings (smudges) & generates all possible
permutations of it (4 digits long). It then compares the actual given
code & returns the index of it in the generated list, which basically
becomes the number of tries.
"""
attempts_to_crack = 0
no_smudges = len(smudges)
if no_smudges == 3:
all_codes = ["".join(digits)
for repeated_num in smudges
for digits in permutations([repeated_num]+smudges)
]
all_codes = list(set(all_codes)) # remove duplicates
elif no_smudges == 4:
all_codes = ["".join(digits)
for digits in permutations(smudges)
]
else:
print "Smudges aren't 3 or 4"
raise ValueError
shuffle(all_codes)
return all_codes.index(actual_code)
print crack_the_code(["1","2","3"],"1232")
# above prints random values between 0 & 35 inclusive.
Note - You may play around with the function if you like int & not str.
PS - I have kept the code self-explanatory, but you can always comment & ask something you don't understand.

Related

Python Bug? What I'm doing wrong?

I'm trying to make a simple iterator which cycles through a list and returns three consecutive numbers from the list in python, but I get really weird result - code works fine only when numbers in the list are in ascending order.
import itertools
c=[0,1,2,3,0,5,6]
counter=itertools.cycle(c)
def func(x):
if x==len(c)-1:
return c[x],c[0],c[1]
elif x==len(c)-2:
return c[x],c[len(c)-1],c[0]
else:
return c[x],c[x+1],c[x+2]
for i in range(len(c)+2):
print(func(next(counter)))
'Im trying to make a simple iterator which cycles through a list and returns three consecutive numbers from the list in python, but I get really weird result - code works fine only when numbers in the list are in ascending order.Atom prints the following in the 5th tuple. Please help..
(0, 1, 2)
(1, 2, 3)
(2, 3, 0)
(3, 0, 5)
(0, 1, 2)
(5, 6, 0)
(6, 0, 1)
(0, 1, 2)
(1, 2, 3)
'
I believe you are confusing the values of c and the indices. It seems in func you expect that an index is passed but you are in fact passing a value from c. NOTE: counter is cycling over the values of c not over indices.
Also please note that in python you can use negative indices so you can write c[-1] as a short of c[len(c) - 1].

Filter generated permutations in python

I want to generate permutations of elements in a list, but only keep a set where each element is on each position only once.
For example [1, 2, 3, 4, 5, 6] could be a user list and I want 3 permutations. A good set would be:
[1,2,3,5,4,6]
[2,1,4,6,5,3]
[3,4,5,1,6,2]
However, one could not add, for example, [1,3,2,6,5,4] to the above, as there are two permutations in which 1 is on the first position twice, also 5 would be on the 5th position twice, however other elements are only present on those positions once.
My code so far is :
# this simply generates a number of permutations specified by number_of_samples
def generate_perms(player_list, number_of_samples):
myset = set()
while len(myset) < number_of_samples:
random.shuffle(player_list)
myset.add(tuple(player_list))
return [list(x) for x in myset]
# And this is my function that takes the stratified samples for permutations.
def generate_stratified_perms(player_list, number_of_samples):
user_idx_dict = {}
i = 0
while(i < number_of_samples):
perm = generate_perms(player_list, 1)
for elem in perm:
if not user_idx_dict[elem]:
user_idx_dict[elem] = [perm.index(elem)]
else:
user_idx_dict[elem] += [perm.index(elem)]
[...]
return total_perms
but I don't know how to finish the second function.
So in short, I want to give my function a number of permutations to generate, and the function should give me that number of permutations, in which no element appears on the same position more than the others (once, if all appear there once, twice, if all appear there twice, etc).
Let's starting by solving the case of generating n or fewer rows first. In that case, your output must be a Latin rectangle or a Latin square. These are easy to generate: start by constructing a Latin square, shuffle the rows, shuffle the columns, and then keep just the first r rows. The following always works for constructing a Latin square to start with:
1 2 3 ... n
2 3 4 ... 1
3 4 5 ... 2
... ... ...
n 1 2 3 ...
Shuffling rows is a lot easier than shuffling columns, so we'll shuffle the rows, then take the transpose, then shuffle the rows again. Here's an implementation in Python:
from random import shuffle
def latin_rectangle(n, r):
square = [
[1 + (i + j) % n for i in range(n)]
for j in range(n)
]
shuffle(square)
square = list(zip(*square)) # transpose
shuffle(square)
return square[:r]
Example:
>>> latin_rectangle(5, 4)
[(2, 4, 3, 5, 1),
(5, 2, 1, 3, 4),
(1, 3, 2, 4, 5),
(3, 5, 4, 1, 2)]
Note that this algorithm can't generate all possible Latin squares; by construction, the rows are cyclic permutations of each other, so you won't get Latin squares in other equivalence classes. I'm assuming that's OK since generating a uniform probability distribution over all possible outputs isn't one of the question requirements.
The upside is that this is guaranteed to work, and consistently in O(n^2) time, because it doesn't use rejection sampling or backtracking.
Now let's solve the case where r > n, i.e. we need more rows. Each column can't have equal frequencies for each number unless r % n == 0, but it's simple enough to guarantee that the frequencies in each column will differ by at most 1. Generate enough Latin squares, put them on top of each other, and then slice r rows from it. For additional randomness, it's safe to shuffle those r rows, but only after taking the slice.
def generate_permutations(n, r):
rows = []
while len(rows) < r:
rows.extend(latin_rectangle(n, n))
rows = rows[:r]
shuffle(rows)
return rows
Example:
>>> generate_permutations(5, 12)
[(4, 3, 5, 2, 1),
(3, 4, 1, 5, 2),
(3, 1, 2, 4, 5),
(5, 3, 4, 1, 2),
(5, 1, 3, 2, 4),
(2, 5, 1, 3, 4),
(1, 5, 2, 4, 3),
(5, 4, 1, 3, 2),
(3, 2, 4, 1, 5),
(2, 1, 3, 5, 4),
(4, 2, 3, 5, 1),
(1, 4, 5, 2, 3)]
This uses the numbers 1 to n because of the formula 1 + (i + j) % n in the first list comprehension. If you want to use something other than the numbers 1 to n, you can take it as a list (e.g. players) and change this part of the list comprehension to players[(i + j) % n], where n = len(players).
If runtime is not that important I would go for the lazy way and generate all possible permutations (itertools can do that for you) and then filter out all permutations which do not meet your requirements.
Here is one way to do it.
import itertools
def permuts (l, n):
all_permuts = list(itertools.permutations(l))
picked = []
for a in all_permuts:
valid = True
for p in picked:
for i in range(len(a)):
if a[i] == p[i]:
valid = False
break
if valid:
picked.append (a)
if len(picked) >= n:
break
print (picked)
permuts ([1,2,3,4,5,6], 3)

How to print all the non repeating products of a list of factors

Before you suggest that this question is similar to another, read P.P.S, pls.
Simillarly to this question, I am looking to find all the non-repeating combinations of a list of factors: But beeing looking for a python solution, and also having a slighty diferent premise, I decided it's worth opening a new question.
The input to the combinations function would be of the form [2,5,3,1,5,1,11,2], a list where the odd entries are the primes and the even are the number of times they are present. This number would be (2^5)*3*5*(11^2), or 58080. My goal is to print all the combinations (in this case products) of the different factors.
My try is the following (b is the list where i have the primes, and div a blank list where I put the divisors(don't mind 1 as a divisor):
n=len(b)//2
a=1
if a<=n:
for i in range (n):
for g in range (1,b[2*i+1]+1):
div.append (b[2*i]**g)
a+=1
if a<=n:
for i in range (n):
for o in range (i+1,n):
for g in range(1,b[2*i+1]+1):
for h in range(1,b[2*o+1]+1):
div.append ((b[2*i]**g)*(b[2*o]**h))
This adds to the list all the combinations of at most two different prime factors, but there must be a way to continue this to numbers of n different prime factors without mannualy adding more code. But the most important is that it will not generate repeated products. If there is an answer out there please redirect me to it. Thanks in advance to all.
P.S. For example, take 60. 60 will be factored by a function (not showed here) into [2, 2, 3, 1, 5, 1]. My desired output is all the divisors of 60, in order or not, like this [1,2,3,4,5,6,10,12,15,30,60] All the combinations of the products of the factors. (2,2*2,3,5,2*3,2*5,2*2*3,2*2*5,3*5,2*2*3*5 (and 1, that is added to div before or after))
P.P.S. The difference to this (another) question relies on 2 things.
First, and most important, the point of this question isn't finding divisors, but combinations. Divisors is just the context for it, but I would like to know for future problems how to make such iterations. Second, like I said in the comments, even if were about divisors, finding the primes and only then combining them is more efficient (for large N) than looking for numbers until the sqrt, and the refered post revolves around that (for an example why, see in comments).
Your friend is itertools:
from itertools import product
from functools import reduce
def grow(factor, power):
#returns list [1, factor, factor^2, ..., factor^power]
array = []
for pw in range(power+1):
if pw != 0:
k *= factor
else:
k = 1
array.append(k)
return array
x = [2,2,3,1,5,1]
prime_factors = [x[i] for i in range(0, len(x), 2)]
powers = [x[i] for i in range(1, len(x), 2)]
divisor_tree = [grow(*n) for n in zip(prime_factors, powers)]
divisor_groups = product(*divisor_tree)
# returns iterator of [(1, 1, 1), (1, 1, 5), (1, 3, 1), (1, 3, 5), (2, 1, 1), (2, 1, 5), (2, 3, 1), (2, 3, 5), (4, 1, 1), (4, 1, 5), (4, 3, 1), (4, 3, 5)]
result = [reduce(lambda x,y: x*y, n) for n in divisor_groups]
print(result)
Output:
[1, 5, 3, 15, 2, 10, 6, 30, 4, 20, 12, 60]
Now I introduce what it does:
Extracts prime_factors and their powers from your list
zip(prime_factors, powers) pairs them with each other
grow returns list consecutive powers as commented
divisor_groups is iterable of all possible selections from group of these lists, each item taken from separate list
reduce(lambda x,y: x*y, n) maps selected tuple of factors to a product of these factors, e.g. (2,3,5) -> 30
Edit:
You might like to implement grow in this way, less efficient but more readable:
def grow(factor, power):
return [factor**i for i in range(power+1)]

Is there an alternative to python's permutations for generator input?

I am trying to use an unbounded generator in itertools.permutations but it doesn't seem to be working. The return generator is never created because the function just runs forever. To understand what I mean, consider:
from itertools import count, permutations
all_permutations = permutations(count(1), 4)
How I imagine this working is that it generates all possible 4-length permutations of the first 4 natural numbers. Then it should generate all possible 4-length permutations of the first 5 natural numbers, with no repeats so 5 must be included in all of these. What happens though is that python is hung on creating all_permutations.
Before I go off and create my own function from scratch, I am wondering if there is another library that might be able to do what I'm looking for? Also, shouldn't the built-in function here be able to handle this? Is this perhaps a bug that should be worked out?
EDIT: For some iterations...
1 2 3 4
1 2 4 3
...
4 3 2 1
1 2 3 5
1 2 5 3
...
5 3 2 1
1 2 4 5
1 2 5 4
...
Nice question! Here's an efficient method that generates them systematically, without repetitions (and without any need to check):
First the permutations of the first n elements;
then the permutations involving the n+1st element and n-1 of the previous ones;
then those involving the n+2nd element and n-1 of the previous ones, etc.
In other words, the last element drawn is always included in the current batch. This only keeps around a tuple of the consumed source elements (unavoidable, since we'll keep using all of them in permutations).
As you can see, I simplified the implementation a little: Instead of step 1, I initialize the base with n-1 elements and go straight to the main loop.
from itertools import islice, permutations, combinations
def step_permutations(source, n):
"""Return a potentially infinite number of permutations, in forward order"""
isource = iter(source)
# Advance to where we have enough to get started
base = tuple(islice(isource, n-1))
# permutations involving additional elements:
# the last-selected one, plus <n-1> of the earlier ones
for x in isource:
# Choose n-1 elements plus x, form all permutations
for subset in combinations(base, n-1):
for perm in permutations(subset + (x,), n):
yield perm
# Now add it to the base of elements that can be omitted
base += (x,)
Demonstration:
>>> for p in step_permutations(itertools.count(1), 3):
print(p)
(1, 2, 3)
(1, 3, 2)
(2, 1, 3)
(2, 3, 1)
(3, 1, 2)
(3, 2, 1)
(1, 2, 4)
(1, 4, 2)
(2, 1, 4)
(2, 4, 1)
(4, 1, 2)
(4, 2, 1)
(1, 3, 4)
(1, 4, 3)
(3, 1, 4)
(3, 4, 1)
(4, 1, 3)
(4, 3, 1)
(2, 3, 4)
(2, 4, 3)
(3, 2, 4)
...
Something like this:
from itertools import count, permutations
def my_permutations(gen, n=4):
i = iter(gen)
population = []
seen = set()
while True:
for p in permutations(population, n):
if p not in seen:
yield p
seen.add(p)
population.append(next(i))
Beware, the memory usage is growing forever, but as far as I can see there is no way around that.
More efficient version:
def my_permutations(gen, n=4):
i = iter(gen)
population = []
while True:
population.append(next(i))
*first, last = population
perms = permutations(first, n-1)
yield from (p[:i] + (last,) + p[i:] for p in perms for i in range(n))

Number permutations in python iterative

I need to generate permutations of digits, the number can be bigger than the digit count. For my current purpose I need to generate permutations of these digits 0, 1, 2 to get numbers of upto 20 digits length. For example I the first few permutations would be 0, 1, 2, 10, 11, 12, ... 1122, 1211.
There are existing answers using Iterator in Python here or here and those gives the full permutation directly.
But I need to perform some tests over each permutations and if I keep the entire permutations list in memory it becomes too big, especially for 20 digits it comes to 320 permutations.
So my question is can it be done without recursion, so that I can perform the tests over each permutations.
Edit:
I'm looking at permutations with repetitions. So for a number of say 20 digits, each digit can take value from [0, 1, 2]. That's why the number of permutations in that case will come to 320.
What you are looking is called a Cartesian product, not a permutation. Python itertools have a method itertools.product() to produce the desired result:
import itertools
for p in itertools.product(range(3), repeat=4):
print p
Output is 3^4 lines:
(0, 0, 0, 0)
(0, 0, 0, 1)
(0, 0, 0, 2)
(0, 0, 1, 0)
(0, 0, 1, 1)
...
(2, 2, 2, 1)
(2, 2, 2, 2)
To produce output tuples with length form 1 to 4, use an additional iteration:
for l in range(1, 5):
for p in itertools.product(range(3), repeat=l):
print p
Finally, this works for string elements, too:
for i in range(5):
for p in itertools.product(('0', '1', '2'), repeat=i):
print ''.join(p),
print
Output:
0 1 2 00 01 02 10 11 12 20 21 22 000 001 002 010 [...] 2220 2221 2222
Yes, your program could like like this:
import itertools
def perform_test(permutation):
pass
# permutations() does not construct entire list, but yields
# results one by on.
for permutation in itertools.permutations([1, 2, 3, 4, 5], 2):
perform_test(permutation)
While there are ways to do this using itertools etc, here is a way that is a bit different from what you would normally do.
If you were to have a list of these permutations in order, what you would actually have is ternary numbers that represent their place in the list. e.g. list[4] is 11 which is 4 in ternary (3*1+1*1). So you could convert the index value that you want to test into ternary and that would produce the correct value.
While python can do conversion from an integer to its form in that base (e.g. int("11",3) outputs 4) the reverse is not implicitly implemented. There are lots of implementations out there though. Here is a good one (modified for your case):
def digit_to_char(digit):
if digit < 10:
return str(digit)
return chr(ord('a') + digit - 10)
def perm(number):
(d, m) = divmod(number, 3)
if d > 0:
return perm(d) + digit_to_char(m)
return digit_to_char(m)
So if you wanted to find the 20th permutation, you could do perm(20), which would give you 202. So now you can just do a regular loop through the index values that you want. With no storage of big lists in memory.
permutation = 0
i = 0
while len(str(permutation)) < 20:
permutation = perm(i)
do_test(permutation)
i += 1

Categories

Resources