Bir dizenin / tamsayının tüm permütasyonlarını listeleme


159

Röportajları programlamada yaygın bir görev (yine de röportaj deneyimimden değil) bir dize veya bir tam sayı almak ve olası her permütasyonu listelemektir.

Bunun nasıl yapıldığına ve böyle bir sorunu çözmenin arkasındaki mantığa bir örnek var mı?

Birkaç kod parçacığı gördüm ama onlar iyi yorum / açıklama ve böylece takip etmek zor değildi.


1
Permutations için bir grafik , ancak C # değil , iyi açıklayan cevapları olan bir soru .
kullanıcı bilinmiyor

Yanıtlar:


152

Her şeyden önce: tabii ki özyineleme gibi kokuyor !

Siz de prensibi bilmek istediğiniz için, insan dilini açıklamak için elimden geleni yaptım. Bence özyineleme çoğu zaman çok kolaydır. Sadece iki adımı kavramanız gerekir:

  1. İlk adım
  2. Diğer tüm adımlar (hepsi aynı mantıkla)

Gelen İnsan dilinin :

Kısaca:
1. 1 elementin permütasyonu bir elementtir.
2. Bir dizi elemanın permütasyonu, elementlerin her birinin, diğer elementlerin her permütasyonu ile birleştirilmiş bir listedir.

Misal:

Setin sadece bir elemanı varsa ->
geri döndürün.
perma (a) -> a

Kümenin iki karakteri varsa: içindeki her öğe için: öğeyi, öğelerin geri kalanının permütasyonu eklenmiş şekilde döndürün, şöyle:

perma (ab) ->

a + perma (b) -> ab

b + perma (a) -> ba

Ayrıca: setteki her karakter için: setin geri kalanı> ile birleştirilmiş bir karakter döndürür

perma (abc) ->

a + perma (bc) -> abc , acb

b + perma (ac) -> bac , bca

c + perma (ab) -> kabin , cba

perma (abc ... z) ->

a + perma (...), b + perma (....)
....

Bulduğum pseudocode üzerinde http://www.programmersheaven.com/mb/Algorithms/369713/369713/permutation-algorithm-help/ :

makePermutations(permutation) {
  if (length permutation < required length) {
    for (i = min digit to max digit) {
      if (i not in permutation) {
        makePermutations(permutation+i)
      }
    }
  }
  else {
    add permutation to list
  }
}

C #

