Python — Iterating Through Two Lists Simultaneously - python

I'm trying to make a translator for a cipher I made up, and it needs to be able to check 2 lists side by side, but my program does not return any output.
def encrypt(message, key):
for i in range(len(message)):
ans = ""
char = message[i]
if(char == 1):
for j in range(len(key)):
if(key[j] == 1):
ans = 'a'
if(key[j] == 0):
ans = 'c'
if(char == 0):
for j in range(len(key)):
if(key[j] == 1):
ans = 'd'
if(key[j] == 0):
ans = 'b'
return ans
print(encrypt(str(1010), str(1100)))
I want this to output "adcb". I think there's a problem with where I put the return statement? I don't know.

It appears you want to loop over two strings of equal length, containing only the characters '0' and '1' and you want to return a string of equal length containing an 'a' if both characters at that position are '1' for the inputs, etc.
So, something like:
def encrypt(message, key):
code = {('1', '1'): 'a', ('1', '0'): 'c', ('0', '1'): 'd', ('0', '0'): 'b'}
return ''.join(
code[(m, k)] for m, k in zip(message, key)
)
print(encrypt('1010', '1100'))
Result:
adcb
And to provide a 'fixed' version of what you were trying with a loop and indexing (which isn't very pythonic):
def encrypt(message, key):
ans = ''
for i in range(len(message)):
m = message[i]
k = key[i]
if m == '1':
if k == '1':
ans += 'a'
if k == '0':
ans += 'c'
if m == '0':
if k == '1':
ans += 'd'
if k == '0':
ans += 'b'
return ans
Note that I've already removed the superfluous parentheses and the extra loops that give a wrong result.
You could further simplify:
def encrypt(message, key):
ans = ''
for i in range(len(message)):
if message[i] == '1':
if key[i] == '1':
ans += 'a'
else:
ans += 'c'
else:
if key[i] == '1':
ans += 'd'
else:
ans += 'b'
return ans
And from there:
def encrypt(message, key):
ans = ''
for m, k in zip(message, key):
if m == '1':
if k == '1':
ans += 'a'
else:
ans += 'c'
else:
if k == '1':
ans += 'd'
else:
ans += 'b'
return ans
As an added bonus, here's a gnarly one-liner that goes just a bit too far, beyond being pythonic:
def encrypt(message, key):
return ''.join('bdca'[int(m)*2 + int(k)] for m, k in zip(message, key))

The problem is at the conditional statements char == 1 and char == 0. Since a string and a integer are two different data types, the conditional statements evaluate to False and the inner for loops never run, so the function returns no output. To resolve this, change them to char == '1' and char == '0'. Same rule applies for key[j] == 1 and key[j] == 0, because key is passed into the function as a string.

Related

slow runtime for large input values

This code is supposed to take a string of r (red), b (blue) and y (yellow) values and combine them, pairwise, to find one resulting colour. For example, 'rybbry' -> 'brbyb' -> 'yyrr' -> 'ybr' -> 'ry' -> 'b'. It works for small inputs, but forever for big ones. Too many nested loops?
def colour_trio(colours):
while len(colours) > 1:
new_colours = ''
for i in range(len(colours) - 1):
if colours[i:i+2] == 'rr' or colours[i:i+2] == 'by' or colours[i:i+2] == 'yb':
new_colours = new_colours + 'r'
elif colours[i:i+2] == 'bb' or colours[i:i+2] == 'ry' or colours[i:i+2] == 'yr':
new_colours = new_colours + 'b'
else:
new_colours = new_colours + 'y'
colours = new_colours
if len(colours) == 1:
if colours[0] == 'b':
return 'b'
if colours[0] == 'y':
return 'y'
if colours[0] == 'r':
return 'r'
Tried this as well. Still runtime is too long.
def colour_trio(colours):
colours = list(colours)
length = len(colours)
for i in range(length-1):
for k in range(length - 1 - i):
colours[k] = color_sum(colours[k:k+2])
return colours[0]
def color_sum(pair):
if pair[0] == pair[1]:
return pair[0]
if 'r' in pair:
if 'b' in pair:
return 'y'
return 'b'
return 'r'
Here is what the tester file looks like:
def colour_trio_generator(seed):
rng = random.Random(seed)
items = ''
for n in islice(pyramid(3, 4, 1), 10000):
items += rng.choice('ryb')
yield items
if len(items) == n:
items = rng.choice('ryb')
Your code makes use of repeated string concatencation. Each addition operation on a string takes O(n) time, because strings are immutable. This operation occurs O(n^2) times, so the runtime of your algorithm is O(n^3), where n is the length of the string.
One optimization is to use a list to store the letters, and then call ' '.join(), taking the runtime from O(n^3) to O(n^2):
def colour_trio(colours):
result = colours
while len(result) != 1:
letters = []
for fst, snd in zip(result, result[1:]):
if fst == snd:
letters.append(fst)
else:
[letter_to_add] = list(set('ryb') - set(fst) - set(snd))
letters.append(letter_to_add)
result = ''.join(letters)
return result
print(colour_trio("rybbry")) # Prints "b"
I think this is faster than the last solution I posted and faster than other solutions, I think classes make everything tidy looking, but you can re-work it to become a single function.
PS. Thank you for the feedback #KellyBundy! Now the algorithm is correct, it took even fewer chars!
New Solution
colors = ['r', 'b', 'y']
def split(x):
return [f'{Couple(i)}' for i in [f'{x[i]}{x[i+1]}' for i in range(len(x)-1)]]
class Couple:
def __init__(self, x) -> None:
self.a = x[0]
self.b = x[1]
def __repr__(self) -> str:
if self.a == self.b:
return self.a
return (set(colors).difference(set((self.a, self.b))).pop())
def ColoursTrio(x):
while len(x) > 1:
x = split(x)
return x[0]
print(ColoursTrio('bbr'))
or even shorter (and faster):
def ColoursTrio(x):
while len(x) > 1:
x = [f'''{i[0] if i[0] == i[1]
else set("rby").difference(set((i[0],
i[1]))).pop()}''' for i in [f'{x[i]}{x[i+1]}'
for i in range(len(x)-1)]]
return x[0]
print(ColoursTrio('bbr'))
Old Solution
This is almost a bit convoluted but I think it's faster, I also think it's very similar to the one posted by #BrokenBenchmark (and I think his is better) so I'll add his example string too!
COLORS = ['r','b','y']
class ColorsCouple:
def __init__(self, x:str, y:str) -> None:
self.x = x
self.y = y
def mix(self):
if self.x == self.y:
return f'{self.x}'
if 'y' in (self.x, self.y):
return set(COLORS).difference((self.x, self.y)).pop()
return 'y'
class Colors:
def __init__(self, colors:str) -> None:
self.colors = self.get_couples([*colors])
def get_couples(self, element):
return [(element[i],element[i+1]) for i in range(len(element)-1)]
def get_color(self):
colors = [ColorsCouple(*couples).mix() if len(couples)==2 else couples[0] for couples in self.colors]
while len(colors)>1:
colors = self.get_couples(colors)
colors = [ColorsCouple(*couples).mix() if len(couples)==2 else couples[0] for couples in colors]
return colors[0]
def __repr__(self) -> str:
return f'{self.get_color()}'
print(Colors('bbr'))
print(Colors('rybbry'))
Another solution, first some benchmark results, with random strings of 70 to 560 letters (I think 140 is the maximum in the actual tests):
n= 70 n=140 n=280 n=560
0.03 0.06 0.11 0.24 Kelly_linear (I might show this one later)
0.47 1.71 6.58 25.85 Kelly (this is the one I am already showing)
0.77 3.17 12.50 52.57 Jacopo_2
1.59 6.38 25.62 107.81 Jacopo_1
1.69 7.09 27.93 110.67 Fabio
1.82 7.69 30.17 120.77 BrokenBenchmark
My solution is the function Kelly below. First I double every letter, then trim first and last letter. So rybbry becomes ryybbbbrry. Essentially that gives me all the pairs, if you think spaces into it then it would be ry yb bb br ry. Then I replace pairs of different letters with what that pair shall become, but in uppercase. So I get BRbbYB. Then replace pairs of equal letters with what that pair shall become: BRBYB. Then just go back to lower case. Repeat until done.
Full benchmark code (Try it online!):
def Kelly(colours):
while len(colours) != 1:
colours = (colours
.replace('b', 'bb')
.replace('r', 'rr')
.replace('y', 'yy')
[1:-1]
.replace('br', 'Y').replace('rb', 'Y')
.replace('by', 'R').replace('yb', 'R')
.replace('ry', 'B').replace('yr', 'B')
.replace('bb', 'B')
.replace('rr', 'R')
.replace('yy', 'Y')
.lower())
return colours
def Jacopo_1(colours):
while len(colours) > 1:
new_colours = ''
for i in range(len(colours) - 1):
if colours[i:i+2] == 'rr' or colours[i:i+2] == 'by' or colours[i:i+2] == 'yb':
new_colours = new_colours + 'r'
elif colours[i:i+2] == 'bb' or colours[i:i+2] == 'ry' or colours[i:i+2] == 'yr':
new_colours = new_colours + 'b'
else:
new_colours = new_colours + 'y'
colours = new_colours
if len(colours) == 1:
if colours[0] == 'b':
return 'b'
if colours[0] == 'y':
return 'y'
if colours[0] == 'r':
return 'r'
def Jacopo_2(colours):
colours = list(colours)
length = len(colours)
for i in range(length-1):
for k in range(length - 1 - i):
colours[k] = color_sum(colours[k:k+2])
return colours[0]
def color_sum(pair):
if pair[0] == pair[1]:
return pair[0]
if 'r' in pair:
if 'b' in pair:
return 'y'
return 'b'
return 'r'
def BrokenBenchmark(colours):
result = colours
while len(result) != 1:
letters = []
for fst, snd in zip(result, result[1:]):
if fst == snd:
letters.append(fst)
else:
[letter_to_add] = list(set('ryb') - set(fst) - set(snd))
letters.append(letter_to_add)
result = ''.join(letters)
return result
def Fabio(x):
while len(x) > 1:
x = [f'''{i[0] if i[0] == i[1]
else set("rby").difference(set((i[0],
i[1]))).pop()}''' for i in [f'{x[i]}{x[i+1]}'
for i in range(len(x)-1)]]
return x[0]
lengths = 70, 140, 280, 560
funcs = [
Kelly,
Jacopo_2,
Jacopo_1,
Fabio,
BrokenBenchmark,
]
from timeit import default_timer as timer, repeat
import random
Tss = [[] for _ in funcs]
for length in lengths:
tss = [[] for _ in funcs]
for _ in range(5):
colours = ''.join(random.choices('bry', k=length))
def run():
global result
result = func(colours)
expect = None
for func, ts in zip(funcs, tss):
t = min(repeat(run, number=1))
if expect is None:
expect = result
assert result == expect
ts.append(t)
# print(*('%7.3f ms ' % (t * 1e3) for t in sorted(ts)[:3]), func.__name__)
# print()
print(*(f' n={length:3} ' for length in lengths))
for func, Ts, ts in zip(funcs, Tss, tss):
Ts.append(min(ts))
print(*('%6.2f ' % (t * 1e3) for t in Ts), func.__name__)
print()

How do i roll mutiples dices in one command

I'm making a discord bot, i already made a dice rolling systen, but I want to improve it. I want to roll multiple dices at once, like Avrae can do.
#client.command(aliases=['r', 'dado', 'dice'])
async def roll(ctx, dados='', numero=20, conta='', ficha=''):
rolagem = random.randint(1,int(numero))
if conta == '':
total = (int(rolagem))
elif conta == '+':
total = (int(rolagem) + int(ficha))
elif conta == '-':
total = (int(rolagem) - int(ficha))
elif conta == 'x':
total = (int(rolagem) * int(ficha))
elif conta == '/':
total = (int(rolagem) / int(ficha))
if ficha == '':
ficha = ''
if total < 0:
total = '1'
if total == 0:
total == '1'
if rolagem == 20:
rolagem = '**20**'
if rolagem == 1:
rolagem = '**1**'
await ctx.send(f'{ctx.author.mention} 🎇 \n**Resultado**: D{numero} ({rolagem}) {conta} {ficha}\n**Total**: {total}')
So, the command should work like: (prefix)r (number of dices to roll) (dice), and show the results like: (number of dices rolled):(dices results)(sum of the dices result)
For exemple: -r 5 d20; Results for 5 D20:(1, 5, 8, 16, 20) (sum).
I want to know how I shoul do it
Here is a roll function I wrote, modified to use strings like you did:
import random
def roll(n='1', d='20', a='+', m='0'):
res = []
for i in range(int(n)):
res.append(random.randint(1, int(d)))
roll_modified = sum(res)
if a == '+' or a == '-' or a == '*' or a == '/':
roll_modified = eval('{}{}{}'.format(roll_modified, a, int(m)))
if roll_modified < 1:
roll_modified = 1
return roll_modified, res
It uses eval to apply the modifiers, so use with caution. I check the value of the operator and use int() on the number to ensure it is a safe expression.
Use it like this:
roll_modified, res = roll('3', '20', '-', '2')
roll_modified is the result after applying sum and modifiers.
res is a list of all the original rolls
You could then print your results with:
res_str = ["**{}**".format(x) if x == 20 or x == 1 else str(x) for x in res]
print(roll_modified, "[{}]".format(", ".join(res_str)))
will output:
36 [14, 4, **20**]

Iterating through list (Python)

I have a list that extends on like this and I would like to sort them based on the first 2 digits of the second part of each. I'm really rushed right now so some help would be nice.
collection = ["81851 19AJA01", "68158 17ARB03", "104837 20AAH02",
I tried this and it didn't work. I'm not doing this for a class I'd really appropriate some help
for x in collection:
counter = 0
i=0
for y in len(str(x)):
if (x[i] == '1'):
counter == 1
elif (x[i] == '2'):
counter == 2
elif x[i] == '0' and counter == 2:
counter = 2
elif x[i] == '9' and counter == 1:
counter = 3
elif x[i] == '8' and counter == 1:
counter = 4
elif x[i] == '7' and counter == 1:
counter = 5
i = i + 1
if (counter==2):
freshmen.append(x)
elif (counter==3):
sophomores.append(x)
elif (counter==4):
juniors.append(x)
elif (counter==5):
seniors.append(x)
Use the key function to define a custom sorting rule:
In [1]: collection = ["81851 19AJA01", "68158 17ARB03", "104837 20AAH02"]
In [2]: sorted(collection, key=lambda x: int(x.split()[1][:2]))
Out[2]: ['68158 17ARB03', '81851 19AJA01', '104837 20AAH02']

Given a String, What is the Length of the One of the Longest WFF in Polish Notation?

I'm trying to write a version of always popular Count-A-WFF section of the WFF 'N Proof game (no copyright infringement intended) in Python. Alright, not so popular.
I think I have everything up and running up as desired for up to the case of a 4 letter string.
def maximum_string(s):
if cs(s) == True:
return len(s)
elif len(s) == 2:
l1 = [cs(s[0]), cs(s[1])]
if True in l1:
return len(s) - 1
else:
return 0
elif len(s) == 3:
first = s[0] + s[1]
second = s[0] + s[2]
third = s[1] + s[2]
l1 = [cs(first), cs(second), cs(third)]
if True in l1:
return len(s) - 1
l2 = [cs(s[0]), cs(s[1]), cs(s[2])]
if True in l2:
return len(s) - 2
else:
return 0
elif len(s) == 4:
first = s[0]+s[1]+s[2]
second = s[0]+s[1]+s[3]
third = s[1]+s[2]+s[3]
fourth = s[0]+s[2]+s[3]
l1 = [cs(first), cs(second), cs(third), cs(fourth)]
if True in l1:
return 3
first = s[0] + s[1]
second = s[0] + s[2]
third = s[0] + s[3]
fourth = s[1] + s[2]
fifth = s[1] + s[3]
sixth = s[2] + s[3]
l2 = [cs(first), cs(second), cs(third), cs(fourth), cs(fifth), cs(sixth)]
if True in l2:
return 2
first = s[0]
second = s[1]
third = s[2]
fourth = s[3]
l3 = [cs(first), cs(second), cs(third), cs(fourth)]
if True in l3:
return 1
else:
return 0
def cs(string):
global length_counter, counter, letter
counter = 1
length_counter = 0
letters_left = len(string)
while letters_left != 0 and length_counter < len(string):
letter = string[length_counter]
if letter == 'C' or letter == 'A' or letter == 'K' or letter == 'E' or letter == "K":
counter += 1
elif letter == 'N':
counter += 0
else:
counter -= 1
length_counter += 1
letters_left -= 1
if counter == 0 and len(string) == length_counter:
return True
else:
return False
The maximum_string helper function is intended to, given any string S, find the length of one of the longest possible wffs that you can make from just the letters of S. Of course, I can continue the pattern I currently have for the maximum_string helper function up to a length of 13. But, combinatorial explosion is evident. Thus, is there a more elegant way to finish off the maximum string helper function?
In effect one of the functions I had earlier would return a distance of how far away a string is from having a permutation in Polish notation. Thus this was surprisingly simpler to fix than I expected. Here's what I was looking for:
def maximum_string(string):
global length_counter, counter, letter
counter = 1
length_counter = 0
letters_left = len(string)
while letters_left != 0 and length_counter < len(string):
letter = string[length_counter]
if letter == 'C' or letter == 'A' or letter == 'K' or letter == 'E' or letter == "K":
counter += 1
elif letter == 'N':
counter += 0
else:
counter -= 1
length_counter += 1
letters_left -= 1
if ('p' in string) or ('q' in string) or ('r' in string) or ('s' in string) or ('t' in string) or ('u' in string):
return len(string) - abs(counter)
else:
return 0

Vigenere Cipher Python 2.0

Im having trouble with encoding / decoding programming for a vigenere cipher. Im only supposed to use lists, dictionaries and loops.
EDIT: I added in the decrypt i have. GetCharList() just gets a list containing the alphabet. I dont know what is wrong that its making the output of the decrpyt not the original message.
def encryptVig(msg, keyword):
alphabet = getCharList() #Get char list is another function which creates a list containing a - z
key = keyword.upper()
keyIndex = 0
dicList = []
for symbol in msg:
num = alphabet.find(key[keyIndex])
if num != -1:
num += alphabet.find(key[keyIndex])
alphabet.find(key[keyIndex])
num%= len(alphabet)
if symbol.isupper():
dicList.append(alphabet[num])
elif symbol.islower():
dicList. append(alphabet[num].lower())
keyIndex += 1
if keyIndex == len(key):
keyIndex = 0
else:
dicList.append(symbol)
return " " .join(dicList)
def decryptVig(msg, keyword):
getCharList()
key = keyword.upper()
keyIndex = 0
dicList = []
for symbol in msg:
num = alphabet.find(key[keyIndex])
if num != -1:
num -= alphabet.find(key[keyIndex])
alphabet.find(key[keyIndex])
num%= len(alphabet)
if symbol.isupper():
dicList.append(alphabet[num])
elif symbol.islower():
dicList. append(alphabet[num].lower())
keyIndex -= 1
if keyIndex == len(key):
keyIndex = 0
else:
dicList.append(symbol)
return " " .join(dicList)
Rather than hacking through the alphabet yourself, another approach would be to use ord and chr to remove some of the complexity of working with letters. At the very least consider using itertools.cycle and itertools.izip to construct a list of the encryption/decryption pairs. Here's how I would solve it:
def letters_to_numbers(str):
return (ord(c) - ord('A') for c in str)
def numbers_to_letters(num_list):
return (chr(x + ord('A')) for x in num_list)
def gen_pairs(msg, keyword):
msg = msg.upper().strip().replace(' ', '')
msg_sequence = letters_to_numbers(msg)
keyword_sequence = itertools.cycle(letters_to_numbers(keyword))
return itertools.izip(msg_sequence, keyword_sequence)
def encrypt_vig(msg, keyword):
out = []
for letter_num, shift_num in gen_pairs(msg, keyword):
shifted = (letter_num + shift_num) % 26
out.append(shifted)
return ' '.join(numbers_to_letters(out))
def decrypt_vig(msg, keyword):
out = []
for letter_num, shift_num in gen_pairs(msg, keyword):
shifted = (letter_num - shift_num) % 26
out.append(shifted)
return ' '.join(numbers_to_letters(out))
msg = 'ATTACK AT DAWN'
keyword = 'LEMON'
print(encrypt_vig(msg, keyword))
print(decrypt_vig(encrypt_vig(msg, keyword), keyword))
>>> L X F O P V E F R N H R
A T T A C K A T D A W N
I don't know how Vigenere is supposed to work. However I am quite sure that after
num = alphabet.find(key[keyIndex])
if num != -1:
num -= alphabet.find(key[keyIndex])
num is zero.

Categories

Resources