【问题标题】:F Sharp convert list of tuples into list of mapped tuplesF Sharp 将元组列表转换为映射元组列表
【发布时间】:2020-01-09 05:39:20
【问题描述】:

声明一个将一对列表转换为关系的函数。

type Relation<'a,'b> = ('a * 'b list) list

基本上,转这个:

[(2,"c");(1,"a");(2,"b")]

进入这个:

[(2,["c";"b"]);(1,["a"])]

以这种形式:

toRel:(’a*’b) list -> Rel<’a,’b> 

有什么想法吗?这不是家庭作业,只是自学,考虑到表格不允许积累,这让我有点难过。

【问题讨论】:

  • Seq.groupBy 正是我认为的(或至少是困难的部分)
  • 已经尝试过 (List.groupBy) ,这让情况变得更糟。虽然它被正确分组,但每个值也包含键。 [(2, [(2, "c"); (2, "b")]); (1, [(1, "a")])]

标签: f# functional-programming


【解决方案1】:
[(2,"c");(1,"a");(2,"b")]
|> List.groupBy fst
|> List.map (fun (x,y)->x,List.map snd y)

结果:

[(2, ["c"; "b"]); (1, ["a"])]

类型推断对于 toRel 位很方便:

let toRel xs = 
  xs
  |> List.groupBy fst
  |> List.map (fun (x,y)->x,List.map snd y)

用法:

toRel [(2,"c");(1,"a");(2,"b")]

【讨论】:

  • 哦,好吧...这太简单了。谢谢!
【解决方案2】:

您可以使用各种内置函数和转换来重建行为,但最好具备递归的基本知识以从头开始构建特殊函数。

使用递归来学习将问题分解为更小的问题以学习如何解决更大的问题也是一个好主意。

如果你以递归的方式思考,你需要做的是:

  1. 您删除列表的头部以获得一个元素。
  2. 然后使用键和指定键的所有值创建元组
  3. 您删除了所有具有您处理的当前键并在此列表中重复出现的元素。
  4. 如果输入列表为空,则输出也为空。

所以你开始:

let rec group list =
    if List.isEmpty list then []
    else
        ???

您使用List.head 删除列表的第一个元素。但我们也想 将元组提取到它的两个组件中。您可以通过

let k,v = List.head list

我们要创建的是一个新的元组,它具有相同的键,但输入列表的所有值。我们还没有那个函数,但我们只是假设我们有一个函数valuesOf,我们可以传递一个键和一个列表,它只是返回我们定义的键的所有值。

(k, (valuesOf k list))

在您定义的输入列表的第一次迭代中,我们将2 设为k,并且我们假设valuesOf 2 list 返回["b";"c"]

所以上面的代码将返回(2, ["b";"c"]) 作为一个值。现在递归调用。我们再次假设我们有一个函数removeKey,我们可以传递一个键和一个列表,它返回一个新列表,其中删除了指定键的所有元素。

(removeKey k list)

举个例子

removeKey 2 [(1,"a");(2,"a");(2,"b");(3,"a")]

应该返回

[(1,"a");(3,"a")]

removeKey 返回的这个新列表是您需要重复的列表:

(group (removeKey k list))

您只需将两个部分放在一起。您要返回的是带有递归结果的新元组。

(k, (valuesOf k list)) :: (group (removeKey k list))

并作为一个功能。

let rec group list =
    if List.isEmpty list then []
    else
        let k,v = List.head list
        (k, (valuesOf k list)) :: group (removeKey k list)

我们还没有完成,我们还需要创建valuesOfremoveKey

let rec valuesOf key list =
    match list with
    | []      -> []
    | x::list ->
        let k,v = x
        if   k = key
        then v :: (valuesOf key list)
        else (valuesOf key list)

valuesOf 使用模式匹配来解构列表,而不是使用List.headList.tail。我们只检查元素是否具有指定的键。如果有,我们返回与剩余列表连接的当前值。否则我们只返回递归调用的结果并删除当前值。

removeKey 类似。我们检查它们是否匹配元组的键,如果是,我们删除整个元素并返回递归调用,否则我们使用递归调用返回当前元素。

let rec removeKey key list =
    match list with
    | []      -> []
    | x::list ->
        let k,v = x
        if   k = key
        then (removeKey key list)
        else x :: (removeKey key list)

现在我们完成了。一次性完成所有操作

let rec valuesOf key list =
    match list with
    | []      -> []
    | x::list ->
        let k,v = x
        if   k = key
        then v :: (valuesOf key list)
        else (valuesOf key list)

let rec removeKey key list =
    match list with
    | []      -> []
    | x::list ->
        let k,v = x
        if   k = key
        then (removeKey key list)
        else x :: (removeKey key list)

let rec group list =
    if List.isEmpty list then []
    else
        let k,v = List.head list
        (k, (valuesOf k list)) :: group (removeKey k list)

group [(2,"c");(1,"a");(2,"b");(2,"d");(3,"a");(1,"b")]
// returns: [(2, ["c"; "b"; "d"]); (1, ["a"; "b"]); (3, ["a"])]

上述函数不是尾递归的。但是您可以使用List.foldList.foldBack 轻松重写valuesOfremoveKey。在这里我使用List.fold,因为我认为元素的顺序是否改变并不重要。

let valuesOf key list =
    List.fold (fun acc (k,v) ->
        if k = key
        then v :: acc
        else acc
    ) [] list

let removeKey key list =
    List.fold (fun acc (k,v) ->
        if   k = key
        then acc
        else (k,v) :: acc
    ) [] list

group 不能轻易地用List.foldList.foldBack 重写,因为我们需要访问整个列表。但是实现尾递归仍然不难。

let group list =
    let rec loop result list =
        if List.isEmpty list then result
        else
            let k,v = List.head list
            loop
                ((k, (valuesOf k list)) :: result)
                (removeKey k list)
    loop [] list

如果您不希望有包含数千个或更多键值对的列表,那么您也可以保留非尾递归函数。

即使使用已提供的函数(如 List.groupByList.map)创建更小的代码感觉很好,您也应该能够自己创建此类递归函数。为什么?

不可变链表是一种递归定义的数据结构,使用递归函数是很自然的。如果您自己不知道如何创建此类函数,那么您在创建自己的递归数据结构时就会遇到麻烦,因为您已经可以使用零个预定义函数,例如 groupBymap。您必须自己构建这些功能。

尝试重建List 模块中定义的函数或您在此处描述的类似方法实际上是您应该自己进行的良好培训方法。

【讨论】:

    【解决方案3】:

    鉴于每个键只出现一次,您也可以这样做:

    [(2,"c");(1,"a");(2,"b")]
    |> List.fold (fun (m:Map<int, string>) (k, v) -> m.Add (k, v))
                 Map.empty
    

    如果一个键多次出现,则保留最后一个值。

    【讨论】:

      猜你喜欢
      • 2016-07-31
      • 2015-02-13
      • 2011-07-27
      • 1970-01-01
      • 1970-01-01
      • 2018-07-20
      • 2015-07-14
      • 2016-09-28
      相关资源
      最近更新 更多