Tamam ve daha ayrıntılı bir şey (ve c # olarak etiketlendiğinden), http://radio.weblogs.com/0111551/stories/2002/10/14/permutations.html : Oldukça uzun, ama kopyalamaya karar verdim neyse, bu nedenle yazı orijinaline bağlı değildir.

İşlev bir karakter dizesi alır ve bu dizenin olası tüm permütasyonlarını yazar; örneğin, "ABC" sağlandıysa, dökülmelidir:

ABC, ACB, BAC, BCA, CAB, CBA.

Kod:

class Program
{
    private static void Swap(ref char a, ref char b)
    {
        if (a == b) return;

        var temp = a;
        a = b;
        b = temp;
    }

    public static void GetPer(char[] list)
    {
        int x = list.Length - 1;
        GetPer(list, 0, x);
    }

    private static void GetPer(char[] list, int k, int m)
    {
        if (k == m)
        {
            Console.Write(list);
        }
        else
            for (int i = k; i <= m; i++)
            {
                   Swap(ref list[k], ref list[i]);
                   GetPer(list, k + 1, m);
                   Swap(ref list[k], ref list[i]);
            }
    }

    static void Main()
    {
        string str = "sagiv";
        char[] arr = str.ToCharArray();
        GetPer(arr);
    }
}

21
Biraz daha fazla netlik için k "recursionDepth" ve m "maxDepth" diyorum.
Nerf Herder

3
2. takas ( Swap(ref list[k], ref list[i]);) kullanılamaz.
dance2die

1
Bu çözüm için teşekkürler. Bu kemanı ( dotnetfiddle.net/oTzihw ) ondan (k ve m yerine uygun adlandırma ile) oluşturdum. Algoyu anladığım kadarıyla, orijinal diziyi yerinde düzenlediğiniz için ikinci Takas (geri izlemek için) gereklidir.
Andrew

3
küçük bir nokta: Swap yönteminin geçici bir arabellek değişkeni ile uygulanması ve XORs kullanılmaması daha iyi gibi görünüyor ( dotnetperls.com/swap )
Sergioet

7
C # 7 tuples kullanarak takas çok daha zarif yapabilirsiniz:(a[x], a[y]) = (a[y], a[x]);
Darren

81

LINQ'nun kullanımına izin verilirse, yalnızca iki satır kod bulunur. Lütfen cevabımı buraya bakın .

DÜZENLE

İşte T listesinden tüm permütasyonları (kombinasyonları değil) döndürebilen genel fonksiyonum:

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

Misal:

IEnumerable<IEnumerable<int>> result =
    GetPermutations(Enumerable.Range(1, 3), 3);

Çıktı - tamsayı listelerinin listesi:

{1,2,3} {1,3,2} {2,1,3} {2,3,1} {3,1,2} {3,2,1}

Bu işlev LINQ kullandığından, .net 3.5 veya üstü gerektirir.


3
kombinasyonlar ve permütasyonlar farklı şeylerdir. benzer, ancak cevabınız bir dizi öğenin tüm permütasyonlarından farklı bir soruna cevap veriyor gibi görünüyor.
Shawn Kovac

@ShawnKovac, bunu belirttiğin için teşekkürler! Kodumu kombinasyondan permütasyona güncelledim.
Pengyang

1
@Pengyang Diğer cevabınıza baktım ve bunun bana çok yardımcı olduğunu söyleyeceğim, ancak doğru çözme yolunu gösterip göstermediğinizi bilmediğim başka bir durumum var. 'CADILAR BAYRAMI' gibi bir kelimenin tüm permütasyonlarını bulmak istedim ama sonuç kümesine hem 'L'leri hem de' E'leri dahil etmek istediğimi buldum. Yinelemelerimde, her yinelemeyle (uzunluk ++) artan uzunluk vererek yönteminiz üzerinde döngü yapıyorum ve HALLOWEEN (9 karakter) kelimesinin tam uzunluğu göz önüne alındığında 9 karakter uzunluğunda sonuçlar alacağımı beklerdim ... Sadece 7 alıyorum (1 L ve 1 E atlandı)
MegaMark

Ayrıca 9 'H' kelimesini 'H' kelimesinde sadece bir kez göründüğü bir durum görmek istemediğimi belirtmek isterim.
MegaMark

4
@MegaMark Bu işlev, öğelerin benzersiz olmasını gerektirir. const string s = "HALLOWEEN"; var result = GetPermutations(Enumerable.Range(0, s.Length), s.Length).Select(t => t.Select(i => s[i]));
Şunu

36

Burada çözümü buldum. Java ile yazılmış, ancak C # dönüştürdüm. Umarım bu sana yardımcı olur.

Resim açıklamasını buraya girin

İşte C # kod:

static void Main(string[] args)
{
    string str = "ABC";
    char[] charArry = str.ToCharArray();
    Permute(charArry, 0, 2);
    Console.ReadKey();
}

static void Permute(char[] arry, int i, int n)
{
    int j;
    if (i==n)
        Console.WriteLine(arry);
    else
    {
        for(j = i; j <=n; j++)
        {
            Swap(ref arry[i],ref arry[j]);
            Permute(arry,i+1,n);
            Swap(ref arry[i], ref arry[j]); //backtrack
        }
    }
}

static void Swap(ref char a, ref char b)
{
    char tmp;
    tmp = a;
    a=b;
    b = tmp;
}

Başka bir dilden taşınmış mı? Görüntü için kesinlikle +1, çünkü gerçekten değer katıyor. Ancak, kodun kendisinin belirli bir iyileştirme potansiyeli olduğu görülmektedir. Bazı küçük parçalar gerekli değildir ama en önemlisi, bir şey gönderirken ve parametreler sağlamak ve döndürülen bir değeri almak yerine bir şeyler yaptığımızda bu C ++ hissini alıyorum. Aslında, resminizi bir C # tarzı C # kodunu uygulamak için kullandım (stil elbette benim kişisel algım) ve bana çok yardımcı oldu, bu yüzden yayınladığımda kesinlikle sizden çalarım (ve kredi sizin için).
Konrad Viltersten

21

Özyineleme gerekli değildir, işte bu çözüm hakkında iyi bilgiler.

var values1 = new[] { 1, 2, 3, 4, 5 };

foreach (var permutation in values1.GetPermutations())
{
    Console.WriteLine(string.Join(", ", permutation));
}

var values2 = new[] { 'a', 'b', 'c', 'd', 'e' };

foreach (var permutation in values2.GetPermutations())
{
    Console.WriteLine(string.Join(", ", permutation));
}

Console.ReadLine();

Bu algoritmayı yıllardır kullanıyorum, O (N) zamanı var her permütasyonu hesaplamak için ve alan karmaşıklığına sahip .

public static class SomeExtensions
{
    public static IEnumerable<IEnumerable<T>> GetPermutations<T>(this IEnumerable<T> enumerable)
    {
        var array = enumerable as T[] ?? enumerable.ToArray();

        var factorials = Enumerable.Range(0, array.Length + 1)
            .Select(Factorial)
            .ToArray();

        for (var i = 0L; i < factorials[array.Length]; i++)
        {
            var sequence = GenerateSequence(i, array.Length - 1, factorials);

            yield return GeneratePermutation(array, sequence);
        }
    }

    private static IEnumerable<T> GeneratePermutation<T>(T[] array, IReadOnlyList<int> sequence)
    {
        var clone = (T[]) array.Clone();

        for (int i = 0; i < clone.Length - 1; i++)
        {
            Swap(ref clone[i], ref clone[i + sequence[i]]);
        }

        return clone;
    }

    private static int[] GenerateSequence(long number, int size, IReadOnlyList<long> factorials)
    {
        var sequence = new int[size];

        for (var j = 0; j < sequence.Length; j++)
        {
            var facto = factorials[sequence.Length - j];

            sequence[j] = (int)(number / facto);
            number = (int)(number % facto);
        }

        return sequence;
    }

    static void Swap<T>(ref T a, ref T b)
    {
        T temp = a;
        a = b;
        b = temp;
    }

    private static long Factorial(int n)
    {
        long result = n;

        for (int i = 1; i < n; i++)
        {
            result = result * i;
        }

        return result;
    }
}

Kutudan çıkar çıkmaz çalışır!
revobtz

1
belki O (n) notasyonunu anlamıyorum. N, algoritmanızın çalışması için kaç tane "iç döngü" gerekli olduğunu belirtmiyor mu? Bana N numaranız varsa, O (N * N!) gibi görünüyor (çünkü N! kez N takası yapmak zorunda). Artı, bir dizi dizi kopyalama yapmak zorunda. Bu kod "temiz", ama ben kullanmak olmaz.
eric frazer

@ erikfrazer Her permütasyon sadece bir dizi kopya kullanır ve O(N-1)sıra O(N)için ve takas için kullanılır O(N). Ve bunu hala üretimde kullanıyorum ama sadece bir permütasyonla şu gibi bir permütasyon üretiyorum: GetPermutation(i)nerede 0 <= i <= N!-1. Bundan daha iyi performansa sahip bir şey kullanmaktan mutluluk duyacağım, bu yüzden daha iyi bir şey için referans çağırmaktan çekinmeyin, alternatiflerin çoğu O(N!)bellekte kullanır , böylece bunu da kontrol edebilirsiniz.
Najera

11
void permute (char *str, int ptr) {
  int i, len;
  len = strlen(str);
  if (ptr == len) {
    printf ("%s\n", str);
    return;
  }

  for (i = ptr ; i < len ; i++) {
    swap (&str[ptr], &str[i]);
    permute (str, ptr + 1);
    swap (&str[ptr], &str[i]);
  }
}

Karakterleri değiştirmek için takas işlevinizi yazabilirsiniz.
Buna permute (dize, 0) denir;


5
Bu C'ye benziyor, C # değil.
Jon Schneider

9

Her şeyden önce, kümelerin dizgiler veya tamsayılar değil, permütasyonları vardır, bu yüzden sadece bir dizedeki karakter kümesini kastettiğini varsayacağım.

N boyutunda bir setin n olduğunu unutmayın! n-permütasyon.

K = 1 ... n ile çağrılan aşağıdaki sahte kod (Wikipedia'dan)! tüm permütasyonları verecektir:

function permutation(k, s) {
    for j = 2 to length(s) {
        swap s[(k mod j) + 1] with s[j]; // note that our array is indexed starting at 1
        k := k / j; // integer division cuts off the remainder
    }
    return s;
}

Eşdeğer Python kodu (0 tabanlı dizi dizinleri için):

def permutation(k, s):
    r = s[:]
    for j in range(2, len(s)+1):
        r[j-1], r[k%j] = r[k%j], r[j-1]
        k = k/j+1
    return r

5
Bu hangi dil? soru C # olarak işaretlenmiştir. ne yaptığını bilmiyorum k := k / j;.
Shawn Kovac

8

HERHANGİ tip bir dizide gerekli permütasyonları veren C # 'da biraz değiştirilmiş versiyon.

    // USAGE: create an array of any type, and call Permutations()
    var vals = new[] {"a", "bb", "ccc"};
    foreach (var v in Permutations(vals))
        Console.WriteLine(string.Join(",", v)); // Print values separated by comma


public static IEnumerable<T[]> Permutations<T>(T[] values, int fromInd = 0)
{
    if (fromInd + 1 == values.Length)
        yield return values;
    else
    {
        foreach (var v in Permutations(values, fromInd + 1))
            yield return v;

        for (var i = fromInd + 1; i < values.Length; i++)
        {
            SwapValues(values, fromInd, i);
            foreach (var v in Permutations(values, fromInd + 1))
                yield return v;
            SwapValues(values, fromInd, i);
        }
    }
}

private static void SwapValues<T>(T[] values, int pos1, int pos2)
{
    if (pos1 != pos2)
    {
        T tmp = values[pos1];
        values[pos1] = values[pos2];
        values[pos2] = tmp;
    }
}

Bu uygulama ile ilgili ufak bir uyarı: yalnızca numaralandırma değerini saklamaya çalışmazsanız düzgün çalışır. Böyle bir şey yapmaya çalışırsanız Permutations(vals).ToArray(), aynı diziye N referansı ile sonuçlanırsınız. Sonuçları saklamak istiyorsanız, manuel olarak bir kopya oluşturmanız gerekir. ÖrnPermutations(values).Select(v => (T[])v.Clone())
Pharap

8
class Program
{
    public static void Main(string[] args)
    {
        Permutation("abc");
    }

    static void Permutation(string rest, string prefix = "")
    {
        if (string.IsNullOrEmpty(rest)) Console.WriteLine(prefix);

        // Each letter has a chance to be permutated
        for (int i = 0; i < rest.Length; i++)
        {                
            Permutation(rest.Remove(i, 1), prefix + rest[i]);
        }
    }
}

1
Deli çözüm. Teşekkür ederim!
Cristian E.

7

Basit olduğu için FBryant87 yaklaşımını beğendim . Ne yazık ki, diğer pek çok "çözüm", aynı rakamı birden fazla içeriyorsa tüm permütasyonları veya örneğin bir tamsayıyı sunmaz. Örnek olarak 656123'ü ele alalım. Çizgi:

var tail = chars.Except(new List<char>(){c});

tüm oluşumlarını neden dışında olacaktır kullanılarak kaldırılması, yani zaman c = 6, iki basamak kaldırılır ve biz, ben kendimi denemek ve bunu çözmek için karar bu çözüldü çalıştı çözümlerin hiçbiri bu yana 5123. örneğin kalır FBryant87 's baz olarak kodlayın. Ben geldi budur:

private static List<string> FindPermutations(string set)
    {
        var output = new List<string>();
        if (set.Length == 1)
        {
            output.Add(set);
        }
        else
        {
            foreach (var c in set)
            {
                // Remove one occurrence of the char (not all)
                var tail = set.Remove(set.IndexOf(c), 1);
                foreach (var tailPerms in FindPermutations(tail))
                {
                    output.Add(c + tailPerms);
                }
            }
        }
        return output;
    }

Sadece .Remove ve .IndexOf kullanarak ilk bulunan olayı kaldırmak. En azından benim kullanımım için tasarlanmış gibi görünüyor. Eminim daha akıllıca yapılabilir.

Yine de dikkat edilmesi gereken bir şey: Sonuçta oluşan liste kopyalar içerebilir, bu yüzden ya yöntem döndürmeyi örneğin bir HashSet yaptığınızdan emin olun ya da dönüşten sonra istediğiniz herhangi bir yöntemi kullanarak kopyaları kaldırın.


Mutlak bir güzellik gibi çalışır, ilk önce yinelenen karakterleri işlediğini buldum +1
Jack Casey


5

İşte tamamen işlevsel bir F # uygulaması:


let factorial i =
    let rec fact n x =
        match n with
        | 0 -> 1
        | 1 -> x
        | _ -> fact (n-1) (x*n)
    fact i 1

let swap (arr:'a array) i j = [| for k in 0..(arr.Length-1) -> if k = i then arr.[j] elif k = j then arr.[i] else arr.[k] |]

let rec permutation (k:int,j:int) (r:'a array) =
    if j = (r.Length + 1) then r
    else permutation (k/j+1, j+1) (swap r (j-1) (k%j))

let permutations (source:'a array) = seq { for k = 0 to (source |> Array.length |> factorial) - 1 do yield permutation (k,2) source }

CLR dizilerinin değişebilir doğasından faydalanmak için takas değiştirilerek performans büyük ölçüde geliştirilebilir, ancak bu uygulama kaynak dizisi açısından iş parçacığı açısından güvenlidir ve bazı bağlamlarda istenebilir. Ayrıca, 16'dan fazla elemanı olan diziler için int faktöriyel 17 bir int32 taşmasına neden olacağı için int daha büyük / keyfi hassasiyete sahip tiplerle değiştirilmelidir.


5

İşte özyineleme kullanarak c # basit bir çözüm,

void Main()
{
    string word = "abc";
    WordPermuatation("",word);
}

void WordPermuatation(string prefix, string word)
{
    int n = word.Length;
    if (n == 0) { Console.WriteLine(prefix); }
    else
    {
        for (int i = 0; i < n; i++)
        {
            WordPermuatation(prefix + word[i],word.Substring(0, i) + word.Substring(i + 1, n - (i+1)));
        }
    }
}

Çok basit ve kısa bir çözüm için teşekkürler! :)
Kristaps Vilerts

4

Burada girdi olarak hem dize hem de tamsayı için anlaşılması kolay bir permütasyon fonksiyonudur. Bununla , çıkış uzunluğunu bile ayarlayabilirsiniz (normal durumda giriş uzunluğuna eşittir)

sicim

    static ICollection<string> result;

    public static ICollection<string> GetAllPermutations(string str, int outputLength)
    {
        result = new List<string>();
        MakePermutations(str.ToCharArray(), string.Empty, outputLength);
        return result;
    }

    private static void MakePermutations(
       char[] possibleArray,//all chars extracted from input
       string permutation,
       int outputLength//the length of output)
    {
         if (permutation.Length < outputLength)
         {
             for (int i = 0; i < possibleArray.Length; i++)
             {
                 var tempList = possibleArray.ToList<char>();
                 tempList.RemoveAt(i);
                 MakePermutations(tempList.ToArray(), 
                      string.Concat(permutation, possibleArray[i]), outputLength);
             }
         }
         else if (!result.Contains(permutation))
            result.Add(permutation);
    }

ve Integer için sadece caller yöntemini değiştirin ve MakePermutations () dokunulmaz kalır:

    public static ICollection<int> GetAllPermutations(int input, int outputLength)
    {
        result = new List<string>();
        MakePermutations(input.ToString().ToCharArray(), string.Empty, outputLength);
        return result.Select(m => int.Parse(m)).ToList<int>();
    }

örnek 1: GetAllPermutations ("abc", 3); "abc" "acb" "bac" "bca" "kabin" "cba"

örnek 2: GetAllPermutations ("abcd", 2); "ab" "ac" "reklam" "ba" "bc" "bd" "ca" "cb" "cd" "da" "db" "dc"

örnek 3: GetAllPermutations (486,2); 48 46 84 86 64 68


Çözümünüzü seviyorum çünkü bunun anlaşılması kolay, bunun için teşekkürler! Yine de onunla gitmeyi seçtim: stackoverflow.com/questions/756055/… . Bunun nedeni ToList, ToArray ve RemoveAt öğelerinin tümünün zaman karmaşıklığı O (N) 'dir. Temel olarak koleksiyonun tüm unsurlarını gözden geçirmelisiniz (bkz. Stackoverflow.com/a/15042066/1132522 ). Int için dönüştürmek için sonunda tüm öğeleri tekrar üzerinden temel int için aynı. Bunun "abc" veya 486 için fazla bir etkisi olmadığını kabul ediyorum.
Andrew

2

İşte tüm permütasyonu basacak fonksiyon. Bu işlev peter tarafından açıklanan mantığı uygular.

public class Permutation
{
    //http://www.java2s.com/Tutorial/Java/0100__Class-Definition/RecursivemethodtofindallpermutationsofaString.htm

    public static void permuteString(String beginningString, String endingString)
    {           

        if (endingString.Length <= 1)
            Console.WriteLine(beginningString + endingString);
        else
            for (int i = 0; i < endingString.Length; i++)
            {

                String newString = endingString.Substring(0, i) + endingString.Substring(i + 1);

                permuteString(beginningString + endingString.ElementAt(i), newString);

            }
    }
}

    static void Main(string[] args)
    {

        Permutation.permuteString(String.Empty, "abc");
        Console.ReadLine();

    }

2

Aşağıda benim permütasyon uygulamam var. Değişken isimlerini aldırma, eğlenmek için yapıyordum :)

class combinations
{
    static void Main()
    {

        string choice = "y";
        do
        {
            try
            {
                Console.WriteLine("Enter word :");
                string abc = Console.ReadLine().ToString();
                Console.WriteLine("Combinatins for word :");
                List<string> final = comb(abc);
                int count = 1;
                foreach (string s in final)
                {
                    Console.WriteLine("{0} --> {1}", count++, s);
                }
                Console.WriteLine("Do you wish to continue(y/n)?");
                choice = Console.ReadLine().ToString();
            }
            catch (Exception exc)
            {
                Console.WriteLine(exc);
            }
        } while (choice == "y" || choice == "Y");
    }

    static string swap(string test)
    {
        return swap(0, 1, test);
    }

    static List<string> comb(string test)
    {
        List<string> sec = new List<string>();
        List<string> first = new List<string>();
        if (test.Length == 1) first.Add(test);
        else if (test.Length == 2) { first.Add(test); first.Add(swap(test)); }
        else if (test.Length > 2)
        {
            sec = generateWords(test);
            foreach (string s in sec)
            {
                string init = s.Substring(0, 1);
                string restOfbody = s.Substring(1, s.Length - 1);

                List<string> third = comb(restOfbody);
                foreach (string s1 in third)
                {
                    if (!first.Contains(init + s1)) first.Add(init + s1);
                }


            }
        }

        return first;
    }

    static string ShiftBack(string abc)
    {
        char[] arr = abc.ToCharArray();
        char temp = arr[0];
        string wrd = string.Empty;
        for (int i = 1; i < arr.Length; i++)
        {
            wrd += arr[i];
        }

        wrd += temp;
        return wrd;
    }

    static List<string> generateWords(string test)
    {
        List<string> final = new List<string>();
        if (test.Length == 1)
            final.Add(test);
        else
        {
            final.Add(test);
            string holdString = test;
            while (final.Count < test.Length)
            {
                holdString = ShiftBack(holdString);
                final.Add(holdString);
            }
        }

        return final;
    }

    static string swap(int currentPosition, int targetPosition, string temp)
    {
        char[] arr = temp.ToCharArray();
        char t = arr[currentPosition];
        arr[currentPosition] = arr[targetPosition];
        arr[targetPosition] = t;
        string word = string.Empty;
        for (int i = 0; i < arr.Length; i++)
        {
            word += arr[i];

        }

        return word;

    }
}

2

İşte Peter'ın verdiği insan dili açıklamasını gösteren üst düzey bir örnek :

    public List<string> FindPermutations(string input)
    {
        if (input.Length == 1)
            return new List<string> { input };
        var perms = new List<string>();
        foreach (var c in input)
        {
            var others = input.Remove(input.IndexOf(c), 1);
            perms.AddRange(FindPermutations(others).Select(perm => c + perm));
        }
        return perms;
    }

Bu çözüm aslında dize kümesi herhangi bir yinelenen karakter içeriyorsa başarısız olur. Örneğin, 'test' kelimesinde, Hariç Komutu, gerektiğinde yalnızca ilk ve son yerine 't' örneğinin her iki örneğini de kaldıracaktır.
Middas

1
@Orta iyi tespit, neyse ki sarılmak bu sorunu çözmek için bir çözüm buldu.
FBryant87

1

Performans ve bellek bir sorunsa, bu çok verimli uygulamayı öneririm. Wikipedia'daki Heap algoritmasına göre , en hızlısı olmalı. Umarım ihtiyacınızı karşılar :-)!

Bunun 10 için bir Linq uygulamasıyla karşılaştırılması gibi! (kod dahil):

  • Bu: 235 milisaniye içinde 36288000 ürün
  • Linq: 50051 milisaniyede 36288000 ürün

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using System.Text;
    
    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
            /// </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) =>
                {
                    Debug.Print(String.Join("", vals));
                    return false;
                });
    
                int[] values = new int[] { 0, 1, 2, 4 };
    
                Debug.Print("Non Linq");
                ForAllPermutation(values, (vals) =>
                {
                    Debug.Print(String.Join("", vals));
                    return false;
                });
    
                Debug.Print("Linq");
                foreach(var v in GetPermutations(values, values.Length))
                {
                    Debug.Print(String.Join("", v));
                }
    
                // Performance
                int count = 0;
    
                values = new int[10];
                for(int n = 0; n < values.Length; n++)
                {
                    values[n] = n;
                }
    
                Stopwatch stopWatch = new Stopwatch();
                stopWatch.Reset();
                stopWatch.Start();
    
                ForAllPermutation(values, (vals) =>
                {
                    foreach(var v in vals)
                    {
                        count++;
                    }
                    return false;
                });
    
                stopWatch.Stop();
                Debug.Print($"Non Linq {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();
                Debug.Print($"Linq {count} items in {stopWatch.ElapsedMilliseconds} millisecs");
    
            }
        }
    }

1

İşte benim JavaScript (NodeJS) çözümü. Ana fikir, her seferinde bir öğe alıp dizeden "kaldır", karakterlerin geri kalanını değiştirmemiz ve öğeyi öne eklememizdir.

function perms (string) {
  if (string.length == 0) {
    return [];
  }
  if (string.length == 1) {
    return [string];
  }
  var list = [];
  for(var i = 0; i < string.length; i++) {
    var invariant = string[i];
    var rest = string.substr(0, i) + string.substr(i + 1);
    var newPerms = perms(rest);
    for (var j = 0; j < newPerms.length; j++) {
      list.push(invariant + newPerms[j]);
    }
  }
  return list;
}

module.exports = perms;

İşte testler:

require('should');
var permutations = require('../src/perms');

describe('permutations', function () {
  it('should permute ""', function () {
    permutations('').should.eql([]);
  })

  it('should permute "1"', function () {
    permutations('1').should.eql(['1']);
  })

  it('should permute "12"', function () {
    permutations('12').should.eql(['12', '21']);
  })

  it('should permute "123"', function () {
    var expected = ['123', '132', '321', '213', '231', '312'];
    var actual = permutations('123');
    expected.forEach(function (e) {
      actual.should.containEql(e);
    })
  })

  it('should permute "1234"', function () {
    // Wolfram Alpha FTW!
    var expected = ['1234', '1243', '1324', '1342', '1423', '1432', '2134', '2143', '2314', '2341', '2413', '2431', '3124', '3142', '3214', '3241', '3412', '3421', '4123', '4132'];
    var actual = permutations('1234');
    expected.forEach(function (e) {
      actual.should.containEql(e);
    })
  })
})

1

İşte aklıma gelen en basit çözüm:

let rec distribute e = function
  | [] -> [[e]]
  | x::xs' as xs -> (e::xs)::[for xs in distribute e xs' -> x::xs]

