如何使用递归收益率生成替换的所有组合?C#
这里的目标是使用递归生成具有替换的所有组合,以使其不超过RAM。为此设计了一个屈服算子。我想使用收益率操作符,因为如果我不这样做,我将超过可用RAM。我正在组合的元素的数量导致了数十亿个组合 我决定了如何使用递归收益率生成所有组合,而不进行替换。以下是我写的例子:如何使用递归收益率生成替换的所有组合?C#,c#,recursion,yield,C#,Recursion,Yield,这里的目标是使用递归生成具有替换的所有组合,以使其不超过RAM。为此设计了一个屈服算子。我想使用收益率操作符,因为如果我不这样做,我将超过可用RAM。我正在组合的元素的数量导致了数十亿个组合 我决定了如何使用递归收益率生成所有组合,而不进行替换。以下是我写的例子: public static IEnumerable<int[]> GetCombinationsWithYield(this int[] elements, int length) { return Combinat
public static IEnumerable<int[]> GetCombinationsWithYield(this int[] elements, int length)
{
return Combinations2(elements, length, 0, new int[length]);
}
private static IEnumerable<int[]> Combinations2(int[] input, int len, int startPosition, int[] result)
{
if (len == 0)
{
yield return result;
}
else
{
for (int i = startPosition; i <= input.Length - len; i++)
{
result[result.Length - len] = input[i];
// need to return the results of the recursive call
foreach (var combination in Combinations2(input, len - 1, i + 1, result))
{
yield return combination;
}
}
}
}
但正如您所看到的,该示例是没有替换的
另一方面,我想将此与替换一起使用,以便输出如下所示:
1,1,
1,2,
1,3,
2,1,
2,2,
2,3,
3,1,
3,2,
3,3,
这是我使用Linq解决方案能够实现的。但是它没有使用yield操作符,并且溢出了我的内存。这就是解决方案:
public static List<List<T>> GetCombinations<T>(this IEnumerable<T> pool, int comboLength, bool isWithReplacement)
{
if (isWithReplacement)
return GetCombinations(pool, comboLength).Select(c => c.ToList()).ToList();
}
private static IEnumerable<IEnumerable<T>> GetCombinations<T>(IEnumerable<T> list, int length)
{
if (length == 1) return list.Select(t => new[] {t});
return GetCombinations(list, length - 1).SelectMany(t => list, (t1, t2) => t1.Concat(new[] {t2}));
}
publicstaticlist-getcompositions(这个IEnumerable池,int-comboLength,bool-isWithReplacement)
{
如果(带替换件的ISF)
返回GetCombinations(pool,comboLength);
}
私有静态IEnumerable GetCombinations(IEnumerable列表,int-length)
{
if(length==1)返回列表。选择(t=>new[]{t});
返回GetCombinations(list,length-1);
}
如蒙协助,将不胜感激。熟悉Knuth算法的人是理想人选。您使用的LINQ操作是使用迭代器块在内部实现的。实际上,您需要一个将这些操作内联到解决方案中的解决方案这将导致与当前解决方案完全相同的问题。它会导致您创建大量昂贵的状态机,这些状态机都被丢弃了,几乎没有什么用处。为了避免极高的内存占用,首先需要避免创建太多的状态机。编写递归迭代器块并不能实现这一点。编写一个迭代的,而不是递归的解决方案(无论它是否是迭代器块)将是实现这一点的一种方法 迭代解决方案非常简单,并且内存占用是恒定的。您只需要计算存在的组合的数量,然后,对于每个组合,使用唯一索引计算组合(这非常简单)
私有静态IEnumerable GetCombinations(IList列表,int-length)
{
var numberofcombines=(long)Math.Pow(list.Count,length);
对于(长i=0;i
我想你已经有了解决方案。我对你的代码做了一些小修改
public static IEnumerable<List<T>> GetCombinations<T>(IEnumerable<T> pool, int comboLength,
bool isWithReplacement) // changed this to return an enumerable
{
foreach (var list in GetCombinations(pool, comboLength).Select(c => c.ToList()))
{
yield return list; // added a yield return of the list instead of returning a to list of the results
}
}
private static IEnumerable<IEnumerable<T>> GetCombinations<T>(IEnumerable<T> list, int length)
{
if (length == 1) return list.Select(t => new[] { t });
return GetCombinations(list, length - 1).SelectMany(t => list, (t1, t2) => t1.Concat(new[] { t2 }));
}
公共静态IEnumerable GetCombinations(IEnumerable池,int-comboLength,
bool isWithReplacement)//将此更改为返回可枚举项
{
foreach(GetCombinations(pool,comboLength)中的var列表。选择(c=>c.ToList())
{
yield return list;//添加了列表的yield return,而不是返回结果列表
}
}
私有静态IEnumerable GetCombinations(IEnumerable列表,int-length)
{
if(length==1)返回列表。选择(t=>new[]{t});
返回GetCombinations(list,length-1);
}
我用这个测试:
List<int> items = new List<int>();
for (int i = 1; i < 100; i++)
{
items.Add(i);
}
Stopwatch s = new Stopwatch();
s.Start();
int count = 0;
foreach (var list in GetCombinations(items, 4))
{
count++;
}
s.Stop();
Console.WriteLine(count);
Console.WriteLine(s.ElapsedMilliseconds);
Console.ReadLine();
List items=newlist();
对于(int i=1;i<100;i++)
{
项目.添加(i);
}
秒表s=新秒表();
s、 Start();
整数计数=0;
foreach(GetCompositions中的var列表(第4项))
{
计数++;
}
s、 停止();
控制台写入线(计数);
控制台写入线(s.elapsedmillyses);
Console.ReadLine();
这在7587毫秒内运行良好,没有内存问题,生成了96059601个组合。如果不想具体化,为什么要在所有这些地方调用ToList?应该完全避免使用递归。使用递归迭代器块将导致您特别试图避免的问题。嗯,在任何给定点上都不应该只有少量的迭代器处于活动状态吗?一个状态机,通常会有一个堆栈帧。@usr否,因为序列是由其他序列组成的,这些序列是由其他序列组成的,这些序列是由其他序列组成的,等等。我不确定我是否理解。你的意思是由其他序列组成,由其他序列组成,由其他序列组成,由其他序列组成,由其他序列组成,由其他序列组成,由其他序列组成,由其他序列组成,由其他序列组成,由其他序列组成,由其他序列组成,由其他序列组成,哪些是由其他序列组成的,哪些是由其他序列组成的,哪些是由其他序列组成的,哪些是由其他序列组成的,哪些是由其他序列组成的?使用David显示的测试(见下文),我在我的计算机上得到以下基准:Servy的解决方案:1864[ms]和David的解决方案:1861[ms]。这两种解决方案都不会导致系统RAM被消耗(至少不会明显消耗)。它们在功能上等同于IMHO。@saphbucket Um…David没有编写解决方案。他只是用了你的,告诉你这对他很有效。如果你自己的解决方案不起作用,尽管你在问题中说它对你不起作用,那么为什么你在问题中说它对你不起作用?谢谢,这是一个很好的解决方案。但我只能接受一个答案,Servy首先得到了答案。我的机器上只有1861毫秒,哟
private static IEnumerable<IEnumerable<T>> GetCombinations<T>(IList<T> list, int length)
{
var numberOfCombinations = (long)Math.Pow(list.Count, length);
for(long i = 0; i < numberOfCombinations; i++)
{
yield return BuildCombination(list, length, i);
}
}
private static IEnumerable<T> BuildCombination<T>(
IList<T> list,
int length,
long combinationNumber)
{
var temp = combinationNumber;
for(int j = 0; j < length; j++)
{
yield return list[(int)(temp % list.Count)];
temp /= list.Count;
}
}
public static IEnumerable<List<T>> GetCombinations<T>(IEnumerable<T> pool, int comboLength,
bool isWithReplacement) // changed this to return an enumerable
{
foreach (var list in GetCombinations(pool, comboLength).Select(c => c.ToList()))
{
yield return list; // added a yield return of the list instead of returning a to list of the results
}
}
private static IEnumerable<IEnumerable<T>> GetCombinations<T>(IEnumerable<T> list, int length)
{
if (length == 1) return list.Select(t => new[] { t });
return GetCombinations(list, length - 1).SelectMany(t => list, (t1, t2) => t1.Concat(new[] { t2 }));
}
List<int> items = new List<int>();
for (int i = 1; i < 100; i++)
{
items.Add(i);
}
Stopwatch s = new Stopwatch();
s.Start();
int count = 0;
foreach (var list in GetCombinations(items, 4))
{
count++;
}
s.Stop();
Console.WriteLine(count);
Console.WriteLine(s.ElapsedMilliseconds);
Console.ReadLine();