Question

I-m creating a C# WPF application that lets a user take orders (think restaurant/bar).

For simplicity, I'm restricting orders into simple purchases of individual items (you can buy multiple items, like buying 4 beers). Each purchase is logged as a Purchase object:

class Purchase
{
    public int id {get;set;}
    public DateTime time {get;set;}
    public double price {get;set;}
    public int quantity {get;set;}
}

and each purchase gets added to a DataTable.

I want to get the 3 most - and 3 least bought items in a given time and I'm having trouble writing a suitable query for this.

So far what I have is this but it's not working:

 public List<int> GetMostBought()
    {
        DateTime lastMinute = DateTime.UtcNow.Add(new TimeSpan(0, -1, 0)); //one minute ago
        int howMany = 3; // how many items to get.
        var query =
           (from p in purchases.AsEnumerable()
            where p.Field<DateTime>("time")>= lastMinute //purchases made less than a minute ago
            select new { Product = p, Quantity = p.Field<int>("quantity") } into productQty
            group productQty by productQty.Product into pg
            let totalQuantity = pg.Sum(prod => prod.Quantity)
            orderby totalQuantity descending
            select pg.Key).Take(howMany);

        List<int> mostBoughtIDs = new List<int>();
        foreach (DataRow dr in query)
        {
            mostBoughtIDs.Add(Int32.Parse(dr[0].ToString()));
        }
        return mostBoughtIDs;

Any ideas?

Was it helpful?

Solution

var mostBoughtIDs = purchases.AsEnumerable()
                             .Where(r=>r.Field<DateTime>("time")>= lastMinute)
                             .GroupBy(r=>r.Field<int>("id"))
                             .OrderByDescending(g=>g.Sum(r=>r.Field<int>("quantity"))
                             .Select(g=>g.First().Field<int>("id"))
                             .Take(howMany);

OTHER TIPS

taking that into account, my answer is nearly identical to king kings...

var items = 
    purchases.AsEnumerable()
    .Where(e => e.Field<DateTime>("time") >= lastMinute)
    .GroupBy( g => g.Field<int>("id") )
    .OrderByDecending( i => i.Sum( e => e.Field<int>("quantity") ) )
    .Select( g => g.First().Field<int>("id"))
    .Take(3)

You can create a DataTable from a List with a nice little method found here:

public DataTable ConvertToDataTable<T>(IList<T> data)
    {
        PropertyDescriptorCollection properties =
           TypeDescriptor.GetProperties(typeof(T));
        DataTable table = new DataTable();
        foreach (PropertyDescriptor prop in properties)
            table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
        foreach (T item in data)
        {
            DataRow row = table.NewRow();
            foreach (PropertyDescriptor prop in properties)
                row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
            table.Rows.Add(row);
        }
        return table;

    }

public List<int> GetMostAndLeastBought(int howMany){
List<Purchase> purchasesList = new List<Purchase>{
                                            new Purchase{ id = 1, time = DateTime.UtcNow.Add(new TimeSpan(0, 0, -1)), quantity = 1},
                                            new Purchase{id = 1, time = DateTime.UtcNow.Add(new TimeSpan(0, 0, -1)), quantity = 1},
                                            new Purchase{id = 2, time = DateTime.UtcNow.Add(new TimeSpan(0, 0, -1)), quantity = 1},
                                            new Purchase{id = 2, time = DateTime.UtcNow.Add(new TimeSpan(0, 0, -1)), quantity = 1},
                                            new Purchase{id = 3, time = DateTime.UtcNow.Add(new TimeSpan(0, 0, -1)), quantity = 1},
                                            new Purchase{id = 3, time = DateTime.UtcNow.Add(new TimeSpan(0, 0, -1)), quantity = 1},
                                            new Purchase{id = 4, time = DateTime.UtcNow.Add(new TimeSpan(0, 0, -1)), quantity = 1},
                                            new Purchase{id = 4, time = DateTime.UtcNow.Add(new TimeSpan(0, 0, -1)), quantity = 1},
                                            new Purchase{id = 5, time = DateTime.UtcNow.Add(new TimeSpan(0, 0, -1)), quantity = 1},
                                            new Purchase{id = 6, time = DateTime.UtcNow.Add(new TimeSpan(0, 0, -1)), quantity = 1},
                                            new Purchase{id = 7, time = DateTime.UtcNow.Add(new TimeSpan(0, 0, -1)), quantity = 1}                                                                              
                                        };

        DataTable purchases = ConvertToDataTable(purchasesList);

I thought the query syntax also works nicely here for the main query:

 DateTime lastMinute = DateTime.UtcNow.Add(new TimeSpan(0, -1, 0)); //one minute ago  

var query = from p in purchases.AsEnumerable()
            where p.Field<DateTime>("time") >= lastMinute
            group p by p.Field<int>("id") into g                    
            select new {Id = g.Key, TotalQuantity = g.Sum(x => x.Field<int>("quantity"))} into s
            orderby s.TotalQuantity descending, s.Id
            select s.Id;

You can then get most and least at the same if you wanted to at the same time. You want to convert List before doing both because then you can efficiently get the last elements, like so:

var list = query.ToList();

var most = list.GetRange(0, howMany);
var least = list.GetRange(list.Count - howMany, howMany);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top