Traverse list to add elements using .NET - c#

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

Related

Grouping object by date and sum-ing value in C#

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) })

DateTime processing methodology

I'm facing an issue which I can't find a proper and elegant solution. I have a List of Videos, which is a class that contains informations about a video. Among those informations there is a startDate,endDate and an cameraId property.
My current database has the following values:
startDate endDate
I want to iterate through those values and when a video is within 5 minutes difference from the last one and has the same cameraId it should be counted as one. But I can't find a proper nor elegant way to accomplish this task.
The output for the videos list shown above should be
1st: 2013:03:01 18:25:26 -> 2013-03-01 18:34:29
2nd: 2013:03:01 18:40:26 -> 2013:03:01 18:59:29
This is the code I have so far:
private void ProcessVideos(List<Video> videos)
{
bool isSameVideo = false;
Video lastVideo = null;
//debugar e ver esquema do ultimo valor do database
DateTime startDate = DateTime.MinValue;
DateTime endDate = DateTime.MinValue;
for (int i = 1; i < videos.Count; i++)
{
TimeSpan timeSpan = new TimeSpan(videos[i].DataInicio.Ticks - videos[i - 1].DataFim.Ticks);
if (timeSpan.Minutes > 0 && timeSpan.Minutes < 5 && videos[i].IdCamera == videos[i - 1].IdCamera)
{
if (!isSameVideo)
{
isSameVideo = true;
startDate = videos[i - 1].DataInicio;
endDate = videos[i].DataFim;
}
else
{
endDate = videos[i].DataFim;
}
}
else
{
if (isSameVideo)
{
i++;
isSameVideo = false;
Debug.WriteLine("inicio: {0} fim: {1}", startDate, endDate);
startDate = DateTime.MinValue;
endDate = DateTime.MinValue;
}
Debug.WriteLine("inicio: {0} fim: {1}", videos[i - 1].DataInicio, videos[i - 1].DataFim);
}
}
if (startDate != DateTime.MinValue)
{
Debug.WriteLine("inicio: {0} fim: {1}", startDate, endDate);
}
}
The main question is: What is a good logic to iterate through those values and output a combinations of values according to the timespan specification?
I created a small example to show you:
My container object:
internal class Container
{
public int Id { get; set; }
public DateTime Start { get; set; }
public DateTime Stop { get; set; }
public override string ToString()
{
return "ID " + Id + ": " + Start + " -> " + Stop;
}
}
My method:
private static IEnumerable<Container> DoMerge(List<Container> elements, TimeSpan maxDiff)
{
var closedContainers = new List<Container>();
var lastContainers = new Dictionary<int, Container>();
foreach (Container container in elements.OrderBy(e => e.Start))
{
//First case, no previous container
if (!lastContainers.ContainsKey(container.Id))
{
lastContainers[container.Id] = container;
}
else if (container.Start - lastContainers[container.Id].Stop > maxDiff)
//We have a container, but not in our windows of 5 minutes
{
closedContainers.Add(lastContainers[container.Id]);
lastContainers[container.Id] = container;
}
else
{
//We have to merge our two containers
lastContainers[container.Id].Stop = container.Stop;
}
}
//We have now to put all "lastContainer" in our final list
foreach (KeyValuePair<int, Container> lastContainer in lastContainers)
{
closedContainers.Add(lastContainer.Value);
}
return closedContainers;
}
And we just have to give our max timespan and list of elements:
private static void Main(string[] args)
{
var elements = new List<Container>
{
new Container {Id = 1, Start = new DateTime(2013, 3, 1, 18, 25, 26), Stop = new DateTime(2013, 3, 1, 18, 27, 29)},
new Container {Id = 1, Start = new DateTime(2013, 3, 1, 18, 30, 26), Stop = new DateTime(2013, 3, 1, 18, 34, 29)},
new Container {Id = 1, Start = new DateTime(2013, 3, 1, 18, 40, 26), Stop = new DateTime(2013, 3, 1, 18, 52, 29)},
new Container {Id = 1, Start = new DateTime(2013, 3, 1, 18, 55, 26), Stop = new DateTime(2013, 3, 1, 18, 59, 29)},
};
foreach (Container container in DoMerge(elements, TimeSpan.FromMinutes(5)))
{
Console.WriteLine(container);
}
Console.ReadLine();
}
This give me your expected results we two objects lefts.
Result with the mentionned data:
Here's a solution. The crux of the method is shown in the ExtractVidTimes method. The rest is just for creating the sample data
[TestFixture]
public class TestyMcTest
{
public class Vid
{
public int CamId;
public DateTime Start;
public DateTime End;
}
[Test]
public void Test()
{
var list = new List<Vid>
{
//=====Combination1=======
new Vid
{
CamId = 1,
Start = new DateTime(2000, 1, 1, 0, 0, 0),
End = new DateTime(2000, 1, 1, 0, 3, 0)
},
new Vid
{
CamId = 1,
Start = new DateTime(2000, 1, 1, 0, 5, 0),
End = new DateTime(2000, 1, 1, 0, 7, 0)
},
//=====Combination2=======
new Vid
{
CamId = 1,
Start = new DateTime(2000, 1, 1, 0, 15, 0),
End = new DateTime(2000, 1, 1, 0, 18, 0)
},
//=====Combination3=======
new Vid
{
CamId = 2,
Start = new DateTime(2000, 1, 1, 0, 0, 0),
End = new DateTime(2000, 1, 1, 0, 3, 0)
},
//=====Combination4=======
new Vid
{
CamId = 2,
Start = new DateTime(2000, 1, 1, 0, 10, 0),
End = new DateTime(2000, 1, 1, 0, 13, 0)
}
};
//here is your list of vids grouped by the cam id
var result = ExtractVidTimes(list);
}
//THE METHOD
private static List<List<Vid>> ExtractVidTimes(List<Vid> list)
{
//Group by cam ID
var vidGroups = list.GroupBy(vid => vid.CamId).ToList();
//extract vids with aggregate times
var result = vidGroups.Select(vids =>
{
var vidTimes = new List<Vid>();
var finalVid = vids.OrderBy(vid=> vid.Start).Aggregate((a, b) =>
{
if (a.End.AddMinutes(5) > b.Start)
{
a.End = b.End;
return a;
}
vidTimes.Add(a);
return b;
});
vidTimes.Add(finalVid);
return vidTimes;
}).ToList();
//return result.SelectMany(x=>x); //if you want a List<vid> return ed instead of a nested list
return result;
}
}

