Skip to content

Commit

Permalink
Added Python Example and Explanation
Browse files Browse the repository at this point in the history
  • Loading branch information
hrszpuk committed Jul 25, 2022
1 parent 8c62a88 commit 46a942a
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 25 deletions.
150 changes: 150 additions & 0 deletions Python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Thief
In this code challenge we write a program that displays every possible combination of a 4 digit PIN.

## Problem
A thief has managed to find out the four digits for an online PIN code, but doesn't know the correct sequence needed to hack into the account.<br>

Design and write a program that displays all the possible combinations for any four numerical digits entered by the user. The program should avoid displaying the same combination more than once.

## Solution
In the solution (found in `solution.py`) we are going to try and produce every possible combination including duplicates.
I find this solution will be easier for a beginner to follow as it focuses on looping, and less on recursion and generators.

```python
# Function gets every pattern made from a string's characters.
def permutations(string, idx=0):
# We check if the length-1 is the same as the index.
if idx == len(string) - 1:
# This indicates a cycle has been completed and a new pattern has been created.
# We output the new pattern to the user.
print("".join(string))

# Here we simply loop through every character of the string
# NOTE: idx does not change from 0 in this scope only in the recursive scopes
for j in range(idx, len(string)):
letters = list(string)
# Python magic, we are swapping the elements at letters[idx] and letters[j]
letters[idx], letters[j] = letters[j], letters[idx]
# We make a recursive call and increase idx
# Remember once idx is the same as length-1 we will exit back into the original scope.
# Once in the original scope, idx is still 0, and j will increment.
permutations(letters, idx + 1)
```
To see why this solution works, it's best to unravel the loops and function calls.
This allows us to see exactly what's happening without all the hidden parts.
Since unravelling everything can make our code very long, we will swap out a 4 digit PIN with a set of two letters.
```python
# Original scope of the function
permutations(string, idx=0):
string = "ab"
idx = 0
if idx == len(string) - 1: # This is false
print("".join(string))

# First cycle of for loop
j = 0
letters = list(string)
letters[idx], letters[j] = letters[j], letters[idx] # swap "a" with "a" (does nothing)

# Recursive function call (increment idx by 1)
permutations(letters, idx + 1):
idx = 1
if idx == len(letters) - 1: # This is true
print("".join(letters)) # print "ab"
j = 1
letters[idx], letters[j] = letters[j], letters[idx] # swap "b" with "b" (does nothing)

# Recursive function call (increment idx by 1)
permutations(letters, idx + 1):
idx = 2
if idx == len(string) - 1: # This is false
print("".join(string))
j = 2
# Loop does not occur as j and idx are the same.

# Loop finishes because j is equal to len(string).

# Next cycle of the for loop
j = 1
letters[idx], letters[j] = letters[j], letters[idx] # swap "a" with "b" (letters is now "ba")

# Recursive function call (increment idx by 1)
permutations(letters, idx + 1):
idx = 1
if idx == len(letters) - 1: # This is true
print("".join(letters)) # print "ab"
j = 1
letters[idx], letters[j] = letters[j], letters[idx] # swap "b" with "b" (does nothing)

# Recursive function call (increment idx by 1)
permutations(letters, idx + 1):
idx = 2
if idx == len(string) - 1: # This is false
print("".join(string))
j = 2
# Loop does not occur as j and idx are the same.

# Loop finishes because j is equal to len(string).

# Loop finishes
# Function exits
# We have printed both "ab" and "ba", all possible combinations.
```

## Extension
In the extension, our goal is to ensure none of the combinations are duplicates.

### Extension Solution
In our extension solution, we make use of Python's generators, and recursion to make a generator that calls itself and yields back along calls until the user of the generator receives the calls.
The solution below can be found in `solution_advanced.py`.
```python
# Our new function is a generator.
# This means it yields values every cycle of a loop.
def permutations(string):
if len(string) <= 1:
yield string
else:
# The recursive function calls yield values backwards until they reach the
# original function scope, in which they are yielded back to the user.
for i in range(len(string)):
for p in permutations(string[:i] + string[i + 1:]):
yield string[i] + p
```
We would then draw yielded values from the generator using our own loop.
```python
for pattern in permutations("ABC"):
print(pattern)
```
This will print every possible combination from the string "ABC".
However, we must still filter out the duplicates.
To do this, we would create another list and append all values from patterns that is not already in our list.
This means when we first come across a value we append it, if we find a value that is the same we will not append it as it is already in the list.
```python
# Here we loop through the generator's yields to get every pattern possible.
patterns = [pattern for pattern in permutations(PIN)]

# Here we filter out repeated values
unique_patterns = []
for pattern in patterns:
# If a pattern is already in unique_patterns then it is a duplicate.
# We do not print or append the duplicate pattern.
if pattern not in unique_patterns:
unique_patterns.append(pattern)
print(pattern)
```

### Extension Solution 2 (Real world solution)
Python is a language with "batteries included" a phrase meaning the language has lots of built-in ways of handling tasks we may want to do.
In some programming languages, writing our own `permutation` function is required as the language does not have a built-in way of getting the permutations of a string or list.
However, in Python, there is a library called `itertools` already available to us.
`itertools` has a function called `permutations` that allows us to get every possible combination of a string or list very quickly.

