Перестановки в Haskell, включающие списки, рекурсию и функцию удаления - PullRequest
1 голос
/ 03 марта 2020

Я начинающий Haskell, следуя упражнениям из книги. Первый вопрос попросил меня определить функцию, которая удаляет первое вхождение целого числа из списка целых чисел.

Например,

delete 5 [1,5,3,5,1]

вывод:

[1,3,5,1]

Второй вопрос просит меня создать функцию, которая использует только что определенную функцию удаления, которая принимает в качестве аргумента список целых чисел и выводит список всех перестановок в виде списков.

Например,

perms [1,2,3]

вывод:

[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

Я очень старался, сдался и погуглил решение.

Вот что я нашел:

perms [] = [[]]
perms xs = [ i:j | i <- xs, j <- perms $ delete i xs ]

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

I Я просто немного растерялся, пытаясь понять, что именно делает этот код. Я ищу пошаговое объяснение через рекурсию, чтобы понять, как этот код создает список перестановок?

Ответы [ 2 ]

2 голосов
/ 03 марта 2020

Как и любая рекурсивная функция, которая работает со списками, эту можно разбить на два случая:

1) Что должна делать функция в пустом списке?

2) Если я знать, что функция делает со списком длины n, могу ли я использовать это, чтобы выяснить, что функция должна делать со списком длины n + 1.

Как только вы узнаете эти две вещи, у вас есть определение, которое будет работать в любом списке (по крайней мере, одного конечной длины - такая процедура, конечно, никогда не закончится для бесконечной длины; здесь это не имеет значения, так как нет смысла говорить о перестановках из бесконечный список). [Если у вас есть какой-либо математический фон, вы узнаете это как простую формулировку закона математической индукции .]

Для функции perms ясно, что есть только один способ перестановки 0 элементов пустого списка: другой пустой список. Это дает [[]] для базового случая, как в первой строке примера решения.

Для рекурсивного / индуктивного шага, скажем, у нас есть список xs длины n (где n > 0) и предположим (как нам позволено), что мы уже знаем, как вычислить все перестановки любого списка длины n - 1.

Каждая перестановка должна начинаться с определенного элемента xs - давайте назовем этот элемент i и подумаем, как получить все перестановки xs, первый элемент которых i. Должно быть ясно, что они точно соответствуют всем перестановкам списка delete i xs (то есть xs с одним удаленным i) - учитывая перестановку j последнего, список i : j является перестановкой xs, который начинается с i, и наоборот, все такие перестановки xs могут быть получены таким образом.

Обратите внимание, что это именно тот список [ i:j | j <- perms $ delete i xs ]

( И мимоходом обратите внимание, что, поскольку мы предположили, что i находится в xs, delete i xs действительно имеет длину n - 1, поэтому по индуктивной гипотезе мы знаем, как это вычислить.)

* Конечно, 1046 * было выбрано совершенно произвольно, и все элементы xs должны быть учтены как первый элемент некоторых перестановок. Таким образом, мы просто собрали все вышеперечисленное для всех элементов i в xs - что в точности соответствует выражению в рекурсивном шаге:

[ i:j | i <- xs, j <- perms $ delete i xs ]

Возможно, вам потребуется прочитать некоторые из Выше, медленно, несколько раз, прежде чем это имеет смысл - но это принципиально очень элементарные логики c (и, как и большинство элементарных логик c, имеет неприятную привычку часто выглядеть сложнее, чем на самом деле).

1 голос
/ 03 марта 2020
  • Один за другим возьмите один элемент i из xs
  • Удалите i из xs и добавьте i к j (каждый элемент списка perms из xs меньше i) вплоть до истощения всех i.
...