Split a list into multiple lists using lambda expression - c#

class order {
Guid employeeId;
DateTime time;
}
I need to filter a list of orders into 4 lists based on the time range. 0-9AM to 1st list, 9AM-2PM to 2nd, 2-6PM to 3rd and 6-12PM to a 4th list.
I am curious if this can be achieved using lambda expressions in a efficient way? otherwise what would be the best way to split the list?

This should work:
var orders = list.OrderBy(o => o.time);
var first = orders.TakeWhile(o => o.time.TimeOfDay.TotalHours <= 9);
var second = orders.SkipWhile(o => o.time.TimeOfDay.TotalHours <= 9)
.TakeWhile(o => o.time.TimeOfDay.TotalHours <= 14);
var third = orders.SkipWhile(o => o.time.TimeOfDay.TotalHours <= 14)
.TakeWhile(o => o.time.TimeOfDay.TotalHours <= 18);
var fourth = orders.SkipWhile(o => o.time.TimeOfDay.TotalHours <= 18);
Here's another, maybe more efficient, more flexible and concise approach which uses Enumerable.GroupBy:
var groups = list.Select(o => new
{
Order = o,
DayPart = o.time.TimeOfDay.TotalHours <= 9 ? 1
: o.time.TimeOfDay.TotalHours > 9 && o.time.TimeOfDay.TotalHours <= 14 ? 2
: o.time.TimeOfDay.TotalHours > 14 && o.time.TimeOfDay.TotalHours <= 18 ? 3 : 4
})
.GroupBy(x => x.DayPart)
.OrderBy(g => g.Key);
var first = groups.ElementAt(0);
var second = groups.ElementAt(1);
// ...

Most readable way would be to use a named function to do the grouping and pass it as a delegate to the GroupBy()
var orderGroups = orders.GroupBy(GetOrderGroup)
private int GetOrderGroup(order o)
{
//implement your groups
}

This should do the trick:
var first = orders.Where(o => o.time.Hour >= 0 && o.time.Hour < 9);
var second = orders.Where(o => o.time.Hour >= 9 && o.time.Hour < 14);
var third = orders.Where(o => o.time.Hour >= 14 && o.time.Hour < 18);
var fourth = orders.Where(o => o.time.Hour >= 18 && o.time.Hour < 24);

