Generating Permutations using LINQ

后端 未结 8 1947
我寻月下人不归
我寻月下人不归 2020-12-01 04:59

I have a set of products that must be scheduled. There are P products each indexed from 1 to P. Each product can be scheduled into a time period 0 to T. I need to construct

相关标签:
8条回答
  • 2020-12-01 05:35

    Here's a simple permutation extension method for C# 7 (value tuples and inner methods). It's derived from @AndrasVaas's answer, but uses only a single level of laziness (preventing bugs due to mutating items over time), loses the IComparer feature (I didn't need it), and is a fair bit shorter.

    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);
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-01 05:36

    If I understand the question: you are looking for all sequences of integers of length P, where each integer in the set is between 0 and T, and the sequence is monotone nondecreasing. Is that correct?

    Writing such a program using iterator blocks is straightforward:

    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));
        }
    }
    

    Which produces the desired output: nondecreasing sequences of length 4 drawn from 0 through 2.

    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
    

    Note that the usage of multiply-nested iterators for concatenation is not very efficient, but who cares? You already are generating an exponential number of sequences, so the fact that there's a polynomial inefficiency in the generator is basically irrelevant.

    The method M generates all monotone nondecreasing sequences of integers of length p where the integers are between t1 and t2. It does so recursively, using a straightforward recursion. The base case is that there is exactly one sequence of length zero, namely the empty sequence. The recursive case is that in order to compute, say P = 3, t1 = 0, t2 = 2, you compute:

    - 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.
    

    And that's the result.

    Alternatively, you could use query comprehensions instead of iterator blocks in the main recursive method:

    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);
    }
    

    That does basically the same thing; it just moves the loops into the SelectMany method.

    0 讨论(0)
提交回复
热议问题