Using LINQ how can I prioritise an IEnumerable based on ranges? - c#

If my data is a List<Cat> and each Cat has an age of 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
I want to re-order the list according to the ranges 1-3, 4-8, 9-10
such that the resulting list will be
3, 2, 1, 8, 7, 6, 5, 4, 10, 9
How can I do that?
I first considered creating three queries:
var band1 = myList.Where(c => c <= 3);
var band2 = myList.Where(c => c => 4 && c <= 8);
var band3 = myList.Where(c => c >= 9);
but then I don't know how to combine the results using LINQ, as I have three IEnumerable<Cat>.
What's the correct method to use, or do I have to use a foreach?

You can use Concat.
var band1 = myList.Where(c => c <= 3);
var band2 = myList.Where(c => c => 4 && c <= 8);
var band3 = myList.Where(c => c >= 9);
var result = band1.Concat(band2).Concat(band3);

Create a GetRange(Cat cat) method order your list by it:
myCatList.OrderBy(cat=>GetRange(cat));

This is how you would use a single LINQ statement to do what you're asking for.
var ordered = items
.OrderBy(x => {
if(x <= 3) return 0;
if(x <= 8) return 1;
return 2;
})
.ThenByDescending(x => x);
Alternatively (assuming the items are already in descending order):
var ordered = myList
.OrderBy(x => {
if (x <= 3) { return 1; }
if (x <= 8) { return 2; }
return 3;
});

//define your ranges
var ranges = new[] { 3,8,10};
var grouped = listOfCats.GroupBy( x => ranges.FirstOrDefault( r => r >= x.Age ) );

All you need is to concat the IEnumerable<int> with IEnumerable<int>.Concat()
var band1 = myList.Where(c => c <= 3);
var band2 = myList.Where(c => c >= 4 && c <= 8);
var band3 = myList.Where(c => c >= 9);
List<int> finalList = band1.Concat(band2).Concat(band3).ToList();

Related

Select last wrong item from the list

I have a list with items, that have a Time property. If I want to select all items where Time is equal or bigger then some startTime, then I write something like this:
var newList = list.Where(i => (i.Time >= startTime));
But now I also want to get the last item, where the time is smaller than startTime. Is there a better way to implement this?
For example I have list where items have Time from this list:
[5:32, 5:46, 5:51, 6:07, 6:11, 6:36]
We specify a startTime as 6:00.
Now we want to get this times:
[5:51, 6:07, 6:11, 6:36]
Getting the whole List at once:
var newList = list
.OrderByDescending(i => i.Time)
.Take(list.Count(j => j.Time >= startTime) + 1)
.OrderBy(k => k.Time); //Optional
With Cognition's suggestion:
var newList = list
.OrderBy(i => i.Time)
.Skip(list.Count(j => j.Time < startTime - 1));
var result=list
.Where(i=>i.Time<startTime)
.OrderBy(i=>i.Time)
.Last()
.Concat(list
.OrderBy(i=>i.Time)
.Where(i=>i.Time>=startTime)
);
or
var result=list
.OrderBy(i=>i.Time)
.Last(i=>i.Time<startTime)
.Concat(list
.OrderBy(i=>i.Time)
.Where(i=>i.Time>=startTime)
);
var smallerThan = list
.Where(i => i.Time < startTime)
.OrderByDescending(o => o.Time)
.Take(1)
.Concat(list.Where(i => i.Time => startTime));
As your list is in order of the property you want to find, you can do something along the lines of
List<int> things = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8 };
int threshold = 4;
var newThings = things.Skip(things.FindIndex(x => x >= threshold) - 1);
Console.WriteLine(string.Join(", ", newThings));
Which outputs
3, 4, 5, 6, 7, 8
Extending it to use a class with a Time property which happens to be a TimeSpan:
class Z
{
public TimeSpan Time { get; set; }
};
class Program
{
static void Main(string[] args)
{
Random rand = new Random();
List<Z> zs = new List<Z>();
for (int i = 0; i < 10; i++)
{
zs.Add(new Z { Time = new TimeSpan(i, rand.Next(0,61), rand.Next(0,61)) });
}
TimeSpan threshold = new TimeSpan(4,0,0);
var newThings = zs.Skip(zs.FindIndex(x => x.Time >= threshold) - 1);
Console.WriteLine(string.Join(", ", newThings.Select(x => x.Time.ToString("c"))));
Console.ReadLine();
}
}
Sample output:
03:03:57, 04:09:37, 05:14:44, 06:58:55, 07:40:33, 08:37:06, 09:10:06
Many of the answers seem to require a descending orderby. But you can easily avoid this with a clean one liner and good efficiency:
var newList = list.Skip(list.Count(j => j.Time < startTime) - 1);
var newList = list
.Where(i => (i.Time >= startTime))
.ToList()
.Add(list
.Where(i => (i.Time < startTime))
.OrderByDescending(o => o.Time)
.FirstOrDefault()
)
int lastItemIndex = list.OrderBy(D => D.TimeOfDay).ToList()
.FindLastIndex(D => D.TimeOfDay < startTime);
var newList = list.Where(D => list.IndexOf(D) > lastItemIndex);

