Как я могу конвертировать между F # List и F # Tuple? - PullRequest
17 голосов
/ 27 мая 2010

Есть ли какой-нибудь способ преобразования между F # List и F # Tuple?

Например:

[1;2;3] -> (1,2,3)    
(1,2,3,4) -> [1;2;3;4]

Для этого мне нужны две функции:

let listToTuple list = ...
let tupleToList tuple = ...

Заранее спасибо.

Ответы [ 5 ]

18 голосов
/ 27 мая 2010

Кроме listToTuple, у pblasucci есть правильный ответ. Но вы не будете довольны результатом, если вы не знаете что-то о типах типов или если вы не хотите много заниматься боксом и распаковкой.

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)
13 голосов
/ 27 мая 2010

Как уже указывалось, это сложная проблема, потому что кортеж не является единственным типом - это семейство типов, таких как 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))

Я полагаю, что это, вероятно, максимально приближенная к типу безопасная и многократно используемая функция для преобразования между кортежами и списками, насколько это возможно. Это не идеально (вообще), но это связано с тем, что вы пытаетесь реализовать очень редко используемую операцию. В F # различие между кортежами и списками более четкое, чем, например, в Python (который является динамическим, поэтому он не должен иметь дело с безопасностью статического типа).

4 голосов
/ 27 мая 2010

На самом деле вам нужны 2*n функции, где n - это максимальный размер кортежа, который вы хотите поддерживать. Кортеж, содержащий три целых, имеет совершенно другой тип, чем кортеж, содержащий четыре целых, поэтому вам нужно написать функцию tupleToList и listToTuple для каждого из них отдельно.

Также обратите внимание, что для listToTuple вам нужно знать размер кортежа, который вы хотите получить во время компиляции. То есть Вы не можете создать функцию, которая решает, возвращать ли (int, int, int) или (int, int) в зависимости от длины списка ввода (поскольку, как я уже сказал, они совершенно разных типов). Вы должны иметь функцию listToNTuple, которая принимает список из по крайней мере N элементов и возвращает N-кортеж.

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

2 голосов
/ 21 марта 2013

Используя структуру 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
0 голосов
/ 27 мая 2010

Ну, это не красиво, но:

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(' '))

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...