1

I have a list:

List<BtnCountViews> btnCountViewsList;

The BtnCountViews class looks like this:

public class BtnCountViews
{
    public int DayOfYear { get; set; }
    public int BtnCount { get; set; }
    public int Views { get; set; }
}

I have a rather unusual requirement and I am not sure how to go about starting to implement it.

What I would like to do is to fill in the btnCountViewsList with `BtnCountViews for the missing DayOfYear with objects that have a BtnCount of 0 and Views of 0.

To give me a start can anyone tell me how I can find the min and max DayOfYear in the btnCountViewsList. Note I tagged this with LINQ but I'm not sure if this is the best tool to use.

Also would be happy if someone can suggest a way to fill in the missing objects but that's not really the focus of this question as I think I need to find out how to get the min and max first.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
Alan2
  • 23,493
  • 79
  • 256
  • 450
  • 1
    Use `LINQ`, it has methods for **min** and **max** – Markiian Benovskyi Dec 12 '17 at 14:45
  • 1
    Finding min and max is just the beginning - ultimately, OP wants to do something entirely different. Voting to reopen. – Sergey Kalinichenko Dec 12 '17 at 14:51
  • @dasblinkenlight - Thanks. I wasn't sure if I should just ask everything in the question or start off by asking about filling in the dates as I thought that might be too much of a programming question for me to ask on SO. – Alan2 Dec 12 '17 at 14:55

7 Answers7

1

This is working on linqpad:

Int32 max = 0, min = 0;
btnCountViewsList.ForEach(x => {
     min = Math.Min(x.Views, min);
     max = Math.Max(x.Views, max);
});
Giulio Caccin
  • 2,962
  • 6
  • 36
  • 57
1

You can add missing days without finding min and max explicitly:

  • Sort the list by DayOfYear in ascending order (how?)
  • Start a loop index i at the end of the list, and work your way backward; stop when i reaches zero
  • Compare DayOfYear attribute at i and i-1
  • If the two days differ by one, move down to the next i
  • Otherwise insert a new record with DayOfYear set to that of btnCountViewsList[i] minus one.

At the end of this process your list would contain entries for each value of DayOfYear. Here is a sample implementation:

items.Sort((x, y) => x.DayOfYear.CompareTo(y.DayOfYear));
Console.WriteLine("Before: {0}", string.Join(", ", items.Select(x => x.DayOfYear)));
int i = items.Count-1;
while (i > 0) {
    if (items[i].DayOfYear == items[i-1].DayOfYear+1) {
        i--;
    } else {
        items.Insert(i, new BtnCountViews { DayOfYear = items[i].DayOfYear-1 });
    }
}

Demo.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Thanks for the suggestion. When you say "Start a loop index i at the end of the list, and work your way backward;" Do you mean a forEach? I am not sure how to loop through the elements of a list as I assume it is different from an array. – Alan2 Dec 12 '17 at 14:53
  • @Alan2 `foreach` would not work here, because you'd be modifying the list as you iterate over it. I would use a `while` loop and an `int` index defined outside of the loop. – Sergey Kalinichenko Dec 12 '17 at 14:55
  • 1
    @Alan2 you can loop through list just as through array, it also has `[]` property – Markiian Benovskyi Dec 12 '17 at 14:55
  • @MarkBenovsky - Thanks. I didn't know I could address a list with a [ ] like an array. – Alan2 Dec 12 '17 at 14:56
  • @Alan2 [Here](https://msdn.microsoft.com/en-gb/library/system.collections.ilist.item(v=vs.110).aspx) is an example how it works, if you are interested – Markiian Benovskyi Dec 12 '17 at 14:59
  • @MarkBenovsky - Thanks very much ! – Alan2 Dec 12 '17 at 15:11
1

What I would like to do is to fill in the btnCountViewsList with `BtnCountViews for the missing DayOfYear with objects that have a BtnCount of 0 and Views of 0.

