Loop "забуде", щоб видалити деякі елементи

в цьому коді я намагаюся створити функцію anti_vowel, яка видаляє всі голосні (aeiouAEIOU) з рядка. Я думаю, повинен працювати нормально, але, коли я його запускаю, зразок тексту "Гей, дивись слова!" повертається як "Hy LS Words!". Він "забув" видалити останній "о". Як це може бути?

text = "Hey look Words!"

def anti_vowel(text):

    textlist = list(text)

    for char in textlist:
        if char.lower() in 'aeiou':
            textlist.remove(char)

    return "".join(textlist)

print anti_vowel(text)
76
remove_vowels буде краще, ніж anti_vowel
додано Автор Gordon Gustafson, джерело
@CrazyJugglerDrummer Я б оцифрував цей коментар п'ять разів, якщо зможу.
додано Автор Henry Keiter, джерело
Тестування та видалення має складність N ^ 2: просто видаліть символ, незалежно від того, присутній він чи ні ... (або використовуйте інші запропоновані рішення)
додано Автор Don, джерело
Так, але якщо "якщо" не так багато (він має складність "5"): N ^ 2 має значення "for" і ".remove"
додано Автор Don, джерело
Просто спростити логіку: для символу в 'aeiouAEIOU': textlist.remove (char)
додано Автор Don, джерело
@NickT Дон правий. Поведінка O (n ^ 2) полягає в тому, що всередині remove є вкладений цикл, а не через ітерацію над голосними. Це ітерація для символу в текстовому списку , а також видалення з textlist , який є вкладеного пошуку textlist .
додано Автор Kaz, джерело
@Don: O (n ^ 2) де n таке, довжина вхідного тексту?
додано Автор LarsH, джерело
@Don не було б просто O (n * k), де k - це кількість голосних для перевірки, і якщо k << n в основному O (n)?
додано Автор Nick T, джерело
Кожного разу я бачу фрагменти з Python, які я думаю " хех, забавні імена методів ". Це, ймовірно, вибіркова пам'ять, хоча ... (відмова від відповідальності: смішно! == добре)
додано Автор fjdumont, джерело
Спробуйте використати еквівалент ASCII, щоб він не пропускав жодних голосних
додано Автор ButtahNBred, джерело

10 Відповіді

Ви модифікуєте список, який ви повторюєте, що обов'язково призведе до деякої неінутивної поведінки. Замість цього створіть копію списку, щоб ви не видаляли елементи з того, що ви повторюєте.

for char in textlist[:]: #shallow copy of the list
    # etc

Щоб прояснити поведінку, яку ви бачите, перевірте це. Покладіть print char, textlist на початку вашого (оригінального) циклу. Ви б очікували, можливо, що це буде надруковувати вашу струну вертикально разом зі списком, але те, що ви дійсно отримаєте, це:

