Yoda
Yoda

Reputation: 18068

How to efficiently filter elements in the List based on values in another list

I have list of cars and list of rentals of those cars. Every rental has DateTime From and DateTime To and Car Car properties.

I would like to exclude from list of cars, those cars which are unavailable at the chosen period. The beginning and end of this period are values of two DateTimePickers.

I achieved my goal by this code:

  public void FilterAvailableCars() {             
            List<Car> cars = ObjectPlus.Objects[typeof(Car)].Select(o => (Car)o).ToList();               
            List<Rental> rentals = ObjectPlus.Objects[typeof(Rental)].Select(r => (Rental)r).ToList();          
            foreach (var rent in rentals) {
                if (fromDateTimePicker.Value < rent.To && rent.From < toDateTimePicker.Value) {
                    cars.Remove(rent.Car);
                }            
            }
            carListBox.DataSource = cars;               
        }

I mixed LINQ and for loops, but I would like to know how pure and efficient LINQ solution would look like.

Upvotes: 0

Views: 88

Answers (3)

Ivan Stoev
Ivan Stoev

Reputation: 205589

The most efficient way usually is to correlate the data using join (or antijoin like in your case):

var rentals = ObjectPlus.Objects[typeof(Rental)].Cast<Rental>()
    .Where(rent => fromDateTimePicker.Value < rent.To && rent.From < toDateTimePicker.Value);
carListBox.DataSource = 
    (from car in ObjectPlus.Objects[typeof(Car)].Cast<Car>()
     join rent in rentals on car equals rent.Car into carRentals
     from carRent in carRentals.DefaultIfEmpty()
     where carRent == null
     select car).ToList();

Upvotes: 1

Sinatr
Sinatr

Reputation: 21999

Few fixes:

// filter rentals within entered date
var rentals = ObjectPlus.Objects[typeof(Rental)].Cast<Rental>()
    .Where(rent => fromDateTimePicker.Value < rent.To && rent.From < toDateTimePicker.Value);
// filter rented cars 
carListBox.DataSource = ObjectPlus.Objects[typeof(Car)].Cast<Car>()
    .Where(car => !rentals.Any(rent => rent == car)).ToList();

Use Cast/OfType and do filtering with single Where (creating list and removing items from it as you do is kind of inefficient).

I don't know how efficient is Except, if you don't instantiate query result (using ToList()) then it should be same efficient.

It may be more efficient (depending on number of items of Rental and Car) to check for cars inside rental's Where.

Upvotes: 1

cuongle
cuongle

Reputation: 75306

First, you select all the rental cars based your condition:

  var rentalCars = rentals.Where(rent => 
                  fromDateTimePicker.Value < rent.To && rent.From < toDateTimePicker.Value)
      .Select(r => r.Car);

Then, use Except method to get the result:

  carListBox.DataSource = cars.Except(rentalCars);

Upvotes: 1

Related Questions