5

I have an IEnumerable<IEnumerable<T>> collection that I want to convert to a single dimension collection. Is it possible to achieve this with a generic extension method? Right now I'm doing this to achieve it.

List<string> filteredCombinations = new List<string>();

//For each collection in the combinated results collection
foreach (var combinatedValues in combinatedResults)
{
    List<string> subCombinations = new List<string>();
    //For each value in the combination collection
    foreach (var value in combinatedValues)
    {

        if (value > 0)
        {
            subCombinations.Add(value.ToString());
        }
    }
    if (subCombinations.Count > 0)
    {
       filteredCombinations.Add(String.Join(",",subCombinations.ToArray()));
    }
}

If it's not possible to get a generic solution, how can I optimize this in an elegant fashioned way.

Jørn Schou-Rode
  • 37,718
  • 15
  • 88
  • 122
John Doe
  • 53
  • 2
  • 1
    the question in the title is answered here: http://stackoverflow.com/questions/1590723/flatten-list-in-linq – Matt Ellen Feb 10 '10 at 22:34

5 Answers5

18

You can use the Enumerable.SelectMany extension method for this.

If I read your code correctly, the code for that would be:

var filteredCombinations = combinatedResults.SelectMany(o => o)
    .Where(value => value > 0)
    .Select(v => v.ToString());

Edit: As commented, the above code is not joining each element of the subsets to a string, as the original code does. Using the built-in methods, you can do that using:

var filteredCombinations = combinatedResults
     .Where(resultSet => resultSet.Any(value => value > 0)
     .Select(resultSet => String.Join(",",
         resultSet.Where(value => value > 0)
                  .Select(v => v.ToString()).ToArray()));
driis
  • 161,458
  • 45
  • 265
  • 341
  • What if you want every subset to be one element of the new collection? That's what I do with the String.Join... – John Doe Feb 10 '10 at 21:30
  • @Earwicker, I fail to see how I will get empty strings in the result. The resultSet must start out as an enumerable of a numeric type, since the elements is being compared to zero in the orignal code. (I admit I typed the code without Visual Studio, so feel free to correct me :-) – driis Feb 10 '10 at 22:06
  • There are two `if` statements in the original code. You only have one `Where` clause. You've kept the one that filters out the zero values, but you have missed the one that filters out empty sub-lists. A list containing all zeros will become an empty array, which you pass to `Join`. The original code doesn't do that. – Daniel Earwicker Feb 10 '10 at 22:09
  • Hmm, now you're querying every `resultSet` twice with the same expression. Sorry to be picky... you could just copy my answer to save time! :) – Daniel Earwicker Feb 10 '10 at 22:14
3

I would personally use Enumerable.SelectMany, as suggested by driis.

However, if you wanted to implement this yourself, it would be much cleaner to do:

IEnumerable<T> MakeSingleEnumerable<T>(IEnumerable<IEnumerable<T>> combinatedResults)
{
    foreach (var combinatedValues in combinatedResults) {
         foreach (var value in combinatedValues)
              yield return value;
    }
}
Community
  • 1
  • 1
Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
3

Here you go:

var strings = combinedResults.Select
    (
        c => c.Where(i => i > 0)
        .Select(i => i.ToString())
    ).Where(s => s.Any())
    .Select(s => String.Join(",", s.ToArray());
Matt Howells
  • 40,310
  • 20
  • 83
  • 102
  • 1
    This whole question has been bizarre! The wording asked one question, the example asked another. And you've answered a third, different question altogether, and got accepted... I've already posted the correct answer, and have no votes (not that I mind, it's just amusing.) – Daniel Earwicker Feb 10 '10 at 22:05
  • I think your `Implode` would be more elegant if it just operated on `IEnumerable`. If you want a conversion on each item of some other type of sequence, use `Select` first. – Daniel Earwicker Feb 10 '10 at 22:17
  • Your implementation of Implode would probably be more efficient and certainly more concise if it simply used `string.Join` rather than StringBuilder. i.e. `return string.Join(separator,sequence.ToArray())` – Eamon Nerbonne Nov 17 '10 at 10:35
  • @Eamon: Having done a bit of digging into the String.Join implementation, I think you are dead right. Fixed. – Matt Howells Nov 25 '10 at 13:43
2

You asked two different questions. The one you described in the title is already answered by drilis.

But your example code is a different problem. We can refactor it in stages. Step 1, build the subCombinations list using some Linq:

List<string> filteredCombinations = new List<string>();

//For each collection in the combinated results collection
foreach (var combinatedValues in combinatedResults)
{
    var subCombinations = combinatedValues.Where(v => v > 0)
                                          .Select(v => v.ToString())
                                          .ToList();

    if (subCombinations.Count > 0)
       filteredCombinations.Add(string.Join(",",subCombinations.ToArray()));
}

Now the outer loop, leaving us with just this:

var filteredCombinations = combinatedResults
    .Select(values => values.Where(v => v > 0)
                            .Select(v => v.ToString())
                            .ToArray())
    .Where(a => a.Count > 0)
    .Select(a => string.Join(",", a));
Daniel Earwicker
  • 114,894
  • 38
  • 205
  • 284
0

use linq SelectMany

Preet Sangha
  • 64,563
  • 18
  • 145
  • 216