您可以使用各种内置函数和转换来重建行为,但最好具备递归的基本知识以从头开始构建特殊函数。
使用递归来学习将问题分解为更小的问题以学习如何解决更大的问题也是一个好主意。
如果你以递归的方式思考,你需要做的是:
- 您删除列表的头部以获得一个元素。
- 然后使用键和指定键的所有值创建元组
- 您删除了所有具有您处理的当前键并在此列表中重复出现的元素。
- 如果输入列表为空,则输出也为空。
所以你开始:
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"])]
上述函数不是尾递归的。但是您可以使用List.fold 或List.foldBack 轻松重写valuesOf 和removeKey。在这里我使用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 模块中定义的函数或您在此处描述的类似方法实际上是您应该自己进行的良好培训方法。