let permute xs = Seq.fold (fun ps x -> List.collect (distribute x) ps) [[]] xs

distributeİşlev yeni elemanı alır eve bir n-eleman listesi ve bir listesini verir n+1, bunların her biri listeleri efarklı bir yerde yerleştirilmiş. Örneğin 10, listedeki dört olası yerin her birine ekleme [1;2;3]:

> distribute 10 [1..3];;
val it : int list list =
  [[10; 1; 2; 3]; [1; 10; 2; 3]; [1; 2; 10; 3]; [1; 2; 3; 10]]

permuteFonksiyon, şimdiye kadar birikmiş fazla permütasyon dağıtarak tüm permütasyon sonuçlanan sırayla her öğenin üzerine katlanabilir. Örneğin, listenin 6 permütasyonu [1;2;3]:

> permute [1;2;3];;
val it : int list list =
  [[3; 2; 1]; [2; 3; 1]; [2; 1; 3]; [3; 1; 2]; [1; 3; 2]; [1; 2; 3]]

Ara akümülatörleri tutmak foldiçin a ayarını değiştirmek, scanpermütasyonların her seferinde nasıl bir eleman oluşturduğuna ışık tutar:

> Seq.scan (fun ps x -> List.collect (distribute x) ps) [[]] [1..3];;
val it : seq<int list list> =
  seq
    [[[]]; [[1]]; [[2; 1]; [1; 2]];
     [[3; 2; 1]; [2; 3; 1]; [2; 1; 3]; [3; 1; 2]; [1; 3; 2]; [1; 2; 3]]]

