То, что вы пытаетесь сделать в swap
должны быть
let rec swap = function
| a :: b :: tail -> b :: a :: (swap tail)
| whatever -> whatever
Таким образом, нужно поменять местами два элемента на голове и продолжить то, что после них.
Однако есть проблемы. Давайте посмотрим, что происходит, скажем, в случае swap [1; 2; 3; 4; 5; 6]
вызова:
swap
вызывается с [1; 2; 3; 4; 5; 6]
a
согласованный с 1, b
сочетается с 2, и ...
.. ..2.1. swap
называется [3; 4; 5; 6]
.... 2.2. внутри этого второго вызова a
соответствует 3, b
соответствует 4 и ...
........ 2.2.1. swap
называется [5; 6]
........ 2.2.2. внутри этого третьего вызова a
соответствует 5, b
соответствует 6 и ...
............ 2.2.2.1. swap
вызывается с []
(пустой список)
............ 2.2.2.2. Этот вызов возвращает обратно пустой список
........ 2.2.3. ... и подобрано в предыдущем swap
вызова добавить b
и a
начала его, производя 6 :: 5 :: []
или [6; 5]
........ 2.2.4. который возвращается
.... 2.3. ... и подобрано в предыдущем swap
вызова, чтобы добавить (свой собственный) b
и a
начала его, производя 4 :: 3 :: [6; 5]
или [4; 3; 6; 5]
.... 2.4. который возвращается
- ...и подобран в предыдущем
swap
вызова, чтобы добавить (собственный) b
и a
начала него, производя 2 :: 1 :: [4; 3; 6; 5]
или [2; 1; 4; 3; 6; 5]
- , который возвращается в качестве конечного результата
Обратите внимание, как всю сеть из swap
звонков пришлось быть пройдено в обоих направлениях:
вызова 1, swap [1; 2; 3; 4; 5; 6]
=> вызов 2, swap [3; 4; 5; 6]
=> называют 3, swap [5; 6]
=> называют 4, swap []
, возвращает []
, теперь возвращается => называют 3, возвращается [6; 5]
=> вызов 2, возвращает [4; 3; 6; 5]
=> вызов 1, возвращает [2; 1; 4; 3; 6; 5]
.
Это означает, что все время, когда программа идет «вниз», она должна сохранять предыдущий путь в памяти, чтобы он мог вернуться назад. Если в вашем списке, скажем, два миллиона записей, весь путь будет составлять миллион звонков, и он должен храниться в памяти. Если вы запустите такую вещь, у вас закончится пространство стека.
Как решить эту проблему? Обычная техника - использовать какой-то «аккумулятор».
let rec swap_helper acc = function
| a :: b :: tail -> swap_helper (a :: b :: acc) tail
| a :: [] -> a :: acc
| [] -> acc ;;
let swap2 lst = List.rev (swap_helper [] lst)
Что это за функции? Попробуем посмотреть, что произойдет, если мы позвоним swap2 [1; 2; 3; 4; 5; 6]
. Он начинается с вызова swap_helper [] [1; 2; 3; 4; 5; 6]
.
a
сопоставляется с 1, b
сочетается с 2, tail
сочетается с [3; 4; 5; 6]
, поэтому мы называем swap_helper [1; 2] [3; 4; 5; 6]
. Но вот и вот! Когда этот следующий вызов возвращается, мы ничего не делаем с результатом, поэтому нет причин возвращаться - компилятор выдает код, чтобы вернуть результат, который мы получим в swap_helper [1; 2] [3; 4; 5; 6]
сразу, не возвращаясь к исходному коду - потому что в этом исходном вызове ничего не осталось. Это называется оптимизацией хвостового вызова.
Сейчас мы находимся в swap_helper [1; 2] [3; 4; 5; 6]
вызова, где a
согласован с 3, b
согласован с 4 и tail
согласован с [5; 6]
, поэтому мы называем следующий swap_helper [3; 4; 1; 2] [5; 6]
и там опять ничего не оставалось делать после этого, поэтому мы не собираюсь возвращаться сюда больше!
Итак, swap_helper [3; 4; 1; 2] [5; 6]
звонок. Здесь a
- 5, b
- 6, tail
- []
. Мы переходим к swap_helper [5; 6; 3; 4; 1; 2] []
, и мы находимся здесь , а не, так как вам больше нечего делать!
swap_helper [5; 6; 3; 4; 1; 2] []
попадает в последний случай матча и немедленно возвращает [5; 6; 3; 4; 1; 2]
- но не каких-либо промежуточных swap_helper
вызовов, они уже забыли, нет никакой работы, чтобы сделать в тех, кто остался, - но прямо к первоначальному swap2
звонку!
Сейчас в swap2
мы поменяем список мы получили от swap_helper
и получить наш [2; 1; 4; 3; 6; 5]
, как хотелось бы.
Итак, чтобы повторить: в этом коде, когда swap_helper
называет себя, ничего не остается, поэтому нет причин возвращаться, поэтому нет причин тратить память на запоминание «пути назад», поскольку мы не собираемся вернуться назад. В исходном коде мы не могли этого сделать, потому что swap
звонил сам себе и все еще должен был склеить a
и b
на результат этого второго вызова после его готовности.
Вы должны показать код, который вы написали, и объяснить, почему он не работает. Как общий намек, если вы разбиваете список длины n на две части длины 2 и n - 2, вам легко решить проблему (длины 2) и меньший экземпляр той же проблемы. –
let rec swap = function | a :: (b :: _ as t) -> b :: a :: swap t ;; – dj007
@ dj007 вы должны отредактировать свой вопрос с содержанием вашего комментария и немного объяснить, что не работает с кодом, который вы показываете. Подсказка: предупреждение о неисчерпывании, которое вы, вероятно, получаете от компилятора/интерпретатора, важно. Как уже сказал Джеффри Скофилд, что происходит, когда аргумент swap имеет форму '[a]' (список с одним элементом) или '[]' (пустой список)? – Virgile