1

I'm working on a C# script within a Unity3D Project where I'm trying to take a list of strings and get a 2D list of the permutations. Using this answer's GetPermutations() in the following fashion:

List<string> ingredientList = new List<string>(new string[] { "ingredient1", "ingredient2", "ingredient3" });

List<List<string>> permutationLists = GetPermutations(ingredientList, ingredientList.Count);

But it throws an implicit conversion error:

IEnumerable<IEnumerable<string>> to List<List<string>> ... An explicit conversion exists (are you missing a cast)?

So I looked at a few places, such as here and came up with the following modification:

List<List<string>> permutationLists = GetPermutations(ingredientList, ingredientList.Count).Cast<List<string>>().ToList();

But it breaks at runtime, gets handled internally, and allows it to continue without indicating a failure – probably because it's running in Unity3D. Here is what I see in Unity3D after I stop debugging the script:

InvalidCastException: Cannot cast from source type to destination type.
System.Linq.Enumerable+<CreateCastIterator>c__Iterator0`1[System.Collections.Generic.List`1[System.String]].MoveNext ()
System.Collections.Generic.List`1[System.Collections.Generic.List`1[System.String]].AddEnumerable (IEnumerable`1 enumerable) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Collections.Generic/List.cs:128)
System.Collections.Generic.List`1[System.Collections.Generic.List`1[System.String]]..ctor (IEnumerable`1 collection) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Collections.Generic/List.cs:65)
System.Linq.Enumerable.ToList[List`1] (IEnumerable`1 source)

Which I interpret as still casting incorrectly, so I also attempted the following approaches and more that I can't remember:

List<List<string>> permutationLists = GetPermutations(ingredientList, ingredientList.Count).Cast<List<List<string>>>();

List<List<string>> permutationLists = GetPermutations(ingredientList.AsEnumerable(), ingredientList.Count);

as well as explicitly casting with parenthesis before the method call like you would in C or Java, still to no avail.


So how should I be casting the results from the GetPermutations() function to get a List<List<string>>? Or alternatively, how could I modify the function to only return List<List<string>> since I don't need it to work for a generic type? I tried to modify the method myself to be the following:

List<List<string>> GetPermutations(List<string> items, int count)
{
    int i = 0;
    foreach(var item in items)
    {
        if(count == 1)
            yield return new string[] { item };
        else
        {
            foreach(var result in GetPermutations(items.Skip(i + 1), count - 1))
                yield return new string[] { item }.Concat(result);
        }

        ++i;
    }
}

However, having removed the <T> from the function name it breaks stating that the body cannot be an iterator block. I have no prior experience with C# and I'm rusty with template functions in strongly typed languages, so any explanation/help is appreciated.

I wasn't sure how to look this issue up, so if this is a duplicate just post it here and I'll delete this post immediately.

Darrel Holt
  • 870
  • 1
  • 15
  • 39
  • 1
    You need something like `GetPermutations(...).Select(c => c.ToList()).ToList()` – Evk May 04 '18 at 21:26

2 Answers2

8

So how should I be casting the results from the GetPermutations() function to get a List<List<string>>

Best solution: don't. Why do you need to turn the sequence into a list in the first place? Keep it as a sequence of sequences.

If you must though:

GetPermutations(...).Select(s => s.ToList()).ToList()

If you want to modify the original method, just do the same thing:

IEnumerable<List<string>> GetPermutations(List<string> items, int count)
{
    int i = 0;
    foreach(var item in items)
    {
        if(count == 1)
            yield return new List<T>() { item };
        else
        {
            foreach(var result in GetPermutations(items.Skip(i + 1), count - 1))
                yield return (new string[] {item}.Concat(result)).ToList();
        }

        ++i;
    }
}

And then do GetPermutations(whatever).ToList() and you have a list of lists. But again, do not do this. Keep everything in sequences if you possibly can.

I want to turn the sequence into a list so that I can sort the elements alphabetically and re-join them as a sorted, single comma-delimited string.

OK, then do that. Let's rewrite your method as an extension method Permute(). And let's make some new one-liner methods:

static public string CommaSeparate(this IEnumerable<string> items) =>
  string.Join(",", items);

static public string WithNewLines(this IEnumerable<string> items) =>
  string.Join("\n", items);