1

Bir dizenin permütasyonlarını listeler. Karakterler tekrarlandığında çoğaltmayı önler:

using System;
using System.Collections;

class Permutation{
  static IEnumerable Permutations(string word){
    if (word == null || word.Length <= 1) {
      yield return word;
      yield break;
    }

    char firstChar = word[0];
    foreach( string subPermute in Permutations (word.Substring (1)) ) {
      int indexOfFirstChar = subPermute.IndexOf (firstChar);
      if (indexOfFirstChar == -1) indexOfFirstChar = subPermute.Length;

      for( int index = 0; index <= indexOfFirstChar; index++ )
        yield return subPermute.Insert (index, new string (firstChar, 1));
    }
  }

  static void Main(){
    foreach( var permutation in Permutations ("aab") )
      Console.WriteLine (permutation);
  }
}

2
Halihazırda çok sayıda çalışma çözümü bulunduğunda, çözümünüzü diğer tüm çözümlerden neyin öne çıkardığını açıklamak isteyebilirsiniz.
nvoigt

Karakterler tekrarlandığında çoğaltmayı önler (başka bir cevap için chindirala tarafından). "Aab" için: aab aba baa
Val

1

@ Peter'ın çözümüne dayanarak, burada Permutations()çalışan herhangi bir LINQ tarzı uzantı yöntemini bildiren bir sürüm IEnumerable<T>.

