0

I have written a following code which basically groups my sales by days and then I show them to users on graph like following:

   var DailyGraph = userStores.ToList().GroupBy(x => x.TransactionDate.Value.DayOfWeek).Select(pr => new { Day = pr.Key.ToString(), Sales = pr.Sum(x => x.QuantitySoldTransaction) });
   var dayIndex = new List<string> { "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY" };
   ViewBag.DailyGraph = DailyGraph.OrderBy(e => dayIndex.IndexOf(e.Day.ToUpper())).ToList();

Okay so what happens here, in first line I group all of the available sales by day of the week parameter which is Enumeration... Then I create a list of strings with exact day names like the Enumeration , I compare them so that I can order them in exact line as days go (from Monday to Sunday)...

My problem here is now that if user didn't had sales in specific days... Let's say Monday and Thursday he didn't have sales... How could I now add these missing days to my list here ? I'm a little bit confused on how do to that since i'm working with enumeration...

So the current output would be:

Tuesday    5 sales
Wednesday  9 sales
Friday     4 sales
Saturday   13 sales
Sunday     5 sales

And the desired output is:

Monday     0 sales // Add missing day enumeration and assign 0 value
Tuesday    5 sales
Wednesday  9 sales
Thursday   0 sales // Add missing day enumeration and assign 0 value
Friday     4 sales
Saturday   13 sales
Sunday     5 sales

Can someone help me out?

Dan , something like this?

for (int i = 0; i < DailyGraph.Count(); i++)
                    {
                        if (!dayIndex.Contains(DailyGraph[i].Day.ToUpper()))
                        {
                            DailyGraph.Add(new HourlyGraph { Day = dayIndex.ElementAt(i), Sales = 0 });
                        }
                    }
User987
  • 3,663
  • 15
  • 54
  • 115
  • 1
    Personally I'd just write a for loop and use an if statement within checking if there are results for a given day. Linq is great but it shouldn't be used at the expense of readibility/maintenance. – lazarus Mar 03 '17 at 22:27
  • @danm could you show me exactly what you mean ? And if it works so that I may accept ur answer... =) – User987 Mar 03 '17 at 22:28
  • @danm a you mean like for loop after i have them grouped ? – User987 Mar 03 '17 at 22:28
  • 1
    Yes, then loop over your enum and if you don't have a value from your linq just add a zero for the given day. – lazarus Mar 03 '17 at 22:29
  • 1
    Note that you really do not need your `dayIndex` list - you can just order them by the enum (which by default starts `Sunday`, but you can change that for your culture) –  Mar 03 '17 at 22:31
  • @StephenMuecke very good point as well =D – User987 Mar 03 '17 at 22:32
  • @StephenMuecke I've posted the code based on Dan's answer, something like that ? – User987 Mar 03 '17 at 22:32
  • 1
    Not very elegant, but looks like it would work. –  Mar 03 '17 at 22:39
  • 1
    Using a `.ToLookup()` would probably be the most efficient way to do this (and in far less code) –  Mar 03 '17 at 23:18

4 Answers4

2

You could add missing day on the end, something like:

DailyGraph.AddRange(
   dayIndex.Except(DailyGraph.Select(t => t.Day)).Select(r => new { Day = r, Sales = 0 }));
Maksim Simkin
  • 9,561
  • 4
  • 36
  • 49
2

First I would not use a string as your key, since this might not work if someone isn't using English.

You could do something like this:

var dayIndex = new List<DayOfWeek> { DayOfWeek.Monday, DayOfWeek.Tuesday, /* etc */   };
var DailySales = dayIndex.Select(d => new { Day = d, Sales =
     userStores.Where(x => x.TransactionDate.Value.DayOfWeek == d)
     .Sum(u => u.QuantitySoldTransaction) }).ToList();
