klappvisor
klappvisor

Reputation: 1099

How to concatenate result of GroupBy using Linq

Let say you have list of items and you want to partition them, make operation on one partition and concatenate partitions back into list.

For example there is list of numbers and I want to partition them by parity, then reverse odds and concatenate with evens. [1,2,3,4,5,6,7,8] -> [7,5,3,1,2,4,6,8]

Sounds simple, but I've got stuck on merging back two groups. How would you do it with LINQ?

IEnumerable<int> result = Enumerable.Range(0, 1000)
    .GroupBy(i => i % 2)
    .Select(p => p.Key == 1 ? p.Reverse() : p)
    .??? // need to concatenate

Edit

[1,2,3] is the representation of array which I want to get as the result, not output, sorry if I confused you by that.

Upvotes: 3

Views: 2644

Answers (5)

Salah Akbari
Salah Akbari

Reputation: 39946

Just use OrderBy like this:

List<int> arr = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8 };
var result = arr.OrderBy(i => i % 2 == 0 ? 1 : 0) 
                .ThenBy(i => i % 2 == 0 ? i : int.MaxValue) 
                .ThenByDescending(i => i); 

This should give you your desired result as you want:

[1,2,3,4,5,6,7,8] will be converted into [7,5,3,1,2,4,6,8]

Upvotes: 0

Pidon
Pidon

Reputation: 275

The GroupBy method returns an IEnumerable<IGrouping<TKey, TSource>>. As IGrouping implements IEnumerable, you can use SelectMany to concatenate multiple IEnumerable<T> instances into one.

Enumerable.Range(0, 1000)
    .GroupBy(i => i % 2)
    .Select(p => p.Key == 1 ? p.Reverse() : p)
    .OrderByDescending(p => p.Key)
    .SelectMany(p => p);

Upvotes: 3

MikeT
MikeT

Reputation: 5500

There are a few ways to achieve this,

so if we start with your function

Enumerable.Range(0, 1000)
.GroupBy(i => i % 2)
.Select(p => p.Key == 1 ? p.Reverse() : p)

you could then use an Aggregate

.Aggregate((aggrgate,enumerable)=>aggrgate.Concat(enumerable))

this will then go though your list of results and concat them all into a collection and return it, you just need to make sure that aggrgate and enumerable are the same type in this case a IEnumerable<int>

another would be to call SelectMany()

.SelectMany(enumerable=>enumerable)

this then likewise pulls all the enumerables together into a single enumerable, again you need to ensure the types are IEnumerable<int>

other options would be to hard code the keys as Tim is suggesting or pull out of linq and use a loop

Upvotes: 2

Tim Schmelter
Tim Schmelter

Reputation: 460038

You could use this approach using a Lookup<TKey, TElement>:

var evenOddLookup = numbers.ToLookup(i => i % 2);
string result = String.Join(",", evenOddLookup[1].Reverse().Concat(evenOddLookup[0]));

If you don't want a string but an int[] as result:

int[] result = evenOddLookup[1].Reverse().Concat(evenOddLookup[0]).ToArray();

Upvotes: 1

Hari Prasad
Hari Prasad

Reputation: 16956

You could do something like this.

    var number = string.Join(",",
                    Enumerable.Range(0, 1000)
                            .GroupBy(i => i % 2)            // Separate even/odd numbers 
                            .OrderByDescending(x=>x.Key)    // Sort to bring odd numbers first.
                            .SelectMany(x=> x.Key ==1?      // Sort elements based on even or odd. 
                                            x.OrderByDescending(s=>s) 
                                          : x.Where(s=> s!=0).OrderBy(s=>s))
                            .ToArray());        

   string output = string.Format("[{0}]", number);

Check this Demo

Upvotes: 0

Related Questions