H ['H', 'e', 'y', ' ', 'l', 'o', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!']
e ['H', 'e', 'y', ' ', 'l', 'o', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!']
  ['H', 'y', ' ', 'l', 'o', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!'] # !
l ['H', 'y', ' ', 'l', 'o', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!']
o ['H', 'y', ' ', 'l', 'o', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!']
k ['H', 'y', ' ', 'l', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!'] # Problem!!
  ['H', 'y', ' ', 'l', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!']
W ['H', 'y', ' ', 'l', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!']
o ['H', 'y', ' ', 'l', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!'] 
d ['H', 'y', ' ', 'l', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!']
s ['H', 'y', ' ', 'l', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!']
! ['H', 'y', ' ', 'l', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!']
Hy lk Words!

Так що відбувається? Приємний цикл for x in y в Python - це просто синтаксичний цукор: він як і раніше отримує доступ до елементів списку за індексом. Таким чином, коли ви видалите елементи зі списку, перекриваючи його, ви починаєте пропускати значення (як ви бачите вище). У результаті ви ніколи не побачите другий o в "look" ; ви пропускаєте його, оскільки індекс просунув "минуле", коли ви видалили попередній елемент. Тоді, коли ви потрапляєте в o в "Words" , ви йдете, щоб видалити першу появу 'o' , яка є такою Ви пропустили раніше.


Як інші згадували, перелік сприйняття, мабуть, ще краще (чистіше, зрозуміліше), як це зробити. Використовуйте той факт, що рядки Python є ітерабельними:

def remove_vowels(text): # function names should start with verbs! :)
    return ''.join(ch for ch in text if ch.lower() not in 'aeiou')
152
додано
@ TC1 Є приклад для filter і, звичайно ж, для str.translate . Я особисто думаю, що розуміння списку є більш читабельним, ніж будь-який з цих двох; отже мій вибір :)
додано Автор Henry Keiter, джерело
str є ітерабельний, filter , можливо, буде чистішим, ніж розуміння списку.
додано Автор TC1, джерело

Інші відповіді говорять вам, чому for пропускає елементи при зміні списку. Ця відповідь розповідає вам, як потрібно видалити символи в рядку без явного циклу.

Використовуйте str.translate() :

vowels = 'aeiou'
vowels += vowels.upper()
text.translate(None, vowels)

Це видаляє всі символи, перелічені у другому аргументі.

Демо:

>>> text = "Hey look Words!"
>>> vowels = 'aeiou'
>>> vowels += vowels.upper()
>>> text.translate(None, vowels)
'Hy lk Wrds!'
>>> text = 'The Quick Brown Fox Jumps Over The Lazy Fox'
>>> text.translate(None, vowels)
'Th Qck Brwn Fx Jmps vr Th Lzy Fx'

У Python 3 метод str.translate() (Python 2: unicode.translate() ) відрізняється тим, що він не приймає deletechars параметр; Перший аргумент - це словник, який вказує унікальні унікальні значення (цілі значення) на нові значення. Використовуйте None для будь-якого символу, який потрібно видалити:

# Python 3 code
vowels = 'aeiou'
vowels += vowels.upper()
vowels_table = dict.fromkeys(map(ord, vowels))
text.translate(vowels_table)

Ви також можете скористатись str.maketrans() статичний метод для створення цього відображення:

vowels = 'aeiou'
vowels += vowels.upper()
text.translate(text.maketrans('', '', vowels))
65
додано
Можливо, для python3 може бути корисною примітка: text.translate (dict.fromkeys (map (ord, vowels)))
додано Автор Bakuriu, джерело
@ Бакуріу: дійсно; те ж саме відноситься до unicode.translate() на Python 2, який у будь-якому випадку є одним і тим же.
додано Автор Martijn Pieters, джерело

Цитування з документів :

Примітка : існує тонкість, коли послідовність змінюється на   цикл (це може відбуватися лише для змінних послідовностей, тобто списку). Ан   Внутрішній лічильник використовується для відстеження того, який елемент використовується далі, і   це збільшується на кожну ітерацію. Коли цей лічильник досяг   довжина послідовності закінчується циклом. Це означає, що якщо   Люкс видаляє поточний (або попередній) елемент з послідовності   наступний пункт буде пропущено (оскільки він отримує індекс поточного елемента   який вже оброблений). Аналогічно, якщо в комплект вставляється a   елемент у послідовності перед поточним елементом, поточний елемент буде   знову обробляється в наступному циклі. Це може призвести до неприємності   помилки, які можна уникнути, зробивши тимчасову копію, використовуючи шматочок   вся послідовність, наприклад,

for x in a[:]:
    if x < 0: a.remove(x)

Ітератуйте над неглибокою копією списку за допомогою [:] . Ви модифікуєте список, повторюючи його, це призведе до втрати деяких букв.

Цикл for відстежує індекс, тому при видаленні елемента за індексом i наступний елемент у позиції i + 1 зміщується на поточний індекс ( i ), а значить, у наступній ітерації ви дійсно виберете елемент i + 2 .

Давайте зробимо легкий приклад:

>>> text = "whoops"
>>> textlist = list(text)
>>> textlist
['w', 'h', 'o', 'o', 'p', 's']
for char in textlist:
    if char.lower() in 'aeiou':
        textlist.remove(char)

Iteration 1: Index = 0.

char = 'W' as it is at index 0. As it doesn't satisfies that condition you'll do noting.

Ітерація 2: Індекс = 1.

char = 'h' as it is at index 1. Nothing more to do here.

Ітерація 3: Індекс = 2.

char = 'o' as it is at index 2. As this item satisfies the condition so it'll be removed from the list and all the items to it's right will shift one place to the left to fill the gap.

тепер textlist стає таким:

   0    1    2    3    4
`['w', 'h', 'o', 'p', 's']`

Як ви бачите інший 'o' , переміщений до індексу 2, тобто поточний індекс, тому його буде пропущено у наступній ітерації. Отже, це є причиною того, що деякі елементи призводять до пропуску в ітерації. Щоразу, коли ви видаляєте елемент, наступний елемент пропускається з ітерації.

Ітерація 4: Індекс = 3.

char = 'p' as it is at index 3.

....


Виправити:

Зверніть увагу на неглибоку копію списку, щоб вирішити цю проблему:

for char in textlist[:]:        #note the [:]
    if char.lower() in 'aeiou':
        textlist.remove(char)

Інші альтернативи:

Сприйняття списку:

Однокласник, який використовує str.join та comprehension list :

vowels = 'aeiou'
text = "Hey look Words!"
return "".join([char for char in text if char.lower() not in vowels])

регулярний вираз:

>>> import re
>>> text = "Hey look Words!"
>>> re.sub('[aeiou]', '', text, flags=re.I)
'Hy lk Wrds!'
31
додано
re.sub ('[aeiou]', '', flags = re.I) простіше (особливо, якщо список символів зростає довше).
додано Автор Jon Clements, джерело

Ви змінюєте дані, які ви повторюєте. Не робіть цього.

''.join(x for x in textlist in x not in VOWELS)
16
додано
text = "Hey look Words!"

print filter(lambda x: x not in "AaEeIiOoUu", text)

Вихід

Hy lk Wrds!
8
додано

Ви повторюєте список і видаляєте елементи з нього одночасно.

По-перше, я повинен переконатися, що ви чітко розумієте роль char в для символу в текстовому списку: ... . Візьміть ситуацію, коли ми дійшли букви "я". Ситуація не виглядає так:

['H', 'e', 'y', ' ', 'l', 'o', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!']
                      ^
                    char

Не існує зв'язку між char та позицією літери "l" у списку. Якщо ви змінюєте char , список не буде змінено. Ситуація виглядає так:

['H', 'e', 'y', ' ', 'l', 'o', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!']
                      ^
char = 'l'

Зверніть увагу, що я зберіг символ ^ . Це прихований покажчик, що код, що керує циклом for char in listlist: ... , використовується для відстеження його позиції в циклі. Щоразу, коли ви вводите тіло циклу, покажчик перевертається, а літера, на яку посилається вказівник, копіюється в char .

Ваша проблема виникає, коли ви дві послідовні голосні. Я покажу тобі, що відбувається від того місця, де ви досягнете "л". Зверніть увагу, що я також змінив слово "look" на "стрибок", щоб зробити це більш чітким, що відбувається:

заздалегідь вкажіть наступний символ ('l') та скопіюйте його до char

['H', 'e', 'y', ' ', 'l', 'e', 'a', 'p', ' ', 'W', 'o', 'r', 'd', 's', '!']
                   -> ^
char = 'l'

char ('l') не є гласним, тому нічого не робити

перемістіть вказівник на наступний символ ('e') і скопіюйте його до char

['H', 'e', 'y', ' ', 'l', 'e', 'a', 'p', ' ', 'W', 'o', 'r', 'd', 's', '!']
                        -> ^
char = 'e'

char ('e') є гласним, тому видаліть перше входження char ('e')

['H', 'e', 'y', ' ', 'l', 'e', 'a', 'p', ' ', 'W', 'o', 'r', 'd', 's', '!']
                           ^

['H', 'e', 'y', ' ', 'l',      'a', 'p', ' ', 'W', 'o', 'r', 'd', 's', '!']
                           ^

['H', 'e', 'y', ' ', 'l',   <- 'a', 'p', ' ', 'W', 'o', 'r', 'd', 's', '!']
                           ^

['H', 'e', 'y', ' ', 'l', 'a', 'p', ' ', 'W', 'o', 'r', 'd', 's', '!']
                           ^

переміщуйте вказівник на наступний символ ('p') і скопіюйте його до char

['H', 'e', 'y', ' ', 'l', 'a', 'p', ' ', 'W', 'o', 'r', 'd', 's', '!']
                             -> ^
char = 'p'

Коли ви видалили "e" всі символи після того, як "e" перемістилося з одного місця вліво, це було так, якби remove висунув покажчик. Результат полягає в тому, що ви пропустили повз "a".

Загалом, слід уникати модифікації списків під час повторення над ними. Краще побудувати новий список з нуля, і розпізнавання списку Python є ідеальним інструментом для цього. Наприклад,

print ''.join([char for char in "Hey look Words" if char.lower() not in "aeiou"])

Але якщо ви ще не дізналися про розуміння, найкращим способом, імовірно, є:

text = "Hey look Words!"

def anti_vowel(text):

  textlist = list(text)
  new_textlist = []

  for char in textlist:
    if char.lower() not in 'aeiou':
      new_textlist.append(char)

    return "".join(new_textlist)

print anti_vowel(text)
6
додано

List Comprehensions:

vowels = 'aeiou'
text = 'Hey look Words!'
result = [char for char in text if char not in vowels]
print ''.join(result)
4
додано

Інші вже пояснили проблему з вашим кодом. Для вашого завдання вираз генератора простіше і менш схильний до помилок.

>>> text = "Hey look Words!"
>>> ''.join(c fабоc in text if c.lower() not in 'aeiou')
'Hy lk Wrds!'

або

>>> ''.join(c fабоc in text if c not in 'AaEeIiOoUu')
'Hy lk Wrds!'

Проте str.translate - найкращий спосіб перейти.

3
додано

You shouldn't delete items from list you iterating through: But you can make new list from the old one with list comprehension syntax. List comprehension is very useful in this situation. You can read about list comprehension here

Таким чином, рішення буде виглядати так:

text = "Hey look Words!"

def anti_vowel(text):
    return "".join([char for char in list(text) if char.lower() not in 'aeiou'])

print anti_vowel(text)

Це гарно, чи не так: П.

0
додано
Я не бачу нічого поганого, надаючи альтернативне рішення. Особливо чистий і коротший. Таким чином, навіть якщо він прямо не відповідає на запитання, він вирішує корінь проблеми.
додано Автор Eduard Luca, джерело
@RandomSeed Я теж спочатку так думав, але насправді це відповідає на питання.
додано Автор Eduard Luca, джерело
Це не дає відповіді на питання. Щоб опублікувати критику або отримати пояснення від автора, залиште коментар під своїм повідомленням.
додано Автор RandomSeed, джерело
@EduardLuca Це може зробити те, що OP хотів зробити (я поняття не маю), але він не відповідає на питання: "Як це може бути?". Насправді, дуже мало відповідей тут дійсно відповідає на це питання.
додано Автор RandomSeed, джерело

Спробуйте не використовувати функцію list() на рядок. Це зробить речі набагато складнішими.

На відміну від Java, в Python рядки розглядаються як масиви. Потім спробуйте використати індекс для циклу та ключове слово del.

for x in range(len(string)):
    if string[x].lower() in "aeiou":
        del string[x]
0
додано
ІТ КПІ - Python
ІТ КПІ - Python
625 учасників

Канал обговорень про всякі штуки зі світу пайтону. Прохання: 0. мати повагу одне до одного; 1. не матюкатися в сторону людей; 2. не захламляти тред повідомленнями по одному слову;