My suggestion is that we don't try to find the missing days, we create all:

BtnCountViews[] arr = new BtnCountViews[365]; // or 366?

// suppose DayOfYear begins with 0.
for (int i = 0; i < arr.Length; i++)
{
    arr[i] = new BtnCountViews { DayOfYear = i };
}
foreach (BtnCountViews item in btnCountViewsList)
{
    arr[item.DayOfYear].BtnCount = item.BtnCount;
    arr[item.DayOfYear].Views = item.Views;
}

then arr is what you want.

And if the result should be the btnCountViewsList:

btnCountViewsList.Clear();
btnCountViewsList.AddRange(arr);
skyoxZ
  • 362
  • 1
  • 3
  • 10
1

So the lazy in me says, make a backfill list and use your existing (and gappy) list as a map.

public static IList<BtnCountViews> GetDefaultList()
{
    var defaultList = Enumerable.Range(1, 365).Select(e =>
        new BtnCountViews
        {
            DayOfYear = e,
            BtnCount = 0,
            Views = 0
        }
    ).ToList();

    return defaultList;
}

Iterate through the backfill list and consult the map to see if the DayOfYear value exists as a key, and if not, then add it to the map.

public static IList<BtnCountViews> GetBackFilledList(IList<BtnCountViews> incoming)
{
    var map = incoming.ToDictionary(k => k.DayOfYear, v => v);
    var defaultList = GetDefaultList();

    foreach(var itm in defaultList)
    {
        if (map.ContainsKey(itm.DayOfYear)) continue;

        map.Add(itm.DayOfYear, itm);
    }

    return map.Select(m => m.Value).ToList();
}

Once the iteration is finished, convert the map into a list, which should now consist of the original values + default values for missing DayOfYear entries as well.

return map.Select(m => m.Value).ToList();

Dotnetfiddle of a sample program here: https://dotnetfiddle.net/wSJy56

Is there a more elegant way to do this? Most surely. But this code executes in about 0.011 seconds, which to me is pretty decent so long as you're not calling this functionality over and over again (e.g. you decide to analyze 30 years of data and need to get that done in 0.011 seconds). But then we'd have to be looking more towards parallelism rather than code elegance to solve that can of worms.

Hope this helps...

code4life
  • 15,655
  • 7
  • 50
  • 82
0

Try the following

btnCountViewsList = btnCountViewsList.Where(b => b.BtnCount == 0).Where(v => v.Views == 0).ToList();

If I understood what you were asking, you want to get objects where BtnCount = 0 and Views = 0.

This will select all the objects where Views = 0, and then that IEnumarable will be through another LINQ expression where it only selects the other property that equals to 0.

RedNet
  • 416
  • 5
  • 12
0

The shortest linq way, using an left outer join (LEFT OUTER JOIN in LINQ) and Range

 var result = (from a in Enumerable.Range(0, 365)
                         join lst in btnCountViewsList on a equals lst.DayOfYear into ps
                         from p in ps.DefaultIfEmpty()
                         select (p==null) ? new BtnCountViews() { DayOfYear = a}:p).ToList()
FrankM
  • 541
  • 3
  • 15
0

among the lines of some other responses, but without hard coding the total days of the year as leap years will have 366 days

var range = new 
{
    Start = new DateTime(2017, 1, 1),
    End = new DateTime(2017, 12, 31),
};

var days = Enumerable.Range(range.Start.DayOfYear, range.End.DayOfYear);

var query = from day in days
            from counter in 
            (
                from temp in btnCountViewsList
                where temp.DayOfYear == day
                select temp
            ).DefaultIfEmpty()
            select new BtnCountViews
            {
                DayOfYear = day,
                BtnCount = counter == null ? 0 : counter.BtnCount,
                Views = counter == null ? 0 : counter.Views,
            };

will give you something like

enter image description here

Dan Dohotaru
  • 2,809
  • 19
  • 15