Split large integer array into groups of multiples using one performant LINQ query

I want to sort a large integer array into 2 groups, i.e. 1 group the multiples of 4 and the other group the multiples of 5. How can I do this using just one query? Keep an eye on the performance which is really important in my case.
To further explain what I need, suppose my list of numbers is { 2, 7, 8, 10, 12, 14, 19,20, 25} then I would expect my output to be this:
new[]
{
new
{
Remainder = 4,
Numbers = new List<int>(){ 8, 12, 20}
},
new
{
Remainder = 5,
Numbers = new List<int>(){10, 20, 25}
}
}
Here's what I have gotten so far:
var numberGroupsTimes5 =
from n in numbers
group n by n % 5 into g
where g.Key == 0
select new { Remainder = g.Key, Numbers = g };
var numberGroupsTimes4 =
from n in numbers
group n by n % 4 into g
where g.Key == 0
select new { Remainder = g.Key, Numbers = g };
As you can see it gets me close with 2 queries but as I said I would like a single query.
You could use Concat:
var something = numberGroupsTimes5.Concat(numberGroupsTimes4);
to simply concatenate two sequences.
It's not entire clear why you use a GroupBy, then filter for Key == 0. Remainder will always be 0.
Maybe a simple Where is enough?
You can simply "combine" your queries by using a logical OR (||):
var something = numbers.Where(x => x%4 == 0 || x%5 == 0);
In response to your comment: Are you looking for something like this?
var result = new[] {4, 5}
.Select(d => new
{
Divider = d,
Values = numbers.Where(n => n % d == 0).ToList()
});
Do you mean?
var numberGroupsTimes4or5 = from n in numbers
group n by n into g
where g.Key % 4 == 0 || g.Key % 5 == 0
select new { Remainder = g.Key, Numbers = g };
Maybe this?
var result = new[] { 4, 5 }
.SelectMany(x => numbers.Select(n => (n, x)))
.Where(g => g.n % g.x == 0)
.GroupBy(g => g.x, (Key, g) =>
new { Remainder = Key, Numbers = g.Select(z => z.n) });
which gives this result
Here is a similar approach but this time using a query syntax like in your question.
var numbersAndRemainders = new[] { 4, 5 }
.SelectMany(rem => numbers.Select(n => (n, rem)));
var numberGroups =
from n in numbersAndRemainders
group n by new { remainder = n.n % n.rem, n.rem } into g
where g.Key.remainder == 0
select new { Remainder = g.Key.rem, Numbers = g.Select(z => z.n) };
There are two LINQ methods you could use for this:
//This will join the lists, excluding values that already appear once
var result = numberGroupsTimes5.Union(numberGroupsTimes4)
//This will simply append one list the the other
var result = numberGroupsTimes5.Concat(numberGroupsTimes4)

Find the longest sequence in a List<int>

