using LINQ getting previous and next element - linq

One of my colleague was looking for something like picking up previous and next values from a list for a given value. I wrote a little function with some help of Google, which works but I wanted to see
1. if is this an efficient way to do this?
2. Any other way in LINQ to do this?
private static List<double> GetHighLow(double value)
{
List<double> tenorList = new List<double> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 20, 30 };
double previous = tenorList.OrderByDescending(s => s).Where(s => s.CompareTo(value) < 0).FirstOrDefault();
double next = tenorList.OrderBy(s => s).Where(s => s.CompareTo(value) > 0).FirstOrDefault();
List<double> values = new List<double> { previous, next };
return values;
}
thanks
Pak

Ordering just to find a single item would make me suspicious.
You can do it in linear time this way:
double prev = double.MinValue;
double nx = double.MaxValue;
foreach (var item in tenorList) {
if (item < value && item > prev) { prev = item; }
if (item > value && item < nx) { nx = item; }
}
List<double> values = new List<double> { prev, nx };

Related

Print all possible course schedules algorithm

This is famous course schedule question, but want to print out all possible course schedules.
Q: There are ‘N’ courses, labeled from ‘0’ to ‘N-1’.
Each course can have some prerequisite courses which need to be completed before it can be scheduled.
Given the number of courses and a list of prerequisite pairs, write a method to print all possible ordering of courses meeting all prerequisites.
Assume that there is no cycle.
For the example in the main method, need to print
[3, 2, 0, 1]
[3, 2, 1, 0]
but my code prints only one of them
[3, 2, 1, 0]
Backtracking is needed to make it work, but at some point my backtracking is wrong and not sure how to fix this since it keeps choosing the same order after backtrack. Once chose 1, 0 and then backtrack, it should choose 0, 1, but keeps choosing the same order 1, 0.
Can someone help me to make it work?
class AllCourseOrders {
static Map<Integer, List<Integer>> map = null;
static int[] visited = null;
static int n = 0;
public static void printOrders(int courses, int[][] prerequisites) {
List<Integer> sortedOrder = new ArrayList<>();
// init
n = courses;
visited = new int[courses];
map = new HashMap<>();
for(int i =0; i < courses; i++)
map.put(i, new ArrayList<>());
// 1. build graph
for(int[] pre: prerequisites) {
int from = pre[0], to = pre[1];
List<Integer> list = map.get(from);
list.add(to);
}
// 2. dfs
List<List<Integer>> results = new ArrayList<List<Integer>>();
List<Integer> result = new ArrayList<>();
for(Integer u: map.keySet()) {
if(visited[u] == 0) {
dfs(u, result, results);
if(result.size() == n) {
results.add(new ArrayList<>(result));
result.remove(result.size()-1);
visited[u] = 0;
}
}
}
results.forEach(res -> System.out.println(res));
}
static void dfs(Integer u, List<Integer> result, List<List<Integer>> results) {
visited[u] = 1;
for(Integer v: map.get(u)) {
if(visited[v] == 0 ) {
dfs(v, result, results);
}
}
visited[u] = 2;
result.add(0, u);
}
public static void main(String[] args) {
printOrders(4, new int[][] { new int[] { 3, 2 }, new int[] { 3, 0 }, new int[] { 2, 0 }, new int[] { 2, 1 } });
}
}
Your algorithm finds the first solution it can, not every single one. Every time you are presented with multiple vertices to take next (you can take different starting nodes, certain class you can take in any order), each choice will lead to a different result.
The course problem is simply trying to topologically sort a directed, acyclic graph. GeeksForGeeks provides the algorithm on their site in java, where the vertices are the courses and the edges are the prereqs.

How to use LINQ to find all items in list which have the most members in another list?

Given:
class Item {
public int[] SomeMembers { get; set; }
}
var items = new []
{
new Item { SomeMembers = new [] { 1, 2 } }, //0
new Item { SomeMembers = new [] { 1, 2 } }, //1
new Item { SomeMembers = new [] { 1 } } //2
}
var secondList = new int[] { 1, 2, 3 };
I need to find all the Items in items with the most of it's SomeMembers occurring in secondList.
In the example above I would expect Items 0 and 1 to be returned but not 2.
I know I could do it with things like loops or Contains() but it seems there must be a more elegant or efficient way?
This can be written pretty easily:
var result = items.Where(item => item.SomeMembers.Count(secondList.Contains) * 2
>= item.SomeMembers.Length);
Or possibly (I can never guess whether method group conversions will work):
var result = items.Where(item => item.SomeMembers.Count(x => secondList.Contains(x)) * 2
>= item.SomeMembers.Length);
Or to pull it out:
Func<int, bool> inSecondList = secondList.Contains;
var result = items.Where(item => item.SomeMembers.Count(inSecondList) * 2
>= item.SomeMembers.Length);
If secondList becomes large, you should consider using a HashSet<int> instead.
EDIT: To avoid evaluating SomeMembers twice, you could create an extension method:
public static bool MajoritySatisfied<T>(this IEnumerable<T> source,
Func<T, bool> condition)
{
int total = 0, satisfied = 0;
foreach (T item in source)
{
total++;
if (condition(item))
{
satisfied++;
}
}
return satisfied * 2 >= total;
}
Then:
var result = items.Where(item => item.MajoritySatisfied(secondList.Contains));

