I want to transform an array:
["a", "b", "b", "a", "b", "a", "b"]
to
["a", "a", "b", "b", "a", "b", "b"]
or ["b", "b", "a", "a", "b", "b", "a"]
I want to group the array in a way where a special condition matches. In my case when item == 'a'
or item == 'b'
. And these groups I want to chunck into groups of 2. I'm currently a bit confused how to do it the elegant way.
Can anyone help?
Maybe the following makes it more clear:
I like to group the array into 'a' and 'b'-items first like so:
a-group:
["a","a","a"]
and
b-group:
["b","b","b","b"]
then I want to chunk this into groups of 2:
a-group:
["a","a"]
["a"]
b-group:
["b","b"]
["b","b"]
And now I want to merge them together to get the result:
["a","a","b","b","a","b","b"]
(always 2 of each group merged together)
First you need to GroupBy
your data. Let assume the object are string
but it's irrelevant anyway it's your grouping condition that will change if you have anything other than that.
For this to work you will need MoreLinq or simply include the Batch extension which does the "group by 2 and 1 for left overs". Details can be found here
note that the Batch(2)
can be changed to whatever you need. If you put Batch(5)
and
you have 7 elements it will make 2 groups, one with 5 elements and one of 2 elements
// be my data, 4 x a, 3 x b, 1 x c, 2 x d, As a list for easier linQ
var data = new[] { "a", "a", "c", "b", "a", "b", "b", "d", "d", "a" }.ToList();
// group by our condition. Here it's the value so very simple
var groupedData = data.GroupBy(o => o).ToList();
// transform all groups into a custom List of List of List so they are grouped by 2 internally
// each list level represent Grouping -> Groups of 2 (or 1) -> element
var groupedDataBy2 = groupedData.Select(grp => grp.ToList().AsEnumerable().Batch(2).ToList()).ToList();
// find the group with the maximum amount of groups or 2 (and 1)
var maxCount = groupedDataBy2.Max(grp => grp.Count());
// will contain our final objects listed
var finalObjects = new List<string>();
// loop on the count we figured out and try add each group one by one
for (int i = 0; i < maxCount; i++)
{
// try each group
foreach (var group in groupedDataBy2)
{
// add the correct index to our final list only if the current group has enough to fill
if (i < group.Count)
{
// add the data to our final list
finalObjects.AddRange(group[i]);
}
}
}
// result here is : a,a,c,b,b,d,d,a,a,b
var results = string.Join(",", finalObjects);
I think for simplicity's sake converting your arrays into lists and then using findall()
would be the easiest way to do this.
List<char> list = new List<char>(['a', 'a', 'b'...]);
if you use predicates, then you can automatically filter a list to pull out each of the two elements like below
a_elements = list.findall((char c) -> { return c == item; });
Then for b you can do
b_elements = list.findall((char c) -> { return c != item; });
where (char c) -> { return c... }
is a predicate, a method returns true when an item in the list qualifies for the condition and false otherwise, you can also use Microsoft's Predicate class to form these, but I prefer lambda functions.
Now a_elements
and b_elements
are arrays containing matches for each corresponding findall()
call
From there all you have to do is group the arrays in groups of two