3

I have two IEnumerables:

IEnumerable<string> first = ...
IEnumerable<string> second = ...

I want to create a second IEnumerable<string> that is the concatenation of each element of each IEnumerable.

For example:

IEnumerable<string> first = new [] {"a", "b"};
IEnumerable<string> second = new [] {"c", "d"};

foreach (string one in first)
{
   foreach (string two in second)
   {
      yield return string.Format("{0} {1}", one, two);
   }
}

This would produce:

"a c"; "a d"; "b c"; "b d";

The problem is, sometimes one of the two IEnumerables is empty:

IEnumerable<string> first = new string[0];
IEnumerable<string> second = new [] {"c", "d"};

In this case, the nested foreach construct never reaches the yield return statement. When either IEnumerable is empty, I would like the result to just be the list of the non-empty IEnumerable.

How can I produce the combinations I am looking for?

EDIT: In reality, I have three different IEnumerables I am trying to combine, so adding if conditions for every possible permutation of empty IEnumerable seems bad. If that's the only way, then I guess I'll have to do it that way.

michaelkoss
  • 516
  • 7
  • 19
  • 1
    Look into the Zip linq operator. It takes two enumerables and allows you to traverse through both. If sizes aren't the same put logic in to account for that. – JWP May 02 '17 at 14:10
  • 2
    @JohnPeters He's not zipping the sequences. – Servy May 02 '17 at 14:11
  • 2
    "When either IEnumerable is empty, I would like the result to just be the list of the non-empty IEnumerable" Then you should handle that case before your loops. – MakePeaceGreatAgain May 02 '17 at 14:11
  • Use indexes, if zip doesn't work for you. – JWP May 02 '17 at 14:12
  • Just for clarity can you please include example outputs in the question for `first` being empty and for `second` being empty. – Scott Chamberlain May 02 '17 at 14:14
  • You can assume list contains one null element in case it is null or empty (so, first ?? new string[] {null}) and act accordingly in yield (not include null strings in result). – Evk May 02 '17 at 14:35

5 Answers5

3

You can simply check that first enumerable is not empty:

IEnumerable<string> first = new [] {"a", "b"};
IEnumerable<string> second = new [] {"c", "d"};

var firstList = first.ToList();

if (!firstList.Any()) {
    return second;
}

foreach (string one in firstList)
{
   foreach (string two in second)
   {
      yield return string.Format("{0} {1}", one, two);
   }
}

To eliminate double IEnumerable evaluation in positive cases just convert first enumerable to list

hmnzr
  • 1,390
  • 1
  • 15
  • 24
  • 1
    That's the correct solution - the OP wants a special case if a list is empty, so the correct solution is to check for the special condition. Why is this downvoted? – Heinzi May 02 '17 at 14:16
  • Why the `ToList` call? – Kenneth K. May 02 '17 at 14:20
  • In case when `first` is not empty, there will be two `IEnumerable` evaluations without list conversion – hmnzr May 02 '17 at 14:21
  • Probably should also check if the second is empty and return the first in that case, but that's up to the OP's requirements. – juharr May 02 '17 at 14:23
  • 1
    Instead of calling ToList you can check if first implements IList. Not sure materializing IEnumerable of unknown size is such a good idea, given that Any will lookup at most 1 item (in case first does not implement IList). Actually I guess Any will do that check for you anyway. – Evk May 02 '17 at 14:37
  • @Evk he added it because of my earlier (now deleted) comment that doing both `Any()` and the foreach causes double evaluation of the `IEnumerable`. If the `IEnumerable` is backed by function with a `yield return` it will cause multiple executions of that function. – Scott Chamberlain May 02 '17 at 15:29
1

Your current approach should work until any of the collections is empty. If this is the case you need some check in front:

if(!first.Any())
    foreach(var e in second) yield return e;
else if(!second.Any())
    foreach(var e in first) yield return e;

foreach (string one in first)
{
   foreach (string two in second)
   {
      yield return string.Format("{0} {1}", one, two);
   }
}

However you should consider making an immediate execution using ToList in front to avoid multiple iterations of the same collection.

MakePeaceGreatAgain
  • 35,491
  • 6
  • 60
  • 111
  • 1
    Be careful, this results in a double evaluation of the IEnumerable you call `.Any()` on.. – Scott Chamberlain May 02 '17 at 14:14
  • Yes, my actual code uses a .ToList() on the second to avoid multiple iterations over the IEnumerable. If I have to use .Any(), I will include the .ToList() on the first as well. – michaelkoss May 02 '17 at 14:33
1

Assuming you're output for case :

IEnumerable<string> first = new string[0];
IEnumerable<string> second = new [] {"c", "d"};

would be :

c
d

This would work :

var query = from x in first.Any() ? first : new [] { "" }
            from y in second.Any() ? second : new[] { "" }
            select x + y;

Less code , easier to maintain and debug !

Edit : If you have any other IEnumerable is just 1 extra line per IEnumerable ( includes the check )

var query = from x in first.Any() ? first : new [] { "" }
            from y in second.Any() ? second : new[] { "" }
            from z in third.Any() ? third : new[] { "" }
            select x + y + z;

Edit 2 : you can just add the spaces at the end :

select (x + y + z).Aggregate(string.Empty, (c, i) => c + i + ' ');
doremifasolasido
  • 428
  • 5
  • 17
1

Simply use Enumerable.DefaultIfEmpty() to enumerate collection even if there is no items.

