Вот как вы можете сделать это, используя простую рекурсивную функцию:
fun toTuple [] = ([], [])
| toTuple ((x,y)::pairs) =
case toTuple pairs of
(xs, ys) => (x::xs, y::ys)
Обрабатывает оставшиеся pairs
рекурсивно, распаковывает результат как (xs, ys)
и добавляет x
и y
к этому результату впоследствии.Вместо case-of вы можете использовать let-binding:
fun toTuple [] = ([], [])
| toTuple ((x,y)::pairs) =
let val (xs, ys) = toTuple pairs
in (x::xs, y::ys)
end
И если вы не выполняли этот тип сопоставления с шаблоном непосредственно в функции toTuple
, вам, возможно, придется переместитьраспаковка и повторная упаковка результата в отдельную функцию:
fun add (x,y) (xs,ys) = (x::xs, y::ys)
fun toTuple [] = ([], [])
| toTuple (pair::pairs) = add pair (toTuple pairs)
Эти три подхода более или менее эквивалентны.
В ответ на хвост-рекурсивный вариант @ pyon,
fun loop (xs, ys, nil) = (rev xs, rev ys)
| loop (xs, ys, (x, y) :: zs) = loop (x :: xs, y :: ys, zs)
fun toTuple xs = loop (nil, nil, xs)
Поскольку я пытаюсь узнать больше о языке в целом, могу я спросить, почему используется вторая функция?Чтобы сделать это проще и передать два пустых значения, которые в конечном итоге станут первой и второй частями возвращаемого кортежа?Кроме того, есть ли способ сделать это без использования вторичной функции?Ваше решение будет работать, но я хотел бы больше узнать об этой проблеме.
Если вы сравните мои три решения с pyon, его решение в чем-то отличается: у него есть вспомогательная функция loop
это рекурсивно, в то время как версия, которую я написал с помощью вспомогательной функции add
, add
, не является рекурсивной и управляет только распаковкой и повторной упаковкой кортежа.toTuple
все еще имеет только один аргумент, но loop
имеет еще два, один для хранения временных результатов x :: xs
и один для y :: ys
.
Когда вы обрабатываете список слева направо,но накапливая результат в аргументе, первый элемент на входе становится первым, который будет добавлен к накопленному результату, что означает, что он заканчивается как последний элемент результата.(Вы можете думать о списке как о стеке.)
Это не так удачно, когда накопленный результат представлял собой список элементов, которые должны быть в том же порядке, что и входные данные.Лучше всего это можно увидеть, оценив функции вручную:
Во-первых, для моего варианта версии toTuple [(1,2),(3,4),(5,6)]
:
toTuple [(1,2),(3,4),(5,6)]
~> case toTuple [(3,4),(5,6)] of
(xs, ys1) => (1::xs1, 2::ys)
~> case (case toTuple [(5,6)] of
(xs', ys') => (3::xs', 4::ys')) of
(xs, ys) => (1::xs, 2::ys)
~> case (case (case toTuple [] of
(xs'', ys'') => (5::xs'', 6::ys'')) of
(xs', ys') => (3::xs', 4::ys')) of
(xs, ys) => (1::xs, 2::ys)
~> case (case (case ([], []) of
(xs'', ys'') => (5::xs'', 6::ys'')) of
(xs', ys') => (3::xs', 4::ys')) of
(xs, ys) => (1::xs, 2::ys)
~> case (case (5::[], 6::[]) of
(xs', ys') => (3::xs', 4::ys')) of
(xs, ys) => (1::xs, 2::ys)
~> case (3::5::[], 4::6::[]) of
(xs, ys) => (1::xs, 2::ys)
~> (1::3::5::[], 2::4::6::[]
~> ([1,3,5], [2,4,6])
Во-вторых, для версии @ pyon:
toTuple [(1,2),(3,4),(5,6)]
~> loop ([], [], [(1,2),(3,4),(5,6)])
~> loop (1::[], 2::[], [(3,4),(5,6)])
~> loop (3::1::[], 4::2::[], [(5,6)])
~> loop (5::3::1::[], 6::4::2::[], [])
~> (rev [5,3,1], rev [6,4,2])
~> ...
~> ([1,3,5], [2,4,6])
Редактировать: Как указывает @pyon в комментарии, они используют одинаковое количество памяти и времени.Разница заключается в моей версии, использующей неявный стек (вызов), а в его версии - явный стек (аргумент).