Algorithm to find continuous days in a week

The user can select any number of week days from a list. An algorithm shall find the longest continuous group of selected days. The start day can be after the end day, if the group spans two weeks. If it makes it simpler, only a group of at least 3 days needs to be detected. With crossing the week border, this makes for a maximum of one group. (There can be no two groups of 3 days within a week that are not connected.)
For example, if the user selects Monday, Tuesday, Wednesday and Saturday from a list, the display should be something like "Monday-Wednesday and Saturday".
Another example is: Wed, Fri, Sat, Sun, Mon -> "Wed, Fri-Mon".
Is there an efficient algorithm for that, preferrably in C# or a similar language? My C# hackwork is now over a page long (incl. few comments) and still not finished.
Use this answer, slightly changed:
Use a modified version of dtb's GroupAdjacentBy which accepts a minCount as a parameter:
public static IEnumerable<IEnumerable<T>> GroupAdjacentBy<T>(
this IEnumerable<T> source, Func<T, T, bool> predicate, int minCount)
{
using (var e = source.GetEnumerator())
{
if (e.MoveNext())
{
var list = new List<T> { e.Current };
var pred = e.Current;
while (e.MoveNext())
{
// if adjacent, add to list
if (predicate(pred, e.Current))
{
list.Add(e.Current);
}
else
{
// otherwise return previous elements:
// if less than minCount elements,
// return each element separately
if (list.Count < minCount)
{
foreach (var i in list)
yield return new List<T> { i };
}
else
{
// otherwise return entire group
yield return list;
}
// create next group
list = new List<T> { e.Current };
}
pred = e.Current;
}
yield return list;
}
}
}
and change the criteria for GroupAdjacentBy to group on week transitions also:
// week starts with Monday, so this should
// represent: Wed, Fri, Sat, Sun, Mon
int[] array = new int[] { 1, 2, 4, 5, 6, 0 };
Func<int, int, bool> adjacentCriteria = (x, y) => (x+1==y) || (x==6 && y==0);
string result = string.Join(", ", array
.GroupAdjacentBy(adjacentCriteria, 3)
.Select(g => new int[] { g.First(), g.Last() }.Distinct())
.Select(g => string.Join("-", g)));
Console.WriteLine(result); // output: 1, 2, 4-0
I've finished my version of it. It's a bit longer than the other one, but then again it also handles the text representation and does exactly this task. How about that?
using System;
using System.Text;
namespace WeekMathTest
{
class Program
{
static void Main(string[] args)
{
string[] weekDayNames = new string[] {
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat",
"Sun"
};
WeekDays weekDays = WeekDays.Monday | WeekDays.Tuesday | WeekDays.Thursday | WeekDays.Saturday | WeekDays.Sunday;
Console.WriteLine(WeekDayGroup(weekDays, weekDayNames));
}
static string WeekDayGroup(WeekDays weekDays, string[] weekDayNames)
{
int groupStart = 0, groupEnd = 0, groupLength = 0;
int maxGroupStart = 0, maxGroupEnd = 0, maxGroupLength = 0;
// Iterate all days in a repeated range
// (Sat/Sun doesn't need to be repeated or it would be in the first group)
for (int day = 1; day <= 7 + 5; day++)
{
// Is this day set?
int bitValue = 1 << ((day - 1) % 7);
bool daySet = ((int) weekDays & bitValue) != 0;
if (daySet)
{
if (groupStart == 0)
{
// First day set, remember it as group start
groupStart = day;
groupEnd = day;
groupLength = 1;
}
else
{
// Group has already been started, set new end
groupEnd = day;
groupLength = groupEnd - groupStart + 1;
if (groupLength == 7)
{
// Seen every day of the week, stop here
break;
}
}
}
else
{
if (groupLength >= 3 && groupLength > maxGroupLength)
{
// Group was long enough and longer than the last one, save it
maxGroupStart = groupStart;
maxGroupEnd = groupEnd;
maxGroupLength = groupLength;
}
// Reset operation variables
groupStart = 0;
groupEnd = 0;
groupLength = 0;
}
}
// Final check
if (groupLength >= 3 && groupLength > maxGroupLength)
{
// Group was long enough and longer than the last one, save it
maxGroupStart = groupStart;
maxGroupEnd = groupEnd;
maxGroupLength = groupLength;
}
// Clear all group days from the original value
for (int day = maxGroupStart; day <= maxGroupEnd; day++)
{
int bitValue = 1 << ((day - 1) % 7);
weekDays = (WeekDays) ((int) weekDays & ~bitValue);
}
// Generate output string
StringBuilder sb = new StringBuilder();
for (int day = 1; day <= 7; day++)
{
int bitValue = 1 << ((day - 1) % 7);
bool daySet = ((int) weekDays & bitValue) != 0;
if (daySet)
{
if (sb.Length > 0) sb.Append(", ");
sb.Append(weekDayNames[day - 1]);
}
else if (day == maxGroupStart)
{
if (sb.Length > 0) sb.Append(", ");
sb.Append(weekDayNames[day - 1]);
sb.Append("-");
sb.Append(weekDayNames[(maxGroupEnd - 1) % 7]);
}
}
return sb.ToString();
}
[Flags]
enum WeekDays
{
Monday = 1,
Tuesday = 2,
Wednesday = 4,
Thursday = 8,
Friday = 16,
Saturday = 32,
Sunday = 64
}
}
}

