Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to perform a LINQ aggregate over a collection of one type to a different result type?

Tags:

c#

linq

aggregate

I would like to use a single LINQ aggregate over a collection of (latitude, longitude) pairs and produce two (latitude, longitude) pairs:

public Location {
   public double Latitude;
   public double Longitude;
}

List<Location> border = ...;

I can easily obtain a minimum (latitude, longitude) pair by:

var minBorder =  border.Aggregate( new Location()
                                 { Latitude = double.MaxValue, Longitude = double.MaxValue },
                                 (current, next) =>
                                   new Location()
                                   {
                                     Latitude = (next.Latitude < current.Latitude) ? next.Latitude : current.Latitude,
                                     Longitude = (next.Longitude < current.Longitude) ? next.Longitude : current.Longitude
                                   }) ;

If possible, I would like to use a single aggregate to return two Locations; a minimum (latitude, longitude) pair and a maximum (latitude, longitude) pair instead of one.

If I declare a class for results:

public class BorderBounds {

   public double MinLatitude;
   public double MinLongitude;

   public double MaxLatitude;
   public double MaxLongitude;
}

and modify the aggregate:

var borderBounds =  border.Aggregate( new Location()
                                 { Latitude = double.MaxValue, Longitude = double.MaxValue },
                                 (current, next) =>
                                   new BorderBounds()
                                   {
                                    ...
                                   }) ;

the (current, next) parameters are assumed to be of type BorderBounds instead of Location.

Is there a way to construct such an aggregate? Would it be best to simply convert this to a foreach?

like image 511
Doug Kimzey Avatar asked Jan 05 '18 16:01

Doug Kimzey


2 Answers

You can do it. I would suggest making the bounds mutable, or making a mutable bounds builder that can create a bounds object afterwards, just to save on the unnecessary memory allocation:

locations.Aggregate(new Bounds(), (bounds, location) =>
        {
            if (bounds.MinLat > location.Latitude) bounds.MinLat = location.Latitude;
            if (bounds.MaxLat < location.Latitude) bounds.MaxLat = location.Latitude;
            if (bounds.MinLon > location.Longitude) bounds.MinLon = location.Longitude;
            if (bounds.MaxLon < location.Longitude) bounds.MaxLon = location.Longitude;
            return bounds;
        });

And the classes

internal class Location
{
    public double Latitude { get; set; }
    public double Longitude { get; set; }
}

internal class Bounds
{
    public Bounds()
    {
        MinLat = double.MaxValue;
        MaxLat = double.MinValue;
        MinLon = double.MaxValue;
        MaxLon = double.MinValue;
    }

    public double MinLat { get; set; }
    public double MaxLat { get; set; }
    public double MinLon { get; set; }
    public double MaxLon { get; set; }
}
like image 167
Adam Brown Avatar answered Oct 18 '22 04:10

Adam Brown


border
    .Aggregate(new BorderBounds() 
             { MinLatitude = double.MaxValue, 
               MinLongitude = double.MaxValue, 
               MaxLongitude = double.MinValue, 
               MaxLatitude = double.MinValue }, 
               (current, next) => new BorderBounds {
                   MinLatitude = next.Latitude < current.MinLatitude ? next.Latitude : current.MinLatitude,
                   MinLongitude = next.Longitude < current.MinLongitude ? next.Longitude : current.MinLongitude,
                   MaxLatitude = next.Latitude > current.MaxLatitude ? next.Latitude : current.MaxLatitude,
                   MaxLongitude = next.Longitude > current.MaxLongitude ? next.Longitude : current.MaxLongitude
                } 
     ); 

The return type of an Aggregate function is the same as the seed passed in, and not of the collection itself.

I'm assuming you're constructing something like a bounding box here.

like image 22
Jonathon Chase Avatar answered Oct 18 '22 03:10

Jonathon Chase