Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LINQ Aggregate with Sub-Aggregates

Tags:

c#

linq

Using a semi-complex structure, I am trying to 'combine' several objects into one using the Linq Aggregate method (though if there is a better way, I am open to ideas).

Here is my basic class design.

class Aspect {
  string Name { get; set; }
}
class Arrangement {
  Aspect Aspect { get; set; }
  IList<Aperture> Apertures { get; set; }
  IList<Step> Steps { get; set; }
}

class Step {
  int Rank { get; set; }
  int Required { get; set; }
}
class Aperture {
  string Name { get; set; }
  int Size { get; set; }
}

Basically, I am trying to aggregate the entire hierarchy of an IEnumerable<Arrangement> and keep everything on the base level, but where things can appropriately overwrite, I want to overwrite them.

Update

I want to get all Arrangements that share the same Aspect.Name, and get a complete list of Steps, overwriting lower level Steps where higher level Arrangements have the same Rank with a different Required value.

So take for instance...

var list = new List<Arrangement>{
    new Arrangement{
      Aspect = Aspects.Named("One"),
      Steps = new List<Step>{
            new Step {
               Rank = 1,
               Required = 2
            },
            new Step {
               Rank = 2,
               Required = 4
            }
        }
    },
    new Arrangement{
      Aspect = Aspects.Named("One"),
      Steps = new List<Step>{
            new Step {
               Rank = 1,
               Required = 3
            }
        }
    }
  }

When aggregated properly, it should come out to look like ...

Arrangement
 - Aspect
    - Name : One
 - Steps 
    - Rank : 1
    - Required : 3
    - Rank : 2
    - Required : 4

I have attempted to use Distinct and Aggregate and it just isn't getting me anywhere. I keep ending up not getting one list or the other. Can anyone help with this?

Update

Here is an example of my current aggregation.

public static Layouts.Template Aggregate(this IList<Layouts.Template> source) {
            return source.Aggregate(
                source.First(),
                (current, next) => new Layouts.Template {
                    Apertures = (current.Apertures.Concat(next.Apertures).Distinct().ToList()),
                    Arrangements = (current.Arrangements.Concat(next.Arrangements).Distinct().ToList()),
                    Pages = (current.Pages.Concat(next.Pages).Distinct().ToList())

    });
        }

My problem is that I'm having a lot of trouble wrapping my head around how to do this at all, much less in one expression. I'm not unwilling to use multiple methods, but if I could encapsulate it all, it would be really useful. I am fascinated by LINQ in general and I really want to get my head around this.

Update 2

The other collection, Apertures, will work in a similar manner, but it is unrelated to the Steps. Simply two different arrays I must do the same thing to, but they have nothing in common with one another. Learning how to do this with one will give me the knowledge to do it with the other.

like image 735
Ciel Avatar asked May 23 '26 08:05

Ciel


2 Answers

If there's no correlation between steps and apertures you can do this:

var result = new Arrangement{
    Steps = list.SelectMany(arrangement => arrangment.Steps)
                .GroupBy(step => step.Rank)
                .Select(l => l.Last())
                .OrderBy(step => step.Rank)
                .ToList()),
}

If there is you'll need to combine the two somehow. If steps index into apertures then you can use something similar.

like image 94
Talljoe Avatar answered May 24 '26 23:05

Talljoe


After your updates:

var query = arrangements
    .GroupBy(a => a.Aspect.Name)
    .Select(g => 
    new Arrangement
    { 
        Steps = ga.SelectMany(a => a.Steps)
                  .GroupBy(s => s.Rank)
                  .Select(gs => gs.Last()),
        Aspect = ga.First().Aspect
    });

This will create output as in your example.

Now, how to merge it with your current aggregation method? As to my understanging, you want to create one big layout from all current layout contents (including arrangements, pages, etc)?

You don't need aggregate at all, just split it into 3 LINQ queries:

// get all arrangements object from all layouts [flattening with SelectMany]
var arrangements = source.SelectMany(s => s.Arrangements);
// and filter them
var filteredArrangements = // enter larger query from above here         

// repeat the same for Apertures and Pages

... 

// and return single object
return new Layouts.Template 
       {
           Apertures = filteredApertures,
           Arrangements = filteredArrangements,
           Pages = filteredPages
       };
like image 21
k.m Avatar answered May 24 '26 22:05

k.m



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!