3

I have an IEnumerable<object> which may or may not contain some nested collections. For example, my starting point might look something like this:

[ "foo", 2, [1, 2, 3, 4], "bar" ]

And I want to flatten it to:

[ "foo", 2, 1, 2, 3, 4, "bar" ]

I'm thinking a SelectMany should work here but can't quite find the right combination. I could brute force it, but I thought there should be a more elegant solution.

Matt Burland
  • 44,552
  • 18
  • 99
  • 171
  • Have you seen this: http://blog.mijalko.com/2009/12/flatten-array-of-arrays-using-linq.html – Mike Cheel Feb 04 '14 at 20:11
  • @MikeCheel: Actually I did, but that's not quite the same thing. That's an list of lists. This is a list of objects, some of which might be lists. I want to select both the objects that aren't lists and the contents of each list. – Matt Burland Feb 04 '14 at 20:13
  • Is your nesting always 2 levels deep or you can have even more deeply nested arrays? – nemesv Feb 04 '14 at 20:13
  • @MikeCheel: That is not relevant. – SLaks Feb 04 '14 at 20:13
  • @nemesv: It's just the 2 levels at the moment. I don't anticipate it being any deeper (or if it is, I'll really have to rethink the whole thing!) – Matt Burland Feb 04 '14 at 20:14
  • 3
    What kind of list-type objects can be in your list? I ask because `"foo"` is a `string`, which implements `IEnumerable`, and it sounds like you don't want `['f', 'o', 'o', ...]`. – Joel Rondeau Feb 04 '14 at 20:21
  • @JoelRondeau: A very good question - no, I don't want to split strings into individual members, that would be very bad. The types in the list will likely be `int`, `double`, `string` and arrays (or lists) of said types. – Matt Burland Feb 04 '14 at 20:40

3 Answers3

7
IEnumerable<object> source = new object[] { "test", 1, new[] { 1, 2, 3 }, "test" };

var result = source .SelectMany(x => x is Array ? ((IEnumerable)x).Cast<object>() : Enumerable.Repeat(x, 1));

To get it work with nested arrays make the lambda recursive:

IEnumerable<object> source = new object[] { "test", 1, new object[] { 1, 2, new [] { "nested", "nested2" } }, "test" };

Func<IEnumerable<object>, IEnumerable<object>> flatten = null;
flatten = s => s.SelectMany(x => x is Array ? flatten(((IEnumerable)x).Cast<object>()) : Enumerable.Repeat(x, 1));

var result = flatten(source);
MarcinJuraszek
  • 124,003
  • 15
  • 196
  • 263
3

Slightly shorter recursive alternative:

IEnumerable<object> source = new object[] { "test", 1, new object[] { 1, 2, new [] { "nested", "nested2" } }, "test" };

Func<IEnumerable<object>, IEnumerable<object>> flatten = null;
flatten = s => s == null ? null : s.SelectMany(x => 
   flatten(x as IEnumerable<object>) ?? new [] { x }
);
var result = flatten(source);
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
2

This is a bit nasty, but works.. assuming only one level:

a.SelectMany(t => 
     (t is IEnumerable<object>)
      ? (IEnumerable<object>)t 
      : new object[] {t})

I suspect the most "elegant" solution is really to write an extension.

public static IEnumerable<T> Flatten<T>(this IEnumerable<T> @this) {
    foreach (var item in @this) {
        if (item is IEnumerable<T>) {
            foreach (var subitem in Flatten((IEnumerable<T>)item)) {
                yield return subitem;
            }
        }
        else yield return item;
    }
}
Blorgbeard
  • 101,031
  • 48
  • 228
  • 272