Linq - return index of collection using conditional logic - linq

I have a collection
List<int> periods = new List<int>();
periods.Add(0);
periods.Add(30);
periods.Add(60);
periods.Add(90);
periods.Add(120);
periods.Add(180);
var overDueDays = 31;
I have a variable over due days. When the vale is between 0 to 29 then I want to return the index of 0. When between 30 - 59 I want to return index 1. The periods list is from db so its not hard coded and values can be different from what are here. What is the best way to to it using LINQ in one statement.

It's not really what Linq is designed for, but (assuming that the range is not fixed) you could do the following to get the index
List<int> periods = new List<int>();
periods.Add(0);
periods.Add(30);
periods.Add(60);
periods.Add(90);
periods.Add(120);
periods.Add(180);
var overDueDays = 31;
var result = periods.IndexOf(periods.First(n => overDueDays < n)) - 1;

You can use .TakeWhile():
int periodIndex = periods.TakeWhile(p => p <= overDueDays).Count() - 1;

how about this ?
var qPeriods = periods.Where(v => v <= overDueDays)
.Select((result, i) => new { index = i })
.Last();

Assuming that periods is sorted, you can use the following approach:
var result = periods.Skip(1)
.Select((o, i) => new { Index = i, Value = o })
.FirstOrDefault(o => overDueDays < o.Value);
if (result != null)
{
Console.WriteLine(result.Index);
}
else
{
Console.WriteLine("Matching range not found!");
}
The first value is skipped since we're interested in comparing with the upper value of the range. By skipping it, the indices fall into place without the need to subtract 1. FirstOrDefault is used in case overDueDays doesn't fall between any of the available ranges.

Related

Minimum number of steps using only multiply A by 2, or divide A by 2 or increment A by one to go from number A to B

Given two numbers A and B, what is the minimum number of steps to transform number A to become number B.
A step can either be A *= 2, A++ or A /= 2 if and only if A is an even number.
What is the most efficient algorithm to achieve this?
Suppose A and B can be really large numbers.
Here's my take, done in C#.
var a = 2;
var b = 15;
var found = new HashSet<int>() { a };
var operations = new (string operation, Func<int, bool> condition, Func<int, int> projection)[]
{
("/2", x => x % 2 == 0, x => x / 2),
("*2", x => x <= int.MaxValue / 2, x => x *2),
("+1", x => true, x => x + 1),
};
IEnumerable<(int count, string operations, int value)> Project((int count, string operations, int value) current)
{
foreach (var operation in operations)
{
if (operation.condition(current.value))
{
var value = operation.projection(current.value);
if (!found.Contains(value))
{
found.Add(value);
yield return (current.count + 1, $"{current.operations}, {operation.operation}", value);
}
}
}
}
var candidates = new[] { (count: 0, operations: $"{a}", value: a) };
while (!found.Contains(b))
{
candidates =
candidates
.SelectMany(c => Project(c))
.ToArray();
}
var result = candidates.Where(x => x.value == b).First();
Console.WriteLine($"{result.count} operations: {result.operations} = {result.value}");
That outputs:
5 operations: 2, +1, *2, +1, *2, +1 = 15
Basically, this is starting with a at the zeroth step. It then takes this generation and produces all possible values from the operations to create the next generation. If it produces a value that it has already seen it discards the value as there is an equal or faster operation to produce the value. It keeps repeating until b is found.

Ranking in Linq

There's a generic list of numbers, say
{980, 850,700, 680}---n nos.
I try to compare the above list with a decimal no. say 690., the O/p I need is,to get the ranking of the number which I'm gonna input("692). i,e the desired O/P should be Ranking ="4"
How can I get the O/p for above scenario..??
Following on from Alex's post I think you are looking for
var numbers = new List<int>() { 980, 850, 700, 680 };
var dec = new Decimal(692.0);
var temp = numbers.Count(x => x > dec) + 1;
this will return the position you are looking for
If you want to look for an exact match of a decimal input to a int on the list,you can use FindIndex.
var numbers = new List<int>() { 980, 850, 700, 680 };
var dec = new Decimal(680.0);
var res = numbers.FindIndex(x => x == dec);
It returns the 0-based position of the match.
Your question is not clear, i'm not sure what role 690 is playing.
Assuming that the user can ernter a number and you want to find the rank(index) of the number in the list when it would be inserted. Assuming also that your list should be sorted descending since you want the position of the new int according to it's value:
var input = 692;
var numbers = new List<int>() { 980, 850, 700, 680 };
var firstLower = numbers.OrderByDescending(i => i)
.Select((i, index) => new { Value = i, Index = index })
.FirstOrDefault(x => x.Value < input);
var rank = firstLower == null ? numbers.Count + 1 : firstLower.Index + 1;
Note that the OrderByDescending might be redundant if your list is already sorted, but i assume that your sample data is only sorted accidentally.

