声明一个将对列表转换为关系的函数。
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>
有什么想法吗?这不是家庭作业,只是自学,考虑到形式不允许累积,这个让我感到有点难过。
答案 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)
您可以使用各种内置函数和转换来重建行为,但是从头开始构建特殊函数的递归基础知识也很好。
使用递归来学习将问题分解为更小的问题以学习如何解决更大的问题也是一个好主意。
如果你以递归的方式思考,你需要做的是:
所以你从:
开始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)
我们尚未完成,我们仍需要创建valuesOf
和removeKey
。
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.head
或List.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"])]
上述函数不是尾递归的。但您可以使用valuesOf
或removeKey
轻松重写List.fold
和List.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.fold
或List.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.groupBy
和List.map
创建较小的代码感觉很好,您也应该能够自己创建这种类型的递归函数。为什么呢?
不可变链表是递归定义的数据结构,使用递归函数是很自然的。如果您不知道如何自己创建这样的函数,那么在您创建自己的递归数据结构时就会遇到麻烦,因为那时您已经没有预先定义的函数,例如groupBy
和map
可以使用。你必须自己构建这些功能。
尝试重建List
模块中定义的功能或此处描述的内容实际上是您应该自己做的好的培训方式。
答案 2 :(得分:0)
鉴于每个键仅在您也可以执行一次时出现:
[(2,"c");(1,"a");(2,"b")]
|> List.fold (fun (m:Map<int, string>) (k, v) -> m.Add (k, v))
Map.empty
如果按键多次出现,则最后一个值将被保留。