smead
  • 1,768
  • 15
  • 23
  • 1
    The down-side to this approach is that if `userStores` is backed by a database, you're doing a separate round-trip for each day of the week. – StriplingWarrior Mar 03 '17 at 22:39
  • 1
    That's true. I guess you could work in a GroupBy statement beforehand as in the OP's code to avoid that. – smead Mar 03 '17 at 22:42
  • 2
    The main point here is to use `dayIndex.Select(...)`, which ensures you have exactly one bin for each day of the week. – smead Mar 03 '17 at 22:42
  • Guys userStores is already a materialized list so it' should be fine to do this no ? – User987 Mar 03 '17 at 22:44
  • 1
    Yeah, I'd capture the calculated grouped `Sum` values in a Dictionary or Lookup first, then have my last step use `dayIndex.Select(...)` to get the values from that data structure, but default to zero if they're not there. – StriplingWarrior Mar 03 '17 at 22:44
  • 1
    @User987: Yeah, if you know that your source is materialized in memory, it should be fine. You're still technically doing a bit more work than you need to with the `Where()` clause, but it's bounded to 7 days so you won't likely notice the performance difference. – StriplingWarrior Mar 03 '17 at 22:46
  • @StriplingWarrior yes good point, I'll use this method, it's doing quite good , I don't see any changes in performance =) – User987 Mar 03 '17 at 22:49
2

Get all available days of the week and do a left outer join with the daily graph that you calculated:

var DailyGraph = userStores.ToList().GroupBy(x => x.TransactionDate.Value.DayOfWeek).Select(pr => new { Day = pr.Key, Sales = pr.Sum(x => x.QuantitySoldTransaction) });

from day in Enum.GetValues(typeof(DayOfWeek)).Cast<DayOfWeek>()
from graph in DailyGraph.Where(g => g.Day == day).DefaultIfEmpty()
select new 
{
   Day = day.ToString(),
   Sales =  graph == null ? 0 : graph.Sales 
}
Alex Art.
  • 8,711
  • 3
  • 29
  • 47
1

You can use a .ToLookup() clause, and note that your list of day names is not really necessary assuming your view model is

public class HourlyGraph
{
    public DayOfWeek Day { get; set; }
    public int Sales { get; set; }
}

The query is

var lookup = userStores.ToLookup(x => (int)x.TransactionDate.DayOfWeek);
ViewBag.DailyGraph = from day in Enumerable.Range(0, 7)
                     select new HourlyGraph
                     {
                         Day = (DayOfWeek)day,
                         Sales = lookup[day].Sum(x => x.QuantitySoldTransaction)
                     };

Note that the records will be printed in order Sunday through to Saturday

  • Hey Stephen, thanks for the reply, I thought you got busy with other things. What an amazing reply =) !!! – User987 Mar 04 '17 at 10:08
  • Stephen, can I order them in a fashion like it's done in general (Monday to Sunday) ? Maybe just a simple order by since enumerations are integer numbers actually, no? =) – User987 Mar 04 '17 at 10:09
  • 1
    You could do `from day in new int[]{ 1, 2, ...6, 0 }` (Sunday = 0, Monday = 1, etc). Another alternative is to set the `DateTimeFormatInfo.FirstDayOfWeek` of the server culture –  Mar 04 '17 at 10:12
  • Good point, what'cha think that I simply take Sunday from first position in collection and place it on last one ? xD – User987 Mar 04 '17 at 10:15
  • Stephen, how would I do this alternative way, to set the DateTimeFormatInfo.FirstDayOfWeek? – User987 Mar 04 '17 at 10:15
  • 1
    That's what the code in my previous comment will do :) - `1` is the first and `0` is the last number in the array. –  Mar 04 '17 at 10:17
  • You're the king mate ! =) I could learn quite a lot from you... =D How long do you have experience in C# and .NET in general? I'd say 10 + years? =D – User987 Mar 04 '17 at 10:21
  • 1
    Various ways - for example you can get the current culture using `Thread.CurrentThread.CurrentCulture` and update it (Also refer [docs](https://msdn.microsoft.com/en-us/library/system.globalization.datetimeformatinfo.firstdayofweek(v=vs.110).aspx)) –  Mar 04 '17 at 10:22
  • Well, the goal for me is clear then, reach to the level where you are at currently =D ... Still got then some 6-7 years left haha,... =D – User987 Mar 04 '17 at 10:32