Сортировка студентов и какие экзамены они сдают

У меня есть список кортежей, и кортежи выглядят так (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]]

почему он многократно добавляется к последним экзаменам студентов, а не к каждому индивидуально

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
0
51
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

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)

Этот код намного меньше и надежнее. Для большей ясности я создаю новый и отдельный список для каждого члена основного списка во второй строке, чтобы не возникало указанной проблемы.

И в конце я рекомендую вам использовать более короткие имена переменных!

Другие вопросы по теме