I have three collections. First, a collection of days. Next, a collection of time spans in each day. These time spans are the same for each day. Next, I have a collection of sessions.
There are 4 days. There are 6 time spans. There are 30 sessions.
I need to iterate through each day, assigning all of the time spans to each day the same way for each day. However, I need to assign the sessions to time blocks in sequence. For example, day 1 gets all 6 time spans, but only the first 6 sessions, 1-6. Day 2 gets the same time spans, but gets the next 6 sessions, 7-12.
How can I do this within the same method?
Here's what I have so far, but I'm having trouble wrapping my head around the paged iteration part.
var timeSlots = TimeSlotDataAccess.GetItems(codeCampId);
var assignableSlotCount = timeSlots.Where(t => !t.SpanAllTracks);
// determine how many days the event lasts for
agenda.NumberOfDays = (int)(agenda.CodeCamp.EndDate - agenda.CodeCamp.BeginDate).TotalDays;
// iterate through each day
agenda.EventDays = new List<EventDayInfo>(agenda.NumberOfDays);
var dayCount = 0;
while (dayCount <= agenda.NumberOfDays)
{
var eventDate = agenda.CodeCamp.BeginDate.AddDays(dayCount);
var eventDay = new EventDayInfo()
{
Index = dayCount,
Day = eventDate.Day,
Month = eventDate.Month,
Year = eventDate.Year,
TimeStamp = eventDate
};
// iterate through each timeslot
foreach (var timeSlot in timeSlots)
{
var slot = new AgendaTimeSlotInfo(timeSlot);
// iterate through each session
// first day gets the first set of assignableTimeSlotCount, then the next iteration gets the next set of that count, etc.
slot.Sessions = SessionDataAccess.GetItemsByTimeSlotId(slot.TimeSlotId, codeCampId).ToList();
// iterate through each speaker
foreach (var session in slot.Sessions)
{
session.Speakers=SpeakerDataAccess.GetSpeakersForCollection(session.SessionId, codeCampId);
}
}
agenda.EventDays.Add(eventDay);
dayCount++;
}
I ended up using LINQ in a new method based upon the GetItemsByTimeSlot() method. The new signature and example of getting a matching subset of that collection is below.
Here's how I'm calling it:
slot.Sessions = SessionDataAccess.GetItemsByTimeSlotIdByPage(slot.TimeSlotId,
codeCampId, dayCount + 1, timeSlotCount).ToList();
Here's what it looks like:
public IEnumerable<SessionInfo> GetItemsByTimeSlotIdByPage(int timeSlotId, int codeCampId, int pageNumber, int pageSize)
{
var items = repo.GetItems(codeCampId).Where(t => t.TimeSlotId == timeSlotId);
items.Select(s => { s.RegistrantCount = GetRegistrantCount(s.SessionId); return s; });
// this is the important part
var resultSet = items.Skip(pageSize * (pageNumber - 1)).Take(pageSize);
foreach (var item in resultSet)
{
item.Speakers = speakerRepo.GetSpeakersForCollection(item.SessionId, item.CodeCampId);
}
return resultSet;
}
Related
I have a sorted list of data to populate a gridview control.
The list is ordered by datetime
I need to find the most recent 3 times per unique ref within each day (there may be more than 1 unique refs within a day). If there is only 1 ref row within a day then it is to be ignored.
I suppose it needs to be chunked into days and refs, then ordered by most recent with a count of refs within that 'chunk'. Any ideas appreciated.
It's a standard list of objects (the object has Date, Time and Ref properties) as:
private List<Reading> _listReadings = new List<Reading>();
and is bound to a grid:
DataRow newRow = MyTable.NewRow();
newRow.ItemArray = new object[]
{
new
DateTime(_listReadings.TimeStamp.Year,listReadings.TimeStamp.Month),
GetTime(_listReadings.TimeStamp),
_listReadings.Ref,
};
MyTable.Rows.Add(newRow);
Given a backing source of List<Reading>,
Group your source data by day using GroupBy
For-each day (the value in the group):
Group your source data by ref using GroupBy
Test if the group has 3+ values, or filter the ones that do not (Where and Count)
Order by datetimes of the values using OrderByDescending (latest is first)
Take 3 dates
So something like this:
public static IEnumerable<Reading> Filter(List<Reading> readings)
{
List<Reading> result = new List<Reading>();
var dayGroupings = readings.GroupBy(r => r.Day);
foreach (var dayGroup in dayGroupings)
{
var refGroupings = dayGroup.GroupBy(g => g.Ref);
foreach (var refGroup in refGroupings.Where(g => g.Count() >= 3))
{
result.AddRange(refGroup.OrderByDescending(g => g.Time).Take(3));
}
}
return result;
}
I've a list that pulls timespans and selects and displays the largest timespan in the list. I need to figure out a way to display the name that also matches that timespan. Not exactly sure where to start with this. would anyone be able to get me started. some of the code I've is below.
Code: You can see I create a list of timespans from item where workmodedirectiondescription = AVAIL-IN. I need AgName = AVAIL-IN also = and then to match the agent that has the max time Listoftimespans.max and display the name of the agent that has the max time. I hope this makes sense. The biggest issue I'm having is I can create a separate list of agname but then don't know how to match the times of the agent to display the name that has the largest time. I can't add agname to the list listoftimespans because agname is of type string not timespan.
var listofTimeSpans = new List<TimeSpan>();
List<NewAgent> newAgentList = new List<NewAgent>();
foreach (var item in e.CmsData.Agents)
{
NewAgent newAgents = new NewAgent();
newAgents.AgentName = item.AgName;
newAgents.AgentExtension = item.Extension;
newAgents.AgentDateTimeChange = ConvertedDateTimeUpdated;
newAgents.AuxReasons = item.AuxReasonDescription;
newAgents.LoginIdentifier = item.LoginId;
newAgents.AgentState = item.WorkModeDirectionDescription;
var timeSpanSince = DateTime.Now - item.DateTimeUpdated;
newAgents.AgentDateTimeStateChange = timeSpanSince;
newAgentList.Add(newAgents);
if (item.WorkModeDirectionDescription == "AVAIL-IN")
{
listofTimeSpans.Add(timeSpanSince);
}
var comparetimeandname = new tuple<string, TimeSpan>(item.Agname, listoftimespans.max());
max = comparetimeandname.item2;
maxname = comparetimeandname.item1.
}
Update: occurs right after the above code
var availInAgents = newAgentList.Where(ag => ag.AgentState == "AVAIL-IN").ToList();
availInAgents.Sort((t1, t2)=>t1.AgentDateTimeStateChange.CompareTo(t2.AgentDateTimeStateChange));
var minTimeSpanAgent = availInAgents.FirstOrDefault();
var maxTimeSpanAgent = availInAgents.LastOrDefault();
var min3 = availInAgents.Take(3).ToList();
var max3 = availInAgents.Reverse<NewAgent>().Take(3).ToList();
NextInLine.Text = string.Join(Environment.NewLine, min3);
Edit: screenshot
enter image description here
So let's say, as discussed in comments, you collect a List of tuples representing names and their timespans. By the way, I wouldn't do that in the loop; I'd do this after the loop:
var availInItems =
newAgentList
.Where(ag => ag.WorkModeDirectionDescription == "AVAIL-IN")
.Select(ag =>
new Tuple<String, TimeSpan>(ag.AgentName, ag.AgentDateTimeStateChange))
.ToList();
To get the min and max timespan items, just sort the list by the TimeSpan values of each item, and grab the first and last items. I'm using FirstOrDefault() and LastOrDefault() instead of First() and Last() because they return null on an empty list rather than throwing an exception. That way, we more gracefully handle the case where the list happens to be empty.
List<Tuple<String, TimeSpan>> availInItems = new List<Tuple<string, TimeSpan>>();
// ... populate availInItems in loop ...
availInItems.Sort((t1, t2) => t1.Item2.CompareTo(t2.Item2));
var minTimeSpanTuple = availInItems.FirstOrDefault();
var maxTimeSpanTuple = availInItems.LastOrDefault();
Lowest three, highest three:
var min3 = availInItems.Take(3).ToList();
// There's another overload of Reverse, without the type argument, which
// is a meber of List<T>; it reorders the list in place and returns void.
// You need this one.
var max3 = availInItems.Reverse<Tuple<String, TimeSpan>>().Take(3).ToList();
Those are OK if you have fewer than three items in the list. You'll just get whatever it has.
However
Both pieces of information that we're putting in the tuple are in NewAgent. Why get a tuple involved? No need. I'd just do this:
var availInAgents =
newAgentList
.Where(ag => ag.WorkModeDirectionDescription == "AVAIL-IN")
.ToList();
availInAgents.Sort((t1, t2) =>
t1.AgentDateTimeStateChange.CompareTo(t2.AgentDateTimeStateChange));
var minTimeSpanAgent = availInAgents.FirstOrDefault();
var maxTimeSpanAgent = availInAgents.LastOrDefault();
var min3 = availInAgents.Take(3).ToList();
var max3 = availInAgents.Skip(availInAgents.Count - 3).ToList();
max3.Reverse();
I think LINQ can help you.
For example, if you need the agent with max TimeSpan:
var maxAgent = newAgentList.Max(agnet => agnet.AgentDateTimeStateChange);
Console.writeLine(maxAgent.AgentName);
I am trying to create a usage report where I can plot the number of files created per week by each user. I'm trying to put the data into a DataTable so I can use it in a Chart. The path I'm headed down is clunky and I'm guessing there is a much more elegant way to do this in Linq.
The File class has an OpenDate and a LastModUser value. I want to sum up all the files created for a week for each user. In table form this looks like this:
File# OpenDate LastModUser
1 1/1/2015 ASmith
2 1/2/2015 ASmith
3 1/2/2015 DJones
4 1/2/2015 CBanks
The result of this query would return:
Week# ASmith DJones CBanks
1 2 1 1
2 etc etc etc
Here is what I have thus far.
public static DataTable GetFileCountByUserByClient(Int32 clientID, Int32 weeks)
{
using (EtaDataModelContainer12 etaDbContext = new EtaDataModelContainer12())
{
DataTable table = new DataTable();
table.Columns.Add("week", typeof(Int32));
// These are all the client's files
List<File> files = etaDbContext.Files.Where(b => b.ClientClientId == clientID).ToList();
// Get a list of all users
List<string> users = files.GroupBy(b => b.LastModUser).Select(b =>b.Key).Distinct().ToList();
for (int i = 1; i < users.Count(); i++)
{
// Create one column per user
table.Columns.Add(users[i], typeof(string));
}
// Add rows to the table based on how many files created in a given week
DateTime today = DateTime.Today;
int filecount = 0;
// Loop through the number of selected weeks (rows in DataTable) and populate sums
for (int j = 0; j < weeks; j++)
{
// Look at each file and determine if it fits in the selected week
foreach (File item in files)
{
// If a match is found determine what column in the DataTable should be incremented
}
table.Rows.Add(j, filecount);
}
return table;
}
}
There has to be a more elegant way to do this.
I think, this can solve your problem. You may need to tune some constant to get it to your grid. The week returned by this query will be number of week from year 0.
const long TicksPerWeek = TimeSpan.TicksPerDay * 7;
var userFiles = files.GroupBy(f => f.LastModUser);
var userStats = userFiles.Select(u =>
u.GroupBy(f => file.Date.Ticks / TicksPerWeek)
.Select(f => new { week = f.Key, modifiedCount = f.Count()))
I am trying to grab logs from windows. To make it faster I look for the days where logs are and then for that range of those days I open one thread per day to load it fast. In function work1 the error "Index was outside the bounds of the array" appears. If I make the job in only one thread it works fine but it is very very slow.
I tried to use the information from
"Index was outside the bounds of the array while trying to start multiple threads"
but it does not work.
I think the problem is in IEnumerable when it is loaded, like it is not loaded in time when the loop is started.
Sorry for my english, i am from Uzbekistan.
var result = from EventLogEntry elog in aLog.Entries
orderby elog.TimeGenerated
select elog.TimeGenerated;
DateTime OLDentry = result.First();
DateTime NEWentry = result.Last();
DTN.Add(result.First());
foreach (var dtn in result) {
if (dtn.Year != DTN.Last().Year |
dtn.Month != DTN.Last().Month |
dtn.Day != DTN.Last().Day
) {
DTN.Add(dtn);
}
}
List<Thread> t = new List<Thread>();
int i = 0;
foreach (DateTime day in DTN) {
DateTime yad = day;
var test = from EventLogEntry elog in aLog.Entries
where (elog.TimeGenerated.Year == day.Year) &&
(elog.TimeGenerated.Month == day.Month) &&
(elog.TimeGenerated.Day == day.Day)
select elog;
var tt2 = test.ToArray();
t.Add(new Thread(() => work1(tt2)));
t[i].Start();
i++;
}
static void work1(IEnumerable<EventLogEntry> b) {
var z = b;
for (int i = 0; i < z.Count(); i++) {
Console.Write(z + "\n");
}
}
Replace var tt2 = test; with var tt2 = test.ToArray();
The error is a mistake you do numerous times in your code: you are enumerating over a the data countless times. Calling .Count() enumerates the data again, and in this case the data ends up conflicting with cached values inside the EventLogEntry enumerator.
LINQ does not return a data set. It returns a query. A variable of type IEnumerable<T> may return different values every time you call Count(), First() or Last(). Calling .ToArray() makes C# retrieve the result and store it in an array.
You should generally just enumerate an IEnumerable<T> once.
I have a List of different DayTime (Ticks). I try to get a list of the time remaining from now to each time element.
List<long> diffliste = new List<long>(m_DummyAtTime);
// 864000000000 ≙ 24h
diffliste.ForEach(item => { item -= now; if (item < 0) item += 864000000000; });
// test, does also not work
// diffliste.ForEach(item => { item -= 500; });
However, the list is not changed. Do I miss something?
(now is DateTime.Now.TimeOfDay.Ticks)
var times = diffliste.Select(ticks => new DateTime(ticks) - DateTime.Now);
Will return a collection of TimeSpans between now and each time.
Without using Linq:
List<TimeSpan> spans = diffliste.ConvertAll(ticks => new DateTime(ticks) - DateTime.Now);
(modified as suggested by Marc)
You are changing a standalone copy in a local variable (well, parameter actually), not the actual value in the list. To do that, perhaps:
for(int i = 0 ; i < diffliste.Count ; i++) {
long val = diffliste[i]; // copy the value out from the list
... change it
diffliste[i] = val; // update the value in the list
}
Ultimately, your current code is semantically similar to:
long firstVal = diffliste[0];
firstVal = 42;
which also does not change the first value in the list to 42 (it only changes the local variable).
You cannot change the value of an item inside a foreach cycle.
You can do it using a classic for cycle or creating and assigning items to a new list.
for (int i = 0 ; i < diffliste.Count; i++)
{
long value = diffliste[i];
// Do here what you need
diffliste[i] = value;
}
The iteration var in a foreach cycle is immutable, so you cannot change it. You either have to create a new list or use a for cycle... see also here.