我有一份优惠清单,我想从中创建"链" (例如排列)链长有限。
我已经使用Kw.Combinatorics project创建了排列。 但是,默认行为会在列表计数的长度中创建排列。我不确定如何将链长限制为' n'
这是我目前的代码:
private static List<List<Offers>> GetPerms(List<Offers> list, int chainLength)
{
List<List<Offers>> response = new List<List<Offers>>();
foreach (var row in new Permutation(list.Count).GetRows())
{
List<Offers> innerList = new List<Offers>();
foreach (var mix in Permutation.Permute(row, list))
{
innerList.Add(mix);
}
response.Add(innerList);
innerList = new List<Offers>();
}
return response;
}
实施者:
List<List<AdServer.Offers>> lst = GetPerms(offers, 2);
如果某人有更好的解决方案,我就不会被锁定在KWCombinatorics中。
答案 0 :(得分:1)
您不是在寻找排列,而是在寻找变体。这是一种可能的算法。我更喜欢可能返回很多元素的函数的迭代器方法。这样,调用者可以决定他是否真的需要所有元素:
IEnumerable<IList<T>> GetVariations<T>(IList<T> offers, int length)
{
var startIndices = new int[length];
var variationElements = new HashSet<T>(); //for duplicate detection
while (startIndices[0] < offers.Count)
{
var variation = new List<T>(length);
var valid = true;
for (int i = 0; i < length; ++i)
{
var element = offers[startIndices[i]];
if (variationElements.Contains(element))
{
valid = false;
break;
}
variation.Add(element);
variationElements.Add(element);
}
if (valid)
yield return variation;
//Count up the indices
startIndices[length - 1]++;
for (int i = length - 1; i > 0; --i)
{
if (startIndices[i] >= offers.Count)
{
startIndices[i] = 0;
startIndices[i - 1]++;
}
else
break;
}
variationElements.Clear();
}
}
此算法的想法是使用offers.Count
基数中的数字。对于三个报价,所有数字都在0-2范围内。然后,我们基本上逐步递增此数字,并返回驻留在指定索引处的商品。如果您想允许重复,可以删除支票和HashSet<T>
。
这是一个优化的变体,可以在索引级别上执行重复检查。在我的测试中,它比前一个版本快得多:
IEnumerable<IList<T>> GetVariations<T>(IList<T> offers, int length)
{
var startIndices = new int[length];
for (int i = 0; i < length; ++i)
startIndices[i] = i;
var indices = new HashSet<int>(); // for duplicate check
while (startIndices[0] < offers.Count)
{
var variation = new List<T>(length);
for (int i = 0; i < length; ++i)
{
variation.Add(offers[startIndices[i]]);
}
yield return variation;
//Count up the indices
AddOne(startIndices, length - 1, offers.Count - 1);
//duplicate check
var check = true;
while (check)
{
indices.Clear();
for (int i = 0; i <= length; ++i)
{
if (i == length)
{
check = false;
break;
}
if (indices.Contains(startIndices[i]))
{
var unchangedUpTo = AddOne(startIndices, i, offers.Count - 1);
indices.Clear();
for (int j = 0; j <= unchangedUpTo; ++j )
{
indices.Add(startIndices[j]);
}
int nextIndex = 0;
for(int j = unchangedUpTo + 1; j < length; ++j)
{
while (indices.Contains(nextIndex))
nextIndex++;
startIndices[j] = nextIndex++;
}
break;
}
indices.Add(startIndices[i]);
}
}
}
}
int AddOne(int[] indices, int position, int maxElement)
{
//returns the index of the last element that has not been changed
indices[position]++;
for (int i = position; i > 0; --i)
{
if (indices[i] > maxElement)
{
indices[i] = 0;
indices[i - 1]++;
}
else
return i;
}
return 0;
}
答案 1 :(得分:1)
这是另一个我认为应该比接受的答案更快的实现(并且它肯定是更少的代码)。
public static IEnumerable<IEnumerable<T>> GetVariationsWithoutDuplicates<T>(IList<T> items, int length)
{
if (length == 0 || !items.Any()) return new List<List<T>> { new List<T>() };
return from item in items.Distinct()
from permutation in GetVariationsWithoutDuplicates(items.Where(i => !EqualityComparer<T>.Default.Equals(i, item)).ToList(), length - 1)
select Prepend(item, permutation);
}
public static IEnumerable<IEnumerable<T>> GetVariations<T>(IList<T> items, int length)
{
if (length == 0 || !items.Any()) return new List<List<T>> { new List<T>() };
return from item in items
from permutation in GetVariations(Remove(item, items).ToList(), length - 1)
select Prepend(item, permutation);
}
public static IEnumerable<T> Prepend<T>(T first, IEnumerable<T> rest)
{
yield return first;
foreach (var item in rest) yield return item;
}
public static IEnumerable<T> Remove<T>(T item, IEnumerable<T> from)
{
var isRemoved = false;
foreach (var i in from)
{
if (!EqualityComparer<T>.Default.Equals(item, i) || isRemoved) yield return i;
else isRemoved = true;
}
}
在我的3.1 GHz Core 2 Duo上,我测试了这个:
public static void Test(Func<IList<int>, int, IEnumerable<IEnumerable<int>>> getVariations)
{
var max = 11;
var timer = System.Diagnostics.Stopwatch.StartNew();
for (int i = 1; i < max; ++i)
for (int j = 1; j < i; ++j)
getVariations(MakeList(i), j).Count();
timer.Stop();
Console.WriteLine("{0,40}{1} ms", getVariations.Method.Name, timer.ElapsedMilliseconds);
}
// Make a list that repeats to guarantee we have duplicates
public static IList<int> MakeList(int size)
{
return Enumerable.Range(0, size/2).Concat(Enumerable.Range(0, size - size/2)).ToList();
}
未优化
GetVariations 11894 ms
GetVariationsWithoutDuplicates 9 ms
OtherAnswerGetVariations 22485 ms
OtherAnswerGetVariationsWithDuplicates 243415 ms
使用编译器优化
GetVariations 9667 ms
GetVariationsWithoutDuplicates 8 ms
OtherAnswerGetVariations 19739 ms
OtherAnswerGetVariationsWithDuplicates 228802 ms
答案 2 :(得分:0)
如果我在这里说得对你就是你需要的
这将根据指定的链限制
创建排列 public static List<List<T>> GetPerms<T>(List<T> list, int chainLimit)
{
if (list.Count() == 1)
return new List<List<T>> { list };
return list
.Select((outer, outerIndex) =>
GetPerms(list.Where((inner, innerIndex) => innerIndex != outerIndex).ToList(), chainLimit)
.Select(perms => (new List<T> { outer }).Union(perms).Take(chainLimit)))
.SelectMany<IEnumerable<IEnumerable<T>>, List<T>>(sub => sub.Select<IEnumerable<T>, List<T>>(s => s.ToList()))
.Distinct(new PermComparer<T>()).ToList();
}
class PermComparer<T> : IEqualityComparer<List<T>>
{
public bool Equals(List<T> x, List<T> y)
{
return x.SequenceEqual(y);
}
public int GetHashCode(List<T> obj)
{
return (int)obj.Average(o => o.GetHashCode());
}
}
你会像这样称呼它
List<List<AdServer.Offers>> lst = GetPerms<AdServer.Offers>(offers, 2);
我使这个功能非常通用,所以你也可以将它用于其他目的
例如
List<string> list = new List<string>(new[] { "apple", "banana", "orange", "cherry" });
List<List<string>> perms = GetPerms<string>(list, 2);
结果