web-dev-qa-db-fra.com

Génération de permutations d'un ensemble (le plus efficacement possible)

Je voudrais générer toutes les permutations d'un ensemble (une collection), comme ceci:

Collection: 1, 2, 3
Permutations: {1, 2, 3}
              {1, 3, 2}
              {2, 1, 3}
              {2, 3, 1}
              {3, 1, 2}
              {3, 2, 1}

Ce n'est pas une question de "comment", en général, mais plutôt de savoir comment le plus efficacement possible… .. De plus, je ne voudrais pas générer TOUTES les permutations et les rendre, mais seulement générer une seule permutation, à la fois, et ne continue que si nécessaire (un peu comme les itérateurs - que j'ai aussi essayés mais qui se sont avérés moins efficaces).

J'ai testé de nombreux algorithmes et approches et mis au point ce code, qui est le plus efficace de ceux que j'ai essayés:

public static bool NextPermutation<T>(T[] elements) where T : IComparable<T>
{
    // More efficient to have a variable instead of accessing a property
    var count = elements.Length;

    // Indicates whether this is the last lexicographic permutation
    var done = true;

    // Go through the array from last to first
    for (var i = count - 1; i > 0; i--)
    {
        var curr = elements[i];

        // Check if the current element is less than the one before it
        if (curr.CompareTo(elements[i - 1]) < 0)
        {
            continue;
        }

        // An element bigger than the one before it has been found,
        // so this isn't the last lexicographic permutation.
        done = false;

        // Save the previous (bigger) element in a variable for more efficiency.
        var prev = elements[i - 1];

        // Have a variable to hold the index of the element to swap
        // with the previous element (the to-swap element would be
        // the smallest element that comes after the previous element
        // and is bigger than the previous element), initializing it
        // as the current index of the current item (curr).
        var currIndex = i;

        // Go through the array from the element after the current one to last
        for (var j = i + 1; j < count; j++)
        {
            // Save into variable for more efficiency
            var tmp = elements[j];

            // Check if tmp suits the "next swap" conditions:
            // Smallest, but bigger than the "prev" element
            if (tmp.CompareTo(curr) < 0 && tmp.CompareTo(prev) > 0)
            {
                curr = tmp;
                currIndex = j;
            }
        }

        // Swap the "prev" with the new "curr" (the swap-with element)
        elements[currIndex] = prev;
        elements[i - 1] = curr;

        // Reverse the order of the tail, in order to reset it's lexicographic order
        for (var j = count - 1; j > i; j--, i++)
        {
            var tmp = elements[j];
            elements[j] = elements[i];
            elements[i] = tmp;
        }

        // Break since we have got the next permutation
        // The reason to have all the logic inside the loop is
        // to prevent the need of an extra variable indicating "i" when
        // the next needed swap is found (moving "i" outside the loop is a
        // bad practice, and isn't very readable, so I preferred not doing
        // that as well).
        break;
    }

    // Return whether this has been the last lexicographic permutation.
    return done;
}

Son utilisation consisterait à envoyer un tableau d’éléments et à récupérer un booléen indiquant si c’était la dernière permutation lexicographique ou non, ainsi qu’à modifier le tableau à la permutation suivante.

Exemple d'utilisation:

var arr = new[] {1, 2, 3};

PrintArray(arr);

while (!NextPermutation(arr))
{
    PrintArray(arr);
}

Le fait est que je ne suis pas satisfait de la rapidité du code.

Itérer sur toutes les permutations d'un tableau de taille 11 prend environ 4 secondes . Bien que cela puisse être considéré comme impressionnant, étant donné que le nombre de permutations possibles d'un ensemble de taille 11 est de 11!, ce qui représente près de 40 millions.

Logiquement, avec un tableau de taille 12, cela prendra environ 12 fois plus de temps, puisque 12! est 11! * 12, et avec un tableau de taille 13, cela prendra environ 13 fois plus de temps que le temps nécessaire à la taille 12, et ainsi de suite.

Ainsi, vous pouvez facilement comprendre comment avec un tableau de taille 12 et plus, il faut vraiment beaucoup de temps pour parcourir toutes les permutations.

