删除元组列表,但将冲突保留为列表

时间:2018-09-24 17:45:03

标签: list f# functional-programming tuples

我一般都不熟悉F#和功能语言。我很难用一种递归的方式来通过以下方式对元组列表进行重复数据删除:

 [("Apple", "500");
  ("Orange", "123");
  ("Pineapple", "300");
  ("Apple", "200");
  ("Apple", "100");
  ("Orange", "234");
  ("Cucumber", "900");]

  --becomes-->

  [("Apple", ["500", "200", "100"]);
  ("Orange", ["123", "234"]);
  ("Pineapple", ["300"]);
  ("Cucumber", ["900"]);]

基本上,我想要类似列表的地图。感谢我的解释,因为我仍然很难阅读功能代码。

3 个答案:

答案 0 :(得分:4)

可以使用Seq.groupBy进行分组。

运行Seq.groupBy fst input会产生:

seq
  [("Apple", seq [("Apple", "500"); ("Apple", "200"); ("Apple", "100")]);
   ("Orange", seq [("Orange", "123"); ("Orange", "234")]);
   ("Pineapple", seq [("Pineapple", "300")]);
   ("Cucumber", seq [("Cucumber", "900")])]

这很接近,但不是您想要的,因为生成的元组的第二项包含完整的输入对象,而您的示例指示您要从列表中拉出第二项。您可以使用snd从元组中获取第二个项目,并且由于您希望从中提取第二个元素,所以可以使用Seq.map

let grouped = Seq.groupBy fst input
              |> Seq.map (fun (a, b) -> (a, Seq.map snd b))

printfn "%A" grouped

// yields...
seq
  [("Apple", seq ["500"; "200"; "100"]); ("Orange", seq ["123"; "234"]);
   ("Pineapple", seq ["300"]); ("Cucumber", seq ["900"])]

答案 1 :(得分:2)

或者您可以使用List.fold来实现您的目标:

let input = 
    [
        ("Apple", "500");
        ("Orange", "123");
        ("Pineapple", "300");
        ("Apple", "200");
        ("Apple", "100");
        ("Orange", "234");
        ("Cucumber", "900");
    ]

let output =
    List.fold (fun (acc : Map<string,string list>) (k,v) ->
        match Map.tryFind k acc with
        | Some x -> Map.add k (v :: x) acc
        | None -> Map.add k [v] acc
    ) Map.empty input
    // If you want a list instead of a map in the end, uncomment the next line.
    // |> Map.toList 

产生:

  val输出:Map =     地图       [(“ Apple”,[“ 100”;“ 200”;“ 500”])); (“黄瓜”,[“ 900”]);        (“ Orange”,[“ 234”;“ 123”]); (“菠萝”,[“ 300”])]

groupBy虽然不是fold版本的重点,但在许多场合却是您的瑞士军刀,值得一试。

而且-尽管有一些不错的现成函数,例如fold随F#一起免费提供,但是如果您需要递归定义,则可以编写自己的折叠作为学习练习。它看起来可能像这样,并且应该与我上面使用的相同的lambda一起工作。

let rec myFold folder acc values =
    match values with
    | [] -> acc
    | (x::xs) -> myFold folder (folder acc x) xs

答案 2 :(得分:1)

如果您不想使用为此目的设计的Seq.groupBy函数,则递归分组的方式将是保存分组的不可变数据结构,例如Map << em>,< / em >>。最终枚举时,它将按排序顺序生成元素。如果应保留原始顺序,则使用可变数据结构,例如Dictionary << em>,>,只要对某个函数保持局部性,就可以被认为具有足够的功能。

let groupByFst input =
    let d = System.Collections.Generic.Dictionary<_,_>()
    let rec aux = function
    | [] -> [ for KeyValue(k, vs) in d -> k, List.rev vs ]
    | (k, v)::tail ->
        match d.TryGetValue k with
        | true, vs -> d.[k] <- v::vs
        | _ -> d.Add(k, [v])
        aux tail
    aux input
// val groupByFst : input:('a * 'b) list -> ('a * 'b list) list when 'a : equality

[ "Apple", "500"
  "Orange", "123"
  "Pineapple", "300"
  "Apple", "200"
  "Apple", "100"
  "Orange", "234"
  "Cucumber", "900"]
|> groupByFst
// val it : (string * string list) list =
//   [("Apple", ["500"; "200"; "100"]); ("Orange", ["123"; "234"]);
//    ("Pineapple", ["300"]); ("Cucumber", ["900"])]