F Sharp将元组列表转换为映射元组列表

时间:2017-05-09 11:45:07

标签: f# functional-programming

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

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> 

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

3 个答案:

答案 0 :(得分:7)

[(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")]

答案 1 :(得分:3)

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

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

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

  1. 删除列表的头部以获取一个元素。
  2. 然后使用键和指定键的所有值
  3. 创建元组
  4. 使用当前处理的密钥删除所有元素,并在此列表中重复显示。
  5. 如果您的输入列表为空,则输出也为空。
  6. 所以你从:

    开始
    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"])]
    

    上述函数不是尾递归的。但您可以使用valuesOfremoveKey轻松重写List.foldList.foldBack。在这里,我使用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模块中定义的功能或此处描述的内容实际上是您应该自己做的好的培训方式。

答案 2 :(得分:0)

鉴于每个键仅在您也可以执行一次时出现:

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

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