Sum one column in list based on second column - c#

I would like to sum one column in list collection based on another column.
This is how class looks like
Date Payment
2015-09-09 500
2015-09-09 200
2017-01-03 150
2017-01-03 300
Result should be like this
Date Payment
2015-09-09 700
2017-01-03 450
Code:
var result = from e in scheduleDetails
let k = new
{
Date = e.ActualDueDate
}
group e by k into t
select new
{
Date = t.Key.Date,
Payment = t.Sum(e => e.EscrowPayment)
};
List<ScheduleDetails> temp = result.Select(t => new ScheduleDetails(t.Date, t.Payment)).ToList();
I do not want the anonymous type to be created hence I map it to the original object. For this, I had to create the constructor.
public ScheduleDetails(DateTime? date, decimal? payment)
{
this.ActualDueDate = date;
this.EscrowPayment = payment;
}
Questions:
Is this the best way to achieve this?
I want resultant object to be same as input object only

You don't need to create new range variable and anonymous type:
var temp = scheduleDetails.GroupBy(sd => sd.ActualDueDate)
.Select(g => new ScheduleDetails(g.Key, g.Sum(sd => sd.EscrowPayment)))
.ToList();
Also if ScheduleDetails class has public setters for it's ActualDueDate and EscrowPayment properties, you can use object initializer without adding constructor:
.Select(g => new ScheduleDetails {
ActualDueDate = g.Key,
EscrowPayment = g.Sum(sd => sd.EscrowPayment)
})
Query syntax (note that temp will be IEnumerable<ScheduleDetails> here):
var temp = from sd in scheduleDetails
group sd by sd.ActualDueDate into g
select new ScheduleDetails {
ActualDueDate = g.Key,
EscrowPayment = g.Sum(sd => sd.EscrowPayment)
};

Related

Convert list entries to array

