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

Algorithm 我能更有效地找到给定大小的所有多重集吗?,algorithm,f#,dynamic-programming,Algorithm,F#,Dynamic Programming,给定一组可能的值和一些“数字”,我想找到每个唯一的、无序的值分组。例如,假设您有一个字母表A、B、C。所有3个数字的组合都是: AAA AAB ABB BBB BBC BCC CCC CCA CAA ABC 我试图解决的具体问题稍微简单一点。我正在做一个21点游戏作为F#()的练习。我计算手牌价值的方法是用一张牌的可能价值列表。除Ace之外的所有卡在列表中都有一个项目,但Ace可以是1或11。我在那篇文章中提出的实现产生了大量冗余。例如,3个ACE将创建一个类似于[3;13;13;13;23;

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

AAA
AAB
ABB
BBB
BBC
BCC
CCC
CCA
CAA
ABC
我试图解决的具体问题稍微简单一点。我正在做一个21点游戏作为F#()的练习。我计算手牌价值的方法是用一张牌的可能价值列表。除Ace之外的所有卡在列表中都有一个项目,但Ace可以是1或11。我在那篇文章中提出的实现产生了大量冗余。例如,3个ACE将创建一个类似于
[3;13;13;13;23;23;33]
的列表。现在我正在获取最后一个列表,并通过
Distinct()
运行它,但感觉有点像黑客

把这些联系在一起,ACE的潜在值(1,11)构成了字母表,手上ACE的数量决定了数字的数量。在这种情况下,我希望算法能够得出以下模式:

1, 1 
1, 11
11,11
有些事情告诉我可能会在这里发挥作用,但我缺乏合适的术语,这让我有点卡住了。任何帮助都将不胜感激

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

可以递归地进行。我正在用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;
}
static void gencombineral(int num,int[]base,
最小整数、最大整数、集合dst)
{
如果(num==0){
dst.add(基础);
返回;
}
对于(int i=min;i给定{1,11}的“字母表”,那么您基本上希望生成长度n的所有“单词”,其中n是ACE的数量,这样所有的1(0或更多)顺序不重要,这只是一个简单的方法来迭代你所关心的组合

在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
表示创建一个新列表,该列表是
[1]
副本的串联,即

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

如果
x=3

这里是托马斯·波宁对F#的回答的半忠实翻译。请注意,我并不认为这比使用
独特的
的天真方法更有效,但它肯定更简洁:

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
    | [] -> () 
  }

嗯,在你的例子中,(1)计算Ace(让计数为N),然后(2)生成可能的总值作为列表理解就足够了

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

{i*11+(N-i)*1}|0这个问题是一个很好的脑筋急转弯。它应该是code golf.:)

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
替换它。您还必须
生成一个
而不是
[]

性能更新:

这个问题可以很好地进行记忆,并在非琐碎的情况下提供更好的记忆性能

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)

感谢您的编辑!是时候做更多的挖掘了。这是输出一个包含所有正确字符的列表序列,而不是一个包含许多列表的序列。@gradbot-根据原始帖子,我认为这确实是我想要的(例如,将ABC示例与
combs['a';'B';'C']3的输出进行比较)。但是,要解决的问题没有完全指定,所以我可能错了。够了。我假设他希望输出类似于他的示例数据。+1,更干净。我想把计数器拉出来进行匹配,但时间不多了。删除序列真的把事情搞清楚了。这是我最初使用S的想法的一部分我在我的解决方案中添加了一个我的解决方案变体的备忘录版本。:)我喜欢这样,尽管我对这个问题的理解是,这个问题要求的是
fun ln->permute ln |>Seq.concat
。我已经更新了我的答案,包含了一个基于你的解决方案(希望你不介意)。我不介意。我来这里是为了提高我的技能和分数,只是为了好玩。很难选择你们中的哪一个得到了公认的答案。我认为@kvb的最终变体是我到目前为止最喜欢的。+1这与我发布的答案相同。其他一些答案似乎过于复杂了。它们是我们把事情复杂化了,因为在我添加编辑之前,他们认为我在寻找更普遍的解决方案,以澄清我确实是这样的
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)
// 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)