Kullanım (dize karakterleri örneğinde):

foreach (var permutation in "abc".Permutations())
{
    Console.WriteLine(string.Join(", ", permutation));
}

Çıktılar:

a, b, c
a, c, b
b, a, c
b, c, a
c, b, a
c, a, b

Veya başka herhangi bir koleksiyonda:

foreach (var permutation in (new[] { "Apples", "Oranges", "Pears"}).Permutations())
{
    Console.WriteLine(string.Join(", ", permutation));
}

Çıktılar:

Apples, Oranges, Pears
Apples, Pears, Oranges
Oranges, Apples, Pears
Oranges, Pears, Apples
Pears, Oranges, Apples
Pears, Apples, Oranges
using System;
using System.Collections.Generic;
using System.Linq;

public static class PermutationExtension
{
    public static IEnumerable<T[]> Permutations<T>(this IEnumerable<T> source)
    {
        var sourceArray = source.ToArray();
        var results = new List<T[]>();
        Permute(sourceArray, 0, sourceArray.Length - 1, results);
        return results;
    }

    private static void Swap<T>(ref T a, ref T b)
    {
        T tmp = a;
        a = b;
        b = tmp;
    }

    private static void Permute<T>(T[] elements, int recursionDepth, int maxDepth, ICollection<T[]> results)
    {
        if (recursionDepth == maxDepth)
        {
            results.Add(elements.ToArray());
            return;
        }

        for (var i = recursionDepth; i <= maxDepth; i++)
        {
            Swap(ref elements[recursionDepth], ref elements[i]);
            Permute(elements, recursionDepth + 1, maxDepth, results);
            Swap(ref elements[recursionDepth], ref elements[i]);
        }
    }
}

