【问题标题】:Generating Permutations using LINQ使用 LINQ 生成排列
【发布时间】:2011-05-18 04:05:58
【问题描述】:

我有一组必须安排的产品。有 P 个产品,每个产品的索引从 1 到 P。每个产品可以安排在 0 到 T 的时间段内。我需要构造满足以下约束的产品时间表的所有排列:

If p1.Index > p2.Index then p1.Schedule >= p2.Schedule.

我正在努力构建迭代器。当产品数量是已知常量时,我​​知道如何通过 LINQ 执行此操作,但不确定当产品数量是输入参数时如何生成此查询。

理想情况下,我想使用 yield 语法来构造这个迭代器。

public class PotentialSchedule()
{
      public PotentialSchedule(int[] schedulePermutation)
      {
             _schedulePermutation = schedulePermutation;
      }
      private readonly int[] _schedulePermutation;
}


private int _numberProducts = ...;
public IEnumerator<PotentialSchedule> GetEnumerator()
{
     int[] permutation = new int[_numberProducts];
     //Generate all permutation combinations here -- how?
     yield return new PotentialSchedule(permutation);
}

编辑:_numberProducts = 2 时的示例

public IEnumerable<PotentialSchedule> GetEnumerator()
{
    var query = from p1 in Enumerable.Range(0,T)
                from p2 in Enumerable.Range(p2,T)
                select new { P1 = p1, P2 = p2};

    foreach (var result in query)
          yield return new PotentialSchedule(new int[] { result.P1, result.P2 });
}

【问题讨论】:

  • 您意识到这会很快变得非常大,对吗?你介意我问为什么你需要生成所有可能的时间表吗?
  • 是的——我意识到这将很快变得非常大。这是优化程序的一部分。最棘手的部分是扩展正在考虑的可能性。在最坏的情况下,我的问题大小大约是 P
  • 第二个例子的第四行应该是(p1, T),是吗?
  • 请注意,此类项目的数量等于 T 边的 P 维金字塔格子中的节点数。(因此,如果 P 为 3,T 为 5,则为点数一个最长边有五个点的三维金字塔。)一个边上有 15 个点的 20 维金字塔有 大量 个点。那个东西的点数和宇宙中基本粒子的数量差不多,(非常)粗略的近似。

标签: c# linq permutation


【解决方案1】:

如果我理解这个问题:您正在寻找长度为 P 的所有整数序列,其中集合中的每个整数都介于 0 和 T 之间,并且该序列是单调不减。对吗?

使用迭代器块编写这样的程序很简单:

using System;
using System.Collections.Generic;
using System.Linq;

static class Program
{
    static IEnumerable<T> Prepend<T>(T first, IEnumerable<T> rest)
    {
        yield return first;
        foreach (var item in rest)
            yield return item;
    }

    static IEnumerable<IEnumerable<int>> M(int p, int t1, int t2)
    {
        if (p == 0)
            yield return Enumerable.Empty<int>();
        else
            for (int first = t1; first <= t2; ++first)
                foreach (var rest in M(p - 1, first, t2))
                    yield return Prepend(first, rest);
    }

    public static void Main()
    {
        foreach (var sequence in M(4, 0, 2))
            Console.WriteLine(string.Join(", ", sequence));
    }
}

这会产生所需的输出:从 0 到 2 绘制的长度为 4 的非递减序列。

0, 0, 0, 0
0, 0, 0, 1
0, 0, 0, 2
0, 0, 1, 1
0, 0, 1, 2
0, 0, 2, 2
0, 1, 1, 1
0, 1, 1, 2
0, 1, 2, 2
0, 2, 2, 2
1, 1, 1, 1
1, 1, 1, 2
1, 1, 2, 2
1, 2, 2, 2
2, 2, 2, 2

请注意,用于连接的多重嵌套迭代器的用法是not very efficient,但谁在乎呢?您已经在生成 指数 数量的序列,因此生成器中存在 多项式 效率低下的事实基本上是无关紧要的。

