Reputation: 516
I have two IEnumerable
s:
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 IEnumerable
s 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 IEnumerable
s 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.
Upvotes: 3
Views: 1868
Reputation: 4939
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
Upvotes: 1
Reputation: 236268
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" ]
Upvotes: 1
Reputation: 438
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 + ' ');
Upvotes: 1
Reputation: 1430
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
Upvotes: 3
Reputation: 37050
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.
Upvotes: 1