Let's say I have following data:
Time Status
10:00 On
11:00 Off
12:00 Off
13:00 Off
14:00 Off
15:00 On
16:00 On
How could I group that using Linq into something like
[On, [10:00]], [Off, [11:00, 12:00, 13:00, 14:00]], [On, [15:00, 16:00]]
Create a GroupAdjacent extension, such as the one listed here.
And then it's as simple as:
var groups = myData.GroupAdjacent(data => data.OnOffStatus);
You could also do this with one Linq query using a variable to keep track of the changes, like this.
int key = 0;
var query = data.Select(
(n,i) => i == 0 ?
new { Value = n, Key = key } :
new
{
Value = n,
Key = n.OnOffFlag == data[i - 1].OnOffFlag ? key : ++key
})
.GroupBy(a => a.Key, a => a.Value);
Basically it assigns a key for each item that increments when the current item does not equal the previous item. Of course this assumes that your data is in a List or Array, otherwise you'd have to try a different method
Here is a hardcore LINQ solution by using Enumerable.Zip to compare contiguous elements and generate a contiguous key:
var adj = 0;
var t = data.Zip(data.Skip(1).Concat(new TimeStatus[] { null }),
(x, y) => new { x, key = (x == null || y == null || x.Status == y.Status) ? adj : adj++ }
).GroupBy(i => i.key, (k, g) => g.Select(e => e.x));
It can be done as.
Iterate over collection.
Use TakeWhile<Predicate> condition is text of first element of collection On or Off.
Iterate over the subset of from point one and repeat above step and concatenate string.
Hope it helps..
You could parse the list and assign a contiguous key e.g define a class:
public class TimeStatus
{
public int ContiguousKey { get; set; }
public string Time { get; set; }
public string Status { get; set; }
}
You would assign values to the contiguous key by looping through, maintaining a count and detecting when the status changes from On to Off and so forth which would give you a list like this:
List<TimeStatus> timeStatuses = new List<TimeStatus>
{
new TimeStatus { ContiguousKey = 1, Status = "On", Time = "10:00"},
new TimeStatus { ContiguousKey = 1, Status = "On", Time = "11:00"},
new TimeStatus { ContiguousKey = 2, Status = "Off", Time = "12:00"},
new TimeStatus { ContiguousKey = 2, Status = "Off", Time = "13:00"},
new TimeStatus { ContiguousKey = 2, Status = "Off", Time = "14:00"},
new TimeStatus { ContiguousKey = 3, Status = "On", Time = "15:00"},
new TimeStatus { ContiguousKey = 3, Status = "On", Time = "16:00"}
};
Then using the following query you can extract the Status and grouped Times:
var query = timeStatuses.GroupBy(t => t.ContiguousKey)
.Select(g => new { Status = g.First().Status, Times = g });
Related
I have a List of type DailySummary
public class DailySummary
{
public string AffiliateID { get; set; }
public string TotalCalls { get; set; }
public string Date{ get; set; }
}
with following sample data:
List<DailySummary> DealerTFNDatesTable = new List<DailySummary>();
DealerTFNDatesTable.Add(new DailySummary() { AffiliateID="0", Date = "12/12/2016", TotalCalls = "10"});
DealerTFNDatesTable.Add(new DailySummary() { AffiliateID="0", Date = "12/13/2016", TotalCalls = "74"});
DealerTFNDatesTable.Add(new DailySummary() { AffiliateID="1", Date = "12/22/2016", TotalCalls = "63"});
DealerTFNDatesTable.Add(new DailySummary() { AffiliateID="0", Date = "12/12/2016", TotalCalls = "58"});
Now I want to retrieve Date and TotalCalls grouped by AffiliateID and assign in another list.
for(int i =0; i < DealerTFNDatesTable.Count; i++)
{
List<NewList> newList = new List<NewList>();
newList.Date = //Assign Dintinct dates WHERE AffiliateId = 0
newList.AffiliateID = //AffiliateID=0
newList.TotalCalls= //TotalCalls SUM GROUPBY DATE and AffiliateID = 0
//For Date '12/12/2016' it will be 68, For '12/13/2016' it will be 74 and so on
}
I'm sorry, I'm new to LINQ. Can someone help me or share resources where I can get a hint to achieve this?
This should work for grouping by AffilateID and Date and then getting the sum (though it's weird to store a number as a string for something like this, but whatever floats your boat).
var results = DealerTFNDatesTable
.GroupBy(x => new { x.AffiliateID, x.Date })
.Select(x => new DailySummary {
AffiliateID = x.First().AffiliateID,
Date = x.First().Date,
TotalCalls = x.Sum(y => Convert.ToInt32(y.TotalCalls)).ToString()
});
If you now look at the result, for example with this code, you get exactly the values you wanted:
foreach (var x in results) {
Console.WriteLine($"id = {x.AffiliateID}, date = {x.Date}, totalCalls = {x.TotalCalls}");
}
> id = 0, date = 12/12/2016, totalCalls = 68
> id = 0, date = 12/13/2016, totalCalls = 74
> id = 1, date = 12/22/2016, totalCalls = 63
First off,
Since DealerTFNDatesTable is a variable, you should use camel case. Thus it is dealerTFNDatesTable
Then to complete #andy his answer, as you also want to do a select. You can select it as follows:
var newVariable = from item in dealerTFNDatesTable
group item by new
{
item.Date,
item.AffiliateID,
}
into g
select new
{
Date = g.Key.Date,
Id = g.Key.AffiliateID,
Total = g.Sum(a => a.TotalCalls)
};
This will give you an IEnumerable, of which you can put the relevant parts in a list by doing var otherList = new List<object>(newVariable
.Where(a => a.Total > 0)); or simply add .ToList() after the select if you want the collection as-is.
Note that this is simply another notation than LINQ, the result is the same.
var results = DealerTFNDatesTable.GroupBy(T => new { T.AffiliateID })
Link
Issue
I had asked this question a while back and the requirements has changed a bit since then.
Now, there is a possibility to have a file with lines as follow:
Bryar22053;ADDPWN;Bryar.Suarez#company.com;ACTIVE
Nicole49927;ADDPWN;Nicole.Acosta#company.com;ACTIVE
Rashad58323;ADDPWN;Rashad.Everett#company.com;ACTIVE
Take first line. The first value Bryar22053 is skipped and the same lookup is used:
var columnCount = dataRow.Skip(1).Count();
var modular = 0;
// Simple Enum
var rightsFileType = new RightsFileType();
if (columnCount % 2 == 0)
{
rightsFileType = RightsFileType.WithoutStatus;
modular = 2;
}
else if (columnCount % 3 == 0)
{
rightsFileType = RightsFileType.WithStatus;
modular = 3;
}
var lookup = dataRow.Skip(1).Select((data, index) => new
{
lookup = index % modular,
index,
data
}).ToLookup(d => d.lookup);
The lookup object now has three groups:
> ? lookup[0].ToList() Count = 1
> [0]: { lookup = 0, index = 0, data = "ADDPWN" } ? lookup[1].ToList() Count = 1
> [0]: { lookup = 1, index = 1, data = "Bryar.Suarez#company.com" } ? lookup[2].ToList() Count = 1
> [0]: { lookup = 2, index = 2, data = "ACTIVE" }
If it was the original case where it would be just System1,User1,System2,User2... the lookup would have two groups and following code would work:
List<RightObjectRetrieved> rights;
rights = lookup[0].Join(lookup[1], system => system.index + 1, username => username.index, (system, username) => new
{
system = system.data,
useraname = username.data
}).Where(d => !string.IsNullOrEmpty(d.system)).Select(d => new RightObjectRetrieved {UserIdentifier = userIdentifier, SystemIdentifer = d.system, Username = d.useraname, RightType = rightsFileType}).ToList();
// rights => Key = System Identifier, Value = Username
But with the third 'status' as System1,User1,Status1,System2,User2,Status2..., I'm having issue trying to Join and get all three. Please help.
Edit
Here is what I have for raw data:
// Method has parameter localReadLine (string) that has this:
// Bryar22053;ADDPWN;Bryar.Suarez#company.com;ACTIVE
// Data line
var dataRow = localReadLine.Split(new[] { ToolSettings.RightsSeperator }, StringSplitOptions.None);
// Trim each element
Array.ForEach(dataRow, x => dataRow[Array.IndexOf(dataRow, x)] = x.Trim());
Tried (failed) so far
rights = lookup[0].Join(lookup[1], system => system.index + 1, username => username.index, status => status.index, (system, username, status) => new
{
system = system.data,
useraname = username.data,
status = status.data
}).Where(d => !string.IsNullOrEmpty(d.system)).Select(d => new RightObjectRetrieved {UserIdentifier = userIdentifier, SystemIdentifer = d.system, Username = d.useraname, RightType = rightsFileType}).ToList();
And
rights = lookup[0].Join(lookup[1], system => system.index + 1, username => username.index, (system, username) => new
{
system = system.data,
useraname = username.data
}).Join(lookup[2], status => status.index, (status) => new
{
status = status.data
}).Where(d => !string.IsNullOrEmpty(d.system)).Select(d => new RightObjectRetrieved {UserIdentifier = userIdentifier, SystemIdentifer = d.system, Username = d.useraname, RightType = rightsFileType, Status = ParseStatus(status)}).ToList();
I think you need to split up a little bit your implementation.
Let's declare a class that will hold the data:
class Data
{
public string System { get; set; }
public string Username { get; set; }
public string Status { get; set; }
}
Now, let's define a couple of parsing functions to parse a line.
The first one will parse a line which includes status:
var withStatus = (IEnumerable<string> line) => line
.Select((token, index) => new { Value = token, Index = index })
.Aggregate(
new List<Data>(),
(list, token) =>
{
if( token.Index % 3 == 0 )
{
list.Add(new Data { System = token.Value });
return list;
}
var data = list.Last();
if( token.Index % 3 == 1 )
data.Username = token.Value;
else
data.Status = token.Value;
return list;
});
The second one will parse a line which doesn't include status:
var withoutStatus = (IEnumerable<string> line) => line
.Select((token, index) => new { Value = token, Index = index })
.Aggregate(new List<Data>(),
(list, token) =>
{
if( token.Index % 2 == 0)
list.Add(new Data { System = token.Value });
else
list.Last().Username = token.Value;
return list;
});
With all that in place, you'll need the following:
Determine the modulus
Iterate the lines of the file and parse each line
Group and aggregate the results
The remaining code would look like this:
var lines = streamReader.ReadAllLines(); // mind the system resources here!
var parser = lines.First().Split(';').Length % 2 == 0 ? withoutStatus : withStatus;
var data = lines.Skip(1) // skip the header
.Select(line =>
{
var parts = line.Split(';');
return new
{
UserId = parts.First(),
Data = parser(parts.Skip(1))
};
})
.GroupBy(x => x.UserId)
.ToDictionary(g => g.Key, g => g.SelectMany(x => x.Data));
Now you have a Dictionary<string, Data> which holds the user id and its info.
Of course, a more elegant solution would be to separate each parsing function into its own class and join those classes under a common interface in case there would be more info to add in the future but the code above should work and give you an idea of what you should do.
If you want to use joins:
var result = lookup[0]
.Join(lookup[1],
system => system.index,
username => username.index - 1,
(system, username) => new {system = system.data, username = username.data, system.index})
.Join(lookup[2],
d => d.index,
status => status.index - 2,
(d, status) => new {d.system, d.username, status = status.data})
.ToList();
Another option to group by records and just select data from it (looks more readable from my point of view):
var result = dataRow
.Skip(1)
.Select((data, index) => new {data, record = index / 3})
.GroupBy(r => r.record)
.Select(r =>
{
var tokens = r.ToArray();
return new
{
system = tokens[0].data,
username = tokens[1].data,
status = tokens[2].data
};
})
.ToList();
Sorry for the title being a little vague, couldn't think of a good one.
I have a list of objects that holds some maximum and minimum limit values along with a timestamp.
To illustrate, my grid used to show the contents of that list could be like this (very simplified):
LimitMin | LimitMax | Start Time
1 2 08:00
1 2 08:01
1 2 08:03
2 5 08:05
2 5 08:06
2 5 08:10
Right now, I just do a select distinct, to get the distinct limits and add them to a list, like this:
var limitdistinct = printIDSPC.Select(x => new { x.LimitMin, x.LimitMax }).Distinct();
But I would like to get the timestamp as well, where the limits changed (08:05 in the example above). I cannot seem to figure out, how to accomplish this. I thought about how Distinct actually works behind the scenes, and if you could somehow get the timestamp from the select statement. Do I have to go through the entire list in a foreach loop, and compare the values to see where it changed?
Any help?
The trick here is to use GroupBy instead of Distinct. You could then either get the minimum timestamp for each limits pair:
items
.GroupBy(x => new { x.LimitMin, x.LimitMax })
.Select(x => new {
x.Key.LimitMin,
x.Key.LimitMax,
MinStartTime = x.Min(y => y.StartTime)
});
or, as GroupBy preserves the order of the original items, get the first timestamp for each:
items
.GroupBy(x => new { x.LimitMin, x.LimitMax })
.Select(x => new {
x.Key.LimitMin,
x.Key.LimitMax,
FirstStartTime = x.First().StartTime
});
Try this:-
var limitdistinct = printIDSPC.GroupBy(x => new { x.LimitMax, x.LimitMin })
.Select(x => new
{
LimitMin = x.Key.LimitMin,
LimitMax = x.Key.LimitMax,
MinTime = x.OrderBy(y => y.StartTime).First().StartTime
});
Fiddle.
One solution would be to group by min/max, then order by start time and finally select the first time value:
var list = new List<Foo>
{
new Foo { LimitMin = 1, LimitMax = 2, StartTime = TimeSpan.Parse("08:00") },
new Foo { LimitMin = 1, LimitMax = 2, StartTime = TimeSpan.Parse("08:01") },
new Foo { LimitMin = 1, LimitMax = 2, StartTime = TimeSpan.Parse("08:03") },
new Foo { LimitMin = 2, LimitMax = 5, StartTime = TimeSpan.Parse("08:05") },
new Foo { LimitMin = 2, LimitMax = 5, StartTime = TimeSpan.Parse("08:06") },
new Foo { LimitMin = 2, LimitMax = 5, StartTime = TimeSpan.Parse("08:10") },
};
var tmp = list
.GroupBy(z => new { z.LimitMin, z.LimitMax })
.Select(z =>
new
{
Time = z.OrderBy(z2 => z2.StartTime).First().StartTime,
Min = z.Key.LimitMin,
Max = z.Key.LimitMax
})
.ToList();
I have the following class:
public class MyTrelloCard
{
...
public DateTime? completed { get; set; }
public Decimal? estHours { get; set; }
public bool complete { get; set; }
}
...and I want to query the number of estHours on each date in the completed date. These have to be nullable values as not every card is completed, and not every card has estimated hours added to it.
At the moment I have to run two queries. The first one:
List<burnDownData> bData = (from c in cards
where c.complete
group c by new
{
date = Convert.ToDateTime(c.completed).Date
} into g
select new burnDownData
{
date = g.Key.date,
completedHours = g.Sum(x=>x.estHours) ?? 0
}).ToList();
returns all the cards that have been completed.
The second one is used to iterate through all dates between a start and end period and cumulatively add up the completed hours:
for (DateTime d = start; d.Date <= end; d = d.AddDays(1))
{
if ((d.DayOfWeek >= DayOfWeek.Monday) && (d.DayOfWeek <= DayOfWeek.Friday))
{
List<C> values = new List<C>();
Decimal xx = (from b in bData
where b.date.Date == d
select b.completedHours).SingleOrDefault();
total = total + xx;
values.Add(new C { v = d.ToString("d MMM") });
values.Add(new C { v = total.ToString() });
myRows.Add(new Row { c = values });
}
}
This seems inefficient though. Is it possible to do this directly? i.e. to replace this part of the loop:
Decimal xx = (from b in bData
where b.date.Date == d
select b.completedHours).SingleOrDefault();
With something that queries the cards data directly?
Interpreting your question:
I want to query the number of estHours on each date in the completed
date. These have to be nullable values as not every card is completed,
and not every card has estimated hours added to it
...as "I'm trying to get a sum of estimated hours for each completed date instance, and want to allow for null completed and estHours values".
You could include the check for null in your Where clause:
var x = cards.Where(c => c.completed != null )
.GroupBy(c => c.completed, (key, group) => new
{
dateComp = key.Value,
totEstHrs = group.Sum(i => i.estHours)
});
x.ToList().ForEach(
item => Debug.Print("{0:MM/dd/yyyy} {1}", item.dateComp, item.totEstHrs));
For example:
List<MyTrelloCard> cards = new List<MyTrelloCard>() {
new MyTrelloCard() { completed = new DateTime(2014, 4, 28), estHours = 5, complete = true },
new MyTrelloCard() { completed = null, estHours = 5, complete = false },
new MyTrelloCard() { completed = null, estHours = null, complete = true },
new MyTrelloCard() { completed = new DateTime(2014, 4, 28), estHours = 7, complete = false },
new MyTrelloCard() { completed = new DateTime(2014, 4, 29), estHours = null, complete = false },
new MyTrelloCard() { completed = new DateTime(2014, 4, 29), estHours = 3, complete = false },
};
Produces:
04/28/2014 12
04/29/2014 3
Edit:
To group on just the date portion (omitting the timestamp) you can group on the ToShortDateString() value:
var x = cards.Where(c => c.completed != null)
.GroupBy(c =>
((DateTime)c.completed).ToShortDateString(),
(key, group) => new
{
dateComp = key,
totEstHrs = group.Sum(i => i.estHours)
});
Ah - so you want an entry for every weekday in your range, even if no data exists for that day. In which case, build an array of eligible days, and groupjoin your cards.
Here's how:
var start = DateTime.Today;
var end = start.AddDays(14);
var cards = new[]{new {complete = true, completed = (DateTime?)DateTime.Now, estHours = new decimal?(3)} };
var days = Enumerable.Range(0, end.Subtract(start).Days)
.Select(x => start.AddDays(x))
.Where(x => !new []{DayOfWeek.Saturday,DayOfWeek.Sunday}.Contains(x.DayOfWeek));
var results = from d in days
join c in cards on d equals c.completed.GetValueOrDefault().Date
into cGrp
select new {d, completedHours = cGrp.Sum(x => x.estHours)};
of course you don't need the var cards = line, as you already have your own source for that!
Edit - All timestamps for a day are grouped into the 1 day.
var myDic = new SortedDictionary<DateTime,int> ()
{ { new DateTime(0), 0 },
{ new DateTime(1), 1 },
{ new DateTime(2), 1 },
{ new DateTime(3), 0 },
{ new DateTime(4), 0 },
{ new DateTime(5), 2 }
};
How can group these items (with a LINQ request) like this :
group 1 :
startDate: 0, endDate:0, value:0
group 2 :
startDate: 1, endDate:2, value:1
group 3 :
startDate: 3, endDate:4, value:0
group 4 :
startDate: 5, endDate:5, value:2
group are defined by contiguous date and same values.
Is it possible with a groupby ?
Just use a keyGenerating function. This example presumes your dates are already ordered in the source with no gaps.
int currentValue = 0;
int groupCounter = 0;
Func<KeyValuePair<DateTime, int>, int> keyGenerator = kvp =>
{
if (kvp.Value != currentValue)
{
groupCounter += 1;
currentValue = kvp.Value;
}
return groupCounter;
}
List<IGrouping<int, KeyValuePair<DateTime, int>> groups =
myDictionary.GroupBy(keyGenerator).ToList();
It looks like you are trying to group sequential dates over changes in the value. I don't think you should use linq for the grouping. Instead you should use linq to order the dates and iterate over that sorted list to create your groups.
Addition 1
While you may be able to build your collections with by using .Aggregate(). I still think that is the wrong approach.
Does your data have to enter this function as a SortedDictionary?
I'm just guessing, but these are probably records ordered chronologically.
If so, do this:
public class Record
{
public DateTime Date { get; set; }
public int Value { get; set; }
}
public class Grouper
{
public IEnumerable<IEnumerable<Record>> GroupRecords(IEnumerable<Record> sortedRecords)
{
var groupedRecords = new List<List<Record>>();
var recordGroup = new List<Record>();
groupedRecords.Add(recordGroup);
foreach (var record in sortedRecords)
{
if (recordGroup.Count > 0 && recordGroup.First().Value != record.Value)
{
recordGroup = new List<Record>();
groupedRecords.Add(recordGroup);
}
recordGroup.Add(record);
}
return groupedRecords;
}
}