方法 M 生成长度为 p 的所有单调非递减整数序列,其中整数介于 t1 和 t2 之间。它使用简单的递归递归地执行此操作。基本情况是只有一个长度为零的序列,即空序列。递归情况是,为了计算,比如说 P = 3, t1 = 0, t2 = 2,你计算:

- all sequences starting with 0 followed by sequences of length 2 drawn from 0 to 2.
- all sequences starting with 1 followed by sequences of length 2 drawn from 1 to 2.
- all sequences starting with 2 followed by sequences of length 2 drawn from 2 to 2.

结果就是这样。

或者,您可以在主递归方法中使用查询理解而不是迭代器块:

static IEnumerable<T> Singleton<T>(T first)
{
    yield return first;
}

static IEnumerable<IEnumerable<int>> M(int p, int t1, int t2)
{
    return p == 0 ?

        Singleton(Enumerable.Empty<int>()) : 

        from first in Enumerable.Range(t1, t2 - t1 + 1)
        from rest in M(p - 1, first, t2)
        select Prepend(first, rest);
}

基本上做同样的事情;它只是将循环移动到 SelectMany 方法中。

【讨论】:

  • 我更喜欢查询理解语法以提高可读性——与 yield 实现相比会有什么明显的缺陷吗?
  • @erash:两者的效率都不是特别高或低。这几乎是一个偏好问题。
  • 如果序列元素的范围是从0到k,并且序列的长度是n,那么输出中的序列数由这个公式给出: (n + (k − 1) )! / (k - 1)! /n!通过搜索“重复组合”可以找到许多证明,例如在这篇维基百科文章en.wikipedia.org/wiki/Stars_and_bars_%28combinatorics%29
【解决方案2】:

我使用这个库进行组合,发现它运行良好。示例程序有点混乱,但文章解释了使用代码需要什么。

  • 使用 C# 泛型的排列、组合和变体
  • 阿德里安·阿克森 | 2008 年 5 月 23 日
  • 讨论六种主要类型的组合集合,并提供示例和计数公式。扩展了一组基于 C# 泛型的类,用于枚举每个元集合。
  • http://www.codeproject.com/KB/recipes/Combinatorics.aspx插入