IEnumerable<string> first = new string[0]; 
IEnumerable<string> second = new[] { "a", "b" };
IEnumerable<string> third = new[] { "c", null, "d" };

var permutations = 
     from one in first.DefaultIfEmpty()
     from two in second.DefaultIfEmpty()
     from three in third.DefaultIfEmpty()
     select String.Join(" ", NotEmpty(one, two, three));

Note: I have used String.Join to join items which are not null or empty and method to select non-empty items to be joined (you can inline this code if you don't want to have a separate method):

private static IEnumerable<string> NotEmpty(params string[] items)
{
    return items.Where(s => !String.IsNullOrEmpty(s));
}

Output for sample above is

[ "a c", "a", "a d", "b c", "b", "b d" ]

For two collections and foreach loops (though I would prefere LINQ as above):

IEnumerable<string> first = new[] { "a", "b" };
IEnumerable<string> second = new string[0];

foreach(var one in first.DefaultIfEmpty())
{
     foreach(var two in second.DefaultIfEmpty())
        yield return $"{one} {two}".Trim(); // with two items simple Trim() can be used
}

Output:

[ "a", "b" ]
Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
  • @doremifasolasido what do you mean by that? If you'll make second collection empty, then result will be exactly the same. – Sergey Berezovskiy May 02 '17 at 15:07
  • Ah, beautiful. Basically, the same answer as @doremifasolasido but with a framework supplied method instead of coding the empty arrays. Both of your answers are what I am looking for. – michaelkoss May 02 '17 at 15:14
  • Trim() will remove only the start and end extra white spaces ... not the middle ones – doremifasolasido May 02 '17 at 15:18
  • @michaelkoss there is a **significant** difference - `Any()` will start enumeration. In simplest way it will just create enumerator object and try to get first item. But if your strings come from slow resource like file or database, then it will open file or run database query one more time – Sergey Berezovskiy May 02 '17 at 15:30
  • @SergeyBerezovskiy, I'm not too familiar with the implementation of `DefaultIfEmpty()`, but wouldn't it have to do the same to determine if the `IEnumerable` were empty or not? – michaelkoss May 02 '17 at 15:38
  • @michaelkoss no, it will be called once in `foreach` loop or in linq `from` statement. Then on first iteration if no items found, default will be returned. Otherwise simply items will be returned – Sergey Berezovskiy May 02 '17 at 15:40
1

If you have more than a couple lists, you can setup a recursive iterator. You'll want to be mindful of the stack, and I think the string concatenation is less than ideal, and passing lists of lists is rather clunky, but this should get you started.

using System;
using System.Collections.Generic;
using System.Linq;

namespace en
{
    class Program
    {
        static void Main(string[] args)
        {
            // three sample lists, for demonstration purposes.
            var a = new List<string>() { "a", "b", "c" };
            var b = new List<string>() { "1", "2", "3" };
            var c = new List<string>() { "i", "ii", "iii" };

            // the function needs everything in one argument, so create a list of the lists.
            var lists = new List<List<string>>() { a, b, c };

            var en = DoStuff(lists).GetEnumerator();

            while (en.MoveNext())
            {
                Console.WriteLine(en.Current);
            }
        }

        // This is the internal function. I only made it private because the "prefix" variable
        // is mostly for internal use, but there might be a use case for exposing that ...
        private static IEnumerable<String> DoStuffRecursive(IEnumerable<String> prefix, IEnumerable<IEnumerable<String>> lists)
        {
            // start with a sanity check
            if (object.ReferenceEquals(null, lists) || lists.Count() == 0)
            {
                yield return String.Empty;
            }

            // Figure out how far along iteration is
            var len = lists.Count();

            // down to one list. This is the exit point of the recursive function.
            if (len == 1)
            {
                // Grab the final list from the parameter and iterate over the values.
                // Create the final string to be returned here.
                var currentList = lists.First();
                foreach (var item in currentList)
                {
                    var result = prefix.ToList();
                    result.Add(item);

                    yield return String.Join(" ", result);
                }
            }
            else
            {
                // Split the parameter. Take the first list from the parameter and 
                // separate it from the remaining lists. Those will be handled 
                // in deeper calls.
                var currentList = lists.First();
                var remainingLists = lists.Skip(1);

                foreach (var item in currentList)
                {
                    var iterationPrefix = prefix.ToList();
                    iterationPrefix.Add(item);

                    // here's where the magic happens. You can't return a recursive function
                    // call, but you can return the results from a recursive function call.
                    // http://stackoverflow.com/a/2055944/1462295
                    foreach (var x in DoStuffRecursive(iterationPrefix, remainingLists))
                    {
                        yield return x;
                    }
                }
            }
        }

        // public function. Only difference from the private function is the prefix is implied.
        public static IEnumerable<String> DoStuff(IEnumerable<IEnumerable<String>> lists)
        {
            return DoStuffRecursive(new List<String>(), lists);
        }
    }
}

console output:

a 1 i
a 1 ii
a 1 iii
a 2 i
a 2 ii
a 2 iii
a 3 i
a 3 ii
a 3 iii
b 1 i
b 1 ii
b 1 iii
b 2 i
b 2 ii
b 2 iii
b 3 i
b 3 ii
b 3 iii
c 1 i
c 1 ii
c 1 iii
c 2 i
c 2 ii
c 2 iii
c 3 i
c 3 ii
c 3 iii
BurnsBA
  • 4,347
  • 27
  • 39