static public IEnumerable<string> StringSort(this IEnumerable<string> items) =>
  items.OrderBy(s => s);

Then we have the following -- I'll annotate the types as we go:

string result = 
  ingredients                    // List<string>
  .Permute()                     // IEnumerable<IEnumerable<string>>
  .Select(p => p.StringSort())   // IEnumerable<IEnumerable<string>>
  .Select(p => p.CommaSeparate())// IEnumerable<string>
  .WithNewLines();                   // string

And we're done. Look at how clear and straightforward the code is when you make methods that do one thing and do it well. And look at how easy it is when you keep everything in sequences, as it should be.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • I want to turn the sequence into a list so that I can sort the elements alphabetically and re-join them as a sorted, single comma-delimited string. I began with a list because I was familiar with it, but I will look into using the return value since you say it's possible to use it as-is. – Darrel Holt May 04 '18 at 21:39
  • @DarrelHolt You can sort a sequence just as easily as you can sort a list, and you can join sequences just as easily as you can join lists. – Servy May 04 '18 at 21:50
  • @DarrelHolt: Servy is almost correct. It is *easier* to do the work in sequences than in lists because *you never have to worry that someone mutated the sequence on you*. – Eric Lippert May 04 '18 at 21:58
  • @Servy I don't see a built-in sort function for `IEnumerable`, but I do for `List`, what is the sorting method name for sequences? – Darrel Holt May 04 '18 at 21:58
  • @DarrelHolt: `OrderBy`. **You would do well to read up on the standard sequence operators if you are going to be programming using sequences**. – Eric Lippert May 04 '18 at 21:59
  • @DarrelHolt: I've updated the answer to show how to solve your larger problem. **Use fluent style to make you program both easy to read and obviously correct**. – Eric Lippert May 04 '18 at 22:00
  • @EricLippert so fluent style is chaining the commands so that it seems "Fluent" as if you were reading a sequence of events? I have only seen this concept used in JS, thanks a bunch for your help. – Darrel Holt May 04 '18 at 22:03
  • @DarrelHolt: That's correct. We think of operations on sequences as a workflow of chained operations where each sequence flows values into the next operation. If the subject of producing permutations in C# interests you, I've written many articles on clever ways to do so. – Eric Lippert May 04 '18 at 22:05
  • @EricLippert Awesome, I'll take a look at some of your posts. Thanks again. – Darrel Holt May 04 '18 at 22:07
1

Your question is related to several aspects of C# and .net types system. I will try to provide simple explanation and will provide links as more formal answers.

So, according to your description it looks like GetPermutations(ingredientList, ingredientList.Count); returns IEnumerable<IEnumerable<string>> but you are trying to assign this result to the variable of another type, in pseudo code:

List<List<string>> = IEnumerable<IEnumerable<string>>;

List<T> implements IEnumerable<T>, so in general it is possible to make this assignment:

IEnumerable<T> = List<T>;

but the problem is that in your case T on the left side differs from the T on the right side.

  • for IEnumerable<IEnumerable<string>> T is IEnumerable<string>.
  • for List<List<string>> T is List<string>

To fix your problem we should change the code to have the same T on the left and right sides i.e. convert T to either List<string> or IEnumerable<string>.

You can convert T to the List<string> this way:

IEnumerable<List<string> GetPermutationsList(List<string> items, int count)
{
    return GetPermutations(items, count).Select(x=>x.ToList())
}
IEnumerable<List<string>> permutationLists = GetPermutations(ingredientList.AsEnumerable(), ingredientList.Count);
// or
List<List<string>> permutationLists = GetPermutations(ingredientList.AsEnumerable(), ingredientList.Count).ToList();

but in general it is not good idea to use List in all places. Use lists only where you really need it. Important points here:

  • IEnumerable<T> provides minimum functionality (enumeration only) that should be enougth for your goals.
  • IList <T> (List implements it ) provides maximum functionality (Add, Remove ,“random” access by index). Do you really need maximum functionality?
  • Also using ToList() can cause memory shortage problem for big data.
  • ToList() just forces immediate query evaluation and returns a List<T>

Some useful information: covariance-contr-variance, List, casting

Maxim Kitsenko
  • 2,042
  • 1
  • 20
  • 43