Функция Python теряет элемент списка после рекурсии?

Я только начал изучать Python и получаю странную ошибку в только что написанной программе упражнений. Программа принимает список кортежей (freq_i, value_i), представляющих гистограмму различных значений value_is, и должна выводить список первых перестановок, которые я затем мог бы переставлять по очереди, чтобы получить все возможные уникальные комбинации из k выбранных элементов из исходный список, из которого была сгенерирована входная гистограмма. Извините, если это не ясное объяснение. Мой тестовый пример выглядит следующим образом. Надеюсь, это поможет прояснить, что он должен делать.

Original list = [3,4,4,7]
Input histogram = [(1,3), (2,4), (1,7)]
k = 2
Correct answer = [[3, 4], [4, 4], [3, 7], [4, 7]]
My python program answer = [[3, 4], [4, 4], [3, 7]]

Обратите внимание, что эта функция дает мне только «базовые» перестановки, поскольку она выводит элементы, сохраняя порядок ввода. Я планировал использовать другую функцию для перестановки списков в возвращаемых списках списков, чтобы получить [4, 3], [7, 3] и [7, 4], которые затем будут всеми возможными уникальными способами создания списка из двух элементов. из исходного списка, выбирая каждый элемент не более одного раза.

def acaddlast(listoflists, newelement, freq):
  if (freq <= 0):
    return listoflists
  newlist = list()
  for z in range(freq):
    newlist.append(newelement)
  l = len(listoflists)
  for z in range(l):
    listoflists[z] += newlist
  return listoflists
  
def allcombs2(x, k):
  #print(len(x),k)
  output = list()
  if (len(x) == 0):
    return output 
  if (k == 0):
    return output
  if (k == 1):
    for z in x:
      output.append([z[1]])
    return output  
  countofelements = 0
  for z in x:
    countofelements += z[0]
  if (k > countofelements):
    return output 
  if (len(x) == 1):
    return acaddlast([[]], x[0][1], k)
  if (k == countofelements):
    onelist = list()
    for z in x:
      for zz in range(z[0]):
        onelist.append(z[1])
    output.append(onelist)
    return output 
# 1 < k < countofelements 
  lastelementx = x.pop()
  f = min(k, lastelementx[0])
  for z in range(f+1):
    output += acaddlast(allcombs2(x, k-z), lastelementx[1], z) 
  if (lastelementx[0] >= k):
    output += acaddlast([[]], lastelementx[1], k)
  return output
  
output = allcombs2([(1,3), (2,4), (1, 7)], 2)
print (f"{len(output)} lists\n")
print (output)

Если я раскомментирую первый оператор печати в allcomb2(), я получу следующий вывод. Первый вызов изначально содержит 3 элемента в x, затем последний элемент удаляется, оставляя 2. В цикле «for z in range(f+1):» при этом первом вызове f = 1, поэтому z должно иметь значения 0 и 1, вызывая allcombs2() дважды с x из 2 элементов. Последнее 1 1 ниже должно быть 2 1. Каким-то образом x потерял один из своих элементов внутри этого цикла.

Solving allcombs([(1, 3), (2, 4), (1, 7)],2)
3 2
2 2
1 2
1 1
1 0
1 1
3 lists

[[3, 4], [4, 4], [3, 7]]

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

<?php

function acaddlast($listoflists, $newelement, $freq) {
  if ($freq <= 0) return $listoflists;
  $newlist = array();
  for ($z = 0; $z < $freq; $z++) {
    $newlist[] = $newelement;
  }
  $l = count($listoflists);
  for ($z = 0; $z < $l; $z++) $listoflists[$z] = array_merge($listoflists[$z], $newlist);
  return $listoflists;
}

function allcombs2($x, $k) {
// Returns array of all arrays of length $k where each value occurs at most its frequency times preserving input order.
  $xsize = count($x);
  //echo("($xsize, $k) ");
  $output = array();
  if ($xsize == 0) return $output;
  if ($k == 0) return $output;
  if ($k == 1) {
    foreach($x as $key => $value) $output[] = array($value[1]);
    return $output;
  }
  $countofelements = 0;
  foreach($x as $key => $value) $countofelements += $value[0];
  if ($k > $countofelements) return $output;
  if (count($x) == 1) return acaddlast(array(array()), $x[0][1], $k);
  if ($k == $countofelements) {
    $onelist = array();
    foreach($x as $key => $value) {
      for ($zz = 0; $zz < $value[0]; $zz++) $onelist[] = $value[1];
    }
    return array($onelist);
  }
// 1 < k < countofelements 
  $lastelementx = $x[$xsize-1];
  unset($x[$xsize-1]);
  if ($k < $lastelementx[0]) {
    $f = $k;
  } else {
    $f = $lastelementx[0];
  }
  for ($z = 0; $z <= $f; $z++) $output = array_merge($output, acaddlast(allcombs2($x, $k-$z), $lastelementx[1], $z));
  if ($lastelementx[0] >= $k) $output = array_merge($output, acaddlast(array(array()), $lastelementx[1], $k));
  return $output;
}

function printlistoflists($array) {
  echo "[";
  $first2 = true;
  $listsonline = 0;
  $maxlistsperline = 20;
  foreach($array as $key => $value) {
    if (!$first2) echo ", ";
    if ($listsonline >= $maxlistsperline) {
      echo "\n";
      $listsonline = 0;
    }
    $listsonline++;
    echo "[";
    $first = true;
    foreach($value as $key2 => $value2) {
      if (!$first) echo ", ";
      echo $value2;
      $first = false;
    }
    echo"]";
    $first2 = false;
  }
  echo"]\n";
}

$input = [[1,3], [2, 4], [1, 7]]; 
$listlen = 2;
echo "Choosing $listlen elements from histogram\n";
printlistoflists($input);
$output = allcombs2($input, $listlen);
echo count($output)." lists\n";
printlistoflists($output);
?>

Вывод приведен ниже, если вы раскомментируете первое эхо в allcombs2().

Choosing 2 elements from histogram
[[1, 3], [2, 4], [1, 7]]
(3, 2) (2, 2) (1, 2) (1, 1) (1, 0) (2, 1) 4 lists
[[3, 4], [4, 4], [3, 7], [4, 7]]

Я написал несколько функций меньшего размера, чтобы попытаться создать MRE, но пока не нашел способа получить тот же эффект с помощью чего-то меньшего. Любая помощь или предложения приветствуются, спасибо.

Я использую Python версии 3.8.2 на рабочем столе Ubuntu 20.04.1 LTS.

Откройте отладчик (чтобы вы поняли, почему это 1,1, а не 2,1)!

Scott Hunter 01.07.2024 16:50
listoflists[z] += newlist изменяет listoflists[z] на месте, как и listoflists[z].extend(newlist), но не создает новый список. Для этого используйте listoflists[z] = listoflists[z] + newlist.
Barmar 01.07.2024 17:16
Почему в 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
2
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Это ошибка новичка. В Python аргументы функции передаются по ссылке, и если это изменяемый объект, он модифицируется внутри функции.

В функции allcombs2() простым изменением, обеспечивающим работу рекурсии, является простое создание копии списка перед рекурсией. Эту функцию можно было бы переписать, чтобы сделать ее более эффективной и не требовать создания нового списка на каждой итерации цикла, но код можно исправить, заменив цикл следующим.

for z in range(f+1):
  xcopy = x.copy()
  output += acaddlast(allcombs2(xcopy, k-z), lastelementx[1], z) 

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