Извлечение контуров листьев из n-арного дерева в F#
Вдохновленный этим вопросом, я хотел попробовать свои силы в последней обдумать этот вызов, используя F#
Мой подход, вероятно, совершенно не верен, но в процессе решения этой проблемы я пытаюсь получить список всех перестановок цифр 0-9.
Я смотрю на решение этой проблемы с помощью n-арного дерева следующим образом:
type Node =
| Branch of (int * Node list)
| Leaf of int
Я очень доволен собой, потому что мне удалось решить, как создать дерево, которое я хочу.
Моя проблема сейчас в том, что я не могу понять, как пройти по этому дереву и извлечь "путь" к каждому листу в виде целого числа. Меня смущает то, что мне нужно сопоставлять отдельные узлы, но моя "внешняя" функция должна принимать список узлов.
Моя текущая попытка почти делает правильные вещи, за исключением того, что она возвращает мне сумму всех путей...
let test = Branch(3, [Branch(2, [Leaf(1)]);Branch(1, [Leaf(2)])])
let rec visitor lst acc =
let inner n =
match n with
| Leaf(h) -> acc * 10 + h
| Branch(h, t) -> visitor t (acc * 10 + h)
List.map inner lst |> List.sum
visitor [test] 0 //-> gives 633 (which is 321 + 312)
И я даже не уверен, что это хвостовая рекурсия.
(Вы можете предложить другое решение для поиска перестановок, но я все еще заинтересован в решении этой конкретной проблемы)
РЕДАКТИРОВАТЬ: я разместил общий алгоритм перестановок в F# здесь.
2 ответа
Что касается вашего вопроса о обходе списка - вы можете начать с написания функции, которая возвращает списки, представляющие путь, - я думаю, что это проще, и позже будет легко превратить его в функцию, которая возвращает число.
Он принимает список в качестве первого аргумента (путь до сих пор) и дерево и возвращает список> тип - это все возможные пути из текущей ветви.
let rec visitor lst tree =
match tree with
| Branch(n, sub) -> List.collect (visitor (n::lst)) sub
| Leaf(n) -> [List.rev (n::lst)]
// For example...
> let tr = Branch(1, [Leaf(3); Branch(2, [Leaf(4); Leaf(5)] )]);;
> visitor [] tr;;
val it : int list list = [[1; 3]; [1; 2; 4]; [1; 2; 5]]
В случае с "листом" мы просто добавляем текущий номер в список и возвращаем результат в виде списка, содержащего один список (сначала мы должны изменить его, потому что мы добавляли числа в начало). В случае "ветви" мы добавляем "n" в список и рекурсивно вызываем посетителя для обработки всех подузлов текущей ветви. Это возвращает набор списков, и мы используем 'map_concat', чтобы превратить их в один список, который содержит все пути доступа из текущей ветви.
Теперь вы можете переписать это, чтобы получить список целых чисел:
let rec visitor2 lst tree =
match tree with
| Branch(n, sub) -> List.collect (visitor2 (lst * 10 + n)) sub
| Leaf(n) -> [lst * 10 + n]
// For example...
> visitor2 0 tr;;
val it : int list = [13; 124; 125]
Вместо того, чтобы объединять списки, мы теперь вычисляем число.
Относительно лени - Вы можете сделать это ленивым, используя тип F# "seq" вместо типа "список". Вот пример:
let rec visitor2 lst tree =
match tree with
| Branch(n, sub) -> Seq.map_concat (visitor2 (lst * 10 + n)) sub
| Leaf(n) ->
seq { do printfn "--yielding: %d" (lst * 10 + n)
yield lst * 10 + n };;
"Seq" - это выражение последовательности, представляющее ленивый поток значений. Я добавил "printfn" в код, чтобы мы могли отслеживать, как все выполняется:
> visitor2 0 tr |> Seq.take 2;;
--yielding: 13
--yielding: 124
val it : seq<int> = seq [13; 124]
Вероятно, вы можете использовать что-то вроде Seq.first, чтобы найти первое значение, которое представляет результат.