简体   繁体   中英

Linq: Split list by condition and max size of n

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM