3

I have a collection of Products in a list (List<Product> ) where product holds id, name and price.

If I would order the list in a descending way based on price, is there a one liner or extensionmethod that allows me to insert a new product in the correct position of the list?

public class Product
{
   public int Id {get; set;}
   public string Name {get; set;}
   public int Price {get; set;}   // assume only whole integers for price
}

public class Main()
{
   List<Product> products = new();
   products.Add(new Product(id= 1, Name="Product1", Price=10 };
   products.Add(new Product(id= 2, Name="Product2", Price=15 };
   products.Add(new Product(id= 3, Name="Product3", Price=11 };
   products.Add(new Product(id= 4, Name="Product4", Price=20 }; 


   products = products.OrderByDescending(prd => prd.Price).ToList();

  var newProduct = new({id = 5, Name="new product", Price = 17})

   // Is there an short solution available that allows me to insert a new product with
   // price = 17 and that will be inserted between products with price 15 and 20?
   // Without repeatedly iterating over the list to find the one lower and the one higher 
   // than the new price and recalculate the index...
   var lastIndex = products.FindLastIndex(x => x.Price >= newProduct.Price);
   products.Insert(lastIndex + 1, p5);     
}

Edit for Solution: I upvoted Tim Schmelter's answer as the most correct one. It is not a single line, as it requires a custom extension method, but I think a single line solution isn't available. Adding it and do a OrderByDescending() works, and is simple, but then depends on the OrderByDescending() statement for the rest of the code...

CMorgan
  • 645
  • 2
  • 11
  • 33
  • What do you mean by "correct position of the list"? – Roman Ryzhiy Nov 28 '22 at 16:54
  • 2
    Just call `products = products.OrderByDescending(prd => prd.Price).ToList();` after `.Add` or yes, use `SortedList`. – Roman Ryzhiy Nov 28 '22 at 16:55
  • If the sorting is just for the UI, I would not do this here in the model, but rather where it is needed (e.g. using a CollectionViewSource if you're using WPF). – Klaus Gütter Nov 28 '22 at 17:01
  • 1
    "public class Main" ?? If you provide code please provide compiling code. – Tim Schmelter Nov 28 '22 at 17:02
  • 1
    Does this answer your question? [How to insert item into list in order?](https://stackoverflow.com/questions/12172162/how-to-insert-item-into-list-in-order) – Orace Nov 28 '22 at 17:22

2 Answers2

6

You can use a SortedList<TKey, TValue>:

SortedList<int, Product> productList = new();
var p = new Product{ Id = 1, Name = "Product1", Price = 10 };
productList.Add(p.Price, p);
p = new Product { Id = 2, Name = "Product2", Price = 15 };
productList.Add(p.Price, p);
p = new Product { Id = 3, Name = "Product3", Price = 11 };
productList.Add(p.Price, p);
p = new Product { Id = 4, Name = "Product4", Price = 20 };
productList.Add(p.Price, p);

p = new Product { Id = 5, Name = "Product5", Price = 17 };
productList.Add(p.Price, p);

foreach(var x in productList) 
    Console.WriteLine($"{x.Key} {x.Value.Name}");

outputs:

10 Product1
11 Product3
15 Product2
17 Product5
20 Product4

Edit: Note that it doesn't allow duplicate keys, so like a dictionary. You could solve it by using a SortedList<int, List<Product>>. For example with this extension method:

public static class CollectionExtensions
{
    public static void AddItem<TKey, TValue>(this SortedList<TKey, List<TValue>> sortedList, TValue item, Func<TValue, TKey> getKey)
    {
        TKey key = getKey(item);
        if (sortedList.TryGetValue(key, out var list))
        {
            list.Add(item);
        }
        else
        {
            sortedList.Add(key, new List<TValue> { item });
        }
    }
}

Usage:

SortedList<int, List<Product>> productLists = new();
productLists.AddItem(new Product { Id = 1, Name = "Product1", Price = 10 }, p => p.Price);
productLists.AddItem(new Product { Id = 2, Name = "Product2", Price = 10 }, p => p.Price);
productLists.AddItem(new Product { Id = 3, Name = "Product3", Price = 20 }, p => p.Price);
productLists.AddItem(new Product { Id = 4, Name = "Product4", Price = 20 }, p => p.Price);
productLists.AddItem(new Product { Id = 5, Name = "Product5", Price = 15 }, p => p.Price);

foreach (var x in productLists) 
    Console.WriteLine($"{x.Key} {string.Join("|", x.Value.Select(p => p.Name))}");

outputs:

10 Product1|Product2
15 Product5
20 Product3|Product4
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
1

You could calculate the position of the new element before adding it to the list, and then use List.Insert.

Manuel Fabbri
  • 542
  • 1
  • 7
  • 16
  • If performance matters and the list is guaranteed to be sorted at any time, [List.BinarySearch](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1.binarysearch) will be useful. – Klaus Gütter Nov 28 '22 at 17:33
  • BinarySearch apply to items, while OP need to sort by a property of the items. – Orace Nov 28 '22 at 17:34
  • 1
    @Orace There is an overload accepting a custom comparer, so this is not a restriction – Klaus Gütter Nov 28 '22 at 17:36
  • I think this is what I do in the last two lines of the code example? – CMorgan Nov 28 '22 at 18:08