我能更有效地找到给定大小的所有多重集合吗?

时间:2010-02-17 19:07:55

标签: algorithm f# dynamic-programming

给定一组可能的值和一些“数字”,我想找到每个唯一的,无序的值分组。例如,假设你有一个A,B,C的字母表。所有3位数的组合将是:

AAA
AAB
ABB
BBB
BBC
BCC
CCC
CCA
CAA
ABC

我想解决的具体问题有点简单。我正在做一个BlackJack游戏作为F#中的练习(I've posted about this before)。我正在计算手值的方法是列出卡片可能值的列表。除Ace之外的所有卡在列表中都有一个项目,但Aces可以是1或11.我在该帖子中提出的实现会产生大量冗余。例如,3个ace会创建一个类似[3; 13; 13; 13; 23; 23; 23; 33]的列表。现在我正在进入最终列表并通过Distinct()运行它,但感觉有点像黑客。

将所有这些结合起来,Aces的潜在值(1,11)构成字母表,手中的ace数决定了数字的位数。在这种情况下,我希望算法能够提出以下模式:

1, 1 
1, 11
11,11

有些东西告诉我Dynamic Programming可能会在这里发挥作用,但我缺乏适当的术语让我有点陷入困境。任何帮助将不胜感激。

修改

对于它的价值,我知道对于特定问题有更简单的解决方案,但作为函数式编程的练习,通用性是我的目标之一。

5 个答案:

答案 0 :(得分:3)

嗯,在你的情况下,足以(1)计算A(计数为N),然后(2)生成可能的总值作为列表理解

{ i * 11 + (N - i) * 1 }   |   0 <= i <= N }

...但是你在F#中表达了这一点。无需进行实际排列,组合等。

答案 1 :(得分:2)

这是Thomas Pornin对F#的回答的半忠实翻译。请注意,我不认为这比使用distinct的天真方法更具性能,但它绝对更整洁:

let rec splits l = function
| [] -> Seq.empty
| x::xs -> seq {
    yield [],x,xs
    for l,y,ys in splits xs do
      yield x::l,y,ys
  }

let rec combs s = function
| 0 -> Seq.singleton []
| n -> seq {
    for _,x,rest in splits s do
      for l in combs (x::rest) (n-1) do
        yield x::l
  }

或者,改为对gradbot的解决方案:

let rec permute list = function
| 0 -> Seq.singleton []
| n -> seq { 
    match list with 
    | x::xs ->  
        yield! permute list (n-1) |> Seq.map (fun l -> x::l)
        yield! permute xs n
    | [] -> () 
  }

答案 2 :(得分:2)

这个问题是一个很好的脑筋急转弯。它应该是代码高尔夫。 :)

let rec permute list count =
    seq {
        match list with
        | y::ys when count > 0 -> 
            for n in permute list (count - 1) do
                yield Seq.map (fun li -> y::li) n
            yield Seq.concat (permute ys count)
        | y::ys -> yield Seq.singleton []
        | [] -> ()
    }

Ace示例

permute ["1";"11"] 2
|> Seq.concat
|> Seq.iter (printfn "%A")

["1"; "1"]
["1"; "11"]
["11"; "11"]

ABC示例

permute ["A";"B";"C"] 3
|> Seq.concat
|> Seq.iter (printfn "%A");;

["A"; "A"; "A"]
["A"; "A"; "B"]
["A"; "A"; "C"]
["A"; "B"; "B"]
["A"; "B"; "C"]
["A"; "C"; "C"]
["B"; "B"; "B"]
["B"; "B"; "C"]
["B"; "C"; "C"]
["C"; "C"; "C"]

y::li是所有联合工作发生的地方。如果你想要的只是字符串,你可以用y + li替换它。您还必须yield Seq.singleton "" []

效果更新:

这个问题很好地回忆起来,并且为无琐事的情况提供了更好的性能。

let memoize2 f = 
    let cache = Dictionary<_,_>()
    fun x y -> 
        let ok, res = cache.TryGetValue((x, y))
        if ok then 
            res 
        else 
            let res = f x y
            cache.[(x, y)] <- res
            res

// permute ["A";"B";"C"] 400 |> Seq.concat |> Seq.length |> printf "%A"       
// Real: 00:00:07.740, CPU: 00:00:08.234, GC gen0: 118, gen1: 114, gen2: 4
let rec permute =
    memoize2(fun list count ->
        seq {
            match list with
            | y::ys when count > 0 -> 
                for n in permute list (count - 1) do
                    yield Seq.map (fun li -> y::li) n |> Seq.cache
                yield Seq.concat (permute ys count)
            | y::ys -> yield Seq.singleton []
            | [] -> ()
        } |> Seq.cache)

我还记得kvb解决方案,它比我的快15%。

// permute ["A";"B";"C"] 400 |> Seq.length |> printf "%A"
// Real: 00:00:06.587, CPU: 00:00:07.046, GC gen0: 87, gen1: 83, gen2: 4
let rec permute = 
    memoize2 (fun list n ->
        match n with
            | 0 -> Seq.singleton []
            | n -> 
                seq {
                    match list with 
                    | x::xs ->  
                        yield! permute list (n-1) |> Seq.map (fun l -> x::l)
                        yield! permute xs n
                    | [] -> () 
                } |> Seq.cache)

答案 3 :(得分:0)

你可以递归地做。我用Java写这个;我的F#不够好:

static void genCombInternal(int num, int[] base,
    int min, int max, Collection<int[]> dst)
{
    if (num == 0) {
        dst.add(base);
        return;
    }
    for (int i = min; i <= max; i ++) {
        int[] nb = new int[base.length + 1];
        System.arraycopy(base, 0, nb, 0, base.length);
        nb[base.length] = i;
        genCombInternal(num - 1, nb, i, max, dst);
    }
}

static Collection<int[]> genComb(int num, int min, int max)
{
    Collection<int[]> d = new ArrayList<int[]>();
    genCombInternal(num, new int[0], min, max, d);
    return d;
}

此代码完全未经测试。如果有效,则调用genComb(num, min, max)应生成nummin(包括)范围内max个整数的所有“组合”,这样就不会有两个返回的组合订购时保存相同。

这非常接近生成“真实”组合的代码。诀窍在于每一步允许的整数:如果你在递归调用中将i更改为i+1,那么你应该得到数学组合。

答案 4 :(得分:0)

鉴于{1,11}的“字母”,你基本上想要生成长度 n 的所有“单词”,其中 n 是aces的数量,这样所有的1(0或更多)都在左边,所有的11都在右边。排序无关紧要,这只是迭代您关注的组合的简单方法。

在Python中:

n = 3 # number of aces
hands = []
for i in range(0,n+1):
    hands.append([1] * (n-i) + [11] * i)

甚至在Python中更简单:

hands = [[1]*(n-i) + [11]*i for i in range(0,n+1)]

获得每手总得分:

scores = [sum(hand) for hand in hands]

如果您不熟悉Python语法的注释,括号[]表示列表,[1]*x表示创建一个新列表,该列表是x [1]个副本的串联};就是这样,

[1] * x ==  [1,1,1] 

如果x = 3