У меня есть список кортежей, и кортежи выглядят так (2, 11), что означает, что экзамен 2 должен быть сдан студентом 11. Экзамены пронумерованы от 0 до любого количества экзаменов, и то же самое со студентами. Мне нужно создать 2D-список, где первый список — это экзамены, которые сдает 0-й студент, а второй список — это экзамены, которые сдает студент номер 1 и т. д. У меня есть этот код:
examsEachStudentsIsDoing = []
exams = []
number_of_students = 14
exams_to_students = [(0, 1), (0, 4), (0, 5), (0, 3), (0, 10), (0, 13), (0, 9), (0, 11), (0, 12), (0, 2), (0, 7), (0, 6), (1, 7), (2, 7), (2, 5), (2, 0), (2, 11), (2, 13), (3, 4), (4, 6), (4, 8)]
for i in range(0,number_of_students):
exams.clear()
for j in range(0,len(exams_to_students)):
if (exams_to_students[j][1]==i):
exams.append(exams_to_students[j][0])
examsEachStudentsIsDoing.append(exams)
print(examsEachStudentsIsDoing)
если я добавлю строку печати непосредственно перед examsEachStudentsIsDoing.append(exams)
, я получу результат:
[2]
[0]
[0]
[0]
[0, 3]
[0, 2]
[0, 4]
[0, 1, 2]
[4]
[0]
[0]
[0, 2]
[0]
[0, 2]
[[0, 2], [0, 2], [0, 2], [0, 2], [0, 2], [0, 2], [0, 2], [0, 2], [0, 2], [0, 2], [0, 2], [0, 2], [0, 2], [0, 2]]
почему он многократно добавляется к последним экзаменам студентов, а не к каждому индивидуально
exams
— это список. В Python списки передаются по ссылке, поэтому, когда вы добавляете exams
к examsEachStudentsIsDoing
, вы просто добавляете ссылку на exams
в examsEachStudentsIsDoing
.
В конце цикла для последнего ученика exams
устанавливается на [0,2]
, следовательно, для всех записей в examsEachStudentsIsDoing
вы видите это значение.
Таким образом, вместо того, чтобы добавлять exams
, вы можете добавить копию текущих экзаменов учащегося к examsEachStudentsIsDoing
. Чтобы получить копию списка, у вас есть разные варианты — метод list.copy() , метод copy.copy() или просто нарезка list[:]
.
Попробуйте следующий код -
examsEachStudentsIsDoing = []
exams = []
number_of_students = 14
exams_to_students = [(0, 1), (0, 4), (0, 5), (0, 3), (0, 10), (0, 13), (0, 9), (0, 11), (0, 12), (0, 2), (0, 7), (0, 6), (1, 7), (2, 7), (2, 5), (2, 0), (2, 11), (2, 13), (3, 4), (4, 6), (4, 8)]
for i in range(0,number_of_students):
exams.clear()
for j in range(0,len(exams_to_students)):
if (exams_to_students[j][1]==i):
exams.append(exams_to_students[j][0])
examsEachStudentsIsDoing.append(exams.copy()) #updated
print(examsEachStudentsIsDoing)
Выход:
[[2], [0], [0], [0], [0, 3], [0, 2], [0, 4], [0, 1, 2], [4], [0], [0], [0, 2], [0], [0, 2]]
Чтобы избежать таких проблем, вы можете создать новый список exams
для каждого ученика, поэтому лучшим способом переписать код может быть -
examsEachStudentsIsDoing = []
number_of_students = 14
exams_to_students = [(0, 1), (0, 4), (0, 5), (0, 3), (0, 10), (0, 13), (0, 9), (0, 11), (0, 12), (0, 2), (0, 7), (0, 6), (1, 7), (2, 7), (2, 5), (2, 0), (2, 11), (2, 13), (3, 4), (4, 6), (4, 8)]
for i in range(0,number_of_students):
exams = []
for j in range(0,len(exams_to_students)):
if (exams_to_students[j][1]==i):
exams.append(exams_to_students[j][0])
examsEachStudentsIsDoing.append(exams)
print(examsEachStudentsIsDoing)
По желанию можно использовать словарь , я использовал json просто для удобства печати. Кроме того, данные не обрабатываются, что означает, что один и тот же экзамен может сдаваться дважды.
import json
number_of_students = 14
exams_to_students = [(0, 1), (0, 4), (0, 5), (0, 3), (0, 10), (0, 13), (0, 9), (0, 11), (0, 12), (0, 2), (0, 7), (0, 6), (1, 7), (2, 7), (2, 5), (2, 0), (2, 11), (2, 13), (3, 4), (4, 6), (4, 8)]
"""
(2, 11) which means exam 2 must be taken by student 11
"""
disposal = {}
# Create a key for every student
for i in range(0, number_of_students):
disposal[i] = { 'exams': []}
# loop through tuples
# add exam to the designated student
for value in exams_to_students:
disposal[value[1]]['exams'].append(value[0])
json_object = json.dumps(disposal, indent=4)
print(json_object)
#Output
{
"0": {
"exams": [
2
]
},
"1": {
"exams": [
0
]
},
"2": {
"exams": [
0
]
},
"3": {
"exams": [
0
]
},
"4": {
"exams": [
0,
3
]
},
"5": {
"exams": [
0,
2
]
},
"6": {
"exams": [
0,
4
]
},
"7": {
"exams": [
0,
1,
2
]
},
...
}
Чтобы избежать дублирования значений:
# loop through your tuples
for value in exams_to_students:
if value[0] not in disposal[value[1]]['exams']:
disposal[value[1]]['exams'].append(value[0])
Я согласен с @Daniel Hao, defaultdict предлагает самое простое решение для этого. Старайтесь избегать сложных решений, если можете.
from collections import defaultdict
exams_to_students = [(0, 1), (0, 4), (0, 5), (0, 3),
(0, 10), (0, 13), (0, 9), (0, 11),
(0, 12), (0, 2), (0, 7), (0, 6),
(1, 7), (2, 7), (2, 5), (2, 0),
(2, 11), (2, 13), (3, 4), (4, 6), (4, 8)]
tracker = defaultdict(list)
for (exam, student) in exams_to_students:
tracker[student].append(exam)
print("Exams by student using a defaultdict")
for student in sorted(tracker.keys()):
print(student, tracker[student])
print("Exams by student using a list")
exams_by_student_as_list = [tracker[student] for student in sorted(tracker.keys())]
for exams in exams_by_student_as_list:
print(exams)
Списки в питоне (также и другие объекты) изменяемы. Это подробная статья, но вкратце: когда вы создаете список и сохраняете его в переменной, такой как l
, тогда l
будет указывать на место в памяти, которое List создал там. Когда вы назначаете другую переменную для этого списка с помощью a=l
(или, в вашем случае, вы добавляете ее в другой список), она будет использовать тот же указатель, поэтому a
и l
будут указывать на одно и то же место в вашей памяти.
когда вы фиксируете список (добавляете, удаляете, очищаете,...), эти функции изменят список в вашей памяти, могут сказать, что они меняют ссылку, и когда вам нужны данные, все переменные, указывающие на это место, вернут только такое же значение.
Есть много решений этой проблемы, одно из них — заменить exam.clear()
на exam = []
:
examsEachStudentsIsDoing = []
# no need to write exams = [] here
number_of_students = 14
exams_to_students = [(0, 1), (0, 4), (0, 5), (0, 3), (0, 10), (0, 13), (0, 9), (0, 11), (0, 12), (0, 2), (0, 7), (0, 6), (1, 7), (2, 7), (2, 5), (2, 0), (2, 11), (2, 13), (3, 4), (4, 6), (4, 8)]
for i in range(0,number_of_students):
print(f'{i=}')
exams = [] # replaced exams.clear()
for j in range(0,len(exams_to_students)):
if (exams_to_students[j][1]==i):
print(f'index ({j}) = {exams_to_students[j]}')
exams.append(exams_to_students[j][0])
print(f'{exams=}')
examsEachStudentsIsDoing.append(exams)
print(examsEachStudentsIsDoing)
Но я также могу улучшить ваш код и переписать его так:
exams_to_students = [(0, 1), (0, 4), (0, 5), (0, 3), (0, 10), (0, 13), (0, 9), (0, 11), (0, 12), (0, 2), (0, 7), (0, 6), (1, 7), (2, 7), (2, 5), (2, 0), (2, 11), (2, 13), (3, 4), (4, 6), (4, 8)]
examsEachStudentsIsDoing = [list() for _ in exams_to_students]
for exam, student in exams_to_students:
examsEachStudentsIsDoing[student].append(exam)
Этот код намного меньше и надежнее. Для большей ясности я создаю новый и отдельный список для каждого члена основного списка во второй строке, чтобы не возникало указанной проблемы.
И в конце я рекомендую вам использовать более короткие имена переменных!