I am keeping records of data usage daily and want to display out the total data usage between a date range.
The data resets at 10th every month so an example data usage would be like:
Number: 001xxxxxxxx
Date - Data
8th - 100mb
9th - 120mb
10th - 10mb
11th - 40mb
So to get the total data usage between 8th and 11th, it's not possible to take the data at 11th deducted by 8th (40mb-100mb) as it resets at 10th.
I would need something like, 40mb+(120mb-100mb) = 60mb total usage
here is my method that gives the data, date and number into Dictionarys
private void getNumberDatas(List<int> phoneNo, DateTime dateStart, DateTime dateEnd)
{
Dictionary<int, Dictionary<DateTime, float>> d_PhoneNo_DateDataList = new Dictionary<int, Dictionary<DateTime, float>>();
Dictionary<DateTime, float> d_DateTime_Data = new Dictionary<DateTime, float>();
string strConnectionString = ConfigurationManager.ConnectionStrings["xxx"].ConnectionString;
SqlConnection conn = new SqlConnection(strConnectionString);
string sqlcommand = "SELECT xxx FROM xxx WHERE PhoneNo=#PhoneNo AND date BETWEEN #Date1 AND #Date2";
for (int i = 0; i < phoneNo.Count; i++)
{
d_DateTime_Data.Clear();
using (SqlCommand cmd = new SqlCommand(sqlcommand, conn))
{
conn.Open();
cmd.Parameters.AddWithValue("#PhoneNo", phoneNo[i]);
cmd.Parameters.AddWithValue("#Date1", dateStart);
cmd.Parameters.AddWithValue("#Date2", dateEnd);
cmd.ExecuteNonQuery();
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
d_DateTime_Data.Add(DateTime.Parse(reader["Date"].ToString()), float.Parse(reader["Data"].ToString()));
}
conn.Close();
}
d_PhoneNo_DateDataList.Add(phoneNo[i], d_DateTime_Data);
}
}
So to get a data for today would be:
Dictionary<DateTime, float> temp_Datetime_data = new Dictionary<DateTime, float>();
if (d_PhoneNo_DateDataList.TryGetValue("001xxxxxxxx", out temp_Datetime_data))
{
float dataToday;
if (temp_Datetime_data.TryGetValue(DateTime.Today, out dataToday))
{
Console.WriteLine(dataToday.ToString());
}
}
Now my problem is that I do not know how to implement the calculations if let's say the user chooses 20th May 2016 to 30th June 2016
Let me know if it's too confusing, I don't know the best way to explain this
It's not perfect but it will be a good base for your needs:
private static int getData(Dictionary<DateTime, int> values, DateTime start, DateTime stop)
{
// Reduce the scope
var val = values.Where(x => x.Key >= start && x.Key <= stop);
// Create a list of ranges
List<Dictionary<DateTime, int>> listOfMonth = new List<Dictionary<DateTime, int>>
{
new Dictionary<DateTime, int>()
};
var currentMonth = listOfMonth.Last();
int previousValue = int.MinValue;
foreach (KeyValuePair<DateTime, int> keyValuePair in val)
{
// Reset the month
if (keyValuePair.Value < previousValue)
{
currentMonth = new Dictionary<DateTime, int>()
{
{DateTime.Now, 0} // Add placeholder
};
listOfMonth.Add(currentMonth);
}
previousValue = keyValuePair.Value;
currentMonth.Add(keyValuePair.Key, keyValuePair.Value);
}
return listOfMonth.Sum(dictionary => dictionary.Values.Max() - dictionary.Values.Min());
}
With this data:
Dictionary<DateTime, int> dataDictionary = new Dictionary<DateTime, int>()
{
{new DateTime(2016, 8, 4), 20},
{new DateTime(2016, 8, 5), 50},
{new DateTime(2016, 8, 6), 70},
{new DateTime(2016, 8, 7), 90},
{new DateTime(2016, 8, 8), 100},
{new DateTime(2016, 8, 9), 120},
{new DateTime(2016, 8, 10), 10},
{new DateTime(2016, 8, 11), 40},
};
DateTime start = new DateTime(2016, 8, 7);
DateTime stop = new DateTime(2016, 8, 11);
I get 70 (120-90) + (40 - 0)
As I understand it, the the problem can be restated as
Calculate the sum of the differences between the usages, except
On the reset date, use the amount rather than the difference
For example purposes we can use a small usage class
class Usage
{
public Usage(int day, int totalUsage)
{
Day = day;
TotalUsage = totalUsage;
}
public int Day { get; }
public int TotalUsage { get; }
}
and a list of usages to represent the log
private readonly static List<Usage> Log = new List<Usage>
{
new Usage(8,100),
new Usage(9,120),
new Usage(10,10),
new Usage(11,40),
};
We can calculate the total usage for the periods as follows
private void CalcTotalUsage(int startDate, int endDate, int expectedTotal)
{
// I just used ints for the days for demo purposes, dates will not change the calculation below
var records = Log.Where(u => u.Day >= startDate && u.Day <= endDate);
var total = records.Skip(1).Zip(records, (curr, prev) => (curr.TotalUsage > prev.TotalUsage) ? curr.TotalUsage - prev.TotalUsage : curr.TotalUsage).Sum();
Assert.AreEqual(expectedTotal, total);
}
and the tests to check that it works correctly
[TestMethod]
public void CheckTotals()
{
CalcTotalUsage(8, 9, 20);
CalcTotalUsage(8, 10, 30);
CalcTotalUsage(8, 11, 60);
}
Related
I am wondering if anyone can help me, I have a collection of start times and end times for a work shift of a day. the hours can be spread across the day. I am trying group by the hour in day (like 0, 1, 2...23, 24) and show that hour in intervals of 10 minutes or worked or not worked. So, I would like to get the end result like below:
I want to able distinguish between worked and not on a hourly basis, the input provides the worked but I have calculate the not worked, I created a method for handling if a time falls outside a 10 minute interval it will set to the nearest one. Method called DoRounding:
Example:
9 am => 0 - 10 Worked
10 - 20 Not Worked
20 - 30 Worked
30 - 40 Worked
40 - 50 Worked
50 - 60 Worked
The time that fails out of the period can be be handled like so
private static int DoRounding(DateTime date)
{
if (Enumerable.Range(0, 10).Contains(date.Minute))
return 0;
if (Enumerable.Range(10, 20).Contains(date.Minute))
return 20;
if (Enumerable.Range(20, 30).Contains(date.Minute))
return 30;
if (Enumerable.Range(30, 40).Contains(date.Minute))
return 40;
if (Enumerable.Range(40, 50).Contains(date.Minute))
return 50;
return 60;
}
My method to explode the workblock (I was trying to break down the work period into hours here so I could add the missing parts in another method)
public static IEnumerable<Tuple<int, DateTime>> CalculateIntervals(WorkPeriod workBlock)
{
yield return new Tuple<int, DateTime>(workBlock.StartTime.Hour, workBlock.StartTime);
var dateTime = new DateTime(workBlock.StartTime.Year, workBlock.StartTime.Month, workBlock.StartTime.Day, workBlock.StartTime.Hour, workBlock.StartTime.Minute, 0, workBlock.StartTime.Kind).AddHours(1);
while (dateTime < workBlock.EndTime)
{
yield return new Tuple<int, DateTime>(dateTime.Hour, dateTime);
dateTime = dateTime.AddHours(1);
}
yield return new Tuple<int, DateTime>(workBlock.EndTime.Hour, workBlock.EndTime);
}
My attempt at grouping (I want to group into the time slots here to the hour and the intervals such as 1 pm, 0 - 10 minutes and mark it as worked but if an interval was missing from here add it as not worked)
public static void WorkingHourIntervalStrings(List<WorkPeriod> WorkingHours)
{
var output = new List<Tuple<int, DateTime>>();
foreach (var result in WorkingHours.Select(CalculateIntervals))
output.AddRange(result);
output = output.OrderBy(x => x.Item2).ToList();
var test = output.GroupBy(
p => p.Item1,
p => p.Item2.Minute,
(key, g) => new { Worked = key, Minute = g.ToList() });
}
Class
public class WorkPeriod
{
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
}
Calling
var input = new List<WorkPeriod>
{
new WorkPeriod { StartTime = new DateTime(2020, 5, 25, 9, 40, 56), EndTime = new DateTime(2020, 5, 25, 14, 22, 12) },
new WorkPeriod { StartTime = new DateTime(2020, 5, 25, 9, 50, 56), EndTime = new DateTime(2020, 5, 25, 14, 59, 12) },
new WorkPeriod { StartTime = new DateTime(2020, 5, 25, 13, 40, 56), EndTime = new DateTime(2020, 5, 25, 18, 22, 12) },
new WorkPeriod { StartTime = new DateTime(2020, 5, 25, 19, 40, 56), EndTime = new DateTime(2020, 5, 25, 23, 22, 12) }
};
TimeIntervals.WorkingHourIntervalStrings(input);
Possible output structure:
public class Interval
{
public Interval() => Contents = new List<Contents>();
public int Hour { get; set; }
public List<Contents> Contents { get; set; }
}
public class Contents
{
public bool Worked { get; set; }
public int Start { get; set; }
public int End { get; set; }
}
Based on your above explanations I would do the following:
public class Interval
{
public Interval() => Contents = new List<Contents>();
public int Hour { get; set; }
public List<Contents> Contents { get; set; }
}
public class Contents
{
public bool Worked { get; set; }
public int Start { get; set; }
//public int End { get; set; }
public int End => Start + 10;
}
public class WorkPeriod
{
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
}
Look at the Contents class. The End property is autocalculated from the Start one.
Then I would create the following Calculator class:
public class Calculator
{
public bool[] WorkedIntervals = new bool[24 * 6];
private void SetWork(int Hour, int Min)
{
int pos = Hour * 6 + Min / 10;
WorkedIntervals[pos] = true;
}
private void UpdateIntervals(WorkPeriod period)
{
var cur = period.StartTime;
while (cur < period.EndTime)
{
SetWork(cur.Hour, cur.Minute);
cur = cur.AddMinutes(10);
}
}
private void UpdateIntervals(List<WorkPeriod> periods)
{
foreach (var l in periods)
UpdateIntervals(l);
}
public IEnumerable<Interval> CalcIntervals(List<WorkPeriod> periods)
{
var minTime = (from p in periods
select p.StartTime).Min();
var maxTime = (from p in periods
select p.EndTime).Max();
UpdateIntervals(periods);
for(int h=minTime.Hour; h<=maxTime.Hour; ++h)
{
int pos = h * 6;
var intrvl = new Interval() { Hour = h };
for (int m=0; m<=5; m++)
{
if (WorkedIntervals[pos + m])
intrvl.Contents.Add(new Contents() { Start = m * 10, Worked = true });
else
intrvl.Contents.Add(new Contents() { Start = m * 10, Worked = false });
}
yield return intrvl;
}
}
}
The idea is that you have to flatten all your time intervals to an array of 144 boolean values (24*6) that represents if each of this 10 minute time interval has been worked or not. eg. if the 7th index of the array is true then it means that at Hour 1 (hour 0 is in indexes 0-5) the 10-20 min interval has been worked.
Then, on your main function you do the following.
var input = new List<WorkPeriod>
{
new WorkPeriod { StartTime = new DateTime(2020, 5, 25, 9, 40, 56), EndTime = new DateTime(2020, 5, 25, 14, 22, 12) },
new WorkPeriod { StartTime = new DateTime(2020, 5, 25, 9, 50, 56), EndTime = new DateTime(2020, 5, 25, 14, 59, 12) },
new WorkPeriod { StartTime = new DateTime(2020, 5, 25, 13, 40, 56), EndTime = new DateTime(2020, 5, 25, 18, 22, 12) },
new WorkPeriod { StartTime = new DateTime(2020, 5, 25, 19, 40, 56), EndTime = new DateTime(2020, 5, 25, 23, 22, 12) }
};
Calculator ints = new Calculator();
var res = ints.CalcIntervals(input).ToList();
The res list should contain the hour-intervals from the minimum StartTime to the maximum EndTime with their respected sub-lists.
I'd like to reverse a list of objects with a TimeSpan property, which should maintain it's TimeSpan difference when reversing.
To give an example, consider a route from A to D with the following TimeSpans:
(A 12:00), (B 12:15), (C 12:40), (D 13:40).
Between A and B there is a 15 minute difference, between B and C there is a 25 minute difference and so on. I'd like to reverse this list in an efficient manner, where the result list would look like:
(D: 12:00), (C 13:00), (B 13:25), (A 13:40).
My first idea was creating a list of time differences and using that and the start time to create the new objects with the correct times, however I feel like the solution could be better.
Edit: Added my (working) sample solution. Any feedback is appreciated.
private IList<Activity> ReverseActivities(IList<Activity> activities)
{
IList<TimeSpan> timeDifferences = GetTimeDifferences(activities);
IList<Activity> resultList = new List<Activity>();
TimeSpan timeOfDay = activities.First().TimeOfDay;
for (int i = activities.Count - 1; i >= 0; i--)
{
resultList.Add(new Activity(activities[i].Name, timeOfDay));
timeOfDay = timeOfDay.Add(timeDifferences[i]);
}
return resultList;
}
private IList<TimeSpan> GetTimeDifferences(IList<Activity> activities)
{
IList<TimeSpan> timeDifferences = new List<TimeSpan>();
Activity prev = activities.First();
if (activities.Count > 1)
{
foreach (var curr in activities)
{
timeDifferences.Add(curr.TimeOfDay - prev.TimeOfDay);
prev = curr;
}
}
return timeDifferences;
}
Activity looks as follows:
public class Activity
{
public Activity(string name, TimeSpan timeOfDay)
{
this.Name = name;
this.TimeOfDay = timeOfDay;
}
public string Name { get; }
public TimeSpan TimeOfDay { get; }
}
One trick we can use is to have a single loop that finds the corresponding item from the end of the list based on the current index. We can do this like:
for (int i = 0; i < activities.Count; i++)
var correspondingIndex = activities.Count - i - 1;
Notice that:
When i is 0, correspondingIndex is the last index in the array.
When i is 1, correspondingIndex is the second-to-last index in the array.
When i is activities.Count - 1 (the last index), correspondingIndex is 0
Using this trick, we can get the corresponding time differences at the same time as we populate a new list of Activity objects.
Hopefully this code makes it a little clearer:
public static IList<Activity> ReverseActivities(IList<Activity> activities)
{
// If activities is null or contains less than 2 items, return it
if ((activities?.Count ?? 0) < 2) return activities;
// This will contain the reversed list
var reversed = new List<Activity>();
for (int i = 0; i < activities.Count; i++)
{
// Get the corresponding index from the end of the list
var correspondingIndex = activities.Count - i - 1;
// Get the timespan from the corresponding items from the end of the list
var timeSpan = i == 0
? TimeSpan.Zero
: activities[correspondingIndex + 1].TimeOfDay -
activities[correspondingIndex].TimeOfDay;
// The new TimeOfDay will be the previous item's TimeOfDay plus the TimeSpan above
var timeOfDay = i == 0
? activities[i].TimeOfDay
: reversed[i - 1].TimeOfDay + timeSpan;
reversed.Add(new Activity(activities[correspondingIndex].Name, timeOfDay));
}
return reversed;
}
In use, this would look like:
var original = new List<Activity>
{
new Activity("A", new TimeSpan(0, 12, 0)),
new Activity("B", new TimeSpan(0, 12, 15)),
new Activity("C", new TimeSpan(0, 12, 40)),
new Activity("D", new TimeSpan(0, 13, 40))
};
var reversed = ReverseActivities(original);
Here's the output in the debug window (compare original and reversed):
This is quite simple using a bit of TimeSpan maths.
IList<Activity> input = new List<Activity>()
{
new Activity("A", TimeSpan.Parse("12:00")),
new Activity("B", TimeSpan.Parse("12:15")),
new Activity("C", TimeSpan.Parse("12:40")),
new Activity("D", TimeSpan.Parse("13:40")),
};
TimeSpan min = input.Min(x => x.TimeOfDay);
TimeSpan max = input.Max(x => x.TimeOfDay);
IList<Activity> output =
input
.Select(x => new Activity(
x.Name,
x.TimeOfDay.Subtract(max).Duration().Add(min)))
.OrderBy(x => x.TimeOfDay)
.ToList();
That gives me:
I tested this and it works:
DateTime[] times = { new DateTime(2020, 06, 17, 12, 00, 00),
new DateTime(2020, 06, 17, 12, 15, 00), new DateTime(2020, 06, 17, 12, 40, 00),
new DateTime(2020, 06, 17, 13, 40, 00) };
List<DateTime> newTimes = new List<DateTime>();
newTimes.Add(times[0]);
for (int i = 1; i < times.Length; i++) {
DateTime d = newTimes[i - 1].Add(times[times.Length - i] - times[times.Length - i - 1]);
newTimes.Add(d);
}
Using LinkedList:
static void Main(string[] args)
{
var list = new List<Location>
{
new Location{Name = "A", TimeOffset = DateTimeOffset.MinValue.Add(new TimeSpan(12, 0, 0)) },
new Location{Name = "B", TimeOffset = DateTimeOffset.MinValue.Add(new TimeSpan(12, 15, 0)) },
new Location{Name = "C", TimeOffset = DateTimeOffset.MinValue.Add(new TimeSpan(12, 40, 0)) },
new Location{Name = "D", TimeOffset = DateTimeOffset.MinValue.Add(new TimeSpan(13, 40, 0)) },
};
var route = new LinkedList<Location>(list);
WriteToConsole("Before: ", route);
var reversedRoute = Reverse(route);
Console.WriteLine();
WriteToConsole("After: ", reversedRoute);
Console.WriteLine(); Console.ReadKey();
}
public static LinkedList<Location> Reverse(LinkedList<Location> route)
{
LinkedList<Location> retVal = new LinkedList<Location>();
DateTimeOffset timeOffset = DateTimeOffset.MinValue;
var currentNode = route.Last;
while (currentNode != null)
{
var next = currentNode.Next;
if (next == null)
{
// last node, use the first node offset
timeOffset = DateTimeOffset.MinValue.Add(route.First.Value.TimeOffset - timeOffset);
}
else
{
timeOffset = timeOffset.Add(next.Value.TimeOffset - currentNode.Value.TimeOffset);
}
retVal.AddLast(new Location { Name = currentNode.Value.Name, TimeOffset = timeOffset });
currentNode = currentNode.Previous;
}
return retVal;
}
public static void WriteToConsole(string title, LinkedList<Location> route)
{
Console.Write($"{title}: ");
foreach (var i in route)
{
Console.Write($"\t({i.Name}, {i.TimeOffset.Hour:D2}:{i.TimeOffset.Minute:D2})");
}
}
I have a list of objects where each object has a string and a int. Each string is a date in the yyyy-M-d format.
The list can contain dates 30, 90 or 365 days from the first date
So a list of items (of 30 days) would be:
2017-7-25 10
2017-7-24 3
2017-7-23 7
2017-7-22 4
2017-7-21 2
2017-7-20 4
..
2017-6-27 5
2017-6-26 8
I want to group these dates by 5 days such that:
2017-7-21 30
2017-7-16 (Sum of values from 7-16 till 7-20)
and so on.
I cant figure out the lambda for this.
var grouped = from x in list
group x by DateTime.Parse(x.date)/5
select new { date = x.????????, count = x.Sum()}
If you have declared a class like the following:
internal class DayNumber
{
public string Day { get; set; }
public int Number { get; set; }
}
and you have defined a list like the following one:
var list = new List<DayNumber>
{
new DayNumber {Day = "2017-7-25", Number = 10},
new DayNumber {Day = "2017-7-24", Number = 3},
new DayNumber {Day = "2017-7-23", Number = 7},
new DayNumber {Day = "2017-7-22", Number = 4},
new DayNumber {Day = "2017-7-21", Number = 2},
new DayNumber {Day = "2017-7-20", Number = 4},
new DayNumber {Day = "2017-7-19", Number = 5},
new DayNumber {Day = "2017-7-18", Number = 8},
new DayNumber {Day = "2017-7-17", Number = 2},
new DayNumber {Day = "2017-7-16", Number = 3}
};
then you could try something like this:
var grouped = list.Select(item => new
{
Parsed = DateTime.ParseExact(item.Day, "yyyy-M-dd", CultureInfo.InvariantCulture),
Number = item.Number
})
.OrderBy(item => item.Parsed)
.Select((item, index) => new
{
Index = index,
Item = item
})
.GroupBy(item => item.Index / 5)
.Select(gr => new
{
Date = gr.First().Item.Parsed,
Count = gr.Sum(x => x.Item.Number)
})
.ToList();
This will works regardless of date uniqueness and gaps between dates.
Assuming we have class representing your object
public class MyClass
{
public string DateString { get; set; }
public int SomeInt { get; set; }
}
then we have array of this objects
MyClass[] array = new[]
{
new MyClass { DateString = "2017-7-25", SomeInt = 10 },
new MyClass { DateString = "2017-7-24", SomeInt = 3 },
new MyClass { DateString = "2017-7-23", SomeInt = 7 },
new MyClass { DateString = "2017-7-22", SomeInt = 4 },
new MyClass { DateString = "2017-7-21", SomeInt = 2 },
new MyClass { DateString = "2017-7-20", SomeInt = 4 },
new MyClass { DateString = "2017-7-25", SomeInt = 5 },
new MyClass { DateString = "2017-6-26", SomeInt = 8 }
};
In this case code will be
// get object array with parsed dates
var arrayWithDates = array.Select(el =>
new
{
Date = DateTime.ParseExact(el.DateString, "yyyy-M-d", CultureInfo.InvariantCulture),
SomeInt = el.SomeInt
});
// get minimum date
DateTime minDate = arrayWithDates.Min(el => el.Date);
// get maximum date
DateTime maxDate = arrayWithDates.Max(el => el.Date);
// get total days
int days = (maxDate - minDate).Days;
// getting all dates from minDate to maxDate
IEnumerable<DateTime> dateRange = Enumerable.Range(0, days + 1)
.Select(el => minDate.AddDays(el));
// split all dates into groups of 5 dates
IEnumerable<DateTime[]> groupedDateRanges = dateRange
.Select((el, index) => new { el.Date, index })
.GroupBy(el => el.index / 5)
.Select(g => g.Select(el => el.Date).ToArray());
var results = groupedDateRanges
// getting list of object within each range
.Select(groupedDateRange => arrayWithDates.Where(el => groupedDateRange.Contains(el.Date)))
// selecting minimum date of range, maximum date of range and sum by int value
.Select(item =>
new
{
MinDate = item.Min(el => el.Date),
MaxDate = item.Max(el => el.Date),
Sum = item.Sum(el => el.SomeInt)
});
KeyValuePair<string, int> kvp = new KeyValuePair<string, int>();
IList < KeyValuePair < string, int> > list= new
List<KeyValuePair<string, int>>();
list.Add(new KeyValuePair<string, int>("2017 - 7 - 25", 10));
list.Add(new KeyValuePair<string, int>("2017 - 7 - 24", 3));
list.Add(new KeyValuePair<string, int>("2017 - 7 - 23", 7));
list.Add(new KeyValuePair<string, int>("2017 - 7 - 22", 4));
list.Add(new KeyValuePair<string, int>("2017 - 7 - 21", 2));
list.Add(new KeyValuePair<string, int>("2017 - 7 - 25", 5));
list.Add(new KeyValuePair<string, int>("2017 - 6 - 26", 8));
list.Add(new KeyValuePair<string, int>("2017 - 5 - 26", 18));
TimeSpan interval = TimeSpan.FromDays(5);
var output=list.GroupBy(x => DateTime.Parse(x.Key).Ticks / interval.Ticks)
.Select((n) => new { GroupId = new DateTime(n.Key * interval.Ticks), Sum= n.Sum(x => x.Value) })
I have a collection where I am trying to sort the records first by Quarter and then inside the quarter by highest amounts. My code so far is:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
List<Test> lstTest = new List<Test>();
lstTest.Add(new Test { dt = new DateTime(2017, 1, 2), amount = 2500 });
lstTest.Add(new Test { dt = new DateTime(2017, 1, 2), amount = 10000 });
lstTest.Add(new Test { dt = new DateTime(2017, 1, 5), amount = 4000 });
lstTest.Add(new Test { dt = new DateTime(2017, 1, 10), amount = 40000 });
lstTest.Add(new Test { dt = new DateTime(2017, 1, 15), amount = 2000 });
lstTest.Add(new Test { dt = new DateTime(2017, 1, 25), amount = 12000 });
lstTest.Add(new Test { dt = new DateTime(2017, 2, 5), amount = 38000 });
lstTest.Add(new Test { dt = new DateTime(2017, 2, 10), amount = 38000 });
lstTest.Add(new Test { dt = new DateTime(2017, 2, 15), amount = 4000 });
lstTest.Add(new Test { dt = new DateTime(2017, 2, 20), amount = 2000 });
lstTest.Add(new Test { dt = new DateTime(2017, 2, 20), amount = 20000 });
lstTest.Add(new Test { dt = new DateTime(2017, 3, 15), amount = 2000 });
lstTest.Add(new Test { dt = new DateTime(2017, 3, 20), amount = 2000 });
lstTest.Add(new Test { dt = new DateTime(2017, 3, 20), amount = 4000 });
lstTest.Add(new Test { dt = new DateTime(2017, 3, 31), amount = 1000 });
lstTest.Add(new Test { dt = new DateTime(2017, 4, 9), amount = 50000 });
lstTest.Add(new Test { dt = new DateTime(2017, 4, 11), amount = 2000 });
lstTest.Add(new Test { dt = new DateTime(2017, 4, 21), amount = 1000 });
lstTest.Add(new Test { dt = new DateTime(2017, 4, 21), amount = 10000 });
lstTest.Add(new Test { dt = new DateTime(2017, 4, 28), amount = 5000 });
lstTest.Add(new Test { dt = new DateTime(2017, 5, 5), amount = 45000 });
lstTest.Add(new Test { dt = new DateTime(2017, 5, 7), amount = 98000 });
lstTest.Add(new Test { dt = new DateTime(2017, 5, 9), amount = 7000 });
lstTest.Add(new Test { dt = new DateTime(2017, 5, 25), amount = 2000 });
lstTest.Add(new Test { dt = new DateTime(2017, 5, 31), amount = 1000 });
var result = lstTest.Select(x => new
{
Amount = x.amount,
Date = x.dt,
MonthDiff = GetMonthsDiff(DateTime.Now, x.dt),
Quater = GetQuarter(DateTime.Now, x.dt)
}).OrderBy(o=>o.Quater).ToList();
foreach (var res in result)
{
Console.WriteLine("Amount = {0} Date= {1} MonthDiff= {2} Quater= {3}", res.Amount, res.Date, res.MonthDiff, res.Quater);
}
Console.ReadKey();
}
public static string GetQuarter(DateTime start, DateTime end)// int month)
{
int month = GetMonthsDiff(start, end);
string quarter = month <= 3 ? "Q1" : (month >= 4 && month <= 6) ? "Q2" : (month >= 7 && month <= 9) ? "Q3" : "Q4";
return quarter;
}
public static int GetMonthsDiff(DateTime start, DateTime end)
{
if (start > end)
return GetMonthsDiff(end, start);
int months = 0;
do
{
start = start.AddMonths(1);
if (start > end)
return months;
months++;
}
while (true);
}
}
public class Test
{
public DateTime dt { get; set; }
public int amount { get; set; }
}
}
The output is
If I do OrderBy(o=>o.Quater).OrderByDescending(o=>o.Amount) the output changes to
That is it is first sorting by Quarter and then by Amount.
But I am looking for first sort by Quarter and within the Quarter sort by Amount descending.
The desired output is
What needs to be modified in the program so as achieve the target?
replace
OrderBy(o=>o.Quater).OrderByDescending(o=>o.Amount)
with
OrderBy(o=>o.Quater).ThenByDescending(o=>o.Amount)
ThenByDescending performs a subsequent ordering of the elements in a
sequence in descending order by using a specified comparer.
Here you are sorting the list two times, you are getting the output of the final sort only, if you want to perform the second sort over the sorted result of the first then you have to make use of ThenByDescending(if you want the second sort in descending order or else use ThenBy()) followed by OrderBy as like this:
var sortedItems = lstTest.OrderBy(o=>o.Quater).ThenByDescending(o=>o.Amount);
I have modified the code here in this example, you can check the output in the fiddle
You don't need to sort by a single property only. You can return the sort fields as an anonymous object tuple.
In C# 7 you can write :
OrderBy(o => (o.Quarter,o.Amount));
In previous versions :
OrderBy(o => Tuple.Create(o.Quarter,o.Amount));
If you want to use different sort orders you have to specify the fields one at a time, eg:
OrderBy(o=>o.Quarter).ThenByDescending(o=>o.Amount);
Or you can use query syntax to make the code cleaner:
var result = ( from x in lstTest
let o = new {
Amount = x.amount,
Date = x.dt,
MonthDiff = GetMonthsDiff(DateTime.Now, x.dt),
Quarter = GetQuarter(DateTime.Now, x.dt)
}
orderby o.Quarter, o.Amount descending
select o
).ToList()
I have a list of objects. Each object has an integer quantity and a DateTime variable which contains a month and year value. I'd like to traverse the list and pad the list by adding missing months (with quantity 0) so that all consecutive months are represented in the list. What would be the best way to accomplish this?
Example:
Original List
{ Jan10, 3 }, { Feb10, 4 }, { Apr10, 2 }, { May10, 2 }, { Aug10, 3 }, { Sep10, -3 }, { Oct10, 6 }, { Nov10, 3 }, { Dec10, 7 }, { Feb11, 3 }
New List
{ Jan10, 3 }, { Feb10, 4 }, {Mar10, 0}, { Apr10, 2 }, { May10, 2 }, { Jun10, 0 }, { Jul10, 0 } { Aug10, 3 }, { Sep10, -3 }, { Oct10, 6 }, { Nov10, 3 }, { Dec10, 7 }, { Jan11, 0 }, { Feb11, 3 }
One possible algorithm is to keep track of the previous and current months. If the difference between previous and current is 1 month, append current to the result. If the difference is more than one month, add the missing months first, then afterwards copy the current month.
Foo prev = months.First();
List<Foo> result = new List<Foo> { prev };
foreach (Foo foo in months.Skip(1))
{
DateTime month = prev.Month;
while (true)
{
month = month.AddMonths(1);
if (month >= foo.Month)
{
break;
}
result.Add(new Foo { Month = month, Count = 0 });
}
result.Add(foo);
prev = foo;
}
Results:
01-01-2010 00:00:00: 3
01-02-2010 00:00:00: 4
01-03-2010 00:00:00: 0
01-04-2010 00:00:00: 2
01-05-2010 00:00:00: 2
01-06-2010 00:00:00: 0
01-07-2010 00:00:00: 0
01-08-2010 00:00:00: 3
01-09-2010 00:00:00: -3
01-10-2010 00:00:00: 6
01-11-2010 00:00:00: 3
01-12-2010 00:00:00: 7
01-01-2011 00:00:00: 0
01-02-2011 00:00:00: 3
Other code needed to make this compile:
class Foo
{
public DateTime Month { get; set; }
public int Count { get; set; }
}
List<Foo> months = new List<Foo>
{
new Foo{ Month = new DateTime(2010, 1, 1), Count = 3 },
new Foo{ Month = new DateTime(2010, 2, 1), Count = 4 },
new Foo{ Month = new DateTime(2010, 4, 1), Count = 2 },
new Foo{ Month = new DateTime(2010, 5, 1), Count = 2 },
new Foo{ Month = new DateTime(2010, 8, 1), Count = 3 },
new Foo{ Month = new DateTime(2010, 9, 1), Count = -3 },
new Foo{ Month = new DateTime(2010, 10, 1), Count = 6 },
new Foo{ Month = new DateTime(2010, 11, 1), Count = 3 },
new Foo{ Month = new DateTime(2010, 12, 1), Count = 7 },
new Foo{ Month = new DateTime(2011, 2, 1), Count = 3 }
};
Note: For simplicity I haven't handled the case where the original list is empty but you should do this in production code.
Lets assume the structure is held as a List<Tuple<DateTime,int>>.
var oldList = GetTheStartList();
var map = oldList.ToDictionary(x => x.Item1.Month);
// Create an entry with 0 for every month 1-12 in this year
// and reduce it to just the months which don't already
// exist
var missing =
Enumerable.Range(1,12)
.Where(x => !map.ContainsKey(x))
.Select(x => Tuple.Create(new DateTime(2010, x,0),0))
// Combine the missing list with the original list, sort by
// month
var all =
oldList
.Concat(missing)
.OrderBy(x => x.Item1.Month)
.ToList();
var months = new [] { "Jan", "Feb", "Mar", ... };
var yourList = ...;
var result = months.Select(x => {
var yourEntry = yourList.SingleOrDefault(y => y.Month = x);
if (yourEntry != null) {
return yourEntry;
} else {
return new ...;
}
});
If I am understand correctly with "DateTime" month:
for (int i = 0; i < 12; i++)
if (!original.Any(n => n.DateTimePropery.Month == i))
original.Add(new MyClass {DateTimePropery = new DateTime(2010, i, 1), IntQuantity = 0});
var sorted = original.OrderBy(n => n.DateTimePropery.Month);
One way is to implement an IEqualityComparer<> of your object, then you can create a list of "filler" objects to add to your existing list using the "Except" extension method. Sort of like below
public class MyClass
{
public DateTime MonthYear { get; set; }
public int Quantity { get; set; }
}
public class MyClassEqualityComparer : IEqualityComparer<MyClass>
{
#region IEqualityComparer<MyClass> Members
public bool Equals(MyClass x, MyClass y)
{
return x.MonthYear == y.MonthYear;
}
public int GetHashCode(MyClass obj)
{
return obj.MonthYear.GetHashCode();
}
#endregion
}
And then you can do something like this
// let this be your real list of objects
List<MyClass> myClasses = new List<MyClass>()
{
new MyClass () { MonthYear = new DateTime (2010,1,1), Quantity = 3},
new MyClass() { MonthYear = new DateTime (2010,12,1), Quantity = 2}
};
List<MyClass> fillerClasses = new List<MyClass>();
for (int i = 1; i < 12; i++)
{
MyClass filler = new MyClass() { Quantity = 0, MonthYear = new DateTime(2010, i, 1) };
fillerClasses.Add(filler);
}
myClasses.AddRange(fillerClasses.Except(myClasses, new MyClassEqualityComparer()));
Considering years, speed and extensibility it can be done as enumerable extension (possibly even using generic property selector).
If dates are already truncated to month and list is ordered before FillMissing is executed, please consider this method:
public static class Extensions
{
public static IEnumerable<Tuple<DateTime, int>> FillMissing(this IEnumerable<Tuple<DateTime, int>> list)
{
if(list.Count() == 0)
yield break;
DateTime lastDate = list.First().Item1;
foreach(var tuple in list)
{
lastDate = lastDate.AddMonths(1);
while(lastDate < tuple.Item1)
{
yield return new Tuple<DateTime, int>(lastDate, 0);
lastDate = lastDate.AddMonths(1);
}
yield return tuple;
lastDate = tuple.Item1;
}
}
}
and in the example form:
private List<Tuple<DateTime, int>> items = new List<Tuple<DateTime, int>>()
{
new Tuple<DateTime, int>(new DateTime(2010, 1, 1), 3),
new Tuple<DateTime, int>(new DateTime(2010, 2, 1), 4),
new Tuple<DateTime, int>(new DateTime(2010, 4, 1), 2),
new Tuple<DateTime, int>(new DateTime(2010, 5, 1), 2),
new Tuple<DateTime, int>(new DateTime(2010, 8, 1), 3),
new Tuple<DateTime, int>(new DateTime(2010, 9, 1), -3),
new Tuple<DateTime, int>(new DateTime(2010, 10, 1), 6),
new Tuple<DateTime, int>(new DateTime(2010, 11, 1), 3),
new Tuple<DateTime, int>(new DateTime(2010, 12, 1), 7),
new Tuple<DateTime, int>(new DateTime(2011, 2, 1), 3)
};
public Form1()
{
InitializeComponent();
var list = items.FillMissing();
foreach(var element in list)
{
textBox1.Text += Environment.NewLine + element.Item1.ToString() + " - " + element.Item2.ToString();
}
}
which will result in textbox containing:
2010-01-01 00:00:00 - 3
2010-02-01 00:00:00 - 4
2010-03-01 00:00:00 - 0
2010-04-01 00:00:00 - 2
2010-05-01 00:00:00 - 2
2010-06-01 00:00:00 - 0
2010-07-01 00:00:00 - 0
2010-08-01 00:00:00 - 3
2010-09-01 00:00:00 - -3
2010-10-01 00:00:00 - 6
2010-11-01 00:00:00 - 3
2010-12-01 00:00:00 - 7
2011-01-01 00:00:00 - 0
2011-02-01 00:00:00 - 3