How to get the Max() of a Count() with LINQ

I'm new to LINQ and I have this situation. I have this table:
ID Date Range
1 10/10/10 9-10
2 10/10/10 9-10
3 10/10/10 9-10
4 10/10/10 8-9
5 10/11/10 1-2
6 10/11/10 1-2
7 10/12/10 5-6
I just want to list the Maximun of rows per date by range, like this:
Date Range Total
10/10/10 9-10 3
10/11/10 1-2 2
10/12/10 5-6 1
I want to do this by using LINQ, do you have any ideas of how to do this?
I think something along these lines should work:
List<MyTable> items = GetItems();
var orderedByMax = from i in items
group i by i.Date into g
let q = g.GroupBy(i => i.Range)
.Select(g2 => new {Range = g2.Key, Count = g2.Count()})
.OrderByDescending(i => i.Count)
let max = q.FirstOrDefault()
select new {
Date = g.Key,
Range = max.Range,
Total = max.Count
};
Using extension methods:
List<MyTable> items = GetItems();
var rangeTotals = items.GroupBy(x => new { x.Date, x.Range }) // Group by Date + Range
.Select(g => new {
Date = g.Key.Date,
Range = g.Key.Range,
Total = g.Count() // Count total of identical ranges per date
});
var rangeMaxTotals = rangeTotals.Where(rt => !rangeTotals.Any(z => z.Date == rt.Date && z.Total > rt.Total)); // Get maximum totals for each date
unfortunately I can't test this at the moment but give this a try:
List<MyTable> items = GetItems();
items.Max(t=>t.Range.Distinct().Count());
This approach:
1) Groups by Date
2) For each Date, groups by Range and calculates the Total
3) For each Date, selects the item with the greatest Total
4) You end up with your result
public sealed class Program
{
public static void Main(string[] args)
{
var items = new[]
{
new { ID = 1, Date = new DateTime(10, 10, 10), Range = "9-10" },
new { ID = 2, Date = new DateTime(10, 10, 10), Range = "9-10" },
new { ID = 3, Date = new DateTime(10, 10, 10), Range = "9-10" },
new { ID = 4, Date = new DateTime(10, 10, 10), Range = "8-9" },
new { ID = 5, Date = new DateTime(10, 10, 11), Range = "1-2" },
new { ID = 6, Date = new DateTime(10, 10, 11), Range = "1-2" },
new { ID = 7, Date = new DateTime(10, 10, 12), Range = "5-6" },
};
var itemsWithTotals = items
.GroupBy(item => item.Date) // Group by Date.
.Select(groupByDate => groupByDate
.GroupBy(item => item.Range) // Group by Range.
.Select(groupByRange => new
{
Date = groupByDate.Key,
Range = groupByRange.Key,
Total = groupByRange.Count()
}) // Got the totals for each grouping.
.MaxElement(item => item.Total)); // For each Date, grab the item (grouped by Range) with the greatest Total.
foreach (var item in itemsWithTotals)
Console.WriteLine("{0} {1} {2}", item.Date.ToShortDateString(), item.Range, item.Total);
Console.Read();
}
}
/// <summary>
/// From the book LINQ in Action, Listing 5.35.
/// </summary>
static class ExtensionMethods
{
public static TElement MaxElement<TElement, TData>(this IEnumerable<TElement> source, Func<TElement, TData> selector) where TData : IComparable<TData>
{
if (source == null)
throw new ArgumentNullException("source");
if (selector == null)
throw new ArgumentNullException("selector");
bool firstElement = true;
TElement result = default(TElement);
TData maxValue = default(TData);
foreach (TElement element in source)
{
var candidate = selector(element);
if (firstElement || (candidate.CompareTo(maxValue) > 0))
{
firstElement = false;
maxValue = candidate;
result = element;
}
}
return result;
}
}
According to LINQ in Action (Chapter 5.3.3 - Will LINQ to Objects hurt the performance of my code?), using the MaxElement extension method is one of the most effecient approaches. I think the performance would be O(4n); one for the first GroupBy, two for the second GroupBy, three for the Count(), and four for loop within MaxElement.
DrDro's approach is going to be more like O(n^2) since it loops the entire list for each item in the list.
StriplingWarrior's approach is going to be closer to O(n log n) because it sorts the items. Though I'll admit, there may be some crazy magic in there that I don't understand.