I'm in OSX right now so I can't test the solution, but I'd probably add a property to my order class to calculate the group. I feel like your order would reasonably be concerned with this. So, you could have something like this:
class order {
Guid employeeId;
DateTime time;
public int Group { get { return /* check hours of day to group /} }
}
Then, it should be as easy as orders.GroupBy(o => o.Group);
If you don't feel like your order should know about the groups, you could make another method where you feel it's more important to define the group. Then you could still say orders.GroupBy(o => GetGroupNumber(o)).
If you still need help next time I'm in Windows, I'll write a snippet for you.
EDIT:
I've noticed several of the other answers to this question recommend executing a Where or a Skip-Take strategy (with the overhead of a sort) on the original list for each child list you want to create.
My concern is that there is a performance detriment on large sets. For example, the four .Where evaluations will execute the comparisons on all of your objects four times despite the fact that the groups are mutually exclusive.
I don't know how many data you have, but for your sake I hope it's a LOT of orders :). In any event, I'd probably try to do the grouping and comparisons in one iteration like I recommended. If you don't like that solution, I'd recommend you iterate over the list yourself and built your sets without linq to objects.
Just my two cents.

It's important to use the DateTime.TimeOfDay.TotalHours property, which will return the time represented by whole and fractional hours.
var endTimes = new List<int>() { 9, 14, 18, 24 };
var results = orders.GroupBy(o => endTimes.First(t => o.time.TimeOfDay.TotalHours < t))
.OrderBy(g => g.Key);

Related

Getting the 3x3 groups in a sudoku board with LINQ in C#

I am trying to make a program that checks if a given sudoku board is valid (solved correctly).
Also want to do it using linq however I find it hard to come up with a solution to get all the 3x3 groups from the board.
I want to get them as a IEnumerable<IEnumerable<int>> because of how I wrote the rest of the code.
Here is my solution so far :
public static bool IsValidSudoku(IEnumerable<IEnumerable<int>> sudokuBoard)
{
if (sudokuBoard == null)
{
throw new ArgumentNullException();
}
var columns = Enumerable.Range(0, 9)
.Select(lineCount => Enumerable.Range(0,9)
.Select(columnCount=>sudokuBoard
.ElementAt(columnCount)
.ElementAt(lineCount)
));
var groups = //this is where I got stuck
return columns.All(IsValidGroup) &&
sudokuBoard.All(IsValidGroup) &&
groups.All(IsValidGroup);
}
static bool IsValidGroup(IEnumerable<int> group)
{
return group.Distinct().Count() == group.Count()&&
group.All(x => x <= 9 && x > 0)&&
group.Count() == 9;
}
Performance is not important here.
Thank you for any advice!
You need two enumerables to choose which 3x3 group you're selecting, and then you can use .Skip and .Take to take runs of three elements to fetch those groups.
var groups = Enumerable.Range(0, 3).SelectMany(gy =>
Enumerable.Range(0, 3).Select(gx =>
// We now have gx and gy 0-2; find the three rows we want
sudoBoard.Skip(gy * 3).Take(3).Select(row =>
// and from each row take the three columns
row.Skip(gx * 3).Take(3)
)
));
This should give you an IEnumerable of IEnumerable<IEnumerable<int>>s as requested. However to pass each group to IsValidGroup you'll have to flatten the 3x3 IEnumerable<IEnumerable<int>> into a 9-longIEnumerable<int>s, e.g. groups.Select(group => group.SelectMany(n => n)).

C# .NET sort method (filtering min and max values)

I'm using the .net sort method for generic lists. For example:
MyList.Sort();
Is it possible to use .net to sort a list with min and max values in mind? For example, if I wanted to sort a list and simultaneously only show list items less than 'value1' but greater than 'value2'.
Something like this:
MyList.Sort.Where(MyList > 50 & MyList < 30);
You could use Linq extensions.
// top of code file
using System.Linq;
// code
var filteredSortedList = MyList.Where(x => x > 30 && x < 50).OrderBy(x => x);
// filters values between 30 and 50 not inclusive
// sorts the results
Note that the original list MyList is not changed, instead the result is assigned to a new variable. Also the actions are deferred until you do something on the result like calling ToList or enumerating it.
I think your example is going to yield zero results as nothing is greater than 50 and less than 30. But assuming that was just a typo, is this what you are after?
List<int> s = new List<int>();
s.Where(r => r > 50 && r < 30).OrderBy(r => r);
Hi you can something like below
var res = MyList.Where(val => val > minVal && val < maxVal).OrderBy(num=>num).ToList();
You don't need to sort your list, orderby will do it for you
After you sorted the list, you can filter it using LINQ. In your case, SkipWhile and TakeWhile seem useful:
List<int> MyList = ....
// sort
MyList.Sort();
// filter:
var result = MyList.SkipWhile(i => i < 30).TakeWhile(i => i < 50).ToList();
I'm not sure (meaning I did not test), but I guess most of the times it's faster to filter first and sort afterwards, so fewer elements have to be sorted. You can use LINQ's Where and OrderBy:
List<int> MyList = ....
var result = MyList.Where(i => i > 30 && i < 50).OrderBy(i => i).ToList();
Note that I assumed your condidtion was a typo, there is no number i < 30 && i > 50.
You can use linq query for that
MyList = MyList
.Where(x => x > 50 && x < 30)
.OrderBy(x => x)
.ToList();
Note that x > 50 && x < 30 will not match any item. So you probably want to write
MyList.Where(x => x > 30 && x < 50).OrderBy(x => x);
this will remove any item below 30 and greater than 50
This version should work on most version of .net platform.
System.Collections.Generic.List<int> items = new List<int>();
Predicate<int> FilterMethod = delegate (int item) { return item < 50 && item > 30; };
items.FindAll(FilterMethod).Sort();

Using LINQ to take the top 100 and bottom 100?

I would like to do something like this (below) but not sure if there is a formal/optimized syntax to do so?
.Orderby(i => i.Value1)
.Take("Bottom 100 & Top 100")
.Orderby(i => i.Value2);
basically, I want to sort by one variable, then take the top 100 and bottom 100, and then sort those results by another variable.
Any suggestions?
var sorted = list.OrderBy(i => i.Value);
var top100 = sorted.Take(100);
var last100 = sorted.Reverse().Take(100);
var result = top100.Concat(last100).OrderBy(i => i.Value2);
I don't know if you want Concat or Union at the end. Concat will combine all entries of both lists even if there are similar entries which would be the case if your original list contains less than 200 entries. Union would only add stuff from last100 that is not already in top100.
Some things that are not clear but that should be considered:
If list is an IQueryable to a db, it probably is advisable to use ToArray() or ToList(), e.g.
var sorted = list.OrderBy(i => i.Value).ToArray();
at the beginning. This way only one query to the database is done while the rest is done in memory.
The Reverse method is not optimized the way I hoped for, but it shouldn't be a problem, since ordering the list is the real deal here. For the record though, the skip method explained in other answers here is probably a little bit faster but needs to know the number of elements in list.
If list would be a LinkedList or another class implementing IList, the Reverse method could be done in an optimized way.
You can use an extension method like this:
public static IEnumerable<T> TakeFirstAndLast<T>(this IEnumerable<T> source, int count)
{
var first = new List<T>();
var last = new LinkedList<T>();
foreach (var item in source)
{
if (first.Count < count)
first.Add(item);
if (last.Count >= count)
last.RemoveFirst();
last.AddLast(item);
}
return first.Concat(last);
}
(I'm using a LinkedList<T> for last because it can remove items in O(1))
You can use it like this:
.Orderby(i => i.Value1)
.TakeFirstAndLast(100)
.Orderby(i => i.Value2);
Note that it doesn't handle the case where there are less then 200 items: if it's the case, you will get duplicates. You can remove them using Distinct if necessary.
Take the top 100 and bottom 100 separately and union them:
var tempresults = yourenumerable.OrderBy(i => i.Value1);
var results = tempresults.Take(100);
results = results.Union(tempresults.Skip(tempresults.Count() - 100).Take(100))
.OrderBy(i => i.Value2);
You can do it with in one statement also using this .Where overload, if you have the number of elements available:
var elements = ...
var count = elements.Length; // or .Count for list
var result = elements
.OrderBy(i => i.Value1)
.Where((v, i) => i < 100 || i >= count - 100)
.OrderBy(i => i.Value2)
.ToArray(); // evaluate
Here's how it works:
| first 100 elements | middle elements | last 100 elements |
i < 100 i < count - 100 i >= count - 100
You can write your own extension method like Take(), Skip() and other methods from Enumerable class. It will take the numbers of elements and the total length in list as input. Then it will return first and last N elements from the sequence.
var result = yourList.OrderBy(x => x.Value1)
.GetLastAndFirst(100, yourList.Length)
.OrderBy(x => x.Value2)
.ToList();
Here is the extension method:
public static class SOExtensions
{
public static IEnumerable<T> GetLastAndFirst<T>(
this IEnumerable<T> seq, int number, int totalLength
)
{
if (totalLength < number*2)
throw new Exception("List length must be >= (number * 2)");
using (var en = seq.GetEnumerator())
{
int i = 0;
while (en.MoveNext())
{
i++;
if (i <= number || i >= totalLength - number)
yield return en.Current;
}
}
}
}

Sliding time window for record analysis

I have a data structure of phone calls. For this question there are two fields, CallTime and NumberDialled.
The analysis I want to perform is "Are there more than two calls to the same number in a 10 second window" The collection is sorted by CallTime already and is a List<Cdr>.
My solution is
List<Cdr> records = GetRecordsSortedByCallTime();
for (int i = 0; i < records.Count; i++)
{
var baseRecord = records[i];
for (int j = i; j < records.Count; j++)
{
var comparisonRec = records[j];
if (comparisonRec.CallTime.Subtract(baseRecord.CallTime).TotalSeconds < 20)
{
if (comparisonRec.NumberDialled == baseRecord.NumberDialled)
ReportProblem(baseRecord, comparisonRec);
}
else
{
// We're more than 20 seconds away from the base record. Break out of the inner loop
break;
}
}
}
Whis is ugly to say the least. Is there a better, cleaner and faster way of doing this?
Although I haven't tested this on a large data set, I will be running it on about 100,000 records per hour so there will be a large number of comparisons for each record.
Update The data is sorted by time not number as in an earlier version of the question
If the phone calls are already sorted by call time, you can do the following:
Initialize a hash table that has a counter for every phone number (the hash table can be first empty and you add elements to it as you go)
Have two pointers to the linked list of yours, let's call them 'left' and 'right'
Whenever the timestamp between the 'left' and 'right' call is less than 10 seconds, move 'right' forwards by one, and increment the count of the newly encountered phone number by one
Whenever the difference is above 10 seconds, move 'left' forwards by one and decrement the count for the phone number from which 'left' pointer left by one
At any point, if there is a phone number whose counter in the hash table is 3 or more, you have found a phone number that has more than 2 calls within a 10 seconds window
This is a linear-time algorithm and processes all the numbers in parallel.
I didn't know you exact structures, so I created my own for this demonstration:
class CallRecord
{
public long NumberDialled { get; set; }
public DateTime Stamp { get; set; }
}
class Program
{
static void Main(string[] args)
{
var calls = new List<CallRecord>()
{
new CallRecord { NumberDialled=123, Stamp=new DateTime(2011,01,01,10,10,0) },
new CallRecord { NumberDialled=123, Stamp=new DateTime(2011,01,01,10,10,9) },
new CallRecord { NumberDialled=123, Stamp=new DateTime(2011,01,01,10,10,18) },
};
var dupCalls = calls.Where(x => calls.Any(y => y.NumberDialled == x.NumberDialled && (x.Stamp - y.Stamp).Seconds > 0 && (x.Stamp - y.Stamp).Seconds <= 10)).Select(x => x.NumberDialled).Distinct();
foreach (var dupCall in dupCalls)
{
Console.WriteLine(dupCall);
}
Console.ReadKey();
}
}
The LINQ expression loops through all records and finds records which are ahead of the current record (.Seconds > 0), and within the time limit (.Seconds <= 10). This might be a bit of a performance hog due to the Any method constantly going over your whole list, but at least the code is cleaner :)
I recommand you to use Rx Extension and the Interval method.
The Reactive Extensions (Rx) is a library for composing asynchronous and event-based programs using observable sequences and LINQ-style query operators. Using Rx, developers represent asynchronous data streams with Observables, query asynchronous data streams using LINQ operators, and parameterize the concurrency in the asynchronous data streams using Schedulers
The Interval method returns an observable sequence that produces a value after each period
Here is quick example :
var callsPer10Seconds = Observable.Interval(TimeSpan.FromSeconds(10));
from x in callsPer10Seconds
group x by x into g
let count = g.Count()
orderby count descending
select new {Value = g.Key, Count = count};
foreach (var x in q)
{
Console.WriteLine("Value: " + x.Value + " Count: " + x.Count);
}
records.OrderBy(p => p.CallTime)
.GroupBy(p => p.NumberDialled)
.Select(p => new { number = p.Key, cdr = p.ToList() })
.Select(p => new
{
number = p.number,
cdr =
p.cdr.Select((value, index) => index == 0 ? null : (TimeSpan?)(value.CallTime - p.cdr[index - 1].CallTime))
.FirstOrDefault(q => q.HasValue && q.Value.TotalSeconds < 10)
}).Where(p => p.cdr != null);
In two steps :
Generate an enumeration with the call itself and all calls in the interesting span
Filter this list to find consecutive calls
The computation is done in parallel on each record using the AsParallel extension method.
It is also possible to not call the ToArray at the end and let the computation be done while other code could execute on the thread instead of forcing it to wait for the parallel computation to finish.
var records = new [] {
new { CallTime= DateTime.Now, NumberDialled = 1 },
new { CallTime= DateTime.Now.AddSeconds(1), NumberDialled = 1 }
};
var span = TimeSpan.FromSeconds(10);
// Select for each call itself and all other calls in the next 'span' seconds
var callInfos = records.AsParallel()
.Select((r, i) =>
new
{
Record = r,
Following = records.Skip(i+1)
.TakeWhile(r2 => r2.CallTime - r.CallTime < span)
}
);
// Filter the calls that interest us
var problematic = (from callinfo in callInfos
where callinfo.Following.Any(r => callinfo.Record.NumberDialled == r.NumberDialled)
select callinfo.Record)
.ToArray();
If performance is acceptable (which I think it should be, since 100k records is not particularly many), this approach is (I think) nice and clean:
First we group up the records by number:
var byNumber =
from cdr in calls
group cdr by cdr.NumberDialled into g
select new
{
NumberDialled = g.Key,
Calls = g.OrderBy(cdr => cdr.CallTime)
};
What we do now is Zip (.NET 4) each calls collection with itself-shifted-by-one, to transform the list of call times into a list of gaps between calls. We then look for numbers where there's a gap of at most 10 seconds:
var interestingNumbers =
from g in byNumber
let callGaps = g.Calls.Zip(g.Calls.Skip(1),
(cdr1, cdr2) => cdr2.CallTime - cdr1.CallTime)
where callGaps.Any(ts => ts.TotalSeconds <= 10)
select g.NumberDialled;
Now interestingNumbers is a sequence of the numbers of interest.

LINQ changing local "let" variable

Using a LINQ query (with C#) how would I go about do something like this (pseudocode)?
I'd look to do something like this is in places where, for example, I might generate 1000's of lists of 100's of random (bounded) integers, where I want to track the smallest of them as they're generated.
Best <- null value
Foreach N in Iterations
NewList <- List of 100 randomly generated numbers
If Best is null
Best <- NewList
If Sum(NewList) < Sum(Best)
Best <- NewList
Select Best
I've tried all sorts of things, but I can't really get it working. This isn't for any kind of project or work, just for my own curiosity!
Example of what I was thinking:
let R = new Random()
let Best = Enumerable.Range(0, 100).Select(S => R.Next(-100, 100)).ToArray()
//Where this from clause is acting like a for loop
from N in Iterations
let NewList = Enumerable.Range(0, 100).Select(S => R.Next(-100, 100))
Best = (NewList.Sum() < Best.Sum())? NewList : Best;
select Best
I believe you are looking for fold (aka "reduce") which is known as Aggregate in LINQ.
(IEnumerable.Min/Max are special-cases, but can be written in terms of fold/Aggregate.)
int Max (IEnumerable<int> x) {
return x.Aggregate(int.MinValue, (prev, cur) => prev > cur ? prev : cur);
}
Max(new int[] { 1, 42, 2, 3 }); // 42
Happy coding.
Looks like you're just selecting the minimum value.
var minimum = collection.Min( c => c );
You are effectively finding the minimum value in the collection, if it exists:
int? best = null;
if (collection != null && collection.Length > 0) best = collection.Min();

Categories

Resources