In order to generate a graph using d3 I need to convert my list of time entries to several arrays.
I store my data in a list of work records per day per staff
I need to be able to get an array of all days, and then a array each per member of staff.
So lets say staff x has 3.5h against 01/1/19 and 4.5h against 03/1/19
Staff y has 6h agaist 2/1/19
I'd expect my arrays to look as following:
Dates[1/1/19, 2/1/19, 3/1/19]
X[3.5,0,4.5]
Y[0,6,0]
Some of my code is:
public IEnumerable<TicketWorkRecord> TimeByDateByStaff { get; set; }
public class TicketWorkRecord
{
public DateTime Date { get; set; }
public decimal TimeSpent { get; set; }
}
Assuming you have a class called StaffMember that looks like this:
public class StaffMember
{
public IEnumerable<TicketWorkRecord> TimeByDateByStaff { get; set; }
// Other properties
}
And after adding the following constructor to your TicketWorkRecord class:
public TicketWorkRecord(DateTime date, decimal timeSpent)
{
Date = date;
TimeSpent = timeSpent;
}
Let's create a dummy data for X and Y staff members:
StaffMember X = new StaffMember
{
TimeByDateByStaff = new List<TicketWorkRecord>()
{
new TicketWorkRecord(DateTime.Today.Date, 3.5M),
new TicketWorkRecord(DateTime.Today.Date.AddDays(2), 4.5M)
}
};
StaffMember Y = new StaffMember
{
TimeByDateByStaff = new List<TicketWorkRecord>()
{ new TicketWorkRecord(DateTime.Today.Date.AddDays(1), 6M) }
};
var staffMembers = new List<StaffMember>() { X, Y };
Now, you can construct your desired 3 arrays using the following code:
var dates = staffMembers.SelectMany(s => s.TimeByDateByStaff)
.Select(t => t.Date)
.Distinct()
.OrderBy(d => d).ToArray();
var xTimes = dates.Select(d => X.TimeByDateByStaff
.FirstOrDefault(t => t.Date == d)?.TimeSpent ?? 0).ToArray();
var yTimes = dates.Select(d => Y.TimeByDateByStaff
.FirstOrDefault(t => t.Date == d)?.TimeSpent ?? 0).ToArray();
To test it:
Console.WriteLine("Dates: " + string.Join(",", dates.Select(d => d.ToShortDateString())));
Console.WriteLine("xTimes: " + string.Join(",", xTimes));
Console.WriteLine("yTimes: " + string.Join(",", yTimes));
Output:
Dates: 12/01/2019,13/01/2019,14/01/2019
xTimes: 3.5,0,4.5
yTimes: 0,6,0
If TicketWorkRecord has a property specifying which staff member it is (X or Y), then this would be pretty straight forward using LINQ:
var dates = TimeByDateByStaff.Select(x => x.Date.ToString("MM/dd/yy")).ToArray();
var staffXTimeSpent = TimeByDateByStaff.Select(x => x.StaffMember == "X" ? x.TimeSpent : 0M).ToArray();
var staffYTimeSpent = TimeByDateByStaff.Select(x => x.StaffMember == "Y" ? x.TimeSpent : 0M).ToArray();
Alternatively, if the exact staff members aren't known at compile time then you can get the time entries by staff member at runtime:
var timeSpentByStaffMembers = TimeByDateByStaff
.Select(x => x.StaffMember)
.Distinct()
.ToDictionary(
key => key,
value => TimeByDateByStaff.Select(x => x.StaffMember == value ? x.TimeSpent : 0M).ToArray());
With the following data:
var TimeByDateByStaff = new List<TicketWorkRecord>
{
new TicketWorkRecord
{
Date = new DateTime(2019, 1, 1),
TimeSpent = 3.5M,
StaffMember = "X"
},
new TicketWorkRecord
{
Date = new DateTime(2019, 2, 1),
TimeSpent = 6M,
StaffMember = "Y"
},
new TicketWorkRecord
{
Date = new DateTime(2019, 3, 1),
TimeSpent = 4.5M,
StaffMember = "X"
}
};
The LINQ statements above produce the following output:
If i understand you correctly, you want to split your list of objects into individuals fields arrays.
If yes, Lets say you have the following list
List<Object> ObjectsList = ObjectsList;
string[] ExtractDates = ObjectsList.Select(x=>x.Date).ToArray();
double[] TimeSpent = ObjectsList.Select(x=> x.TimeSpent).ToArray();
and so forth, you can apply where condition to filter based on members

C# Join multiple collections into one

