c#列出有限长度的排列

时间:2014-06-22 08:22:10

标签: c# list permutation

我有一份优惠清单,我想从中创建"链" (例如排列)链长有限。

我已经使用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中。

3 个答案:

答案 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);

结果

permutation sample