我正面临着一个元素列表的所有k组合(无重复)的产生。除了代码中可能的优化外,我还编写了该函数,几乎可以确定它是否可以工作:
// comb :: int -> 'a list -> seq<'a list>
// Generates a sequence of all combinations (no repetition)
let comb k xs =
// subs :: 'a list -> seq<'a list>
// Splits a list in its subsequent non-empty tails
let rec subs xs =
seq {
match xs with
| _::ys -> yield xs
yield! subs ys
| _ -> yield! []
}
let rec comb' k xs rs =
seq {
for zs in subs xs do
match k, zs with
| 0, _ -> yield rs // Solution reached
| _ when k > List.length zs -> yield! [] // Safety (and optimizing) guard
| _, y::ys -> yield! comb' (k - 1) ys (y::rs) // Let's go deeper
| _ -> yield! [] // Not a solution
}
comb' k xs []
此算法的思想是“遍历”所有可能组合的树,并仅选择具有k
个元素的树; subs
函数用于生成元素的子集,以生成同一级别的子树;也就是说,调用:
Seq.toList <| subs [1..3];;
产生:
[[1;2;3];[2;3];[3]]
也许这部分有点令人困惑,但它不应该成为问题的一部分,我认为问题不存在。
该算法不会保留元素的顺序,但是对于我的目的而言不是必需的。
制作一个简单的测试用例:
Seq.toList <| comb 2 [1..3];;
我期待着三种解决方案:
[[2;1];[3;1];[3;2]]
但实际上它仅返回:
[[2;1]]
我使用VS Code进行了一些调试,但我并不真正了解执行的流程。
有人看到问题出在哪里吗?
更新
我意识到我在算法背后隐藏了这个概念。
我像搜索树一样形象地解决了问题;在每个级别上,子树的根都包含由所有其余尾部的头(subs
结果)和父节点列表的连接所获得的解决方案。
比较尾巴的大小和k
的当前值,我可以理解哪些分支实际上可以包含解。
答案 0 :(得分:3)
您的代码几乎正确。唯一的问题是,当xs
中的comb'
为空时,即使subs
为0,k
也将为空(因为没有非空的尾部),但是在这种情况下,您也应该产生rs
。
可以通过测试k
在for循环外是否为0并在其中产生rs
,然后将for循环放入else分支(现在您只需要匹配)来解决此问题在zs
上)
let rec comb' k xs rs =
seq {
if k = 0 then yield rs
elif k <= List.length xs then
for zs in subs xs do
match zs with
| y::ys -> yield! comb' (k - 1) ys (y::rs) // Let's go deeper
| [] -> yield! [] // Not a solution
}
答案 1 :(得分:1)
好吧,您的解决方案非常混乱,也难怪它会产生错误的结果。很难理解,很难遵循。
问题1 :subs
实际上并未产生所有可能的子集。看:在您自己的实验中,您是说subs [1..3]
产生了[[1;2;3]; [2;3]; [3]]
。但这是不正确的:[1;3]
也是可能的子集,但它丢失了!
如果仔细查看subs
的实际操作,您会发现它在每次迭代时都会发出xs
的当前值,然后通过 tail调用自身。
产生所有可能子集的一种显而易见的方法是,在每个迭代级别上,产生一系列除去了一个元素的列表:
let rec subs xs =
if List.isEmpty xs then Seq.empty
else
seq {
yield xs
for i in 0..(List.length xs - 1) do
let xsWithoutI = (List.take i xs) @ (List.skip (i+1) xs)
yield! subs xsWithoutI
}
但是,这当然会产生重复:
> subs [1..3] |> Seq.toList
val it : int list list =
[[1; 2; 3]; [2; 3]; [3]; [2]; [1; 3]; [3]; [1]; [1; 2]; [2]; [1]]
我将保留它作为练习,以排除重复。
问题2 ,功能comb'
根本没有意义。在每次迭代中,zs
是可能的子序列(来自subs
的一个 ,然后与y::ys
匹配,从而使y
成为子序列。该子序列的第一个元素,而ys
-它的尾部。然后,将子序列的第一个元素放在结果之前,然后重复。这意味着您将逐步从每个子序列的前一个元素中构建结果 ,但是顺序相反(因为您正在添加)。因此,结果自然是[2;1]
:2
是第二个子序列的第一个元素,而1
是第一个子序列的第一个元素。
这种方法对我完全没有意义。我不知道导致该实施的思考过程是什么。
如果您拥有所有subs
中所有可生子序列的序列,并且只想要那些k
个元素长的子序列,为什么不仅仅对其进行过滤呢?
let comb' k xs = subs xs |> Seq.filter (fun s -> List.length s = k)
> comb' 2 [1..3]
val it : seq<int list> = seq [[2; 3]; [1; 3]; [1; 2]]