0

İşte tüm permütasyonları tekrar tekrar yazdıracak fonksiyon.

public void Permutations(string input, StringBuilder sb)
    {
        if (sb.Length == input.Length)
        {
            Console.WriteLine(sb.ToString());
            return;
        }

        char[] inChar = input.ToCharArray();

        for (int i = 0; i < input.Length; i++)
        {
            if (!sb.ToString().Contains(inChar[i]))
            {
                sb.Append(inChar[i]);
                Permutations(input, sb);    
                RemoveChar(sb, inChar[i]);
            }
        }
    }

private bool RemoveChar(StringBuilder input, char toRemove)
    {
        int index = input.ToString().IndexOf(toRemove);
        if (index >= 0)
        {
            input.Remove(index, 1);
            return true;
        }
        return false;
    }

0
class Permutation
{
    public static List<string> Permutate(string seed, List<string> lstsList)
    {
        loopCounter = 0;
        // string s="\w{0,2}";
        var lstStrs = PermuateRecursive(seed);

        Trace.WriteLine("Loop counter :" + loopCounter);
        return lstStrs;
    }

    // Recursive function to find permutation
    private static List<string> PermuateRecursive(string seed)
    {
        List<string> lstStrs = new List<string>();

        if (seed.Length > 2)
        {
            for (int i = 0; i < seed.Length; i++)
            {
                str = Swap(seed, 0, i);

                PermuateRecursive(str.Substring(1, str.Length - 1)).ForEach(
                    s =>
                    {
                        lstStrs.Add(str[0] + s);
                        loopCounter++;
                    });
                ;
            }
        }
        else
        {
            lstStrs.Add(seed);
            lstStrs.Add(Swap(seed, 0, 1));
        }
        return lstStrs;
    }
    //Loop counter variable to count total number of loop execution in various functions
    private static int loopCounter = 0;

