Permutaciones usando las mismas letras

I am current working on a project where I need to generate all possible permutations from a given set of characters. I am currently using this code:

public static IEnumerable<string> AllPermutations(this IEnumerable<char> s)
{
    return s.SelectMany(x =>
    {
        var index = Array.IndexOf(s.ToArray(), x);
        return s.Where((y, i) => i != index).AllPermutations().Select(y => new string(new[] { x }.Concat(y).ToArray())).Union(new[] { new string(new[] { x }) });
    }).Distinct();
}

De: este responder.

The problem I have is that it won't generate permuations that use the same letter more than once.

For example if I used abcde as the input I need it to generate combinations like aaaaa y dcc etc.

I'm not experienced enough with LINQ to understand where the code is stopping duplicate letters. Any help is greatly appreciated.

preguntado el 09 de marzo de 12 a las 13:03

Is there a reason to do this in LINQ? -

I didn't write this, so was just looking for code that did the job really. -

'aaaaa' is not a permutation of 'abcde'. If your project requires permutations don't include 'aaaaa', if you include 'aaaaa' don't call it a permutation (or a combination for that matter). You'll just confuse everyone reading your question, including yourself. -

Perhaps what you need is variation with repetitions. -

I also need any shorter versions, from 1 character to the string length, using all the input letters in any order. -

3 Respuestas

Este planteamiento de « podría work, but I'm sure it could be done more efficiently (taking the counting prompt from PeskyGnat):

    static IEnumerable<string> GetVariations(string s)
    {
        int[] indexes = new int[s.Length];
        StringBuilder sb = new StringBuilder();

        while (IncrementIndexes(indexes, s.Length))
        {
            sb.Clear();
            for (int i = 0; i < indexes.Length; i++)
            {
                if (indexes[i] != 0)
                {
                    sb.Append(s[indexes[i]-1]);
                }
            }
            yield return sb.ToString();
        }
    }

    static bool IncrementIndexes(int[] indexes, int limit)
    {
        for (int i = 0; i < indexes.Length; i++)
        {
            indexes[i]++;
            if (indexes[i] > limit)
            {
                indexes[i] = 1;
            }
            else
            {
                return true;
            }
        }
        return false;
    }

Edit: Changed to use yield return as per Rawlings suggestion. Much better memory usage if you don't need to keep all the results and you can start using the results before they've all been generated.

respondido 09 mar '12, 16:03

That is absouletly brilliant! I can't thank you enough. +1 and accept, thanks! - bali c

Whether or not this could be done more efficiently, I'm sure it could be done a lot less efficiently. E.g. my answer :D - Rawling

@Rawling: Actually one thing mine doesn't do that your does is deal with duplicates. For example, if the string is "cca", mine will spit out "caa" twice, once for each "c" which it sees as different. Yours doesn't do that. - matt burland

@Matt Oh, mine doesn't intencionalmente deal with duplicates. Yours is in fact the O(1)-space solution I was thinking of, if you just switch to using yield return en lugar de un List. Edit: in fact the duplicates thing is trivial, just check the length of the input string, then strip it of duplicate characters before using it as your input... - Rawling

Yes, the yield would make this handle larger values without any memory overhead, and adhere more closely to the original interface of returning an Enumerable - MosquitoPesky

I'm amazed this works. It basically goes "make a list of strings from the characters. Then to each string taken from the list, add each character again, and add the resulting strings to the list. Repeat until you've got the right length."

public static IEnumerable<string> BuildStrings(this IEnumerable<char> alphabet)
{
    var strings = alphabet.Select(c => c.ToString());
    for (int i = 1; i < alphabet.Count(); i++)
    {
        strings = strings.Union(strings.SelectMany(s => alphabet.Select(c => s + c.ToString())));
    }
    return strings;
}

respondido 09 mar '12, 14:03

Aha, it only works because I'm doing Union más bien que Concat, otherwise I'd have many, many duplicates... - Rawling

A funny one using only recursive lambdas via a fixpoint operator (thx @Rawling for the Seleccionar muchos)

// Fix point operator   
public static Func<T, TResult> Fix<T, TResult>(Func<Func<T, TResult>, Func<T, TResult>> f)
    {
        return t => f(Fix<T, TResult>(f))(t);
    }

Y entonces

var chars = new[] {'a','b','c','d','e'}.Select(c=>c.ToString()) ;

var result = Fix<int,IEnumerable<string>>(
    f => 
      x => 
       x == 1
         ? chars
         : chars.Union(f(x - 1).SelectMany(s => chars.Select(c => s + c))))(chars.Count());

respondido 09 mar '12, 16:03

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.