3

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.

klappvisor
  • 1,099
  • 1
  • 10
  • 28

5 Answers5

3

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);
Pidon
  • 275
  • 1
  • 6
  • With `SelectMany` I could not make it ordered when odds go first, so I guess `Aggregate` is needed to be used... – klappvisor Jun 20 '16 at 10:45
  • 2
    You can order it before doing the SelectMany with `OrderBy` or `OrderByDescending`. I added it to the answer. – Pidon Jun 20 '16 at 11:19
2

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

MikeT
  • 5,398
  • 3
  • 27
  • 43
1

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();
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
  • Lookup is interesting option, just need to keep in mind the [difference](http://stackoverflow.com/a/13739501/793874) between them – klappvisor Jun 20 '16 at 09:44
  • 1
    @klappvisor: a lookup is like a dictionary which returns always `IEnumerable`(empty if not available). So it has very fast lookup performance and you can use it without querying the datasource again(so an in-memory collection) – Tim Schmelter Jun 20 '16 at 10:04
0

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

Hari Prasad
  • 16,716
  • 4
  • 21
  • 35
0

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]
Salah Akbari
  • 39,330
  • 10
  • 79
  • 109
  • good spot pointing out that the original question was overcomplicated for the stated task, but the OQ was how to concat the results of a group by – MikeT Jun 20 '16 at 09:59
  • @MikeT...Yes. Just see the OP's comment (*I want `[1,2,3,4,5,6,7,8]` to be converted into `[7,5,3,1,2,4,6,8]`*) I think this is all the OP wants and if so simply using `OrderBy` should do what he wants. – Salah Akbari Jun 20 '16 at 10:15
  • @S.Akbari @MikeT alternative solution to do it via `GroupBy` is also interesting, not sure ordering is most optimal though... – klappvisor Jun 20 '16 at 10:37
  • @klappvisor...Why not optimal? If the answer's result is what you want please note it just use a **simple** `OrderBy` without any further method and grouping and concatenating and... – Salah Akbari Jun 20 '16 at 10:41
  • @klappvisor Finally you accepted an answer which it also used OrderByDescending. Could you please explain why **grouping** and **ordering** should be more optimal than a simple `OrderBy` solution as I mentioned here? I'm just curious. – Salah Akbari Jun 22 '16 at 16:56
  • @S.Akbari several reasons: 1) to match title of the question, example with numbers was an illustration to the code in the question, 2) I always find it hard to understand code with multiple ordering (there are three here) in comparison with accepted answer. 3) accepted answer got more upvotes on the moment of accepting. But thanks for your answer! – klappvisor Jun 22 '16 at 19:33