LINQ Grouping: Is there a cleaner way to do this without a for loop

I am trying to create a very simple distribution chart and I want to display the counts of tests score percentages in their corresponding 10's ranges.
I thought about just doing the grouping on the Math.Round((d.Percentage/10-0.5),0)*10 which should give me the 10's value....but I wasn't sure the best way to do this given that I would probably have missing ranges and all ranges need to appear even if the count is zero. I also thought about doing an outer join on the ranges array but since I'm fairly new to Linq so for the sake of time I opted for the code below. I would however like to know what a better way might be.
Also note: As I tend to work with larger teams with varying experience levels, I'm not all that crazy about ultra compact code unless it remains very readable to the average developer.
Any suggestions?
public IEnumerable<TestDistribution> GetDistribution()
{
var distribution = new List<TestDistribution>();
var ranges = new int[] { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110 };
var labels = new string[] { "0%'s", "10%'s", "20%'s", "30%'s", "40%'s", "50%'s", "60%'s", "70%'s", "80%'s", "90%'s", "100%'s", ">110% "};
for (var n = 0; n < ranges.Count(); n++)
{
var count = 0;
var min = ranges[n];
var max = (n == ranges.Count() - 1) ? decimal.MaxValue : ranges[n+1];
count = (from d in Results
where d.Percentage>= min
&& d.Percentage<max
select d)
.Count();
distribution.Add(new TestDistribution() { Label = labels[n], Frequency = count });
}
return distribution;
}
// ranges and labels in a list of pairs of them
var rangesWithLabels = ranges.Zip(labels, (r,l) => new {Range = r, Label = l});
// create a list of intervals (ie. 0-10, 10-20, .. 110 - max value
var rangeMinMax = ranges.Zip(ranges.Skip(1), (min, max) => new {Min = min, Max = max})
.Union(new[] {new {Min = ranges.Last(), Max = Int32.MaxValue}});
//the grouping is made by the lower bound of the interval found for some Percentage
var resultsDistribution = from c in Results
group c by
rangeMinMax.FirstOrDefault(r=> r.Min <= c.Percentage && c.Percentage < r.Max).Min into g
select new {Percentage = g.Key, Frequency = g.Count() };
// left join betweem the labels and the results with frequencies
var distributionWithLabels =
from l in rangesWithLabels
join r in resultsDistribution on l.Range equals r.Percentage
into rd
from r in rd.DefaultIfEmpty()
select new TestDistribution{
Label = l.Label,
Frequency = r != null ? r.Frequency : 0
};
distribution = distributionWithLabels.ToList();
Another solution if the ranges and labels can be created in another way
var ranges = Enumerable.Range(0, 10)
.Select(c=> new {
Min = c * 10,
Max = (c +1 )* 10,
Label = (c * 10) + "%'s"})
.Union(new[] { new {
Min = 100,
Max = Int32.MaxValue,
Label = ">110% "
}});
var resultsDistribution = from c in Results
group c by ranges.FirstOrDefault(r=> r.Min <= c.Percentage && c.Percentage < r.Max).Min
into g
select new {Percentage = g.Key, Frequency = g.Count() };
var distributionWithLabels =
from l in ranges
join r in resultsDistribution on l.Min equals r.Percentage
into rd
from r in rd.DefaultIfEmpty()
select new TestDistribution{
Label = l.Label,
Frequency = r != null ? r.Frequency : 0
};
This works
public IEnumerable<TestDistribution> GetDistribution()
{
var range = 12;
return Enumerable.Range(0, range).Select(
n => new TestDistribution
{
Label = string.Format("{1}{0}%'s", n*10, n==range-1 ? ">" : ""),
Frequency =
Results.Count(
d =>
d.Percentage >= n*10
&& d.Percentage < ((n == range - 1) ? decimal.MaxValue : (n+1)*10))
});
}

LINQ: GroupBy with maximum count in each group

