-2

How can I get the object from a list of objects based on price? In the below example, I have a list of pharmacy where I am trying to compare price among pharmacies and pick one with the best price.

i.e. for "NyQuil", CVS is giving me lower price, so I want to pick that. How can I do that? I am okay with LINQ or for..loop

CODE

    public InventoryItem(string drug, decimal cost) {
      this.Drug = drug;
      this.Cost = cost;
    }

    public class Pharmacy {
        public string Name {get; set; }
        public List<InventoryItem> Inventory {get; set; }
        public decimal? EstimatedOrderItemCost { get; set; }
    
        public Pharmacy(string name, List<InventoryItem> inventories, decimal? estimatedOrderItemCost = null) {
            this.Name = name;
            this.Inventory = inventories;
            this.EstimatedOrderItemCost = estimatedOrderItemCost;
        }
    }

var pharmacyList = new List<Pharmacy> {
            cvsPharmacy, walgreensPharmacy
        };

// Option-1;:
// This gives me cost and drug name only, index[2] results from screen shot
 var test = pharmacyList.Select(x => x.Inventory.Where(y => y.Drug.ToLower() == item.Drug.ToLower()));

// Option-2:
// This gives me ""At least one object must implement IComparable." message but no error.
var test = pharmacyList.Select(x => x.Inventory.Min()).Where(y => y.Drug.ToLower() == item.Drug.ToLower());

INPUT

enter image description here

GThree
  • 2,708
  • 7
  • 34
  • 67
  • `x.Inventory.Min()` This is a problem. Linq has no way to understand how to interate "Give me the minimum inventory" because the "minimum of a set of inventories" is not a well defined concept. How is one inventory less then or greater then another? If the answer is Cost, then you need to tell it that either with one of the overloads to `.Min` that accepts a function to select the bit you want to use for the comparison, or for the type to implement `IComparable` so that it can use that as the basis for the comparisons. – asawyer May 14 '21 at 21:10

1 Answers1

0

How can I get the object from a list of objects based on price?

With LINQ we can use the .Where() method to construct an IEnumerable object that is filled by iterating every element of a collection and running some provided function on each element, if the function returns true then the element is added to the result, if it's false it's ignored.

With that we can 'search', if you will, a list.

Let's find all the pharmacies that contain the drug we're looking for

// define an easier to read function that will tell is if an inventory has a drug
bool ContainsDrug(string DrugName, IEnumerable<InventoryItem> Inventory)
{
    foreach(var item in Inventory)
    {
        if(item.ToUpper().Equals(DrugName))
        {
             return true;
        }
    }
    return false;
}

// get a enumerable that tells us which pharmacies have the drug
var pharmaciesContainingNyQuil = pharmacyList.Where(pharmacy => ContainsDrug("NyQuil", pharmacy.Inventory));

Now that we have all the pharmacies that contain the drug we should sort them by the price of that drug.

For that we will need to use 2 different LINQ commands.

The .Select() command iterates over the enumerable and creates a NEW enumerable with a NEW type based on whatever you return from the function you give the method.

For example to convert a int[] to a string[] we can use the select command.

It will iterate through the int[] and run the function string UnamedLamda(int num) return num.ToString(); on each element and build 'a new enumerable' that only contains strings.

var strings = new int[]{1,2,3,4}.Select( num => num.ToString());

// outputs
strings = {"1","2","3","4"}

We can use that to convert all the pharmacies into an enumerable object that are a bit easier to sort.

To sort we can use the .Sort() System.Collections.Generic command. It's actually fairly simple. We provide .Sort() with a lambda expression just like .Where() and .Select() but this time we give it two parameters left and right(they don't have to be named that way). System.Collections.Generic then iterates through the enumerable and compares each left with each right and sorts them that way.

For example, if we wanted to sort a list of strings by the length of the strings we could use:

List<string> strs = {"1","two", "three", "four"};

// sort the list
strs.Sort((left,right) => left.Length.CompareTo(right.Length));

// this returns
{"1","two","four", "three"}

If we use these two commands we can make a new enumerable that is super easy to sort

decimal FindPrice(string Drug, IEnumerable<InventoryItem> Inventory)
{
    foreach(var item in Inventory)
    {
         if(item.ToUpper().Equals(Drug))
         {
             return item.Cost;
         }
    }
}

var pharmaciesInEasyToSortTuples = pharmaciesContainingNyQuil.Select(pharmacy => (pharmacy, FindPrice("NyQuil",pharmacy.Inventory)));

// sort the pharmacies

var sortedPharmacies = pharmaciesInEasyToSortTuples.ToArray().Sort((left,right)=>left.item2.CompareTo(right.item2));

// return the lowest price
return sortedPharmacies?.FirstOrDefault().pharmacy ?? default;

Now if we put it all together we can get a final method that does what you want it to do, it could look something like this:

Pharmacy FindDrugAtPharmacyWithLowestPrice(string Drug, IEnumerable<Pharmacy> Pharmacies)
{
    // we should check every pharmacy
    // we should check if the pharmacy has the drug
    // after we have all the pharmacies that contain the drug, sort them by price and grab the lowest one,

    // get the pharmacies that have the drug
    string name = Drug.ToUpper();


    // Pharmacies.Where -> looks through the pharmacies
    // pharmacy => pharmacy.Inventory.Select(x => x.Drug.ToUpper()) -> creates a List<string>(not really) that we can check to see if it contains the drug
    var pharmaciesContainingTheDrug =
        // search the pharmacies
        Pharmacies.Where(
            pharmacy =>
                // convert the inventory to a list of strings
                pharmacy.Inventory.Select(x => x.Drug.ToUpper())
                    // check the inventory for the drug
                    .Contains(name));

    if (pharmaciesContainingTheDrug?.Count() is null or 0)
    {
        // no pharmacies containing drug
        return default;
    }

    // now we have a enumerable that has all the pharmacies that contain the drug
    // sort by the lowest price
    // first convert to a more usable format so we can sort

    decimal GetPriceInList(string Drug, IEnumerable<InventoryItem> Inventory)
    {
        return Inventory.Where(x => x.Drug.ToUpper() == Drug)?.FirstOrDefault()?.Cost ?? default;
    }

    var sortedPharmacies = pharmaciesContainingTheDrug.Select(pharmacy => (pharmacy, GetPriceInList(name, pharmacy.Inventory)));

    // don't bother continuing if for some reason we couldn't construct the enumerable tuples
    if (sortedPharmacies?.Count() is null or 0)
    {
        return default;
    }

    // actually sort the pharmacies
    sortedPharmacies.ToList().Sort((left, right) => left.Item2.CompareTo(right.Item2));

    return sortedPharmacies?.FirstOrDefault().pharmacy ?? default;
}
DekuDesu
  • 2,224
  • 1
  • 5
  • 19