Как я могу конвертировать между F # List и F # Tuple?
Есть ли способ конвертировать между F # List и F # Tuple?
Например:
[1;2;3] -> (1,2,3)
(1,2,3,4) -> [1;2;3;4]
Мне нужны две функции:
let listToTuple list = ...
let tupleToList tuple = ...
Спасибо заранее.
Ответы
Ответ 1
Помимо listToTuple, тогда pblasucci имеет правильный ответ.
Но вы не будете довольны результатом, если не знаете что-то о типах типов, или если вы не будете делать много бокса и unboxing.
let tupleToList t =
if Microsoft.FSharp.Reflection.FSharpType.IsTuple(t.GetType())
then Some (Microsoft.FSharp.Reflection.FSharpValue.GetTupleFields t |> Array.toList)
else None
let listToTuple l =
let l' = List.toArray l
let types = l' |> Array.map (fun o -> o.GetType())
let tupleType = Microsoft.FSharp.Reflection.FSharpType.MakeTupleType types
Microsoft.FSharp.Reflection.FSharpValue.MakeTuple (l' , tupleType)
Ответ 2
Как уже указывалось, это сложная проблема, поскольку кортеж не является одним типом - это семейство типов, таких как int * int * int
или int * int
, а F # не предоставляет никакого способа для взятия всего семейство типов в качестве аргумента. Вы можете либо написать много похожих функций (что очень неудобно), либо использовать отражение (которое немного медленное и не безопасно для типов).
В качестве альтернативы вы можете ограничить функцию кортежами с некоторой структурой - например, вместо работы с (1, 2, 3, 4)
вы можете использовать вложенные кортежи, например (1, (2, (3, 4)))
. Это немного менее удобно, но он сохраняет безопасность типа, и это не так плохо.
Тогда вы можете легко написать комбинаторы для создания функций преобразования на лету:
// creates function for converting tuple (possibly with a nested
// tuple in the second component to list
let tl f (a, b) = a::(f b)
// converts last element of the tuple to singleton list
let te a = [a]
Затем вы можете комбинировать функции tl
и te
, чтобы создать безопасную для типа функцию, которая преобразует вложенный кортеж, содержащий 4 элемента, в список, подобный этому:
let l = (1, (2, (3, 4))) |> (tl (tl (tl te)))
Аналогично, вы можете создавать функции для преобразования списка в кортежи - обратите внимание, что это может вызвать исключение, если список не соответствует ожидаемому формату:
let le = function
| [x] -> x
| _ -> failwith "incompatible"
let lt f = function
| [] -> failwith "incompatible"
| x::xs -> (x, f xs)
// convert list to a tuple of four elements
let t = [1; 2; 3; 4] |> lt (lt (lt le))
Я предполагаю, что это, вероятно, так же близко к функции typeafe и reusable для преобразования между кортежами и списками, которые он может получить. Это не идеально (вообще), но это связано с тем, что вы пытаетесь реализовать очень редко используемую операцию. В F # различие между кортежами и списками яснее, чем, например, в Python (динамический, поэтому ему не нужно иметь дело с безопасностью статического типа).
Ответ 3
На самом деле вам нужны функции 2*n
, где n
- наибольший размер кортежа, который вы хотите поддерживать. Кортеж, содержащий три ints, имеет совершенно другой тип, чем кортеж, содержащий четыре ints, поэтому вам нужно написать tupleToList и функцию listToTuple для каждого отдельно.
Также обратите внимание, что для listToTuple вам нужно знать размер кортежа, который вы хотите получить во время компиляции. То есть вы не можете создать функцию, которая решает, возвращать ли (int, int, int)
или (int, int)
в зависимости от длины списка ввода (поскольку, как я уже сказал, они совершенно разные типы). Вы должны иметь функцию listToNTuple
, которая принимает список из по меньшей мере N элементов и возвращает N-кортеж.
Возможно, для этого можно написать независимые от размера функции, используя отражение, но так как вы не могли знать тип любого кортежа, возвращаемого такой функцией во время компиляции, было бы очень больно использовать.
Ответ 4
Используя структуру PropertyInfo, вы можете построить рекурсивный список. Проблема с этим подходом заключается в том, что информация о типе теряется, и результат получается как список obj. Тем не менее, это решает кортеж, чтобы перечислить часть вопроса.
let tupleToList tpl =
let rec loop tpl counter acc =
let getItemPropertyInfo t n = t.GetType().GetProperty(sprintf "Item%d" n)
let getItem t n = (getItemPropertyInfo t n).GetValue(t,null)
match counter with
| 8 ->
match tpl.GetType().GetProperty("Rest") with
| null -> acc
| _ as r ->
let rest = r.GetValue(tpl,null)
loop rest 2 ((getItem rest 1) :: acc)
| _ as n ->
match getItemPropertyInfo tpl n with
| null -> acc
| _ as item -> loop tpl (counter+1) (item.GetValue(tpl,null) :: acc)
loop tpl 1 [] |> List.rev
Ответ 5
Ну, это не очень, но:
let tuple_to_array input =
let temp_str = input.ToString()
temp_str.Substring(1, temp_str.Length - 2)
|> Array.map (fun (x:string) -> x.TrimStart(' '))
Я не уверен, как бы ты пошел по другому пути. И я действительно не уверен, что это было бы разумно, но если вам действительно нужно.