    //Non recursive  version of permuation function
    public static List<string> Permutate(string seed)
    {
        loopCounter = 0;
        List<string> strList = new List<string>();
        strList.Add(seed);
        for (int i = 0; i < seed.Length; i++)
        {
            int count = strList.Count;
            for (int j = i + 1; j < seed.Length; j++)
            {
                for (int k = 0; k < count; k++)
                {
                    strList.Add(Swap(strList[k], i, j));
                    loopCounter++;
                }
            }
        }
        Trace.WriteLine("Loop counter :" + loopCounter);
        return strList;
    }

    private static string Swap(string seed, int p, int p2)
    {
        Char[] chars = seed.ToCharArray();
        char temp = chars[p2];
        chars[p2] = chars[p];
        chars[p] = temp;
        return new string(chars);
    }
}

0

İşte biraz basitleştirilmiş bir C # cevabı.

public static void StringPermutationsDemo()
{
    strBldr = new StringBuilder();

    string result = Permute("ABCD".ToCharArray(), 0);
    MessageBox.Show(result);
}     

static string Permute(char[] elementsList, int startIndex)
{
    if (startIndex == elementsList.Length)
    {
        foreach (char element in elementsList)
        {
            strBldr.Append(" " + element);
        }
        strBldr.AppendLine("");
    }
    else
    {
        for (int tempIndex = startIndex; tempIndex <= elementsList.Length - 1; tempIndex++)
        {
            Swap(ref elementsList[startIndex], ref elementsList[tempIndex]);

            Permute(elementsList, (startIndex + 1));

            Swap(ref elementsList[startIndex], ref elementsList[tempIndex]);
        }
    }

    return strBldr.ToString();
}

