【问题标题】:F# return element pairs in listF# 返回列表中的元素对
【发布时间】:2010-10-04 04:06:48
【问题描述】:

我一直在寻找一种优雅的方式来编写一个函数,该函数接受一个元素列表并返回一个包含所有可能的不同元素对的元组列表,而不考虑顺序,即 (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"

同样有效,但与第一个一样,考虑到相当简单的问题,它似乎不必要地涉及和复杂。我想我的问题是关于风格的,真的,如果有人可以为此想出一个两条线。

【问题讨论】:

  • 糟糕,在我进行编辑时,人们已经发布了建议。谢谢大家,你们都这么快!

标签: f#


【解决方案1】:

我认为您的代码实际上非常接近惯用版本。我要做的唯一更改是在序列表达式中使用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

【讨论】:

  • rec 对不是尾递归的,是吗?
【解决方案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)

【讨论】:

  • 我不需要前两个匹配模式虽然 '| [] | [_] -> []',但可以在最后添加一个全面的内容,对吗? ('| _ -> []')
  • 对。 (最短评论长度的更多文字)
【解决方案3】:

你似乎把事情复杂化了很多。如果你想要一个列表,为什么还要使用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)

【讨论】:

  • 我不是 100% 确定,但我认为使用序列表达式将比使用 @ 连接列表更有效。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多