Say I have a class Slots as follows
public class Slots
{
//Other properties
.
.
.
public DateTime StartTime{get;set;}
public DateTime EndTime{ge;set;}
//Methods
}
And I have List<Slots>, what is the most efficient way to determine consecutive Slotgroups?
Consecutive slots are defined as any group of slots which start the next day relative to the previous slot, there is no overlap.
If there is a day gap(no slots on that day in the list) then it should be considered start of another slot group.
public List<List<Slots>> GetSlotGroups(List<SLots> slots)
{
//return list of slot groups according to logic described above
}
This kind of operation is best achieved with GetEnumerator.
The following code expects the list to be sorted.
IEnumerable<IEnumerable<Slot>> ToGroups(IEnumerable<Slot> slots)
{
using (var ie = slots.GetEnumerator())
{
var range = new List<Slot>();
while (ie.MoveNext())
{
if (range.Count > 0)
{
if (ie.Current.Start > range[range.Count - 1].End)
{
yield return range;
range = new List<Slot>{ie.Current};
continue;
}
}
range.Add(ie.Current);
}
yield return range;
}
}
It's simple. Sort by StartTime, then iterate over the sorted set, if the current item is not consecutive to the previous one, add a new group and make it current. Then simply add the item to the current group.
public static List<List<Slots>> GetSlotGroups(List<Slots> slots)
{
var slotGroups = new List<List<Slots>>();
using (var e = slots.OrderBy(slot => slot.StartTime).GetEnumerator())
{
List<Slots> currentGroup = null;
Slots lastSlot = null;
while (e.MoveNext())
{
var currentSlot = e.Current;
if (lastSlot == null || currentSlot.StartTime.Date.Subtract(lastSlot.EndTime.Date).Days > 1)
slotGroups.Add(currentGroup = new List<Slots>());
currentGroup.Add(currentSlot);
lastSlot = currentSlot;
}
}
return slotGroups;
}
Here's the code I came up with. I don't know if it's the most efficient, but it's readable and reasonably fast.
public static List<List<Slots>> GetGroups(List<Slots> slots)
{
List<List<Slots>> groups = new List<List<Slots>>();
DateTime? nextDate = null;
List<Slots> currentGroup = null;
foreach (var slot in slots.OrderBy(x => x.StartDate))
{
//first time through nextDate and currentGroup are null
//this condition matches the first time through or any time there is a gap in dates
if (nextDate == null || nextDate.Value < slot.StartDate)
{
if (currentGroup != null)
{
//if currentGroups isn't null then we have a completed group
groups.Add(currentGroup);
}
//start a new group
currentGroup = new List<Slots>();
}
nextDate = slot.EndDate.AddDays(1);
currentGroup.Add(slot);
}
//if there are no items in the collection currentGroup will still be null, otherwise currentGroup has the last group in it still. We finished iterating before finding a gap in dates
if (currentGroup != null)
{
groups.Add(currentGroup);
}
return groups;
}
This code keeps track of the next date in the range by adding one to the end date of the previous slot. As we go from slot to slot we append to a temporary list named currentGroup. When our next date is less than the start date of the current slot, we add the current group to the results list named groups and create a new list for our current group. At the end we presumably have some slots in currentGroup for the last group, so we have to add that one as well.
The best answer was based on George's code and Roberts comment, but modified to consider having more than one slot on a given day.
protected IEnumerable<IEnumerable<ExamCalendar>>ToBlocks(IEnumerable<ExamCalendar> slots)
{
using (var ie = slots.OrderBy(slot => slot.StartDate).GetEnumerator())
{
var block = new List<ExamCalendar>();
while (ie.MoveNext())
{
if (block.Count > 0)
{
if (ie.Current.StartDate.Date != block[block.Count - 1].StartDate.Date && ie.Current.StartDate.Date != block[block.Count - 1].EndDate.AddDays(1).Date)
{
yield return block;
block = new List<ExamCalendar> { ie.Current };
continue;
}
}
block.Add(ie.Current);
}
yield return block;
}
}
Related
I am trying to process a list of addresses into 2 collections; a list of addresses that meets requirements; a list of scores for each of those addresses that meets requirements.
I have several different criteria to slice and dice the addresses, but I am running into an incorrect implementation of modifying a collection during parallel processing.
For instance, I take the zip code and for each (+388k records) I determine if the Levenshtein Distance is greater or equal to a filtering value. If it does, then add the address to the compiled list of addresses. Then using the SET method, determine if the score for the address already exists. If is does not, then add. Else use the score currently in the collection and update the appropriate property.
I am hitting the "Collection Has Been Modified" error and I can not think of another way to implement the solution. I am looking for alternative ways to accomplish this.
private LevenshteinDistance _ld = new LevenshteinDistance();
private int _filter_zip = int.Parse(ConfigHelper.GetAppSettingByKey("filter_zip"));
public Factory ScoreAddresses(Factory model)
{
Factory result = model;
IList<Address> _compiledList = new List<Address>();
IList<Scores> _compiledScores = new List<Scores>();
ParallelLoopResult MailLoopResult = Parallel.ForEach(
result.ALL_Addresses, factory =>
{
if (factory.MAIL_ZIP_CODE_NBR != null
&& factory.MAIL_ZIP_CODE_NBR.Length >= 1)
{
int _zipDistance = _ld.Compute(
result.Target_Address.MAIL_ZIP_CODE_NBR,
factory.MAIL_ZIP_CODE_NBR);
if (_zipDistance <= _filter_zip)
{
_compiledList.Add(factory);
Scores _score = new Scores();
_compiledScores = _score.Set(_compiledScores,
factory.INDIVIDUAL_ID, "Levenshtein_MAIL_ZIP_CODE_NBR",
_zipDistance, string.Empty, null);
}
}
});
return result;
}
public IList<Scores> Set(IList<Scores> current, int Individual_ID,
string Score_Type, int Levenshtein, string Methaphone, decimal? other)
{
int currentcount = current.Count();
bool updating = false;
IList<Scores> result = current;
try
{
Scores newscore = new Scores();
//Check if scoreing for individual already present
Scores lookingforIndv = current.AsParallel().Where(
p => p.INDIVIDUAL_ID == Individual_ID).FirstOrDefault();
if (lookingforIndv != null)
{
newscore = lookingforIndv;
updating = true;
}
else
{
newscore.INDIVIDUAL_ID = Individual_ID;
updating = false;
}
//Add score based upon indicated score type
switch (Score_Type)
{
case "Levenshtein_MAIL_ZIP_CODE_NBR":
newscore.Levenshtein_MAIL_ZIP_CODE_NBR = Levenshtein;
result.Add(newscore);
break;
//More and More Cases
}
}
catch (Exception e)
{
}
return result;
}
I have a list of objects with two properties (Start and End). I need to be able to take items whose times fall within a variable (config option) tolerance level and combine them.
Example:
Tolerance: 1 hour
Item A: Start = 1pm, End = 2pm
Item B: Start 2:30pm, End = 4pm
Since the tolerance is one hour, I need to be able to 'combine' these two into a single timespan or other like object with, in this example, the following stats:
Start = 1pm, End 4pm
A sample class that I am using for testing follows. The production class has two like properties, along with several others.
public class TimeTest
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
}
I guess my confusion point is if there is an elegant way of doing this in LINQ. I'm still wrestling with how to compare a list item to another list item and iterate through the list that way.
When you want to iterate a sequence and use some criteria to sometimes combine consecutive items into a single item you can use an iterator block to keep state about the "previous" item while iterating:
static class EnumerableExtensions
{
public static IEnumerable<Item> Combine(this IEnumerable<Item> items)
{
using (var enumerator = items.GetEnumerator())
{
if (!enumerator.MoveNext())
yield break;
var previous = enumerator.Current;
while (enumerator.MoveNext())
{
var next = enumerator.Current;
if (TryCombine(previous, next, out var combined))
{
previous = combined;
continue;
}
yield return previous;
previous = next;
}
yield return previous;
}
}
}
You will have to implement TryCombine to apply your logic. Based on your requirements something like this should work for you:
private static bool TryCombine(Item item1, Item item2, out Item combinedItem)
{
if (item2.Start - item1.End > TimeSpan.FromHours(1))
{
combinedItem = default;
return false;
}
combinedItem = new Item { Start = item1.Start, End = item2.End };
return true;
}
Since this method is an extension method you can use it like this:
var combinedItems = items.Combine();
For more flexibility you could provide the 1 hour threshold as a TimeSpan parameter to the method and also perhaps use some more descriptive names instead of Item and Combine that makes more sense in your domain.
It sounds to me like a simple Min/Max situation.
List<TimeTest> times = new List<TimeTest>();
//... Fill list
if(times.Count == 0) return;
int min = Int32.MaxValue;
int max = Int32.MinValue;
foreach(var item in times)
{
min = Math.Min(item.Start.Hour, min);
max = Math.Max(item.End.Hour, max);
}
Then initialize a 1pm - 4pm construct as you see fit as min and max will contain your start and end times.
Another way to achieve this, using Aggregate.
For Input
var list = new List<Interval>
{
new Interval{Start=new DateTime(2019,1,1,13,0,0), End=new DateTime(2019,1,1,14,0,0)},
new Interval{Start=new DateTime(2019,1,1,14,30,0), End=new DateTime(2019,1,1,16,0,0)},
new Interval{Start=new DateTime(2019,1,1,16,0,0), End=new DateTime(2019,1,1,17,0,0)},
new Interval{Start=new DateTime(2019,1,1,19,0,0), End=new DateTime(2019,1,1,20,0,0)},
new Interval{Start=new DateTime(2019,1,1,22,0,0), End=new DateTime(2019,1,1,23,0,0)},
new Interval{Start=new DateTime(2019,1,1,23,40,0), End=new DateTime(2019,2,1,2,0,0)},
};
Where Interval is defined as
public class Interval
{
public DateTime Start{get;set;}
public DateTime End{get;set;}
}
You can
var result = MergeAndList(list);
Where MergeAndList is defined as
IEnumerable<Interval> MergeAndList(IEnumerable<Interval> intervals)
{
var ret = new List<Interval>(intervals);
int lastCount=0;
do
{
lastCount = ret.Count;
ret = ret.Aggregate(new List<Interval>(),(agg, cur) =>
{
for (int i = 0; i < agg.Count; i++)
{
var a = agg[i];
if(a.End.AddHours(1) >= cur.Start)
{
agg[i] = new Interval{Start=a.Start, End=cur.End};
return agg;
}
else
{
agg[i] = new Interval{Start=a.Start, End=a.End};
}
}
agg.Add(cur);
return agg;
});
} while (ret.Count != lastCount);
return ret;
}
Output
Example in Fiddle
In a foreach loop I want to compare an element with the previous element that was read. How can I do that? What is the syntax for addressing a previous element in a foreach loop?
You don't have that option built in with a foreach loop.
You can either switch to a for loop or use a variable.
Suppose you iterate through a list of objects, these are your options:
object prev = null;
foreach(var current in myListOfObjects)
{
if(current == prev)
{
// do stuff
}
// don't forget the next row!
prev = current;
}
or
for(var i = 1; i < myListOfObjects.count, i++) // Note: starting from 1 to avoid another condition inside the loop.
{
if(myListOfObjects[i] == myListOfObjects[i-1])
{
// do stuff
}
}
Everything is better with Bluetooth extension methods:
public static class EnumerableExtensions
{
public struct CurrentAndPrevious<T>
{
public T Current { get; private set; }
public T Previous { get; private set; }
public CurrentAndPrevious(T current, T previous) : this()
{
Previous = previous;
Current = current;
}
}
public static IEnumerable<CurrentAndPrevious<T>> WithPrevious<T>(this IEnumerable<T> enumerable)
{
var previous = default(T);
using(var enumerator = enumerable.GetEnumerator())
{
while(enumerator.MoveNext())
{
yield return new CurrentAndPrevious<T>(enumerator.Current, previous);
previous = enumerator.Current;
}
}
}
}
var items = new[] { 1, 2, 3, 4, 5 };
foreach(var item in items.WithPrevious())
{
Console.WriteLine(item.Previous + " " + item.Current);
}
You might need to tweak this depending on how you want first and last elements handled.
You can loop over a bit modified source instead of initial, say ListOfMyObjects:
MyObject prior = default(MyObject);
var source = ListOfMyObjects
.Select(item => {
var result = new {
Current = item,
Prior = prior,
};
prior = item; // side effect, not a good practice
return result;
});
So you can loop
foreach(var item in source) {
if (item.Prior == item.Current) {
...
}
}
A foreach itself has no syntax 'for addressing a previous element'. There are two options, depending on the characteristics of the collection and also the notion of a 'previous' element in respect of the first one. The following the examples are a little bit simplistic, but you should be able to choose the right path and fine-tune the details.
Option 1: Use a temporary variable
Works well if there's no cheap (performance-wise) way to index elements in the sequence, and you are OK with 'pretending' there's an empty (null, or default(T)) item before the very first item.
T previous = default(T); // corresponds to null for reference types
foreach (T item in sequence)
{
… work with previous and item here…
// the current 'item' is the new 'previous' for the next iteration
previous = item;
}
Note that if T is a value type, your would be actually copying the values themselves.
Option 2: Use a for loop and indexing
Works well if there is a cheap (performance-wise) way to index individual elements directly. List<T> and arrays are good examples here.
// indexing from 1, i.e. from the second item in the sequence
for (int i = 1; i < sequence.Count; i++)
{
var previous = sequence[i-1]; // this is obviously the previous item
var current = sequence[i]; // this is obviously the current item
}
Similar to using a temp variable, however this solution moves the scope of the temp variable inside the loop
var collection = new List<int>() { 1, 2, 3, 4, 5 };
foreach (var item in collection)
{
var currentIndex = collection.IndexOf(item);
if (currentIndex > 0 && currentIndex < collection.Count)
{
var previousItem = collection[currentIndex - 1];
}
}
As mentioned by Pham X, one easy way to do this would be a temp variable.
ObjectType temp_object = null;
foreach(var entry in ListOfObjects)
{
if(temp_object==null)
{
//this is the first time through...
temp_object=entry;
}
else
{
//it's anything after the first loop
if(entry==temp_object) Console.WriteLine("There is a match between two entries.");
else temp_object=entry;
}
}
We have a function that is informed that we received an item for a specific timestamp.
The purpose of this is to wait that for one specific timestamp, we wait that we receive every item that we are expecting, then push the notification further once we are "synchronized" with all items.
Currently, we have a Dictionary<DateTime, TimeSlot> to store the non-synchronized TimeSlot(TimeSlot = list of all items we received for a specific timestamp).
//Let's assume that this method is not called concurrently, and only once per "MyItem"
public void HandleItemReceived(DateTime timestamp, MyItem item){
TimeSlot slot;
//_pendingTimeSlot is a Dictionary<DateTime,TimeSlot>
if(!_pendingTimeSlot.TryGetValue(timestamp, out slot)){
slot = new TimeSlot(timestamp);
_pendingTimeSlot.Add(timestamp,slot );
//Sometimes we don't receive all the items for one timestamps, which may leads to some ghost-incomplete TimeSlot
if(_pendingTimeSlot.Count>_capacity){
TimeSlot oldestTimeSlot = _pendingTimeSlot.OrderBy(t=>t.Key).Select(t=>t.Value).First();
_pendingTimeSlot.Remove(oldestTimeSlot.TimeStamp);
//Additional work here to log/handle this case
}
}
slot.HandleItemReceived(item);
if(slot.IsComplete){
PushTimeSlotSyncronized(slot);
_pendingTimeSlot.Remove(slot.TimeStamp);
}
}
We have severals instances of this "Synchronizer" in parallels for differents group of items.
It's working fine, except when the system is under heavy loads, we have more incomplete TimeSlot, and the application uses a lot more CPU. The profiler seems to indicate that the Compare of the LINQ query is taking a lot of time(most of the time). So I'm trying to find some structure to hold those references(replace the dictionary)
Here are some metrics:
We have several(variable, but between 10 to 20) instances of this Synchronizer
The current maximum capacity(_capacity) of the synchronizer is 500 items
The shortest interval that we can have between two different timestamp is 100ms(so 10 new Dictionary entry per seconds for each Synchronizer)(most case are more 1 item/second)
For each timestamp, we expect to receive 300-500 items.
So we will do, for one Synchronizer, per second(worst case):
1 Add
500 Get
3-5 Sorts
What would be my best move? I thought to the SortedDictionary But I didn't find any documentation showing me how to take the first element according to the key.
The first thing you can try is eliminating the OrderBy - all you need is the minimum key, no need to sort for getting that:
if (_pendingTimeSlot.Count > _capacity) {
// No Enumerable.Min(DateTime), so doing it manually
var oldestTimeStamp = DateTime.MaxValue;
foreach (var key in _pendingTimeSlot.Keys)
if (oldestTimeStamp > key) oldestTimestamp = key;
_pendingTimeSlot.Remove(oldestTimeStamp);
//Additional work here to log/handle this case
}
What about SortedDictionary, it is an option for sure, although it will consume much more memory. Since it's sorted, you can use simply sortedDictionary.First() to take the key value pair with the minimum key (hence the oldest element in your case).
UPDATE: Here is a hybrid approach using dictionary for fast lookups and ordered double linked list for the other scenarios.
class MyItem
{
// ...
}
class TimeSlot
{
public readonly DateTime TimeStamp;
public TimeSlot(DateTime timeStamp)
{
TimeStamp = timeStamp;
// ...
}
public bool IsComplete = false;
public void HandleItemReceived(MyItem item)
{
// ...
}
// Dedicated members
public TimeSlot PrevPending, NextPending;
}
class Synhronizer
{
const int _capacity = 500;
Dictionary<DateTime, TimeSlot> pendingSlotMap = new Dictionary<DateTime, TimeSlot>(_capacity + 1);
TimeSlot firstPending, lastPending;
//Let's assume that this method is not called concurrently, and only once per "MyItem"
public void HandleItemReceived(DateTime timeStamp, MyItem item)
{
TimeSlot slot;
if (!pendingSlotMap.TryGetValue(timeStamp, out slot))
{
slot = new TimeSlot(timeStamp);
Add(slot);
//Sometimes we don't receive all the items for one timestamps, which may leads to some ghost-incomplete TimeSlot
if (pendingSlotMap.Count > _capacity)
{
// Remove the oldest, which in this case is the first
var oldestSlot = firstPending;
Remove(oldestSlot);
//Additional work here to log/handle this case
}
}
slot.HandleItemReceived(item);
if (slot.IsComplete)
{
PushTimeSlotSyncronized(slot);
Remove(slot);
}
}
void Add(TimeSlot slot)
{
pendingSlotMap.Add(slot.TimeStamp, slot);
// Starting from the end, search for a first slot having TimeStamp < slot.TimeStamp
// If the TimeStamps almost come in order, this is O(1) op.
var after = lastPending;
while (after != null && after.TimeStamp > slot.TimeStamp)
after = after.PrevPending;
// Insert the new slot after the found one (if any).
if (after != null)
{
slot.PrevPending = after;
slot.NextPending = after.NextPending;
after.NextPending = slot;
if (slot.NextPending == null) lastPending = slot;
}
else
{
if (firstPending == null)
firstPending = lastPending = slot;
else
{
slot.NextPending = firstPending;
firstPending.PrevPending = slot;
firstPending = slot;
}
}
}
void Remove(TimeSlot slot)
{
pendingSlotMap.Remove(slot.TimeStamp);
if (slot.NextPending != null)
slot.NextPending.PrevPending = slot.PrevPending;
else
lastPending = slot.PrevPending;
if (slot.PrevPending != null)
slot.PrevPending.NextPending = slot.NextPending;
else
firstPending = slot;
slot.PrevPending = slot.NextPending = null;
}
void PushTimeSlotSyncronized(TimeSlot slot)
{
// ...
}
}
Some additional usages:
Iterating from oldest to newest:
for (var slot = firstPending; slot != null; slot = slot.NextPending)
{
// do something
}
Iterating from oldest to newest and removing items based on a criteria:
for (TimeSlot slot = firstPending, nextSlot; slot != null; slot = nextSlot)
{
nextSlot = slot.NextPending;
if (ShouldRemove(slot))
Remove(slot);
}
Same for reverse scenarios, but using lastPending and PrevPending members instead.
Here is simple sample. The insert method in a list eliminates swapping elements.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
List<Data> inputs = new List<Data>() {
new Data() { date = DateTime.Parse("10/22/15 6:00AM"), data = "abc"},
new Data() { date = DateTime.Parse("10/22/15 4:00AM"), data = "def"},
new Data() { date = DateTime.Parse("10/22/15 6:30AM"), data = "ghi"},
new Data() { date = DateTime.Parse("10/22/15 12:00AM"), data = "jkl"},
new Data() { date = DateTime.Parse("10/22/15 3:00AM"), data = "mno"},
new Data() { date = DateTime.Parse("10/22/15 2:00AM"), data = "pqr"},
};
Data data = new Data();
foreach (Data input in inputs)
{
data.Add(input);
}
}
}
public class Data
{
public static List<Data> sortedData = new List<Data>();
public DateTime date { get; set; }
public string data { get; set;}
public void Add(Data newData)
{
if(sortedData.Count == 0)
{
sortedData.Add(newData);
}
else
{
Boolean added = false;
for(int index = sortedData.Count - 1; index >= 0; index--)
{
if(newData.date > sortedData[index].date)
{
sortedData.Insert(index + 1, newData);
added = true;
break;
}
}
if (added == false)
{
sortedData.Insert(0, newData);
}
}
}
}
}
I have a file that contains a list of delimited sequence numbers as a record key. I need to fill in the missing sequence. So if I have
8
8.2
8.3.4.1
I need to add
8.1
8.3
8.3.1
8.3.2
8.3.3
8.3.4
I have come up with a few algorithms but they're all horribly complex and have too many cases. Is there an easy way to do this or do I have to plod through? I'm using c# but Java would do.
Not sure if my solution is easy to understand, but let's try. The idea is that we recursively insert missing sequences between existing ones.
First, you need to parse your file to create a List of items representing existing sequence. Every item should have reference to the next one (linked list idea).
public class Item
{
public int Value { get; set; }
public Item SubItem { get; set; }
public Item NextItem { get; set; }
public Item(int value, Item subItem)
{
Value = value;
SubItem = subItem;
}
public Item CreatePreviousItem()
{
if (SubItem == null)
{
return Value == 1 ? null : new Item(Value - 1, null);
}
return new Item(Value, SubItem.CreatePreviousItem());
}
public bool IsItemMissingPrior(Item item)
{
if (item == null)
{
return false;
}
return
item.Value - Value > 1
|| (SubItem == null && item.SubItem != null && item.SubItem.Value > 1) //edge case
|| (SubItem != null && SubItem.IsItemMissingPrior(item.SubItem));
}
public override string ToString()
{
return Value + (SubItem != null ? "." + SubItem : "");
}
}
Assuming that sequences are delimited by new line symbol, you can use the following Parse method.
private List<Item> Parse(string s)
{
var result = new List<Item>();
var numberLines = s.Split(new[] {Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries);
foreach (var numberLine in numberLines)
{
var numbers = numberLine.Split(new[] {'.'}).Reverse();
Item itemInstance = null;
foreach (var number in numbers)
{
itemInstance = new Item(Convert.ToInt32(number), itemInstance);
}
if (result.Count > 0)
{
result.Last().NextItem = itemInstance;
}
result.Add(itemInstance);
}
return result;
}
Here is a recursive method which inserts missing sequences between two existing ones
private void UpdateSequence(Item item)
{
if (item.IsItemMissingPrior(item.NextItem))
{
var inBetweenItem = item.NextItem.CreatePreviousItem();
inBetweenItem.NextItem = item.NextItem;
item.NextItem = inBetweenItem;
UpdateSequence(item);
}
}
And finally the use case:
var inputItems = Parse(inputString);
foreach (var item in inputItems)
{
UpdateSequence(item);
}
That's it. To see the result, you just need to get the first item from the list and keep moving forward using NextItem property. For example
var displayItem = inputItems.FirstOrDefault();
while (displayItem != null)
{
Console.WriteLine(displayItem.ToString());
displayItem = displayItem.NextItem;
}
Hope it helps.
One of the easy ways (though not optimal for some cases) will be maintaining a set of existing keys. For each key in your initial sequence you can add all the preceding keys to that set. This can be done in two loops: in inner loop you add a key to a set and decrease last number of a key by one while the number is more than zero, in outer loop you decrease the length of a key by one.
And then you just need to output the set in sorted order.