What is the best way to intersect these two lists?

I have 2 lists in C#:
public class AvailableSlot
{
public DateTime DateTime;
public string Name
}
List<AvailableSlot> list1 = GetList();
List<AvailableSlot> list2 = GetAnotherList();
I want to call intersect on these lists to find out where there are items in both lists for the same date. I know i can use .Intersect to get this info but I have a slightly more complicated requirement. I want to return a intersected list but i want to this list to contain a list of objects with all of the name in them. so something like this:
List<AvailableSlot2> intersectedList . ..
where AvailableSlot2 is this below:
public class AvailableSlot2
{
public DateTime DateTime;
public string[] Names;
}
Is there any ability to do this transformation after trying to intersect between two lists?
I would just union the two lists, group by DateTime and then pull out the names from the group:
var list1 = new List<AvailableSlot>
{
new AvailableSlot { DateTime = new DateTime(2013, 2, 1), Name = "Alpha" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 2), Name = "Bravo" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 3), Name = "Charlie" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 1), Name = "Delta" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 2), Name = "Echo" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 3), Name = "Foxtrot" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 4), Name = "Golf" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 5), Name = "Hotel" }
};
var list2 = new List<AvailableSlot>
{
new AvailableSlot { DateTime = new DateTime(2013, 2, 1), Name = "Apple" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 2), Name = "Bannana" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 1), Name = "Dog" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 2), Name = "Egg" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 5), Name = "Hi" }
};
var list3 = list1.Where (l => list2.Where (li => l.DateTime == li.DateTime).Any ())
.Union(list2.Where (l => list1.Where (li => l.DateTime == li.DateTime).Any ()));
var groupedItems = from slot in list3
group slot by slot.DateTime into grp
select new AvailableSlot2 {
DateTime = grp.Key,
Names = grp.Select (g => g.Name).ToArray()
};
foreach(var g in groupedItems)
{
Console.WriteLine(g.DateTime);
foreach(var name in g.Names)
Console.WriteLine(name);
Console.WriteLine("---------------------");
}
Output:
2/1/2013 12:00:00 AM
Alpha
Delta
Apple
Dog
---------------------
2/2/2013 12:00:00 AM
Bravo
Echo
Bannana
Egg
---------------------
2/5/2013 12:00:00 AM
Hotel
Hi
---------------------
You can use a LINQ to Objects Join() to line up items with the same DateTime property and then collect all the names into an array
var joinedItems = from slot1 in list1
join slot2 in list2
on slot1.DateTime equals slot2.DateTime into g
where g.Any()
select new AvailableSlot2
{
DateTime = slot1.DateTime,
Names = Enumerable.Range(slot1.Name,1).Union(g.Select(s => s.Name)).ToArray()
}
You can make use of ToLookup:
DateTime dt1 = new DateTime(2013, 2, 1);
DateTime dt2 = new DateTime(2013, 3, 1);
DateTime dt3 = new DateTime(2013, 4, 1);
var list1 = new List<AvailableSlot>
{
new AvailableSlot{DateTime = dt1, Name = "n1",},
new AvailableSlot{DateTime = dt2, Name = "n2",},
new AvailableSlot{DateTime = dt1, Name = "n3",},
};
var list2 = new List<AvailableSlot>
{
new AvailableSlot{DateTime = dt1, Name = "n1",},
new AvailableSlot{DateTime = dt2, Name = "n2",},
new AvailableSlot{DateTime = dt3, Name = "n3",},
};
var intersected = list1.Select (l => l.DateTime).
Intersect(list2.Select (l => l.DateTime));
var lookup = list1.Union(list2).ToLookup (
slot => slot.DateTime, slot => slot);
lookup.Where (l => intersected.Contains(l.Key)).Select (
slot => new
{
DateTime=slot.Key,
Names=slot.Select (s => s.Name)
});
Which in this case gives the result:
DateTime Names
01/02/2013 00:00 n1
n3
n1
01/03/2013 00:00 n2
n2
You could of course use Names=slot.Select(s => s.Name).Distinct() to get a distinct list of names.

