我一直在寻找一种优雅的方式来编写一个函数,它接受一个元素列表并返回一个元组列表,其中包含所有可能的不同元素对,不考虑顺序,即(a,b)和( b,a)应该被认为是相同的,只返回其中一个。 我确信这是一个非常标准的算法,它可能是F#文档的封面页面中的一个例子,但我找不到它,甚至没有在Internet上搜索SML或Caml。我想出的是以下内容:
let test = [1;2;3;4;5;6]
let rec pairs l =
seq {
match l with
| h::t ->
yield! t |> Seq.map (fun elem -> (h, elem))
yield! t |> pairs
| _ -> ()
}
test |> pairs |> Seq.toList |> printfn "%A"
这可以工作并返回预期的结果 [(1,2); (1,3); (1,4); (1,5); (1,6); (2,3); (2,4); (2,5); (2,6); (3,4); (3,5); (3,6); (4,5); (4,6); (5,6)] 但它看起来非常单一。 我不需要通过序列表达式然后转换回列表,必须有一个只涉及基本列表操作或库调用的等效解决方案......
编辑:
我这里也有这个
let test = [1;2;3;4;5;6]
let rec pairs2 l =
let rec p h t =
match t with
| hx::tx -> (h, hx)::p h tx
| _ -> []
match l with
| h::t -> p h t @ pairs2 t
| _ -> []
test |> pairs2 |> Seq.toList |> printfn "%A"
同样有效,但与第一个一样,考虑到相当容易的问题,它似乎不必要地涉及和复杂。我想我的问题是关于风格的问题,真的,如果有人可以为此提出双线。
答案 0 :(得分:4)
我认为您的代码实际上非常接近惯用版本。我要做的唯一改变是我会在序列表达式中使用for
,而不是将yield!
与Seq.map
结合使用。我通常也会以不同的方式格式化代码(但这只是个人偏好),所以我会这样写:
let rec pairs l = seq {
match l with
| h::t -> for e in t do yield h, elem
yield! pairs t
| _ -> () }
这与Brian发布的内容几乎相同。如果您想获得一个列表作为结果,那么您可以将整个事件包裹在[ ... ]
而不是seq { ... }
中。
然而,这实际上并没有那么不同 - 在封面下,编译器仍然使用序列,它只是将转换添加到列表中。我认为在你真正需要一个列表之前使用序列实际上是一个好主意(因为序列是经过评估的,所以你可以避免评估不必要的东西)。
如果您想通过将行为的一部分抽象为单独的(通常有用的)函数来使这更简单一些,那么您可以编写一个函数,例如splits
返回列表的所有元素以及列表的其余部分:
let splits list =
let rec splitsAux acc list =
match list with
| x::xs -> splitsAux ((x, xs)::acc) xs
| _ -> acc |> List.rev
splitsAux [] list
例如splits [ 1 .. 3 ]
会给出 [(1,[2; 3]); (2,[3]); (3,[])] 。当你有这个功能时,实现原始问题会变得容易得多 - 你可以写:
[ for x, xs in splits [ 1 .. 5] do
for y in xs do yield x, y ]
作为谷歌搜索的指南 - 该问题被称为从给定集合中查找所有2元素combinations。
答案 1 :(得分:2)
你似乎过于复杂化了很多事情。如果你想要一个清单,为什么甚至使用seq
? <怎么样
let rec pairs lst =
match lst with
| [] -> []
| h::t -> List.map (fun elem -> (h, elem)) t @ pairs t
let _ =
let test = [1;2;3;4;5;6]
printfn "%A" (pairs test)
答案 2 :(得分:2)
这是一种方式:
let rec pairs l =
match l with
| [] | [_] -> []
| h :: t ->
[for x in t do
yield h,x
yield! pairs t]
let test = [1;2;3;4;5;6]
printfn "%A" (pairs test)