4
\$\begingroup\$

I am about two months into programming with Python (no previous experience) and I tried to write a program which would generate a key and assign it to a student.

import random
import string


code_list = []
students = ["Aljoša", "Blažka", "Nana", "Kaja", "Alja", "Tjaša", "Ana",
           "Gal", "Danijela", "Alma", "Neja", "Žiga K.", "Patricija", "Aja",
           "Kristjan",  "Urban",  "Janja", "Lea", "Žana", "Aljaž", "Tilen",
           "Matic", "Marija", "Žiga T."]

#It generates the code and puts it in a list
size = 6
code = string.ascii_uppercase + string.digits
for i in range(24):
    code_list.append(str(''.join(random.choice(code) for _ in range(size))))

#Combines the two lists (students and codes) into one
students_plus_codes = [x + str(" - " + y) for y in students for x in 
                      code_list]

#It prints only every 25th element from the combined list
new = [x for i, x in enumerate(students_plus_codes) if i % 25 == 0]

for i in new:
    print(i)`

My problem is that this code is not very elegant and just want to ask how I could write it more efficiently (especially the last part where I have to manually print out every 25th element of a list because every key gets assigned to every student).

\$\endgroup\$
6
\$\begingroup\$

The reason that you are getting so many more entries is because of this list comprehension:

 students_plus_codes = [x + str(" - " + y) for y in students for x in code_list]

It's a nested for loop. If we expand it we can see what is happening:

students_plus_codes = []
for y in students:  # students has 24 items
    for x in code_list:  # code_list has 24 items
        students_plus_codes.append(x + str(" - " + y))

So for every iteration of students you do 24 iterations of code_list.

Fortunately, python has a builtin function called zip that will merge lists for us. So we can replace the nested list comprehension with this line:

students_plus_codes = list(zip(students, code_list))

Which results in a list of tuples:

[('Aljoša', '256D2B'), ('Blažka', 'OEGJL9'), ('Nana', 'GB1PJL'), ('Kaja', 'F0P0F2'), 
 ('Alja', '62KU94'), ... ('Matic', 'E7CJIP'), ('Marija', '1D2UCL'), ('Žiga T.', '6X1DD5')]

Generating the codes:

First, I would replace for i in range(24): with for i in range(len(students)): as this allows you to change the number of students without having to change other aspects of your code. In this instance 24 is a magic number that could cause issues down the line.

You could even create a function that generates each code, then you can use an easy to read list comprehension.

# <=python3.5
def generate_code(n, characters):
    return ''.join([random.choice(characters) for _ in range(n)]) 

# >=python3.6
def create_code(n, characters):
    return ''.join(random.choices(characters, k=n))

Printing the codes:

It isn't ideal to join the names and codes as a single string as this makes it hard to use them separately later. For this reason, I used a list of tuples to match them as pairs. To print them the for loop has two variables, student and code that takes advantage of unpacking and then prints them using the format function.

for student, code in students_plus_codes:
    print('{} - {}'.format(code, student))

Altogether:

import random
import string


def create_code(n, characters):
    return ''.join(random.choices(characters, k=n))


students = ["Aljoša", "Blažka", "Nana", "Kaja", "Alja", "Tjaša", "Ana",
           "Gal", "Danijela", "Alma", "Neja", "Žiga K.", "Patricija", "Aja",
           "Kristjan",  "Urban",  "Janja", "Lea", "Žana", "Aljaž", "Tilen",
           "Matic", "Marija", "Žiga T."]

# It generates the code and puts it in a list
key_size = 6
chars = string.ascii_uppercase + string.digits
code_list = [create_code(key_size, chars) for _ in range(len(students))]

# Combines the two lists (students and codes) into a list of tuples
students_plus_codes = list(zip(students, code_list))

for student, code in students_plus_codes:
    print('{} - {}'.format(code, student))
\$\endgroup\$
  • \$\begingroup\$ Thank you, this was really helpful, I would never figure it out on my own :D \$\endgroup\$ – urban pečoler Jan 27 at 21:17
  • \$\begingroup\$ I'm glad it's helped. For a next step, I would check that a key hasn't already been generated. As you have a character space of 36 and string length of 6 there are 36^6 possible codes, which is a lot, but it's still possible there could be a collision! \$\endgroup\$ – Alex Jan 27 at 21:48
  • \$\begingroup\$ oh cool, and how would i do that exacty? I am really that new to all this yeah haha \$\endgroup\$ – urban pečoler Jan 27 at 23:37
  • \$\begingroup\$ @urbanpečoler, python has an object type called sets that doesn't allow duplicates. You could add your keys to a set until it reaches the desired length using a while loop. \$\endgroup\$ – Alex Jan 28 at 14:45
5
\$\begingroup\$

I agree with the points from @Alex answer,


import secrets
from string import digits, ascii_uppercase

SIZE = 6

def create_code(n, characters):
    return ''.join(secrets.choice(characters) for _ in range(n))

print(create_code(SIZE, digits + ascii_uppercase))
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.