Расширение понимания списка Python для вложенных циклов - странные результаты - PullRequest
4 голосов
/ 17 июня 2019

Я наткнулся на эту функцию, которая возвращает каждую комбинацию из N значений из массива:

def combs(a, n):
   if n == 0:
       return [[]]
   else:
       return [(x + [y]) for x in combs(a,n-1) for y in a]

Для собственного понимания я попытался преобразовать его в функцию, используя вложенные циклы:

def combinations(array,n):
    if n == 0:
        return [[]]
    else:
        for i in array:
            for j in combinations(array, n-1):
                return [j + [i]]

Но я получаю результаты, отличные от ожидаемых.

>>> threes = ['aA','bB','cC']
>>> 
>>> combs(threes,2)
[['aA', 'aA'], ['aA', 'bB'], ['aA', 'cC'], ['bB', 'aA'], ['bB', 'bB'], ['bB', 'cC'], ['cC', 'aA'], ['cC', 'bB'], ['cC', 'cC']]
>>> 
>>> combinations(threes,2)
[['aA', 'aA']]
>>> 

Я пробовал разные версии линии return [j+[i]], но все безуспешно. Вот несколько примеров:

                return (j + [i])

Traceback (most recent call last):
  File "...", line 24, in <module>
    print(combinations(threes,2))
  File "...", line 16, in combinations
    return (j + [i])
TypeError: can only concatenate str (not "list") to str

.

                return j + [i]

Traceback (most recent call last):
  File "...", line 24, in <module>
    print(combinations(threes,2))
  File "...", line 16, in combinations
    return j + [i]
TypeError: can only concatenate str (not "list") to str

Почему понимание списка первой функции возвращает нечто иное, чем моя расширенная версия вложенных циклов?

Каким будет правильный (действующий) метод для преобразования списка-понимания первой функции и возвращаемого значения в стратегию вложенного цикла?

Ответы [ 2 ]

4 голосов
/ 17 июня 2019

Если вы действительно хотите сделать этот эквивалент, вам нужно сделать один return. Когда вы выполняете понимание, вы создаете список несколько атомарно, то есть список недоступен до тех пор, пока он не будет полностью создан // нет способа получить доступ к списку, пока понимание списка (построение) не будет завершено. Чтобы выполнить это без понимания списка, вам нужно заполнить изменяемый контейнер, а затем сделать один возврат в конце:

def combinations(array,n):
    if n == 0:
        return [[]]
    else:
        result = []
        for i in array:
            for j in combinations(array, n-1):
                result.append(j + [i])
        return result

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

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

Обычно я нахожу эти преимущества достаточно веской причиной для создания большинства подобных функций в качестве генераторов. Для коротких списков подходящее для решения проблемы понимание или создание стандартного списка (как видно из приведенного выше примера).

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

def combinations(array,n):
    if n == 0:
        yield []
    else:
        for i in array:
            for j in combinations(array, n-1):
                yield j + [i]

>>> print(list(combinations(['aA','bB','cC'], 2)))
[['aA', 'aA'], ['bB', 'aA'], ['cC', 'aA'], ['aA', 'bB'], ['bB', 'bB'], ['cC', 'bB'], ['aA', 'cC'], ['bB', 'cC'], ['cC', 'cC']]

Если вы хотите использовать понимание с преимуществами генератора, то вы можете использовать понимание генератора (с ( ) вместо [ ]) и оператор yield from:

def combs(a, n):
   if n == 0:
       yield []
   else:
       yield from (x + [y] for x in combs(a, n - 1) for y in a)
1 голос
/ 17 июня 2019
> The list comprehension always returns a result list. 
> 
> But in your nested loop case you are not returning list so little
> modify in your case
> 
> 
  def combinations(array,n):
>     if n == 0:
>         return [[]]
>     else:
          result=[]
>         for i in array:
>             for j in combinations(array, n-1):
>                 result.append([j + [i]])
>         return result 
 In this you are returning the list of all iterations ..
...