【讨论】:

    【解决方案3】:

    注意:Comparer 完全是可选的。如果您提供一个,则排列将按词法顺序返回。如果你不这样做,但原始项目是有序的,它仍然会按词汇顺序枚举。 Ian Griffiths 6 年前玩过这个,使用了一个更简单的算法(据我所知,它不进行词汇排序):http://www.interact-sw.co.uk/iangblog/2004/09/16/permuterate

    请记住,此代码已有几年历史,并且针对 .NET 2.0,因此没有扩展方法等(但修改起来应该很简单)。

    它使用Knuth calls "Algorithm L"的算法。它是非递归的、快速的,用于 C++ 标准模板库。

    static partial class Permutation
    {
        /// <summary>
        /// Generates permutations.
        /// </summary>
        /// <typeparam name="T">Type of items to permute.</typeparam>
        /// <param name="items">Array of items. Will not be modified.</param>
        /// <param name="comparer">Optional comparer to use.
        /// If a <paramref name="comparer"/> is supplied, 
        /// permutations will be ordered according to the 
        /// <paramref name="comparer"/>
        /// </param>
        /// <returns>Permutations of input items.</returns>
        public static IEnumerable<IEnumerable<T>> Permute<T>(T[] items, IComparer<T> comparer)
        {
            int length = items.Length;
            IntPair[] transform = new IntPair[length];
            if (comparer == null)
            {
                //No comparer. Start with an identity transform.
                for (int i = 0; i < length; i++)
                {
                    transform[i] = new IntPair(i, i);
                };
            }
            else
            {
                //Figure out where we are in the sequence of all permutations
                int[] initialorder = new int[length];
                for (int i = 0; i < length; i++)
                {
                    initialorder[i] = i;
                }
                Array.Sort(initialorder, delegate(int x, int y)
                {
                    return comparer.Compare(items[x], items[y]);
                });
                for (int i = 0; i < length; i++)
                {
                    transform[i] = new IntPair(initialorder[i], i);
                }
                //Handle duplicates
                for (int i = 1; i < length; i++)
                {
                    if (comparer.Compare(
                        items[transform[i - 1].Second], 
                        items[transform[i].Second]) == 0)
                    {
                        transform[i].First = transform[i - 1].First;
                    }
                }
            }
    
            yield return ApplyTransform(items, transform);
    
            while (true)
            {
                //Ref: E. W. Dijkstra, A Discipline of Programming, Prentice-Hall, 1997
                //Find the largest partition from the back that is in decreasing (non-icreasing) order
                int decreasingpart = length - 2;
                for (;decreasingpart >= 0 && 
                    transform[decreasingpart].First >= transform[decreasingpart + 1].First;
                    --decreasingpart) ;
                //The whole sequence is in decreasing order, finished
                if (decreasingpart < 0) yield break;
                //Find the smallest element in the decreasing partition that is 
                //greater than (or equal to) the item in front of the decreasing partition
                int greater = length - 1;
                for (;greater > decreasingpart && 
                    transform[decreasingpart].First >= transform[greater].First; 
                    greater--) ;
                //Swap the two
                Swap(ref transform[decreasingpart], ref transform[greater]);
                //Reverse the decreasing partition
                Array.Reverse(transform, decreasingpart + 1, length - decreasingpart - 1);
                yield return ApplyTransform(items, transform);
            }
        }
    
        #region Overloads
    
        public static IEnumerable<IEnumerable<T>> Permute<T>(T[] items)
        {
            return Permute(items, null);
        }
    
        public static IEnumerable<IEnumerable<T>> Permute<T>(IEnumerable<T> items, IComparer<T> comparer)
        {
            List<T> list = new List<T>(items);
            return Permute(list.ToArray(), comparer);
        }
    
        public static IEnumerable<IEnumerable<T>> Permute<T>(IEnumerable<T> items)
        {
            return Permute(items, null);
        }
    
        #endregion Overloads
    
        #region Utility
    
        public static IEnumerable<T> ApplyTransform<T>(
            T[] items, 
            IntPair[] transform)
        {
            for (int i = 0; i < transform.Length; i++)
            {
                yield return items[transform[i].Second];
            }
        }
    
        public static void Swap<T>(ref T x, ref T y)
        {
            T tmp = x;
            x = y;
            y = tmp;
        }
    
        public struct IntPair
        {
            public IntPair(int first, int second)
            {
                this.First = first;
                this.Second = second;
            }
            public int First;
            public int Second;
        }
    
        #endregion
    }
    
    class Program
    {
    
        static void Main()
        {
            int pans = 0;
            int[] digits = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            Stopwatch sw = new Stopwatch();
            sw.Start();
            foreach (var p in Permutation.Permute(digits))
            {
                pans++;
                if (pans == 720) break;
            }
            sw.Stop();
            Console.WriteLine("{0}pcs, {1}ms", pans, sw.ElapsedMilliseconds);
            Console.ReadKey();
        }
    }
    

    【讨论】:

      【解决方案4】:
      1. 创建另一个长度为 2^n 的数组,其中 n 是产品数
      2. 从 0 到 2^n 以二进制计数,并用每个计数填充数组。例如,如果 n=3 数组将如下所示:

      000 001 010 011 100 101 110 111

      1. 循环遍历二进制数组并在每个数字中找到一个,然后添加具有相同索引的产品:
       for each binaryNumber in ar{
         for i = 0 to n-1{
           if binaryNumber(i) = 1
             permunation.add(products(i))
         }
        permunations.add(permutation) 
      }
      

      示例: 如果 binaryNumber= 001 那么 permunation1 = product1 如果 binaryNumber= 101 那么 permunation1 = product3,product1

      【讨论】:

        【解决方案5】:

        这是 C# 7 的简单排列扩展方法(值元组和内部方法)。它源自@AndrasVaas's answer,但只使用了单一级别的惰性(防止由于项目随着时间的推移而发生的错误),失去了IComparer 功能(我不需要它),并且相当短。

        public static class PermutationExtensions
        {
            /// <summary>
            /// Generates permutations.
            /// </summary>
            /// <typeparam name="T">Type of items to permute.</typeparam>
            /// <param name="items">Array of items. Will not be modified.</param>
            /// <returns>Permutations of input items.</returns>
            public static IEnumerable<T[]> Permute<T>(this T[] items)
            {
                T[] ApplyTransform(T[] values, (int First, int Second)[] tx)
                {
                    var permutation = new T[values.Length];
                    for (var i = 0; i < tx.Length; i++)
                        permutation[i] = values[tx[i].Second];
                    return permutation;
                }
        
                void Swap<U>(ref U x, ref U y)
                {
                    var tmp = x;
                    x = y;
                    y = tmp;
                }
        
                var length = items.Length;
        
                // Build identity transform
                var transform = new(int First, int Second)[length];
                for (var i = 0; i < length; i++)
                    transform[i] = (i, i);
        
                yield return ApplyTransform(items, transform);
        
                while (true)
                {
                    // Ref: E. W. Dijkstra, A Discipline of Programming, Prentice-Hall, 1997
                    // Find the largest partition from the back that is in decreasing (non-increasing) order
                    var decreasingpart = length - 2;
                    while (decreasingpart >= 0 && transform[decreasingpart].First >= transform[decreasingpart + 1].First)
                        --decreasingpart;
        
                    // The whole sequence is in decreasing order, finished
                    if (decreasingpart < 0)
                        yield break;
        
                    // Find the smallest element in the decreasing partition that is
                    // greater than (or equal to) the item in front of the decreasing partition
                    var greater = length - 1;
                    while (greater > decreasingpart && transform[decreasingpart].First >= transform[greater].First)
                        greater--;
        
                    // Swap the two
                    Swap(ref transform[decreasingpart], ref transform[greater]);
        
                    // Reverse the decreasing partition
                    Array.Reverse(transform, decreasingpart + 1, length - decreasingpart - 1);
        
                    yield return ApplyTransform(items, transform);
                }
            }
        }
        

        【讨论】:

          【解决方案6】:

          今天我偶然发现了这一点,并认为我可以分享我的实现。

          对于 N 和 M 之间的所有整数,您必须先创建一个数组:

          IEnumerable<int> Range(int n, int m) {
              for(var i = n; i < m; ++i) {
                  yield return i;
              }
          }
          

          并通过Permutations(Range(1, 10))运行它:

              enum PermutationsOption {
                  None,
                  SkipEmpty,
                  SkipNotDistinct
              }
          
              private IEnumerable<IEnumerable<T>> Permutations<T>(IEnumerable<T> elements, PermutationsOption option = PermutationsOption.None, IEqualityComparer<T> equalityComparer = default(IEqualityComparer<T>)) {
                  var elementsList = new List<IEnumerable<T>>();
                  var elementsIndex = 0;
                  var elementsCount = elements.Count();
                  var elementsLength = Math.Pow(elementsCount + 1, elementsCount);
          
                  if (option.HasFlag(PermutationsOption.SkipEmpty)) {
                      elementsIndex = 1;
                  }
          
                  if (elements.Count() > 0) {
                      do {
                          var elementStack = new Stack<T>();
          
                          for (var i = 0; i < elementsCount; ++i) {
                              var ind = (int)(elementsIndex / Math.Pow(elementsCount + 1, i) % (elementsCount + 1));
                              if (ind == 0) {
                                  continue;
                              }
                              elementStack.Push(elements.ElementAt(ind - 1));
                          }
          
                          var elementsCopy = elementStack.ToArray() as IEnumerable<T>;
          
                          if (option.HasFlag(PermutationsOption.SkipNotDistinct)) {
                              elementsCopy = elementsCopy.Distinct();
                              elementsCopy = elementsCopy.ToArray();
          
                              if (elementsList.Any(p => CompareItemEquality(p, elementsCopy, equalityComparer))) {
                                  continue;
                              }
                          }
          
                          elementsList.Add(elementsCopy);
                      } while (++elementsIndex < elementsLength);
                  }
          
                  return elementsList.ToArray();
              }
          
              private bool CompareItemEquality<T>(IEnumerable<T> elements1, IEnumerable<T> elements2, IEqualityComparer<T> equalityComparer = default(IEqualityComparer<T>)) {
                  if (equalityComparer == null) {
                      equalityComparer = EqualityComparer<T>.Default;
                  }
          
                  return (elements2.Count() == elements2.Count()) && (elements2.All(p => elements1.Contains(p, equalityComparer)));
              }
          

          【讨论】:

            【解决方案7】:

            Lippert 先生的答案的输出可以看作是元素在 4 个槽中 0 和 2 之间的所有可能分布。
            比如
            0 3 1
            读作“没有 0,三个 1 和一个 2”
            这远没有利珀特先生的回答那么优雅,但至少效率不低

            public static void Main()
            {
              var distributions = Distributions(4, 3);
              PrintSequences(distributions);
            }
            
            /// <summary>
            /// Entry point for the other recursive overload
            /// </summary>
            /// <param name="length">Number of elements in the output</param>
            /// <param name="range">Number of distinct values elements can take</param>
            /// <returns></returns>
            static List<int[]> Distributions(int length, int range)
            {
              var distribution = new int[range];
              var distributions = new List<int[]>();
              Distributions(0, length, distribution, 0, distributions);
              distributions.Reverse();
              return distributions;
            }
            
            /// <summary>
            /// Recursive methode. Not to be called directly, only from other overload
            /// </summary>
            /// <param name="index">Value of the (possibly) last added element</param>
            /// <param name="length">Number of elements in the output</param>
            /// <param name="distribution">Distribution among element distinct values</param>
            /// <param name="sum">Exit condition of the recursion. Incremented if element added from parent call</param>
            /// <param name="distributions">All possible distributions</param>
            static void Distributions(int index,
                                      int length,
                                      int[] distribution,
                                      int sum,
                                      List<int[]> distributions)
            {
              //Uncomment for exactness check
              //System.Diagnostics.Debug.Assert(distribution.Sum() == sum);
              if (sum == length)
              {
                distributions.Add(distribution.Reverse().ToArray());
            
                for (; index < distribution.Length; index++)
                {
                  sum -= distribution[index];
                  distribution[index] = 0;
                }
                return;
              }
              if (index < distribution.Length)
              {
                Distributions(index + 1, length, distribution, sum, distributions);
                distribution[index]++;
                Distributions(index, length, distribution, ++sum, distributions);
              }
            }
            
            static void PrintSequences(List<int[]> distributions)
            {
              for (int i = 0; i < distributions.Count; i++)
              {
                for (int j = distributions[i].Length - 1; j >= 0; j--)
                  for (int k = 0; k < distributions[i][j]; k++)
                    Console.Write("{0:D1} ", distributions[i].Length - 1 - j);
                Console.WriteLine();
              }
            }
            

            【讨论】:

              【解决方案8】:
                  public static IList<IList<T>> Permutation<T>(ImmutableList<ImmutableList<T>> dimensions)
                  {
                      IList<IList<T>> result = new List<IList<T>>();
                      Step(ImmutableList.Create<T>(), dimensions, result);
                      return result;
                  }
              
                  private static void Step<T>(ImmutableList<T> previous, 
                      ImmutableList<ImmutableList<T>> rest, 
                      IList<IList<T>> result)
                  {
                      if (rest.IsEmpty)
                      {
                          result.Add(previous);
                          return;
                      }
              
                      var first = rest[0];
                      rest = rest.RemoveAt(0);
              
                      foreach (var label in first)
                      {
                          Step(previous.Add(label), rest, result);
                      }
                  }
              

              【讨论】:

                猜你喜欢
                • 2019-10-22
                • 2013-11-25
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-03-07
                相关资源
                最近更新 更多