Et je suis persuadé que je peux en quelque sorte gagner du temps (sans passer à un autre langage que C #, car l’optimisation du compilateur s’optimise réellement, et je doute que je puisse l’optimiser aussi manuellement, dans Assembly).

Quelqu'un sait-il qu'il existe un autre moyen d'accélérer le processus??. Avez-vous une idée de la manière de rendre l'algorithme actuel plus rapide?

Notez que je ne souhaite pas utiliser une bibliothèque ou un service externe pour le faire. Je veux que le code soit lui-même et je veux qu'il soit aussi efficace que possible.

55
SimpleVar

Mise à jour 2018-05-28:

Un peu trop tard ... 

Selon des tests récents (mise à jour 2018-05-22)

  • Le plus rapide est le mien MAIS pas dans l'ordre lexicographique
  • Pour obtenir un ordre lexicographique le plus rapide, la solution Sani Singh Huttunen semble être la solution.

Résultats des tests de performance pour 10 articles (10!) En publication sur ma machine (millisecs):

  • Ouellet: 29
  • SimpleVar: 95
  • Erez Robinson: 156
  • Sani Singh Huttunen: 37
  • Pengyang: 45047

Résultats des tests de performance pour 13 articles (13!) En publication sur ma machine (secondes):

  • Ouellet: 48.437
  • SimpleVar: 159.869
  • Erez Robinson: 327.781
  • Sani Singh Huttunen: 64.839

Avantages de ma solution:

  • Algorithme de Heap (échange simple par permutation)
  • Pas de multiplication (comme certaines implémentations vues sur le web)
  • Échange en ligne
  • Générique
  • Pas de code dangereux
  • En place (très faible utilisation de la mémoire)
  • Pas de modulo (seulement comparaison du premier bit)

Mon implémentation de l'algorithme de Heap :

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;

namespace WpfPermutations
{
    /// <summary>
    /// EO: 2016-04-14
    /// Generator of all permutations of an array of anything.
    /// Base on Heap's Algorithm. See: https://en.wikipedia.org/wiki/Heap%27s_algorithm#cite_note-3
    /// </summary>
    public static class Permutations
    {
        /// <summary>
        /// Heap's algorithm to find all pmermutations. Non recursive, more efficient.
        /// </summary>
        /// <param name="items">Items to permute in each possible ways</param>
        /// <param name="funcExecuteAndTellIfShouldStop"></param>
        /// <returns>Return true if cancelled</returns> 
        public static bool ForAllPermutation<T>(T[] items, Func<T[], bool> funcExecuteAndTellIfShouldStop)
        {
            int countOfItem = items.Length;

            if (countOfItem <= 1)
            {
                return funcExecuteAndTellIfShouldStop(items);
            }

            var indexes = new int[countOfItem];
            for (int i = 0; i < countOfItem; i++)
            {
                indexes[i] = 0;
            }

            if (funcExecuteAndTellIfShouldStop(items))
            {
                return true;
            }

            for (int i = 1; i < countOfItem;)
            {
                if (indexes[i] < i)
                { // On the web there is an implementation with a multiplication which should be less efficient.
                    if ((i & 1) == 1) // if (i % 2 == 1)  ... more efficient ??? At least the same.
                    {
                        Swap(ref items[i], ref items[indexes[i]]);
                    }
                    else
                    {
                        Swap(ref items[i], ref items[0]);
                    }

                    if (funcExecuteAndTellIfShouldStop(items))
                    {
                        return true;
                    }

                    indexes[i]++;
                    i = 1;
                }
                else
                {
                    indexes[i++] = 0;
                }
            }

            return false;
        }

        /// <summary>
        /// This function is to show a linq way but is far less efficient
        /// From: StackOverflow user: Pengyang : http://stackoverflow.com/questions/756055/listing-all-permutations-of-a-string-integer
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        static IEnumerable<IEnumerable<T>> GetPermutations<T>(IEnumerable<T> list, int length)
        {
            if (length == 1) return list.Select(t => new T[] { t });

            return GetPermutations(list, length - 1)
                .SelectMany(t => list.Where(e => !t.Contains(e)),
                    (t1, t2) => t1.Concat(new T[] { t2 }));
        }

        /// <summary>
        /// Swap 2 elements of same type
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="a"></param>
        /// <param name="b"></param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static void Swap<T>(ref T a, ref T b)
        {
            T temp = a;
            a = b;
            b = temp;
        }

        /// <summary>
        /// Func to show how to call. It does a little test for an array of 4 items.
        /// </summary>
        public static void Test()
        {
            ForAllPermutation("123".ToCharArray(), (vals) =>
            {
                Console.WriteLine(String.Join("", vals));
                return false;
            });

            int[] values = new int[] { 0, 1, 2, 4 };

            Console.WriteLine("Ouellet heap's algorithm implementation");
            ForAllPermutation(values, (vals) =>
            {
                Console.WriteLine(String.Join("", vals));
                return false;
            });

            Console.WriteLine("Linq algorithm");
            foreach (var v in GetPermutations(values, values.Length))
            {
                Console.WriteLine(String.Join("", v));
            }

            // Performance Heap's against Linq version : huge differences
            int count = 0;

            values = new int[10];
            for (int n = 0; n < values.Length; n++)
            {
                values[n] = n;
            }

            Stopwatch stopWatch = new Stopwatch();

            ForAllPermutation(values, (vals) =>
            {
                foreach (var v in vals)
                {
                    count++;
                }
                return false;
            });

            stopWatch.Stop();
            Console.WriteLine($"Ouellet heap's algorithm implementation {count} items in {stopWatch.ElapsedMilliseconds} millisecs");

            count = 0;
            stopWatch.Reset();
            stopWatch.Start();

            foreach (var vals in GetPermutations(values, values.Length))
            {
                foreach (var v in vals)
                {
                    count++;
                }
            }

            stopWatch.Stop();
            Console.WriteLine($"Linq {count} items in {stopWatch.ElapsedMilliseconds} millisecs");
        }
    }
}

An ceci est mon code de test:

Task.Run(() =>
            {

                int[] values = new int[12];
                for (int n = 0; n < values.Length; n++)
                {
                    values[n] = n;
                }

                // Eric Ouellet Algorithm
                int count = 0;
                var stopwatch = new Stopwatch();
                stopwatch.Reset();
                stopwatch.Start();
                Permutations.ForAllPermutation(values, (vals) =>
                {
                    foreach (var v in vals)
                    {
                        count++;
                    }
                    return false;
                });
                stopwatch.Stop();
                Console.WriteLine($"This {count} items in {stopwatch.ElapsedMilliseconds} millisecs");

                // Simple Plan Algorithm
                count = 0;
                stopwatch.Reset();
                stopwatch.Start();
                PermutationsSimpleVar permutations2 = new PermutationsSimpleVar();
                permutations2.Permutate(1, values.Length, (int[] vals) =>
                {
                    foreach (var v in vals)
                    {
                        count++;
                    }
                });
                stopwatch.Stop();
                Console.WriteLine($"Simple Plan {count} items in {stopwatch.ElapsedMilliseconds} millisecs");

                // ErezRobinson Algorithm
                count = 0;
                stopwatch.Reset();
                stopwatch.Start();
                foreach(var vals in PermutationsErezRobinson.QuickPerm(values))
                {
                    foreach (var v in vals)
                    {
                        count++;
                    }
                };
                stopwatch.Stop();
                Console.WriteLine($"Erez Robinson {count} items in {stopwatch.ElapsedMilliseconds} millisecs");
            });

Exemples d'utilisation:

ForAllPermutation("123".ToCharArray(), (vals) =>
    {
        Console.WriteLine(String.Join("", vals));
        return false;
    });

int[] values = new int[] { 0, 1, 2, 4 };
ForAllPermutation(values, (vals) =>
        {
            Console.WriteLine(String.Join("", vals));
            return false;
        });
18
Eric Ouellet

C'est peut-être ce que vous cherchez.

    private static bool NextPermutation(int[] numList)
    {
        /*
         Knuths
         1. Find the largest index j such that a[j] < a[j + 1]. If no such index exists, the permutation is the last permutation.
         2. Find the largest index l such that a[j] < a[l]. Since j + 1 is such an index, l is well defined and satisfies j < l.
         3. Swap a[j] with a[l].
         4. Reverse the sequence from a[j + 1] up to and including the final element a[n].

         */
        var largestIndex = -1;
        for (var i = numList.Length - 2; i >= 0; i--)
        {
            if (numList[i] < numList[i + 1]) {
                largestIndex = i;
                break;
            }
        }

        if (largestIndex < 0) return false;

        var largestIndex2 = -1;
        for (var i = numList.Length - 1 ; i >= 0; i--) {
            if (numList[largestIndex] < numList[i]) {
                largestIndex2 = i;
                break;
            }
        }

        var tmp = numList[largestIndex];
        numList[largestIndex] = numList[largestIndex2];
        numList[largestIndex2] = tmp;

        for (int i = largestIndex + 1, j = numList.Length - 1; i < j; i++, j--) {
            tmp = numList[i];
            numList[i] = numList[j];
            numList[j] = tmp;
        }

        return true;
    }
33

Eh bien, si vous pouvez le gérer en C puis le traduire dans la langue de votre choix, vous ne pourrez pas aller beaucoup plus vite que cela, car le temps sera dominé par print:

void perm(char* s, int n, int i){
  if (i >= n-1) print(s);
  else {
    perm(s, n, i+1);
    for (int j = i+1; j<n; j++){
      swap(s[i], s[j]);
      perm(s, n, i+1);
      swap(s[i], s[j]);
    }
  }
}

perm("ABC", 3, 0);
10
Mike Dunlavey

L'algorithme de permutation le plus rapide que je connaisse est l'algorithme QuickPerm.
Voici l’implémentation, elle utilise le rendement du rendement afin que vous puissiez en effectuer une itération l’une après l’autre.

Code:

public static IEnumerable<IEnumerable<T>> QuickPerm<T>(this IEnumerable<T> set)
    {
        int N = set.Count();
        int[] a = new int[N];
        int[] p = new int[N];

        var yieldRet = new T[N];

        List<T> list = new List<T>(set);

        int i, j, tmp; // Upper Index i; Lower Index j

        for (i = 0; i < N; i++)
        {
            // initialize arrays; a[N] can be any type
            a[i] = i + 1; // a[i] value is not revealed and can be arbitrary
            p[i] = 0; // p[i] == i controls iteration and index boundaries for i
        }
        yield return list;
        //display(a, 0, 0);   // remove comment to display array a[]
        i = 1; // setup first swap points to be 1 and 0 respectively (i & j)
        while (i < N)
        {
            if (p[i] < i)
            {
                j = i%2*p[i]; // IF i is odd then j = p[i] otherwise j = 0
                tmp = a[j]; // swap(a[j], a[i])
                a[j] = a[i];
                a[i] = tmp;

                //MAIN!

                for (int x = 0; x < N; x++)
                {
                    yieldRet[x] = list[a[x]-1];
                }
                yield return yieldRet;
                //display(a, j, i); // remove comment to display target array a[]

                // MAIN!

                p[i]++; // increase index "weight" for i by one
                i = 1; // reset index i to 1 (assumed)
            }
            else
            {
                // otherwise p[i] == i
                p[i] = 0; // reset p[i] to zero
                i++; // set new index value for i (increase by one)
            } // if (p[i] < i)
        } // while(i < N)
    }
8
Erez Robinson

Voici la mise en œuvre la plus rapide, j'ai fini avec:

public class Permutations
{
    private readonly Mutex _mutex = new Mutex();

    private Action<int[]> _action;
    private Action<IntPtr> _actionUnsafe;
    private unsafe int* _arr;
    private IntPtr _arrIntPtr;
    private unsafe int* _last;
    private unsafe int* _lastPrev;
    private unsafe int* _lastPrevPrev;

    public int Size { get; private set; }

    public bool IsRunning()
    {
        return this._mutex.SafeWaitHandle.IsClosed;
    }

    public bool Permutate(int start, int count, Action<int[]> action, bool async = false)
    {
        return this.Permutate(start, count, action, null, async);
    }

    public bool Permutate(int start, int count, Action<IntPtr> actionUnsafe, bool async = false)
    {
        return this.Permutate(start, count, null, actionUnsafe, async);
    }

    private unsafe bool Permutate(int start, int count, Action<int[]> action, Action<IntPtr> actionUnsafe, bool async = false)
    {
        if (!this._mutex.WaitOne(0))
        {
            return false;
        }

        var x = (Action)(() =>
                             {
                                 this._actionUnsafe = actionUnsafe;
                                 this._action = action;

                                 this.Size = count;

                                 this._arr = (int*)Marshal.AllocHGlobal(count * sizeof(int));
                                 this._arrIntPtr = new IntPtr(this._arr);

                                 for (var i = 0; i < count - 3; i++)
                                 {
                                     this._arr[i] = start + i;
                                 }

                                 this._last = this._arr + count - 1;
                                 this._lastPrev = this._last - 1;
                                 this._lastPrevPrev = this._lastPrev - 1;

                                 *this._last = count - 1;
                                 *this._lastPrev = count - 2;
                                 *this._lastPrevPrev = count - 3;

                                 this.Permutate(count, this._arr);
                             });

        if (!async)
        {
            x();
        }
        else
        {
            new Thread(() => x()).Start();
        }

        return true;
    }

    private unsafe void Permutate(int size, int* start)
    {
        if (size == 3)
        {
            this.DoAction();
            Swap(this._last, this._lastPrev);
            this.DoAction();
            Swap(this._last, this._lastPrevPrev);
            this.DoAction();
            Swap(this._last, this._lastPrev);
            this.DoAction();
            Swap(this._last, this._lastPrevPrev);
            this.DoAction();
            Swap(this._last, this._lastPrev);
            this.DoAction();

            return;
        }

        var sizeDec = size - 1;
        var startNext = start + 1;
        var usedStarters = 0;

        for (var i = 0; i < sizeDec; i++)
        {
            this.Permutate(sizeDec, startNext);

            usedStarters |= 1 << *start;

            for (var j = startNext; j <= this._last; j++)
            {
                var mask = 1 << *j;

                if ((usedStarters & mask) != mask)
                {
                    Swap(start, j);
                    break;
                }
            }
        }

        this.Permutate(sizeDec, startNext);

        if (size == this.Size)
        {
            this._mutex.ReleaseMutex();
        }
    }

    private unsafe void DoAction()
    {
        if (this._action == null)
        {
            if (this._actionUnsafe != null)
            {
                this._actionUnsafe(this._arrIntPtr);
            }

            return;
        }

        var result = new int[this.Size];

        fixed (int* pt = result)
        {
            var limit = pt + this.Size;
            var resultPtr = pt;
            var arrayPtr = this._arr;

            while (resultPtr < limit)
            {
                *resultPtr = *arrayPtr;
                resultPtr++;
                arrayPtr++;
            }
        }

        this._action(result);
    }

    private static unsafe void Swap(int* a, int* b)
    {
        var tmp = *a;
        *a = *b;
        *b = tmp;
    }
}

Performances d'utilisation et de test:

var perms = new Permutations();

var sw1 = Stopwatch.StartNew();

perms.Permutate(0,
                11,
                (Action<int[]>)null); // Comment this line and...
                //PrintArr); // Uncomment this line, to print permutations

sw1.Stop();
Console.WriteLine(sw1.Elapsed);

Méthode d'impression:

private static void PrintArr(int[] arr)
{
    Console.WriteLine(string.Join(",", arr));
}

Aller plus loin:

Je n'y ai même pas pensé pendant très longtemps, je ne peux donc que trop expliquer mon code, mais voici l'idée générale:

  1. Les permutations ne sont pas lexicographiques - cela me permet d'effectuer pratiquement moins d'opérations entre permutations.
  2. L'implémentation est récursive et, lorsque la taille de la "vue" est de 3, la logique complexe est ignorée et il suffit d'effectuer 6 échanges pour obtenir les 6 permutations (ou sous-permutations, si vous voulez).
  3. Comme les permutations ne sont pas dans un ordre lexicographique, comment puis-je choisir l'élément à placer au début de la "vue" actuelle (sous-permutation)? Je garde une trace des éléments qui ont déjà été utilisés comme "éléments de départ" dans l'appel récursif actuel de sous-permutation et cherche simplement linéairement un élément qui n'a pas été utilisé dans la queue de mon tableau.
  4. L'implémentation concerne uniquement les entiers. Pour permuter sur une collection générique d'éléments, utilisez simplement la classe Permutations pour permuter les index au lieu de votre collection réelle.
  5. Le Mutex est là juste pour s'assurer que les choses ne se fassent pas mal lorsque l'exécution est asynchrone (notez que vous pouvez passer un paramètre UnsafeAction qui à son tour donnera un pointeur sur le tableau permuté. Vous ne devez pas changer l'ordre des éléments dans ce tableau. (pointeur)! Si vous le souhaitez, vous devez copier le tableau dans un tableau tmp ou tout simplement utiliser le paramètre safe action qui s’occupe de cela pour vous - le tableau transmis est déjà une copie.

Remarque:

Je n'ai aucune idée de la qualité de cette implémentation - je ne l'ai pas touchée depuis si longtemps… .. Testez et comparez vous-même à d'autres implémentations, et laissez-moi savoir si vous avez des commentaires!

Prendre plaisir.

4
SimpleVar

Voici un Finder de permutation générique qui parcourra toutes les permutations d'une collection et appellera une fonction d'évaluation. Si la fonction d'évaluation renvoie true (la réponse recherchée a été trouvée), le Finder de permutation arrête le traitement. 

public class PermutationFinder<T>
{
    private T[] items;
    private Predicate<T[]> SuccessFunc;
    private bool success = false;
    private int itemsCount;

    public void Evaluate(T[] items, Predicate<T[]> SuccessFunc)
    {
        this.items = items;
        this.SuccessFunc = SuccessFunc;
        this.itemsCount = items.Count();

        Recurse(0);
    }

    private void Recurse(int index)
    {
        T tmp;

        if (index == itemsCount)
            success = SuccessFunc(items);
        else
        {
            for (int i = index; i < itemsCount; i++)
            {
                tmp = items[index];
                items[index] = items[i];
                items[i] = tmp;

                Recurse(index + 1);

                if (success)
                    break;

                tmp = items[index];
                items[index] = items[i];
                items[i] = tmp;
            }
        }
    }
}

Voici une implémentation simple:

class Program
{
    static void Main(string[] args)
    {
        new Program().Start();
    }

    void Start()
    {
        string[] items = new string[5];
        items[0] = "A";
        items[1] = "B";
        items[2] = "C";
        items[3] = "D";
        items[4] = "E";
        new PermutationFinder<string>().Evaluate(items, Evaluate);
        Console.ReadLine();
    }

    public bool Evaluate(string[] items)
    {
        Console.WriteLine(string.Format("{0},{1},{2},{3},{4}", items[0], items[1], items[2], items[3], items[4]));
        bool someCondition = false;

        if (someCondition)
            return true;  // Tell the permutation Finder to stop.

        return false;
    }
}
3
Sam

Mise à jour 2018-05-28, une nouvelle version, la plus rapide ... (multithread)

 enter image description here

                            Time taken for fastest algorithms

Besoin: solution Sani Singh Huttunen (lexique le plus rapide) et mon nouveau OuelletLexico3 prenant en charge l'indexation

L'indexation présente deux avantages principaux: 

  • permet d'obtenir directement la permutation de quelqu'un
  • permet le multi-threading (dérivé du premier avantage) 

Article: Permutations: implémentations rapides et nouvel algorithme d'indexation permettant le multithreading

Sur ma machine (6 cœurs hyperthread: 12 threads) Xeon E5-1660 0 @ 3.30Ghz, teste les algorithmes avec des tâches vides à faire pour 13! éléments (temps en millisecs):

  • 53071: Ouellet (implémentation de Heap)
  • 65366: Sani Singh Huttunen (Lexique le plus rapide)
  • 11377: Mix OuelletLexico3 - Sani Singh Huttunen

Remarque secondaire: l'utilisation de propriétés/variables de partage entre les threads pour une action de permutation aura un impact important sur les performances si leur utilisation est une modification (lecture/écriture). Cela générera " false sharing " entre les threads. Vous n'obtiendrez pas les performances attendues. J'ai eu ce comportement lors des tests. Mon expérience a montré des problèmes lorsque j'essaie d'augmenter la variable globale pour le nombre total de permutations.

Usage:

PermutationMixOuelletSaniSinghHuttunen.ExecuteForEachPermutationMT(
  new int[] {1, 2, 3, 4}, 
  p => 
    { 
      Console.WriteLine($"Values: {p[0]}, {p[1]}, p[2]}, {p[3]}"); 
    });

Code:

using System;
using System.Runtime.CompilerServices;

namespace WpfPermutations
{
    public class Factorial
    {
        // ************************************************************************
        protected static long[] FactorialTable = new long[21];

        // ************************************************************************
        static Factorial()
        {
            FactorialTable[0] = 1; // To prevent divide by 0
            long f = 1;
            for (int i = 1; i <= 20; i++)
            {
                f = f * i;
                FactorialTable[i] = f;
            }
        }

        // ************************************************************************
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static long GetFactorial(int val) // a long can only support up to 20!
        {
            if (val > 20)
            {
                throw new OverflowException($"{nameof(Factorial)} only support a factorial value <= 20");
            }

            return FactorialTable[val];
        }

        // ************************************************************************

    }
}


namespace WpfPermutations
{
    public class PermutationSaniSinghHuttunen
    {
        public static bool NextPermutation(int[] numList)
        {
            /*
             Knuths
             1. Find the largest index j such that a[j] < a[j + 1]. If no such index exists, the permutation is the last permutation.
             2. Find the largest index l such that a[j] < a[l]. Since j + 1 is such an index, l is well defined and satisfies j < l.
             3. Swap a[j] with a[l].
             4. Reverse the sequence from a[j + 1] up to and including the final element a[n].

             */
            var largestIndex = -1;
            for (var i = numList.Length - 2; i >= 0; i--)
            {
                if (numList[i] < numList[i + 1])
                {
                    largestIndex = i;
                    break;
                }
            }

            if (largestIndex < 0) return false;

            var largestIndex2 = -1;
            for (var i = numList.Length - 1; i >= 0; i--)
            {
                if (numList[largestIndex] < numList[i])
                {
                    largestIndex2 = i;
                    break;
                }
            }

            var tmp = numList[largestIndex];
            numList[largestIndex] = numList[largestIndex2];
            numList[largestIndex2] = tmp;

            for (int i = largestIndex + 1, j = numList.Length - 1; i < j; i++, j--)
            {
                tmp = numList[i];
                numList[i] = numList[j];
                numList[j] = tmp;
            }

            return true;
        }
    }
}


using System;

namespace WpfPermutations
{
    public class PermutationOuelletLexico3<T> // Enable indexing 
    {
        // ************************************************************************
        private T[] _sortedValues;

        private bool[] _valueUsed;

        public readonly long MaxIndex; // long to support 20! or less 

        // ************************************************************************
        public PermutationOuelletLexico3(T[] sortedValues)
        {
            _sortedValues = sortedValues;
            Result = new T[_sortedValues.Length];
            _valueUsed = new bool[_sortedValues.Length];

            MaxIndex = Factorial.GetFactorial(_sortedValues.Length);
        }

        // ************************************************************************
        public T[] Result { get; private set; }

        // ************************************************************************
        /// <summary>
        /// Sort Index is 0 based and should be less than MaxIndex. Otherwise you get an exception.
        /// </summary>
        /// <param name="sortIndex"></param>
        /// <param name="result">Value is not used as inpu, only as output. Re-use buffer in order to save memory</param>
        /// <returns></returns>
        public void GetSortedValuesFor(long sortIndex)
        {
            int size = _sortedValues.Length;

            if (sortIndex < 0)
            {
                throw new ArgumentException("sortIndex should greater or equal to 0.");
            }

            if (sortIndex >= MaxIndex)
            {
                throw new ArgumentException("sortIndex should less than factorial(the lenght of items)");
            }

            for (int n = 0; n < _valueUsed.Length; n++)
            {
                _valueUsed[n] = false;
            }

            long factorielLower = MaxIndex;

            for (int index = 0; index < size; index++)
            {
                long factorielBigger = factorielLower;
                factorielLower = Factorial.GetFactorial(size - index - 1);  //  factorielBigger / inverseIndex;

                int resultItemIndex = (int)(sortIndex % factorielBigger / factorielLower);

                int correctedResultItemIndex = 0;
                for(;;)
                {
                    if (! _valueUsed[correctedResultItemIndex])
                    {
                        resultItemIndex--;
                        if (resultItemIndex < 0)
                        {
                            break;
                        }
                    }
                    correctedResultItemIndex++;
                }

                Result[index] = _sortedValues[correctedResultItemIndex];
                _valueUsed[correctedResultItemIndex] = true;
            }
        }

        // ************************************************************************
    }
}


using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace WpfPermutations
{
    public class PermutationMixOuelletSaniSinghHuttunen
    {
        // ************************************************************************
        private long _indexFirst;
        private long _indexLastExclusive;
        private int[] _sortedValues;

        // ************************************************************************
        public PermutationMixOuelletSaniSinghHuttunen(int[] sortedValues, long indexFirst = -1, long indexLastExclusive = -1)
        {
            if (indexFirst == -1)
            {
                indexFirst = 0;
            }

            if (indexLastExclusive == -1)
            {
                indexLastExclusive = Factorial.GetFactorial(sortedValues.Length);
            }

            if (indexFirst >= indexLastExclusive)
            {
                throw new ArgumentException($"{nameof(indexFirst)} should be less than {nameof(indexLastExclusive)}");
            }

            _indexFirst = indexFirst;
            _indexLastExclusive = indexLastExclusive;
            _sortedValues = sortedValues;
        }

        // ************************************************************************
        public void ExecuteForEachPermutation(Action<int[]> action)
        {
            //          Console.WriteLine($"Thread {System.Threading.Thread.CurrentThread.ManagedThreadId} started: {_indexFirst} {_indexLastExclusive}");

            long index = _indexFirst;

            PermutationOuelletLexico3<int> permutationOuellet = new PermutationOuelletLexico3<int>(_sortedValues);

            permutationOuellet.GetSortedValuesFor(index);
            action(permutationOuellet.Result);
            index++;

            int[] values = permutationOuellet.Result;
            while (index < _indexLastExclusive)
            {
                PermutationSaniSinghHuttunen.NextPermutation(values);
                action(values);
                index++;
            }

            //          Console.WriteLine($"Thread {System.Threading.Thread.CurrentThread.ManagedThreadId} ended: {DateTime.Now.ToString("yyyyMMdd_HHmmss_ffffff")}");
        }

        // ************************************************************************
        public static void ExecuteForEachPermutationMT(int[] sortedValues, Action<int[]> action)
        {
            int coreCount = Environment.ProcessorCount; // Hyper treading are taken into account (ex: on a 4 cores hyperthreaded = 8)
            long itemsFactorial = Factorial.GetFactorial(sortedValues.Length);
            long partCount = (long)Math.Ceiling((double)itemsFactorial / (double)coreCount);
            long startIndex = 0;

            var tasks = new List<Task>();

            for (int coreIndex = 0; coreIndex < coreCount; coreIndex++)
            {
                long stopIndex = Math.Min(startIndex + partCount, itemsFactorial);

                PermutationMixOuelletSaniSinghHuttunen mix = new PermutationMixOuelletSaniSinghHuttunen(sortedValues, startIndex, stopIndex);
                Task task = Task.Run(() => mix.ExecuteForEachPermutation(action));
                tasks.Add(task);

                if (stopIndex == itemsFactorial)
                {
                    break;
                }

                startIndex = startIndex + partCount;
            }

            Task.WaitAll(tasks.ToArray());
        }

        // ************************************************************************


    }
}
3
Eric Ouellet

Voici une implémentation récursive avec complexité O(n * n!)1 basé sur l'échange des éléments d'un tableau. Le tableau est initialisé avec les valeurs de 1, 2, ..., n

using System;

namespace Exercise
{
class Permutations
{
    static void Main(string[] args)
    {
        int setSize = 3;
        FindPermutations(setSize);
    }
    //-----------------------------------------------------------------------------
    /* Method: FindPermutations(n) */
    private static void FindPermutations(int n)
    {
        int[] arr = new int[n];
        for (int i = 0; i < n; i++)
        {
            arr[i] = i + 1;
        }
        int iEnd = arr.Length - 1;
        Permute(arr, iEnd);
    }
    //-----------------------------------------------------------------------------  
    /* Method: Permute(arr) */
    private static void Permute(int[] arr, int iEnd)
    {
        if (iEnd == 0)
        {
            PrintArray(arr);
            return;
        }

        Permute(arr, iEnd - 1);
        for (int i = 0; i < iEnd; i++)
        {
            swap(ref arr[i], ref arr[iEnd]);
            Permute(arr, iEnd - 1);
            swap(ref arr[i], ref arr[iEnd]);
        }
    }
}
}

À chaque étape récursive, nous échangeons le dernier élément avec l'élément actuel désigné par la variable locale dans la boucle for, puis nous indiquons l'unicité du remplacement en: incrémentant la variable locale de la boucle for et décrémentant la condition de terminaison de la for boucle, qui est initialement définie sur le nombre d'éléments du tableau, lorsque ce dernier devient zéro, nous terminons la récursion.

Voici les fonctions d'assistance:

    //-----------------------------------------------------------------------------
    /*
        Method: PrintArray()

    */
    private static void PrintArray(int[] arr, string label = "")
    {
        Console.WriteLine(label);
        Console.Write("{");
        for (int i = 0; i < arr.Length; i++)
        {
            Console.Write(arr[i]);
            if (i < arr.Length - 1)
            {
                Console.Write(", ");
            }
        }
        Console.WriteLine("}");
    }
    //-----------------------------------------------------------------------------

    /*
        Method: swap(ref int a, ref int b)

    */
    private static void swap(ref int a, ref int b)
    {
        int temp = a;
        a = b;
        b = temp;
    }

1. Il y a n! permutations d'éléments n à imprimer.

2
Ziezi

Il existe une introduction accessible aux algorithmes et un aperçu des implémentations dans Manuel de conception d'algorithmes de Steven Skiena (chapitre 14.4 de la deuxième édition)

Skiena fait référence à D. Knuth. L'art de la programmation informatique, volume 4 Fascicule 2: Générer tous les nuplets et toutes les permutations. Addison Wesley, 2005.

1
Colonel Panic

Je serais surpris s'il y a vraiment des améliorations d'ordre de grandeur à trouver. S'il y en a, alors C # a besoin d'une amélioration fondamentale. De plus, faire quelque chose d'intéressant avec votre permutation demandera généralement plus de travail que de le générer. Le coût de la production sera donc insignifiant dans l’ensemble des choses.

Cela dit, je suggérerais d'essayer les choses suivantes. Vous avez déjà essayé des itérateurs. Mais avez-vous essayé d’avoir une fonction qui prend une fermeture en entrée, puis appelle cette fermeture pour chaque permutation trouvée? En fonction de la mécanique interne de C #, cela peut être plus rapide.

De même, avez-vous essayé d'avoir une fonction qui retourne une fermeture qui va itérer sur une permutation spécifique?

Quelle que soit l'approche choisie, vous pouvez expérimenter un certain nombre de micro-optimisations. Par exemple, vous pouvez trier votre tableau d’entrée, puis vous savez toujours dans quel ordre il se trouve. Par exemple, vous pouvez avoir un tableau de bools indiquant si cet élément est inférieur au suivant et vous regarde ce tableau.

1
btilly

Comme l'auteur de cette question posait des questions sur un algorithme:

[...] générant une seule permutation, à la fois, et ne continuant que si nécessaire 

Je suggérerais de considérer l'algorithme de Steinhaus – Johnson – Trotter.

Algorithme Steinhaus – Johnson – Trotter sur Wikipedia

Magnifiquement expliqué ici

1
misiek

J'ai créé un algorithme légèrement plus rapide que celui de Knuth:

11 éléments:

le mien: 0,39 seconde 

Knuth's: 0.624 secondes

13 éléments:

le mien: 56,615 secondes 

Knuth's: 98.681 secondes

Voici mon code en Java:

public static void main(String[] args)
{
    int n=11;
    int a,b,c,i,tmp;
    int end=(int)Math.floor(n/2);
    int[][] pos = new int[end+1][2];
    int[] perm = new int[n];

    for(i=0;i<n;i++) perm[i]=i;

    while(true)
    {
        //this is where you can use the permutations (perm)
        i=0;
        c=n;

        while(pos[i][1]==c-2 && pos[i][0]==c-1)
        {
            pos[i][0]=0;
            pos[i][1]=0;
            i++;
            c-=2;
        }

        if(i==end) System.exit(0);

        a=(pos[i][0]+1)%c+i;
        b=pos[i][0]+i;

        tmp=perm[b];
        perm[b]=perm[a];
        perm[a]=tmp;

        if(pos[i][0]==c-1)
        {
            pos[i][0]=0;
            pos[i][1]++;
        }
        else
        {
            pos[i][0]++;
        }
    }
}

Le problème est que mon algorithme ne fonctionne que pour un nombre impair d'éléments. J'ai écrit ce code rapidement, alors je suis sûr qu'il existe un meilleur moyen de mettre en œuvre mon idée pour obtenir de meilleures performances, mais je n'ai pas vraiment le temps de travailler dessus pour l'optimiser et pour résoudre le problème lorsque le nombre de les éléments sont égaux.

C'est un échange pour chaque permutation et il utilise un moyen très simple de savoir quels éléments échanger.

J'ai écrit une explication de la méthode derrière le code sur mon blog: http://antoinecomeau.blogspot.ca/2015/01/fast-generation-of-all-permutations.html

1
Antoine Comeau

Il est 1 heure du matin et je regardais la télévision et réfléchissais à la même question, mais avec des valeurs de chaîne. 

Étant donné un mot trouver toutes les permutations. Vous pouvez facilement le modifier pour gérer un tableau, des ensembles, etc.

Il m'a fallu un peu de temps pour y arriver, mais la solution que j'ai trouvée est la suivante:

string Word = "abcd";

List<string> combinations = new List<string>();

for(int i=0; i<Word.Length; i++)
{
    for (int j = 0; j < Word.Length; j++)
    {
        if (i < j)
            combinations.Add(Word[i] + Word.Substring(j) + Word.Substring(0, i) + Word.Substring(i + 1, j - (i + 1)));
        else if (i > j)
        {
            if(i== Word.Length -1)
                combinations.Add(Word[i] + Word.Substring(0, i));
            else
                combinations.Add(Word[i] + Word.Substring(0, i) + Word.Substring(i + 1));
        }
    }
}

Voici le même code que ci-dessus, mais avec quelques commentaires

string Word = "abcd";

List<string> combinations = new List<string>();

//i is the first letter of the new Word combination
for(int i=0; i<Word.Length; i++)
{
    for (int j = 0; j < Word.Length; j++)
    {
        //add the first letter of the Word, j is past i so we can get all the letters from j to the end
        //then add all the letters from the front to i, then skip over i (since we already added that as the beginning of the Word)
        //and get the remaining letters from i+1 to right before j.
        if (i < j)
            combinations.Add(Word[i] + Word.Substring(j) + Word.Substring(0, i) + Word.Substring(i + 1, j - (i + 1)));
        else if (i > j)
        {
            //if we're at the very last Word no need to get the letters after i
            if(i== Word.Length -1)
                combinations.Add(Word[i] + Word.Substring(0, i));
            //add i as the first letter of the Word, then get all the letters up to i, skip i, and then add all the lettes after i
            else
                combinations.Add(Word[i] + Word.Substring(0, i) + Word.Substring(i + 1));

        }
    }
}
0
Raymond

// Permutations are the different ordered arrangements of an n-element
// array. An n-element array has exactly n! full-length permutations.

// This iterator object allows to iterate all full length permutations
// one by one of an array of n distinct elements.

// The iterator changes the given array in-place.

// Permutations('ABCD') => ABCD  DBAC  ACDB  DCBA
//                         BACD  BDAC  CADB  CDBA
//                         CABD  ADBC  DACB  BDCA
//                         ACBD  DABC  ADCB  DBCA
//                         BCAD  BADC  CDAB  CBDA
//                         CBAD  ABDC  DCAB  BCDA

// count of permutations = n!

// Heap's algorithm (Single swap per permutation)
// http://www.quickperm.org/quickperm.php
// https://stackoverflow.com/a/36634935/4208440
// https://en.wikipedia.org/wiki/Heap%27s_algorithm


// My implementation of Heap's algorithm:

template<typename T>
class PermutationsIterator
{
	int b, e, n;
	int c[32];  /* control array: mixed radix number in rising factorial base.
	            the i-th digit has base i, which means that the digit must be
	            strictly less than i. The first digit is always 0,  the second
	            can be 0 or 1, the third 0, 1 or 2, and so on.
	            ArrayResize isn't strictly necessary, int c[32] would suffice
	            for most practical purposes. Also, it is much faster */

public:
	PermutationsIterator(T &arr[], int firstIndex, int lastIndex)
	{
		this.b = firstIndex;  // v.begin()
		this.e = lastIndex;   // v.end()
		this.n = e - b + 1;

		ArrayInitialize(c, 0);
	}

	// Rearranges the input array into the next permutation and returns true.
	// When there are no more permutations left, the function returns false.

	bool next(T &arr[])
	{
		// find index to update
		int i = 1;

		// reset all the previous indices that reached the maximum possible values
		while (c[i] == i)
		{
			c[i] = 0;
			++i;
		}

		// no more permutations left
		if (i == n)
			return false;

		// generate next permutation
		int j = (i & 1) == 1 ? c[i] : 0;    // IF i is odd then j = c[i] otherwise j = 0.
		swap(arr[b + j], arr[b + i]);       // generate a new permutation from previous permutation using a single swap

		// Increment that index
		++c[i];
		return true;
	}

};

0
Amr Ali

J'ai trouvé cet algo sur Rosetta Code et c'est vraiment le plus rapide que j'ai essayé. http://rosettacode.org/wiki/Permutations#C

/* Boothroyd method; exactly N! swaps, about as fast as it gets */
void boothroyd(int *x, int n, int nn, int callback(int *, int))
{
	int c = 0, i, t;
	while (1) {
		if (n > 2) boothroyd(x, n - 1, nn, callback);
		if (c >= n - 1) return;
 
		i = (n & 1) ? 0 : c;
		c++;
		t = x[n - 1], x[n - 1] = x[i], x[i] = t;
		if (callback) callback(x, nn);
	}
}
 
/* entry for Boothroyd method */
void perm2(int *x, int n, int callback(int*, int))
{
	if (callback) callback(x, n);
	boothroyd(x, n, n, callback);
}
 

0
Amr Ali

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
/**
 * http://marknelson.us/2002/03/01/next-permutation/
 * Rearranges the elements into the lexicographically next greater permutation and returns true.
 * When there are no more greater permutations left, the function eventually returns false.
 */

// next lexicographical permutation

template <typename T>
bool next_permutation(T &arr[], int firstIndex, int lastIndex)
{
    int i = lastIndex;
    while (i > firstIndex)
    {
        int ii = i--;
        T curr = arr[i];
        if (curr < arr[ii])
        {
            int j = lastIndex;
            while (arr[j] <= curr) j--;
            Swap(arr[i], arr[j]);
            while (ii < lastIndex)
                Swap(arr[ii++], arr[lastIndex--]);
            return true;
        }
    }
    return false;
}

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
/**
 * Swaps two variables or two array elements.
 * using references/pointers to speed up swapping.
 */
template<typename T>
void Swap(T &var1, T &var2)
{
    T temp;
    temp = var1;
    var1 = var2;
    var2 = temp;
}

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
// driver program to test above function
#define N 3

void OnStart()
{
    int i, x[N];
    for (i = 0; i < N; i++) x[i] = i + 1;

    printf("The %i! possible permutations with %i elements:", N, N);

    do
    {
        printf("%s", ArrayToString(x));

    } while (next_permutation(x, 0, N - 1));

}

// Output:
// The 3! possible permutations with 3 elements:
// "1,2,3"
// "1,3,2"
// "2,1,3"
// "2,3,1"
// "3,1,2"
// "3,2,1"

0
Amr Ali