为什么此序列生成无效?

时间:2019-09-13 13:01:44

标签: f#

我正面临着一个元素列表的所有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的当前值,我可以理解哪些分支实际上可以包含解。

Search tree

2 个答案:

答案 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调用自身。

xs的em>作为参数。可以预见,这会导致原始列表的所有尾巴序列。

产生所有可能子集的一种显而易见的方法是,在每个迭代级别上,产生一系列除去了一个元素的列表:

  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]]