How to diff two lists and get net changes

I have an object and two lists as follows:
public class MyObject
{
public int Key;
public DateTime Day;
public decimal Value;
}
List<MyObject> listA = new List<MyObject>()
{
new MyObject() { Key = 1, Day = new DateTime(2012, 12, 17), Value = 8 },
new MyObject() { Key = 1, Day = new DateTime(2012, 12, 18), Value = 8 },
new MyObject() { Key = 2, Day = new DateTime(2012, 12, 17), Value = 8 },
new MyObject() { Key = 3, Day = new DateTime(2012, 12, 17), Value = 4 },
new MyObject() { Key = 4, Day = new DateTime(2012, 12, 17), Value = 4 }
};
List<MyObject> listB = new List<MyObject>()
{
new MyObject() { Key = 1, Day = new DateTime(2012, 12, 17), Value = 2 },
new MyObject() { Key = 1, Day = new DateTime(2012, 12, 18), Value = 8 },
new MyObject() { Key = 3, Day = new DateTime(2012, 12, 17), Value = 8 },
new MyObject() { Key = 4, Day = new DateTime(2012, 12, 17), Value = 4 },
new MyObject() { Key = 5, Day = new DateTime(2012, 12, 17), Value = 10 }
};
The results I am looking for are:
List<MyObject> listChanges = new List<MyObject>()
{
new MyObject() { Key = 1, Day = new DateTime(2012, 12, 17), Value = -6 },
new MyObject() { Key = 2, Day = new DateTime(2012, 12, 17), Value = -8 },
new MyObject() { Key = 3, Day = new DateTime(2012, 12, 17), Value = 4 },
new MyObject() { Key = 5, Day = new DateTime(2012, 12, 17), Value = 10 }
};
Essentially, I am trying to create a list which contains the changes that would be required to make listA into listB. Thus, while Except, and Intersect from LINQ could be used I don't think they will have the best performance to do such a task as you would still need another comparison to get the difference in the values.
One thought I had is: If I loop through listA, I can remove the item from listA and from listB (if it is found and at this time I can determine the +/- differences). Once I have finished with listA, listB would only contain additions.
How can I get the change results?
This should do it. It'll throw an exception if any of your Key/Day combinations are not unique within either of your inputs.
public static IEnumerable<MyObject> GetChanges(
IEnumerable<MyObject> from, IEnumerable<MyObject> to)
{
var dict = to.ToDictionary(mo => new { mo.Key, mo.Day });
// Check that keys are distinct in from, too:
var throwaway = from.ToDictionary(mo => new { mo.Key, mo.Day });
// Adjustments of items found in "from"
foreach (MyObject mo in from)
{
var key = new { mo.Key, mo.Day };
MyObject newVal;
if (dict.TryGetValue(key, out newVal))
{
// Return item indicating adjustment
yield return new MyObject {
Key = mo.Key, Day = mo.Day, Value = newVal.Value - mo.Value };
dict.Remove(key);
}
else
{
// Return item indicating removal
yield return new MyObject {
Key = mo.Key, Day = mo.Day, Value = -mo.Value };
}
}
// Creation of new items found in "to"
foreach (MyObject mo in dict.Values)
{
// Return item indicating addition
// (Clone as all our other yields are new objects)
yield return new MyObject {
Key = mo.Key, Day = mo.Day, Value = mo.Value };
}
}
You could speed this up by removing the uniqueness check on from or doing it on-the-fly (try adding each item's key-parts to a HashSet), but I don't think you can avoid looping over part of to twice - once to build the dictionary, once to return the remainder.
First I would implement an IEqualityComparer<T> that checks equality based on both the Key and Day properties. Then you could use linq as follows:
var notInA = listB.Except(listA, myEqualityComparer);
var notInB = listA.Except(listB, myEqualityComparer)
.Select(o => {
return new MyObject {
Key = item.Key,
Day = item.Day,
Value = item.Value * -1
};
});
var listA2 = listA.Intersect(listB, myEqualityComparer)
.OrderBy(o => o.Key)
.ThenBy(o => o.Day);
var listB2 = listB.Intersect(listA, myEqualityComparer)
.OrderBy(o => o.Key)
.ThenBy(o => o.Day);
var diff = listA2.Zip(listB2, (first,second) => {
return new MyObject {
Key = first.Key,
Day = first.Day,
Value = second.Value - first.Value
});
diff = diff.Concat(notInA).Concat(notInB);
how about this :
List<MyObject> listA = new List<MyObject>(){
new MyObject() { Key = 1, Day = new DateTime(2012, 12, 17), Value = 8 },
new MyObject() { Key = 2, Day = new DateTime(2012, 12, 17), Value = 8 },
new MyObject() { Key = 1, Day = new DateTime(2012, 12, 18), Value = 8 },
new MyObject() { Key = 4, Day = new DateTime(2012, 12, 17), Value = 4 },
new MyObject() { Key = 3, Day = new DateTime(2012, 12, 17), Value = 4 }
};
List<MyObject> listB = new List<MyObject>(){
new MyObject() { Key = 1, Day = new DateTime(2012, 12, 17), Value = 2 },
new MyObject() { Key = 3, Day = new DateTime(2012, 12, 17), Value = 8 },
new MyObject() { Key = 4, Day = new DateTime(2012, 12, 17), Value = 4 },
new MyObject() { Key = 1, Day = new DateTime(2012, 12, 18), Value = 8 },
new MyObject() { Key = 5, Day = new DateTime(2012, 12, 17), Value = 10 }
};
List<MyObject> listChanges = Comparer(listA, listB);
MyObject[] hasil = listChanges.ToArray();
for (int a = 0; a < hasil.Length;a++ ) {
Console.WriteLine(hasil[a].Key+" "+hasil[a].Day+" "+hasil[a].Value);
}
and the function :
private MyObject[] sort(List<MyObject> input) {
//sort input with it's key
MyObject[] gg = input.ToArray();
for (int a = 0; a < input.Count; a++) {
for (int b = a + 1; b < input.Count; b++) {
if (gg[a].Key > gg[b].Key) {
MyObject temp = gg[a];
gg[a] = gg[b];
gg[b] = temp;
}
}
}
//sort input, if key is same => sort the date
for (int a = 0; a < input.Count; a++) {
int indStart = a;
int indEnd = a;
for (int b = a + 1; b < input.Count; b++) {
if (gg[a].Key == gg[b].Key) {
indEnd++;
} else {
b = input.Count;
}
}
a = indEnd;
for (int c = indStart; c <= indEnd; c++) {
for (int d = c + 1; d <= indEnd; d++) {
if (gg[c].Day > gg[d].Day) {
MyObject temp = gg[c];
gg[c] = gg[d];
gg[d] = temp;
}
}
}
}
return gg;
}
private List<MyObject> Comparer(List<MyObject> listA, List<MyObject> listB) {
List<MyObject> output = new List<MyObject>();
//if you sure that the list was sorted, u just remove the sort function
MyObject[] ff = sort(listA);
MyObject[] gg = sort(listB);
Boolean[] masuk = new Boolean[gg.Length];
//foreach element in listA, search the changes in input
for (int a = 0; a < listA.Count;a++ ) {
//find element in input which contains the changes of element in listA
Boolean ins = false;
for (int b = 0; b < listB.Count;b++ ) {
if (masuk[b])
continue;
if (ff[a].Key >= gg[b].Key) {
if (ff[a].Key == gg[b].Key && ff[a].Day == gg[b].Day){
masuk[b] = true;
if (gg[b].Value != ff[a].Value) {
output.Add(new MyObject() { Key = gg[b].Key, Day = gg[b].Day, Value = gg[b].Value - ff[a].Value });
b = listB.Count;
}
ins = true;
}
} else {
b = listB.Count;
}
}
if (!ins) {
output.Add(new MyObject() { Key = ff[a].Key, Day = ff[a].Day, Value = -ff[a].Value });
}
}
for (int a = 0; a < gg.Length;a++ ) {
if(!masuk[a]){
output.Add(new MyObject() { Key = gg[a].Key, Day = gg[a].Day, Value = gg[a].Value });
}
}
return output;
}
and the output :
1 12/17/2012 12:00:00 AM -6
2 12/17/2012 12:00:00 AM -8
3 12/17/2012 12:00:00 AM 4
5 12/17/2012 12:00:00 AM 10

Dictionary of dates and values, finding value of max date per year in LINQ

Have a list like this:
01/01/2009, 120
04/01/2009, 121
30/12/2009, 520
01/01/2010, 100
04/01/2010, 101
31/12/2010, 540
I need to find the last value for each year, e.g. the result would be 520, 540?
var lastValues = records.OrderByDescending(r => r.Date)
.GroupBy(r => r.Date.Year)
.Select(g => g.First().Value);
class Program
{
static void Main()
{
var list = new[]
{
new { Date = new DateTime(2009, 1, 1), Value = 120 },
new { Date = new DateTime(2009, 4, 1), Value = 121 },
new { Date = new DateTime(2009, 12, 30), Value = 520 },
new { Date = new DateTime(2010, 1, 1), Value = 100 },
new { Date = new DateTime(2009, 4, 1), Value = 101 },
new { Date = new DateTime(2010, 12, 31), Value = 540 },
};
var result = list
.GroupBy(x => x.Date.Year)
.Select(g => new { Date = g.Key, MaxValue = g.Max(x => x.Value) });
foreach (var item in result)
{
Console.WriteLine(item);
}
}
}

Categories