i have problem with joining multiple collections into one
-> I need collections with data from many sensors connect into one to have for each time values from all sensors in output file, f.e. if one sensor have no data, it will fill file with 0
Please help me, I am desperate
public class MeasuredData
{
public DateTime Time { get; }
public double Value { get; }
public MeasuredData(DateTime time, double value)
{
Time = time;
Value = value;
}
}
If you have multiple variables containing List<MeasuredData>, one for each sensor, you can group them in an array and then query them.
First, you need an extension method to round the DateTimes per #jdweng if you aren't already canonicalizing them as you acquire them.
public static DateTime Round(this DateTime dt, TimeSpan rnd) {
if (rnd == TimeSpan.Zero)
return dt;
else {
var ansTicks = dt.Ticks + Math.Sign(dt.Ticks) * rnd.Ticks / 2;
return new DateTime(ansTicks - ansTicks % rnd.Ticks);
}
}
Now you can create an array of the sensor reading Lists:
var sensorData = new[] { sensor0, sensor1, sensor2, sensor3 };
Then you can extract all the rounded times to create the left hand side of the table:
var roundTo = TimeSpan.FromSeconds(1);
var times = sensorData.SelectMany(sdl => sdl.Select(md => md.Time.Round(roundTo)))
.Distinct()
.Select(t => new { Time = t, Measurements = Enumerable.Empty<MeasuredData>() });
Then you can join each sensor to the table:
foreach (var oneSensorData in sensorData)
times = times.GroupJoin(oneSensorData, t => t.Time, md => md.Time.Round(roundTo),
(t, mdj) => new { t.Time, Measurements = t.Measurements.Concat(mdj) });
Finally, you can convert each row to the time and a List of measurements ordered by time:
var ans = times.Select(tm => new { tm.Time, Measurements = tm.Measurements.ToList() })
.OrderBy(tm => tm.Time);
If you wanted to flatten the List of measurements out to fields in the answer, you would need to do that manually with another Select.
Assuming you have something to join on, you can use Enumerable.Join:
var result = collection1.Join(collection2,
/* whatever your join is */ x => x.id,
y => y.id,
(a, b) => new {x = a, y = b}
foreach(var obj in result)
{
Console.WriteLine($"{obj.x.id}, {obj.y.id}")
}
This prints the id's of the two objects, but they could access anything. The link is probably more helpful, but you didn't give us much info

returning specified type for a linq query with anonymous type

i have this query:
DbQuery<Logs> logs = context.GetQuery<Logs>();
var MessageLogs =
logs.Where(
s =>
s.DATE == date.Date
.GroupBy(s => new {s.DATE, s.ID})
.Select(
g => new {Date = g.Key.DATE, SID = g.Key.ID, Count = g.Count()})
.GroupBy(x => x.SID, x => new {x.Date, x.Count});
and I have these two classess:
public class Data
{
public Values[] Val { get; set; }
public string Key { get; set; }
}
and this:
public class Values
{
public string type1 { get; set; }
public string type2 { get; set; }
}
all i want to do is using that query to return type of Data.
key in class Data is SID and list of values should be counts and date as type1 and type2.
i know i can do this with anonymous type but i dont know how, i tried many ways but all of them was wrong.
EDIT:
i have this query
logs.Where(
s =>
s.DATE == date.Date
.GroupBy(s => new {s.DATE, s.ID})
.Select(
g => new {Date = g.Key.DATE, SID = g.Key.ID, Count = g.Count()})
this query returns something like this:
key date count
----------------------------
1021 2012 1
1021 2013 5
1022 2001 10
1023 2002 14
what i want is base on each id a list of values
in fact return type should be type of Data which this ids are key fore example
key=1021 and Values[] should be type1=2012, type2=1 and type1=2013, type2=5
Given that your current query returns elements with key/date/count, it sounds like you probably just want:
var result = query.GroupBy(
x => x.Key,
(key, rows) => new Data {
Key = key,
Val = rows.Select(r => new Values { type1 = r.Date, type2 = r.Count })
.ToArray();
});
Basically this overload takes:
A source
A key selector
A transformation from a key and matching rows to a result element (an instance of Data in your case)

Group and Map into Object with an inner aggregate Object

I have many Object1A, say IEnumerable<Object1A>.
public class Object1A {
public string text;
public datetime date;
public decimal percent;
public Object3 obj;
}
Many of these objects have the same text, date, and percent, but have a different obj. I want to transform the list such that the output will be a IEnumerable<Object1B> where
public class Object1B{
public string text;
public datetime date;
public decimal percent;
public IEnumerable<Object3> objs;
}
My current apporach is a bit clunky, and listed below
IEnumerable<Object1A> a = GetSomeConstruct();
var lookup = a.ToLookup( t => t.text);
var b = new List<Object1b>();
foreach(var group in lookup){
var itemA = group.first();
var itemB = new Object1b(){
text = itemA.text,
date = itemA.date,
percent = itemA.percent
};
itemB.objs = pair.Select(t => t.obj);
b.Add(itemB);
}
Can this approach be refined? It doesn't seem to run to slow, but it seems like it could be better. I'm looking for a more terse approach if possible.
edit: yeah, this was a dumb question, cudos to the downvote....
simple answer
var b_objects = a_objects.GroupBy(t => new {t.Text})
.Select( t => new Object1B
{ Text = t.Key.Text,
Percent = t.First().Percent,
Date = t.First().Date,
Objs = t.Select( o => o.Obj).ToList()
});
Guess you want something like this?
var b = from a in GetSomeConstruct()
group a.obj by new { a.text, a.date, a.percent } into grp
select new Object1B
{
text = grp.Key.text,
date = grp.Key.date,
percent = grp.Key.percent,
objs = grp
};
You can use anonymous types with join and group by. Their GetHashCode and Equals overloads operate on each member.

Insert objects into a collection with LINQ based on a property of the existing objects in the collection

I've got a collection of object which contains data as follows:
FromTime Duration
2010-12-28 24.0000
2010-12-29 24.0000
2010-12-30 24.0000
2010-12-31 22.0000
2011-01-02 1.9167
2011-01-03 24.0000
2011-01-04 24.0000
2011-01-05 24.0000
2011-01-06 24.0000
2011-01-07 22.0000
2011-01-09 1.9167
2011-01-10 24.0000
In the "FromTime" column, there are data "gaps" i.e. 2011-01-01 and 2011-01-08 are "missing". So what I'd like to do is to loop through a range of dates (in this instance 2010-12-28 to 2011-01-10) and "fill in" the "missing" data with a duration of 0.
As I've just started with LINQ, I feel that it should be "fairly" easy but I can't quite get it right. I'm reading the book "LINQ in Action" but feel that I'm still quite a way off before I can resolve this particular issue. So any help would be much appreciated.
David
I'll define class like bellow:
public class DurDate
{
public DateTime date = DateTime.ToDay;
public decimal dure = 0;
}
and will wrote function like bellow:
private IEnumerable<DurDate> GetAllDates(IEnumerable<DurDate> lstDur)
{
var min = lstDur.Min(x => x.date).Date;
var max = lstDur.Max(x => x.date).Date;
var nonexistenceDates = Enumerable.Range(0, (int) max.Subtract(min).TotalDays)
.Where(x =>!lstDur.Any(p => p.date.Date == min.Date.AddDays(x)))
.Select(p => new DurDate {date = min.Date.AddDays(p), dure = 0});
return lstDur.Concat(nonexistenceDates).OrderBy(x=>x.date);
}
Sample test case:
List<DurDate> lstDur = new List<DurDate> { new DurDate { date = DateTime.Today, dure = 10 }, new DurDate { date = DateTime.Today.AddDays(-5), dure = 12 } };
Edit: It works simply, first I'll going to find min and max range:
var min = lstDur.Min(x => x.date).Date;
var max = lstDur.Max(x => x.date).Date;
What are the days not in the given range:
Where(x =>!lstDur.Any(p => p.date.Date == min.Date.AddDays(x)))
After finding this days, I'll going to select them:
Select(p => new DurDate {date = min.Date.AddDays(p), dure = 0})
At last concatenate the initial values to this list (and sort them):
lstDur.Concat(nonexistenceDates).OrderBy(x=>x.date);
Something like that. I didn't tested it, but I believe, that you will got the idea:
var data = new[]
{
new { Date = DateTime.Now.AddDays(-5), Duration = 3.56 },
new { Date = DateTime.Now.AddDays(-3), Duration = 3.436 },
new { Date = DateTime.Now.AddDays(-1), Duration = 1.56 },
};
Func<DateTime, DateTime, IEnumerable<DateTime>> range = (DateTime from, DateTime to) =>
{
List<DateTime> dates = new List<DateTime>();
from = from.Date;
to = to.Date;
while (from <= to)
{
dates.Add(from);
from = from.AddDays(1);
}
return dates;
};
var result = range(data.Min(e => e.Date.Date), data.Max(e => e.Date.Date))
.Join(data, e => e.Date.Date, e => e.Date, (d, x) => new {
Date = d,
Duration = x == null
? 0.0
: x.Duration
});
Also it would be better to replace this range lambda with some static method.

Categories

Resources