I have a list of duplicate numbers:
Enumerable.Range(1,3).Select(o => Enumerable.Repeat(o, 3)).SelectMany(o => o)
// {1,1,1,2,2,2,3,3,3}
I group them and get quantity of occurance:
Enumerable.Range(1,3).Select(o => Enumerable.Repeat(o, 3)).SelectMany(o => o)
.GroupBy(o => o).Select(o => new { Qty = o.Count(), Num = o.Key })
Qty Num
3 1
3 2
3 3
What I really need is to limit the quantity per group to some number. If the limit is 2 the result for the above grouping would be:
Qty Num
2 1
1 1
2 2
1 2
2 3
1 3
So, if Qty = 10 and limit is 4, the result is 3 rows (4, 4, 2). The Qty of each number is not equal like in example. The specified Qty limit is the same for whole list (doesn't differ based on number).
Thanks
Some of the other answers are making the LINQ query far more complex than it needs to be. Using a foreach loop is certainly faster and more efficient, but the LINQ alternative is still fairly straightforward.
var input = Enumerable.Range(1, 3).SelectMany(x => Enumerable.Repeat(x, 10));
int limit = 4;
var query =
input.GroupBy(x => x)
.SelectMany(g => g.Select((x, i) => new { Val = x, Grp = i / limit }))
.GroupBy(x => x, x => x.Val)
.Select(g => new { Qty = g.Count(), Num = g.Key.Val });
There was a similar question that came up recently asking how to do this in SQL - there's no really elegant solution and unless this is Linq to SQL or Entity Framework (i.e. being translated into a SQL query), I'd really suggest that you not try to solve this problem with Linq and instead write an iterative solution; it's going to be a great deal more efficient and easier to maintain.
That said, if you absolutely must use a set-based ("Linq") method, this is one way you could do it:
var grouped =
from n in nums
group n by n into g
select new { Num = g.Key, Qty = g.Count() };
int maxPerGroup = 2;
var portioned =
from x in grouped
from i in Enumerable.Range(1, grouped.Max(g => g.Qty))
where (x.Qty % maxPerGroup) == (i % maxPerGroup)
let tempQty = (x.Qty / maxPerGroup) == (i / maxPerGroup) ?
(x.Qty % maxPerGroup) : maxPerGroup
select new
{
Num = x.Num,
Qty = (tempQty > 0) ? tempQty : maxPerGroup
};
Compare with the simpler and faster iterative version:
foreach (var g in grouped)
{
int remaining = g.Qty;
while (remaining > 0)
{
int allotted = Math.Min(remaining, maxPerGroup);
yield return new MyGroup(g.Num, allotted);
remaining -= allotted;
}
}
Aaronaught's excellent answer doesn't cover the possibility of getting the best of both worlds... using an extension method to provide an iterative solution.
Untested:
public static IEnumerable<IEnumerable<U>> SplitByMax<T, U>(
this IEnumerable<T> source,
int max,
Func<T, int> maxSelector,
Func<T, int, U> resultSelector
)
{
foreach(T x in source)
{
int number = maxSelector(x);
List<U> result = new List<U>();
do
{
int allotted = Math.Min(number, max);
result.Add(resultSelector(x, allotted));
number -= allotted
} while (number > 0 && max > 0);
yield return result;
}
}
Called by:
var query = grouped.SplitByMax(
10,
o => o.Qty,
(o, i) => new {Num = o.Num, Qty = i}
)
.SelectMany(split => split);

LINQ: check whether two list are the same

This should be easy.
I want to check whether two list are the same in that they contain all the same elements or not, orders not important.
Duplicated elements are considered equal, i.e.e, new[]{1,2,2} is the same with new[]{2,1}
var same = list1.Except(list2).Count() == 0 &&
list2.Except(list1).Count() == 0;
Edit: This was written before the OP added that { 1, 2, 2 } equals { 1, 1, 2 } (regarding handling of duplicate entries).
This will work as long as the elements are comparable for order.
bool equal = list1.OrderBy(x => x).SequenceEqual(list2.OrderBy(x => x));
The SetEquals of HashSet is best suited for checking whether two sets are equal as defined in this question
string stringA = "1,2,2";
string stringB = "2,1";
HashSet<string> setA = new HashSet<string>((stringA.Trim()).Split(',').Select(t => t.Trim()));
HashSet<string> setB = new HashSet<string>((stringB.Trim()).Split(',').Select(t => t.Trim()));
bool isSetsEqual = setA.SetEquals(setB);
REFERENCE:
Check whether two comma separated strings are equal (for Content set)
You need to get the intersection of the two lists:
bool areIntersected = t1.Intersect(t2).Count() > 0;
In response to you're modified question:
bool areSameIntersection = t1.Except(t2).Count() == 0 && t2.Except(t1).Count() == 0;
If the count of list1 elements in list2 equals the count of list2 elements in list1, then the lists both contain the same number of elements, are both subsets of each other - in other words, they both contain the same elements.
if (list1.Count(l => list2.Contains(l)) == list2.Count(l => list1.Contains(l)))
return true;
else
return false;

Resources