I have an array consisting of following elements:
var schools = new [] {
new object[]{ new[]{ "1","2" }, "3","4" },
new object[]{ new[]{ "5","6" }, "7","8" },
new object[]{ new[]{ "9","10","11" }, "12","13" }
};
The real object that i try to flatten is from importing data into array of arrays from CSV and then joining it on values of fields:
var q =
from c in list
join p in vocatives on c.Line[name1].ToUpper() equals p.first_name.ToUpper() into ps
from p in ps.DefaultIfEmpty()
select new object[] { c.Line, p == null ? "(No vocative)" : p.vocative, p == null ? "(No sex)" : p.sex };
I want to flatten that array of strings to get:
string[] {
new string[]{ "1","2","3","4" },
new string[]{ "5","6","7","8" },
new string[]{ "9","10","11","12","13" }
}
I already have an solution that does that in loop, its not so performance-wise, but it seems to work ok.
I've tried to use SelectMany
but cannot make up a solution.
Thank you very much for feedback ;) I've tried answer from npo:
var result = schools.Select(z => z.SelectMany(y=> y.GetType().IsArray
? (object[])y : new object[] { y })
);
But CSVwriter class method accepts only explicitly typed:
IEnumerable<string[]>
So how to do it in linq, I've tried to:
List<string[]> listOflists = (List<string[]>)result;
But no go, InvalidCastException
arrises, unfortunately.
In a first step, you have to normalize the data to one kind of type. Then you can iterate over them as you like. So at first create a method to flatten the values from a specific point to an arbitrary depth:
public static class Extensions
{
public static IEnumerable<object> FlattenArrays(this IEnumerable source)
{
foreach (var item in source)
{
if (item is IEnumerable inner
&& !(item is string))
{
foreach (var innerItem in inner.FlattenArrays())
{
yield return innerItem;
}
}
yield return item;
}
}
}
Now you can either iterate on the top level to get a single array of all values:
// Produces one array => ["1", "2", "3", "4", ...]
var allFlat = schools.FlattenArrays().OfType<string>().ToArray();
Or you can create individual array one depth deeper:
foreach (var item in schools)
{
// Produces an array for each top level e.g. ["5", "6", "7", "8"]
var flat = item.FlattenArrays().OfType<string>().ToArray();
}
As per the comments, because your inner array mixes elements of string[]
and string
, it likely won't be trivial to do this directly in Linq.
However, with the assistance of a helper function (I've called Flattener
) you can branch the handling of both of the inner types manually to either return the elements in the array (if it's string[]
), or to return the single element as an enumerable, if it's not. SelectMany
can then be used to flatten the inner level, but the outer level seemingly you want to leave unflattened:
i.e.
var schools = new [] {
new object[]{new[]{"1","2"}, "3","4"},
new object[]{new[]{"5","6"}, "7","8"},
new object[]{new[]{"9","10","11"}, "12","13"}
};
var result = schools
.Select(s => s.SelectMany(o => Flattener(o)));
Which returns a type of IEnumerable<IEnumerable<string>>
Where the messy unpacking bit done by:
public IEnumerable<string> Flattener(object o)
{
if (o is IEnumerable<string> strings)
{
return strings;
}
if (o is string s)
{
return new[]{s};
}
return new[]{"?"};
}
Note the above uses the pattern matching capabilities of C#7.
Result screenshot courtesy of LinqPad:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With