static void Swap(ref char Char1, ref char Char2)
{
    char tempElement = Char1;
    Char1 = Char2;
    Char2 = tempElement;
}

Çıktı:

1 2 3
1 3 2

2 1 3
2 3 1

3 2 1
3 1 2

0

Bu benim çözümüm, anlaması kolay

class ClassicPermutationProblem
{
    ClassicPermutationProblem() { }

    private static void PopulatePosition<T>(List<List<T>> finalList, List<T> list, List<T> temp, int position)
    {
         foreach (T element in list)
         {
             List<T> currentTemp = temp.ToList();
             if (!currentTemp.Contains(element))
                currentTemp.Add(element);
             else
                continue;

             if (position == list.Count)
                finalList.Add(currentTemp);
             else
                PopulatePosition(finalList, list, currentTemp, position + 1);
        }
    }

    public static List<List<int>> GetPermutations(List<int> list)
    {
        List<List<int>> results = new List<List<int>>();
        PopulatePosition(results, list, new List<int>(), 1);
        return results;
     }
}

static void Main(string[] args)
{
    List<List<int>> results = ClassicPermutationProblem.GetPermutations(new List<int>() { 1, 2, 3 });
}

0

Bahsedilen algo bir uygulama daha.

public class Program
{
    public static void Main(string[] args)
    {
        string str = "abcefgh";
        var astr = new Permutation().GenerateFor(str);
        Console.WriteLine(astr.Length);
        foreach(var a in astr)
        {
            Console.WriteLine(a);
        }
        //a.ForEach(Console.WriteLine);
    }
}

class Permutation
{
    public string[] GenerateFor(string s)
    {  

        if(s.Length == 1)
        {

            return new []{s}; 
        }

        else if(s.Length == 2)
        {

            return new []{s[1].ToString()+s[0].ToString(),s[0].ToString()+s[1].ToString()};

        }

        var comb = new List<string>();

        foreach(var c in s)
        {

            string cStr = c.ToString();

            var sToProcess = s.Replace(cStr,"");
            if (!string.IsNullOrEmpty(sToProcess) && sToProcess.Length>0)
            {
                var conCatStr = GenerateFor(sToProcess);



                foreach(var a in conCatStr)
                {
                    comb.Add(c.ToString()+a);
                }


            }
        }
        return comb.ToArray();

    }
}

new Permutation().GenerateFor("aba")çıkışlarıstring[4] { "ab", "baa", "baa", "ab" }
Atomosk

0
    //Generic C# Method
            private static List<T[]> GetPerms<T>(T[] input, int startIndex = 0)
            {
                var perms = new List<T[]>();

                var l = input.Length - 1;

                if (l == startIndex)
                    perms.Add(input);
                else
                {

                    for (int i = startIndex; i <= l; i++)
                    {
                        var copy = input.ToArray(); //make copy

                        var temp = copy[startIndex];

                        copy[startIndex] = copy[i];
                        copy[i] = temp;

                        perms.AddRange(GetPerms(copy, startIndex + 1));

                    }
                }

                return perms;
            }

            //usages
            char[] charArray = new char[] { 'A', 'B', 'C' };
            var charPerms = GetPerms(charArray);


            string[] stringArray = new string[] { "Orange", "Mango", "Apple" };
            var stringPerms = GetPerms(stringArray);


            int[] intArray = new int[] { 1, 2, 3 };
            var intPerms = GetPerms(intArray);

Burada tek başına bırakmak yerine, bu kodun nasıl çalıştığı hakkında biraz ayrıntılı bilgi vermeniz harika olurdu.
iBug

-1
    /// <summary>
    /// Print All the Permutations.
    /// </summary>
    /// <param name="inputStr">input string</param>
    /// <param name="strLength">length of the string</param>
    /// <param name="outputStr">output string</param>
    private void PrintAllPermutations(string inputStr, int strLength,string outputStr, int NumberOfChars)
    {
        //Means you have completed a permutation.
        if (outputStr.Length == NumberOfChars)
        {
            Console.WriteLine(outputStr);                
            return;
        }

        //For loop is used to print permutations starting with every character. first print all the permutations starting with a,then b, etc.
        for(int i=0 ; i< strLength; i++)
        {
            // Recursive call : for a string abc = a + perm(bc). b+ perm(ac) etc.
            PrintAllPermutations(inputStr.Remove(i, 1), strLength - 1, outputStr + inputStr.Substring(i, 1), 4);
        }
    }        
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.