I have the following list:
List<int> days = new List<int> { 1, 4, 5, 6, 7, 8, 20, 24, 25, 26, 30 };
I want to get the start and end numbers of the longest sequence. For the above example I should get the (4, 8). If two sequences are available with the same length, I want the first one.
Note: the list will always have numbers in an increasing order.
so far I have tried this:
List<Tuple<int, int>> seqs = new List<Tuple<int, int>>();
int _start = 0;
for (int i = 0; i <= days.Count; i++)
{
if (i == 0)
{
_start = days[i];
continue;
}
if (i < days.Count)
{
if (days[i] == days[i - 1] + 1)
continue;
else
{
seqs.Add(new Tuple<int, int>(_start, days[i - 1]));
_start = days[i];
}
}
else
{
seqs.Add(new Tuple<int, int>(_start, days[i - 1]));
}
}
var largestSeq = seqs
.OrderByDescending(s => s.Item2 - s.Item1)
.FirstOrDefault();
this solution is shorter but uses a side effect, so it cannot be parallelized:
var days = new List<int> { 1, 4, 5, 6, 7, 8, 20, 24, 25, 26, 30 };
var groupNumber = 0;
var longestGroup = days
.Select((x, i) => new
{
Item = x,
Index = i
})
.GroupBy(x => x.Index == 0 || x.Item - days[x.Index - 1] == 1
? groupNumber :
++groupNumber)
.OrderByDescending(x => x.Count())
.First()
.Select(x => x.Item)
.ToArray();
Console.WriteLine(longestGroup.First()+", "+longestGroup.Last());
output:
4, 8
this version does not use a side effect:
var days = new List<int> { 1, 4, 5, 6, 7, 8, 20, 24, 25, 26, 30 };
var groupEnds = days
.Select((x, i) => new
{
Item = x,
Index = i
})
.Where(x => x.Index > 0)
.Where(x => x.Item - days[x.Index - 1] > 1)
.Select(x => x.Index)
.Concat(new[]{days.Count})
.ToArray();
var groupBounds =
new[]{new{First=0,Last=groupEnds[0]-1}}
.Concat(groupEnds
.Select((x,i) => new{Item=x,Index=i})
.Where(x => x.Index > 0)
.Select(x => new{First=groupEnds[x.Index-1],Last=x.Item-1})
)
.ToArray();
var longestGroup = groupBounds
.OrderByDescending(x => x.Last - x.First)
.First();
Console.WriteLine(days[longestGroup.First] + ", " + days[longestGroup.Last]);
output:
4, 8
My version, which looks pretty similar to to #Gurgadurgen's.
List<int> days = new List<int> { 1, 4, 5, 6, 7, 8, 20, 24, 25, 26, 30 };
int longestSequenceLength = 0;
int startIndexOfLongestSequence = 0;
int currentSequenceLength = 0;
int currentStartSequenceIndex = 0;
for (int i = 0; i < days.Count; i++) {
if (i == 0 || days[i] != days[i - 1] + 1) {
currentSequenceLength = 1;
currentStartSequenceIndex = i;
}
else {
currentSequenceLength++;
}
if (currentSequenceLength > longestSequenceLength) {
longestSequenceLength = currentSequenceLength;
startIndexOfLongestSequence = currentStartSequenceIndex;
}
}
Console.WriteLine(string.Join(",",days.Skip(startIndexOfLongestSequence)
.Take(longestSequenceLength)));
int longestSeqStart = days[0];
int longestSeqEnd = days[0];
int curSeqStart = days[0];
int curSeqEnd = days[0];
int lastVal = days[0];
for(int i = 1; i < days.Count(); i++)
{
if(days[i] == lastVal + 1)
{
curSeqEnd = days[i];
if(curSeqEnd - curSeqStart > longestSeqEnd - longestSeqStart )
{
longestSeqStart = curSeqStart;
longestSeqEnd = curSeqEnd;
}
}
else
{
curSeqStart = curSeqEnd = days[i];
}
lastVal = days[i];
}
Haven't tested it, but I've stared at it for a good five minutes, and it seems pretty sound. I'll go run some tests in ideone and come back to edit them in :P
[EDIT] Tested it, and it indeed works. For a list of integers, "days"
{1,2,3,4,5,12,13,14,15,16,17,18,2,3,4,5}
It spits out 12-18, which is the proper answer.
I'll just post the Ideone link here.
[EDIT 2] Note that this is scarcely optimized, and could (and should) be rendered down further before use in actual code. For example, I realized only after writing this up that "lastVal" isn't actually even needed, as we can just check the value of "days" at the last index (i-1).
This should only serve as a logical basis for which to solve the problem you have, here. Not as a final solution.

Unique Values from a list of of items in C#

I am having a list:
list = { 1,1,1,2,3,3,3,4,4,5,6,6,6}
Now I want to extract list of unique values.
Final list contains {2,5} only.
How can I do that through LINQ or any other function.
One way would be to use the GroupBy method and filter only those which have a count of 1:
var unique = list.GroupBy(l => l)
.Where(g => g.Count() == 1)
.Select(g => g.Key);
Try This:
List<int> list = new List<int>(new int[]{ 1, 1, 1, 2, 3, 3, 3, 4, 4, 5, 6, 6, 6});
List<int> unique=new List<int>();
int count=0;
bool dupFlag = false;
for (int i = 0; i < list.Count; i++)
{
count = 0;
dupFlag = false;
for(int j=0;j<list.Count;j++)
{
if (i == j)
continue;
if (list[i].Equals(list[j]))
{
count++;
if (count >= 1)
{
dupFlag = true;
break;
}
}
}
if (!dupFlag)
unique.Add(list[i]);
}
Try this code:
var lstUnique =
from t1 in list
group t1 by t1 into Gr
where Gr.Count() == 1
select Gr.Key;

What is the C# extension methods equivalent for this Linq query?

In this example:
public void Linq40()
{
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var numberGroups =
from n in numbers
group n by n % 5 into g
select new { Remainder = g.Key, Numbers = g };
foreach (var g in numberGroups)
{
Console.WriteLine("Numbers with a remainder of {0} when divided by 5:",
g.Remainder);
foreach (var n in g.Numbers)
{
Console.WriteLine(n);
}
}
}
What is the pure c# equivalent? I get this...
var numberGroups = numbers.GroupBy(n => n % 5)...
but the into clause is a bit of a mystery, and I can't figure out how to get the Key from the Select.
GroupBy returns an IEnumerable<T> of <IGrouping<TKey, TSource>. With this, you can do a second Select operation, which returns values exactly like above:
var numberGroups = numbers.GroupBy(n => n % 5)
.Select(g => new { Remainder = g.Key, Numbers = g });
numbers.GroupBy(n => n % 5).Select(g => new { Remainder = g.Key, Numbers = g });

Categories