An example of `itertools.permutations` can be found in `solution_advanced2.py`.
```python
# itertools.permutations(PIN) is a generator that yields every pattern.
patterns = [pattern for pattern in itertools.permutations(PIN)]
```
The `itertools` library is part of Python's standard library.
This means, whenever you are using Python, you will always have access to `itertools` and `itertools` is written in the C programming language.
This makes `itertools` much faster than our Python versions.

45 changes: 22 additions & 23 deletions Python/solution.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import itertools

word = "cowboy"

x = itertools.permutations(word)
for i in x:
print(i)

a_string = 'cowboy'


def get_permutation(some_string, idx=0):
if idx == len(some_string) - 1:
print("".join(some_string))
for j in range(idx, len(some_string)):
words_list = [c for c in some_string]
words_list[idx], words_list[j] = words_list[j], words_list[idx]

get_permutation(words_list, idx + 1)


permutations = get_permutation(a_string)
print(permutations)
# Function gets every pattern made from a string's characters.
def permutations(string, idx=0):
# We check if the length-1 is the same as the index.
if idx == len(string) - 1:
# This indicates a cycle has been completed and a new pattern has been created.
# We output the new pattern to the user.
print("".join(string))

# Here we simply loop through every character of the string
# NOTE: idx does not change from 0 in this scope only in the recursive scopes
for j in range(idx, len(string)):
letters = list(string)
# Python magic, we are swapping the elements at letters[idx] and letters[j]
letters[idx], letters[j] = letters[j], letters[idx]
# We make a recursive call and increase idx
# Remember once idx is the same as length-1 we will exit back into the original scope.
# Once in the original scope, idx is still 0, and j will increment.
permutations(letters, idx + 1)


PIN = input("Please enter the PIN: ")
permutations(PIN)
25 changes: 25 additions & 0 deletions Python/solution_advanced.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Our new function is a generator.
# This means it yields values every cycle of a loop.
def permutations(string):
if len(string) <= 1:
yield string
else:
# The recursive function calls yield values backwards until they reach the
# original function scope, in which they are yielded back to the user.
for i in range(len(string)):
for p in permutations(string[:i] + string[i + 1:]):
yield string[i] + p


PIN = input("Please enter your PIN: ")
# Here we loop through the generator's yields to get every pattern possible.
patterns = [pattern for pattern in permutations(PIN)]

# Here we filter out repeated values
unique_patterns = []
for pattern in patterns:
# If a pattern is already in unique_patterns then it is a duplicate.
# We do not print or append the duplicate pattern.
if pattern not in unique_patterns:
unique_patterns.append(pattern)
print(pattern)
17 changes: 17 additions & 0 deletions Python/solution_advanced2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Python has built-in tools for what we want to do
import itertools

PIN = input("Please enter the PIN: ")

# itertools.permutations(PIN) is a generator that yields every pattern.
patterns = [pattern for pattern in itertools.permutations(PIN)]

# Here we filter out the repeated patterns.
unique_patterns = []
for pattern in patterns:
# if a pattern is already in unique_patterns then it is a duplicate
if pattern not in unique_patterns:
unique_patterns.append(pattern)
print(pattern)


35 changes: 33 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,37 @@ Design and write a program that displays all the possible combinations for any f
## Solution
*NOTE: The solution explained here is a generalised solution. If you want a language-specific solution read the explanation in the language folder of your choice.*<br>

In this solution, we will produce every combination possible and then filter out the duplicant combinations. This isn't necessarily the most efficient method but will be the easiest to understand. First, we will write a function that takes a string and an index, and produces a list of strings where each letter is replaced by the letter at the index provided. We will then write another function that loops through every index of a string, applies the function previously stated, and stores the combinations. After all that we will filter the list of combinations for duplicants, then finally display the list of combinations to the user.<br>
The solution to this problem is to get the `permutations` of the list. `permutations` are a mathematical construct in which we get every possible combination of items within a `set`.
An example of this is shown below.
```
The permutation of set A={a,b} is 2, such as {a,b}, {b,a}.
```
In our first solution, we will try to brute force the PIN combinations.
We can do this by looping through every single possible combination of numbers made with the numbers of the PIN,
and then filtering out all duplicate letters.
```
pin = INPUT
LOOP FOR EVERY LETTER IN pin
LOOP FOR EVERY LETTER IN pin
LOOP FOR EVERY LETTER IN pin
LOOP FOR EVERY LETTER IN pin
IF NO LETTERS ARE THE SAME
OUTPUT LETTERS COMBINED
```
We can also write this solution in Python to see how we would handle this in actual code.
```python
PIN = input()
for h in PIN:
for j in PIN:
for k in PIN:
for l in PIN:
if h != j and h != k and h != l and j != k and j != l and k != l:
print(h + j + k + l)
```
This method has the most disadvantages.
Firstly, we are looping through every possible combination that can be made with the numbers of the PIN.
This is extremely inefficient. Secondly, our solution only works if every number of the PIN is unique.
So, PINs such as 1292, 0011, 2474, would not work as they have duplicates already.



First let's write a function that produces a list of string where each letter is replaced by the letter at the index provided.

0 comments on commit 46a942a

Please sign in to comment.