Почему «если буква в слове» работает неправильно в Python? - PullRequest
1 голос
/ 12 февраля 2020

Я пытаюсь написать функцию, которая возвращает строковые буквы, которые не находятся в lettersGuessed

words = list('abcdefghijklmnopqrstuvwxyz')

def getAvailableLetters(lettersGuessed):
    [words.remove(letter) for letter in words if letter in lettersGuessed]
    return ''.join([str(elem) for elem in words]) 

, затем я вызываю свою функцию

getAvailableLetters(['e', 's', 'i', 'k', 'p', 'r'])

Но результат отличается от что я ожидал Мой вывод:

'abcdfghjlmnoqstuvwxyz' 

# the letter 's' in the output, but it shouldn't be there.

Правильный вывод:

'abcdfghjlmnoqtuvwxyz' 

что я делаю не так?

Ответы [ 3 ]

1 голос
/ 12 февраля 2020

Я приведу небольшой пример, чтобы увидеть, что происходит, когда вы удаляете элементы из списка во время итерации:

l = ['a', 'b', 'c', 'd', 'e', 'f']
for i, c in enumerate(l):
    print('_' * 25)
    print('iteration', i)
    print('index value', i)
    print('elemnt at index ', i, ':', l[i])
    print('list length:', len(l))
    l.remove(c)
    print('\nafter removing an element')
    print('list length:', len(l))

    print('index value', i)
    if len(l) > i:
        print('elemnt at index ', i, ':', l[i]) # this element will not be removed

print('_' * 40)
print('list after iterateion:', l)\

вывод:

_________________________
iteration 0
index value 0
elemnt at index  0 : a
list length: 6

after removing an element
list length: 5
index value 0
elemnt at index  0 : b
_________________________
iteration 1
index value 1
elemnt at index  1 : c
list length: 5

after removing an element
list length: 4
index value 1
elemnt at index  1 : d
_________________________
iteration 2
index value 2
elemnt at index  2 : e
list length: 4

after removing an element
list length: 3
index value 2
elemnt at index  2 : f
________________________________________
list after iterateion: ['b', 'd', 'f']

при Можно видеть, что если вы удаляете элемент во время итерации списка, вы изменяете размер списка и переходите от одного этапа к другому, переходя к следующему элементу, значение l oop увеличивается на единицу после каждой итерации. ожидая получить следующий элемент, но вы сжимаете список одним элементом, так что для l oop фактически прыгает 1 элемент

, если вы хотите изменить глобальную переменную words, вы можете использовать:

def getAvailableLetters(lettersGuessed):
    global words
    words = [c for c in words if c not in lettersGuessed]

    return ''.join([str(elem) for elem in words])

getAvailableLetters(['e', 's', 'i', 'k', 'p', 'r'])

вывод:

'abcdfghjlmnoqtuvwxyz'

, если вы хотите только вернуть буквы, которых нет в lettersGuessed без изменения глобальной переменной words:

def getAvailableLetters(lettersGuessed):
    return ''.join(c for c in words if c not in lettersGuessed)
1 голос
/ 12 февраля 2020
Функция

remove() уменьшает длину words, тогда как в Python переменная l oop инициализируется для своих пределов только в начале l oop. Попробуйте понять это на небольшом примере

lis = [1, 2, 4, 6, 5]
for i in lis:
    if i % 2 == 0:
        lis.remove(i)

. Этот код выводит [1, 4, 5], тогда как ожидаемый вывод - [1, 5]. Это связано с тем, что i уже был инициализирован до go с индексом от 0 до 5. В первый раз lis.remove() удалил 2, длина lis уменьшилась, и он обновился до lis = [1, 4, 6, 5], и теперь я должен указать на 4 (следующий элемент), тогда как я теперь указываю на 6, то есть на индекс 2 (потому что 2 был с индексом 1) и, следовательно, никогда не проверяет значение 4. То же самое происходит в вашем коде.

Для решения предпочтительнее использовать while l oop. Эта проблема возникает не с while l oop, а только с for for.

1 голос
/ 12 февраля 2020

Причина в том, что вы изменяете список, перебирая его - это не должно быть сделано. Я поделюсь здесь советом, который @AlexMartelli дал в одном из своих ответов :

Никогда не изменяйте контейнер, для которого вы выполняете цикл, потому что итераторы этого контейнера не собираются быть информированным о ваших изменениях, и, как вы заметили, вполне вероятно, что это приведет к совершенно другому l oop и / или неправильному.

Вот альтернативное решение (также использующее snake_case) с использованием наборов - учитывая предположение, что ни доступные буквы, ни предполагаемые буквы не будут содержать дубликатов (если только по ошибке):

all_letters = set('abcdefghijklmnopqrstuvwxyz')

def get_available_letters(letters_guessed):
    available_letters = all_letters - set(letters_guessed)
    return ''.join([str(letter) for letter in available_letters])

Предупреждение, приведенное выше решение может не соответствовать порядку, в котором вы указали ваши письма.

Если вы все еще хотите придерживаться списков, сделайте это в два этапа:

all_letters = list('abcdefghijklmnopqrstuvwxyz')

def get_available_letters(letters_guessed):
    available_letters = [letter for letter in all_letters if letter not in letters_guessed]
    return ''.join([str(letter) for letter in available_letters])
...