给定一组可能的值和一些“数字”,我想找到每个唯一的,无序的值分组。例如,假设你有一个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可能会在这里发挥作用,但我缺乏适当的术语让我有点陷入困境。任何帮助将不胜感激。
对于它的价值,我知道对于特定问题有更简单的解决方案,但作为函数式编程的练习,通用性是我的目标之一。
答案 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)
应生成num
到min
(包括)范围内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