How do I transfer this logic into a LINQ statement?

I can't get this bit of logic converted into a Linq statement and it is driving me nuts. I have a list of items that have a category and a createdondate field. I want to group by the category and only return items that have the max date for their category.
So for example, the list contains items with categories 1 and 2. The first day (1/1) I post two items to both categories 1 and 2. The second day (1/2) I post three items to category 1. The list should return the second day postings to category 1 and the first day postings to category 2.
Right now I have it grouping by the category then running through a foreach loop to compare each item in the group with the max date of the group, if the date is less than the max date it removes the item.
There's got to be a way to take the loop out, but I haven't figured it out!
You can do something like that :
from item in list
group item by item.Category into g
select g.OrderByDescending(it => it.CreationDate).First();
However, it's not very efficient, because it needs to sort the items of each group, which is more complex than necessary (you don't actually need to sort, you just need to scan the list once). So I created this extension method to find the item with the max value of a property (or function) :
public static T WithMax<T, TValue>(this IEnumerable<T> source, Func<T, TValue> selector)
{
var max = default(TValue);
var withMax = default(T);
var comparer = Comparer<TValue>.Default;
bool first = true;
foreach (var item in source)
{
var value = selector(item);
int compare = comparer.Compare(value, max);
if (compare > 0 || first)
{
max = value;
withMax = item;
}
first = false;
}
return withMax;
}
You can use it as follows :
from item in list
group item by item.Category into g
select g.WithMax(it => it.CreationDate);
UPDATE : As Anthony noted in his comment, this code doesn't exactly answer the question... if you want all items which date is the maximum of their category, you can do something like that :
from item in list
group item by item.Category into g
let maxDate = g.Max(it => it.CreationDate)
select new
{
Category = g.Key,
Items = g.Where(it => it.CreationDate == maxDate)
};
How about this:
private class Test
{
public string Category { get; set; }
public DateTime PostDate { get; set; }
public string Post { get; set; }
}
private void Form1_Load(object sender, EventArgs e)
{
List<Test> test = new List<Test>();
test.Add(new Test() { Category = "A", PostDate = new DateTime(2010, 5, 5, 12, 0, 0), Post = "A1" });
test.Add(new Test() { Category = "B", PostDate = new DateTime(2010, 5, 5, 13, 0, 0), Post = "B1" });
test.Add(new Test() { Category = "A", PostDate = new DateTime(2010, 5, 6, 12, 0, 0), Post = "A2" });
test.Add(new Test() { Category = "A", PostDate = new DateTime(2010, 5, 6, 13, 0, 0), Post = "A3" });
test.Add(new Test() { Category = "A", PostDate = new DateTime(2010, 5, 6, 14, 0, 0), Post = "A4" });
var q = test.GroupBy(t => t.Category).Select(g => new { grp = g, max = g.Max(t2 => t2.PostDate).Date }).SelectMany(x => x.grp.Where(t => t.PostDate >= x.max));
}
Reformatting luc's excellent answer to query comprehension form. I like this better for this kind of query because the scoping rules let me write more concisely.
from item in source
group item by item.Category into g
let max = g.Max(item2 => item2.PostDate).Date
from item3 in g
where item3.PostDate.Date == max
select item3;

Resources