skipWhile in LINQ is not working as excepted - c#

I am trying to learn LINQ and found out from MSD documentation that SkipWhile will tend to skip the value as long as the statement is true. however when I use this statement below I am not getting the result as expected.
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
var allButFirst3Numbers = numbers.SkipWhile(x => x > 9);
foreach (var n in allButFirst3Numbers)
{
Console.WriteLine(n);
}
From the code above the result should be
1,2,3,4,5,6,7,8,9
But I am getting the result as
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
Can anyone point out what exactly am I doing wrong in this, and how to get the expected result.

From the docs for Enumerable.SkipWhile (emphasis mine):
This method tests each element of source by using predicate and skips the element if the result is true. After the predicate function returns false for an element, that element and the remaining elements in source are yielded and there are no more invocations of predicate.
So because the first element yields a false it will return everything. I suspect what you really wanted to write was:
var result = numbers.Where(x => x <= 9);

You are getting what you should get. You are saying skip while x > 9. 1 is less than 9 so it starts taking right away.
Try .SkipWhile(x=>x<=9) ... But that will give you 10,11,12,13,...
You may also try .TakeWhile(x=>x<=9) which will return 1,2,3,4,5,6,7,8,9

The said predicate x => x > 9 will be match in the starting of the list and once that predicate is false it will go ahead and consider all the elements. You should rather try to use Where() extension method like
var allButFirst3Numbers = numbers.Where(x => x > 9);

then you want to take, not skip
var numbers = Enumerable.Range(1, 15);
var result = numbers.TakeWhile(x => x <= 9);
Debug.Print(string.Join(", ", result)); // 1, 2, 3, 4, 5, 6, 7, 8, 9

Related

Group elements of the data set if they are next to each other with LINQ

I have a data set (ex. 1, 1, 4, 6, 3, 3, 1, 2, 2, 2, 6, 6, 6, 7) and I want to group items of the same value but only if they are next to each other minimum 3 times.
Is there a way?
I've tried using combinations of Count and GroupBy and Select in every way I know but I can't find a right one.
Or if it can't be done with LINQ then maybe some other way?
I don't think I'd strive for a 100% LINQ solution for this:
var r = new List<List<int>>() { new () { source.First() } };
foreach(var e in source.Skip(1)){
if(e == r.Last().Last()) r.Last().Add(e);
else r.Add(new(){ e });
}
return r.Where(l => l.Count > 2);
The .Last() calls can be replaced with [^1] if you like
This works like:
have an output that is a list of lists
put the first item in the input, into the output
For the second input items onward, if the input item is the same as the last int in the output, add the input item to the last list in the output,
Otherwise make a new list containing the input int and add it onto the end of the output lists
Keep only those output lists longer than 2
If he output is like:
[
[2,2,2],
[6,6,6]
]
Aggregate can be pushed into doing the same thing; this is simply an accumulator (r), an iteration (foreach) and an op on the result Where
var result = source.Skip(1).Aggregate(
new List<List<int>>() { new List<int> { source.First() } },
(r,e) => {
if(e == r.Last().Last()) r.Last().Add(e);
else r.Add(new List<int>(){ e });
return r;
},
r => r.Where(l => l.Count > 2)
);
..but would you want to be the one to explain it to the new dev?
Another LINQy way would be to establish a counter that incremented by one each time the value in the source array changes compared to the pervious version, then group by this integer, and return only those groups 3+, but I don't like this so much because it's a bit "WTF"
var source = new[]{1, 1, 4, 6, 3, 3, 1, 2, 2, 2, 6, 6, 6, 7};
int ctr = 0;
var result = source.Select(
(e,i) => new[]{ i==0 || e != source[i-1] ? ++ctr : ctr, e}
)
.GroupBy(
arr => arr[0],
arr => arr[1]
)
.Where(g => g.Count() > 2);
You could consider using the GroupAdjacent or the RunLengthEncode operators, from the MoreLinq package. The former groups adjacent elements in the sequence, that have the same key. The key is retrieved by invoking a keySelector lambda parameter. The later compares the adjacent elements, and emits a single KeyValuePair<T, int> for each series of equal elements. The int value of the KeyValuePair<T, int> represents the number of consecutive equal elements. Example:
var source = new[] { 1, 1, 4, 6, 3, 3, 1, 2, 2, 2, 6, 6, 6, 7 };
IEnumerable<IGrouping<int, int>> grouped = MoreLinq.MoreEnumerable
.GroupAdjacent(source, x => x);
foreach (var group in grouped)
{
Console.WriteLine($"Key: {group.Key}, Elements: {String.Join(", ", group)}");
}
Console.WriteLine();
IEnumerable<KeyValuePair<int, int>> pairs = MoreLinq.MoreEnumerable
.RunLengthEncode(source);
foreach (var pair in pairs)
{
Console.WriteLine($"Key: {pair.Key}, Value: {pair.Value}");
}
Output:
Key: 1, Elements: 1, 1
Key: 4, Elements: 4
Key: 6, Elements: 6
Key: 3, Elements: 3, 3
Key: 1, Elements: 1
Key: 2, Elements: 2, 2, 2
Key: 6, Elements: 6, 6, 6
Key: 7, Elements: 7
Key: 1, Value: 2
Key: 4, Value: 1
Key: 6, Value: 1
Key: 3, Value: 2
Key: 1, Value: 1
Key: 2, Value: 3
Key: 6, Value: 3
Key: 7, Value: 1
Live demo.
In the above example I've used the operators as normal methods, because I am not a fan of adding using MoreLinq; and "polluting" the IntelliSense of the Visual Studio with all the specialized operators of the MoreLinq package. An alternative is to enable each operator selectively like this:
using static MoreLinq.Extensions.GroupAdjacentExtension;
using static MoreLinq.Extensions.RunLengthEncodeExtension;
If you don't like the idea of adding a dependency on a third-party package, you could grab the source code of these operators (1, 2), and embed it directly into your project.
If you're nostalgic and like stuff like the Obfuscated C code contest, you could solve it like this.(No best practice claims included)
int[] n = {1, 1, 4, 6, 3, 3, 1, 2, 2, 2, 6, 6, 6, 7};
var t = new int [n.Length][];
for (var i = 0; i < n.Length; i++)
t[i] = new []{n[i], i == 0 ? 0 : n[i] == n[i - 1] ? t[i - 1][1] : t[i - 1][1] + 1};
var r = t.GroupBy(x => x[1], x => x[0])
.Where(g => g.Count() > 2)
.SelectMany(g => g);
Console.WriteLine(string.Join(", ", r));
In the end Linq is likely not the best solution here.
A simple for-loop with 1,2,3 additional loop-variables to track the "group index" and the last value makes likely more sense.
Even if it's 2 lines more code written.
I wouldn't use Linq just to use Linq.
I'd rather suggest using a simple for loop to loop over your input array and populate the output list. To keep track of which number is currently being repeated (if any), I'd use a variable (repeatedNumber) that's initially set to null.
In this approach, a number can only be assigned to repeatedNumber if it fulfills the minimum requirement of repeated items. Hence, for your example input, repeatedNumber would start at null, then eventually be set to 2, then be set to 6, and then be reset to null.
One perhaps good use of Linq here is to check if the minimum requirement of repeated items is fulfilled for a given item in input, by checking the necessary consecutive items in input:
input
.Skip(items up to and including current item)
.Take(minimum requirement of repeated items - 1)
.All(equal to current item)
I'll name this minimum requirement of repeated items repetitionRequirement. (In your question post, repetitionRequirement is 3.)
The logic in the for loop goes a follows:
number = input[i]
If number is equal to repeatedNumber, it means that the previously repeated item continues being repeated
Add number to output
Otherwise, if the minimum requirement of repeated items is fulfilled for number (i.e. if the repetitionRequirement - 1 items directly following number in input are all equal to number), it means that number is the first instance of a new repeated item
Set repeatedNumber equal to number
Add number to output
Otherwise, if repeatedNumber has value, it means that the previously repeated item just ended its repetition
Set repeatedNumber to null
Here is a suggested implementation:
(I'd suggest finding a more descriptive method name)
//using System.Collections.Generic;
//using System.Linq;
public static List<int> GetOutput(int[] input, int repetitionRequirement)
{
var consecutiveCount = repetitionRequirement - 1;
var output = new List<int>();
int? repeatedNumber = null;
for (var i = 0; i < input.Length; i++)
{
var number = input[i];
if (number == repeatedNumber)
{
output.Add(number);
}
else if (i + consecutiveCount < input.Length &&
input.Skip(i + 1).Take(consecutiveCount).All(num => num == number))
{
repeatedNumber = number;
output.Add(number);
}
else if (repeatedNumber.HasValue)
{
repeatedNumber = null;
}
}
return output;
}
By calling it with your example input:
var dataSet = new[] { 1, 1, 4, 6, 3, 3, 1, 2, 2, 2, 6, 6, 6, 7 };
var output = GetOutput(dataSet, 3);
you get the following output:
{ 2, 2, 2, 6, 6, 6 }
Example fiddle here.

C# Linq check the previous record to the current record and update the amount if duplicate

How can I use Linq to check the previous Account Number to see if it matches the current account number and if it does make the amount = 0?
Thanks,
Mark
If you have a list of Account Numbers. You could use Enumerable.Range for comparing to the previous value
var accountNumbers = new List<int> { 0, 1, 2, 3, 4, 5, 5, 5, 6, 7, 3 };
var result = Enumerable.Range(1, accountNumbers.Count - 1)
.Select(i => accountNumbers[i] != accountNumbers[i - 1] ? accountNumbers[i] : 0)
.ToList();
First, we need to clarify what "make the amount = 0" means. LINQ sequences are implicitly immutable, hence it is not possible to modify the amount in-place, but we need to return a new sequence (if you do want to modify the amounts in-place I would recommend using an array and a normal loop).
The answer provided by #Max works in principle, but is not performant as lists (and many other collection types) have only slow access to random elements.
LINQ does not have convenient tools for the task out of the box. Using only in-built methods a solution could be:
IEnumerable<int> accountNumbers = new List<int> { 0, 1, 2, 3, 4, 5, 5, 5, 6, 7, 3 };
IEnumerable<double> amounts = new List<double> { 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100 };
var possiblyInvalidAccountNumbers = accountNumbers.Skip(1); // The first account has no predecessor
var possiblyInvalidAmounts = amounts.Skip(1);
var validatedAmounts =
accountNumbers.Take(possiblyInvalidAccountNumbers.Count())
.Zip(possiblyInvalidAccountNumbers, (prevAccNo, accNo) => new { prevAccNo, accNo })
.Zip(possiblyInvalidAmounts, (numbers, amount) => numbers.prevAccNo != numbers.accNo ? amount : 0.0)
.Prepend(amounts.First());
Obviously, this code is somewhat convoluted, so a possible way to improve the clarity could be to write your own extension methods (e.g. a Zip3() method that takes three inputs would come in handy).

Insert duplicates values linq

In a collection of values
[0, 2, 25, 30]
I'm trying to do with linq
[0, 0, 0, 2, 2, 2, 25, 25, 25, 30, 30, 30] //Replicate 2 times (values repeated 3 times)
Is there someway to do it with linq?
With value types it's easy, just use Enumerable.Repeat:
var result = collection.SelectMany(x => Enumerable.Repeat(x, 3));
If it was an array use ToArray if it was a list use ToList at the end.
With reference types it depends if you really want the same reference, then you can also use Repeat. Otherwise you need to create "deep-clones" of the instance, for example by using a copy constructor if available:
var result = collection
.SelectMany(x => Enumerable.Range(1, 3).Select(i => new YourType(x)));
Of course Tim's answer does answer this question. But posting this as an alternative answer (if you have to repeat for fewer times)
List<int> list = new List<int>() { 0, 1, 2, 3 };
List<int> newList = list.SelectMany(x => new List<int>(3) { x, x, x }).ToList();

Reverse Paging Using Linq

I have a scenario where I have to get paginated records in a reversed way using LINQ. Lets Assume I have 15 items in the order they were posted:
1, 2, 3, 4, 5.......... 15
Having a pages size of 5, if the user sends 1 as the currentPage in my method. Then I should be able to return 11, 12, 13, 14, 15 as the result set, if he sends 2 as the currentPage then I should return 6, 7, 8, 9, 10 and so on. How do I set the value that I give to the Skip method so that it would give me records in this way? And yes, I'm ordering the items in an ascending order by their dates too.
Just a simple example of the logic:
var nums = new[] {1, 2, 3, ..., 15};
var lastFew = nums.Reverse().Take(5).Reverse().ToArray();
Or alternatively, as a list:
var nums = new List<int> { 1, 2, 3, ..., 15};
nums.Reverse();
nums.RemoveRange(5); // removes from index 5 till end
nums.Reverse();
The actual implementation can vary depending on how your code is designed, etc, but that should get you started.
The key methods to use here are: Reverse, Take to limit the returned amount, and Skip to choose the starting index. If you use Skip, you can most likely avoid using Reverse altogether.
I would do like this (using EF):
var resultByDesc = db.Entities
.OrderByDescending(o => o.DateTime)
.Skip(page * itemsPerPage)
.Take(itemsPerPage)
.ToList(); // important! eager loading!
// it will be executed in your client side, not SQL.
var actualResult = resultByDesc.Reverse();

Is there a way to organise an IEnumerable into batches in column-major format using Linq?

In several of my most recent projects, I've found the need to divide a single collection up into m batches of n elements.
There is an answer to this question that suggests using morelinq's Batch method. That is my preferred solution (no need to re-invent the wheel and all that).
While the Batch method divides the input up in row-major format, would it also be possible to write a similar extension that divides the input up in column-major format? That is, given the input
{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }
you would call ColumnBatch(4), generating an output of
{
{ 1, 4, 7, 10 },
{ 2, 5, 8, 11 },
{ 3, 6, 9, 12 }
}
Does morelinq already offer something like this?
UPDATE: I'm going to change the convention slightly. Instead of using an input of 4, I'll change it to 3 (the number of batches rather than the batch size).
The final extension method will have the signature
public static IEnumerable<IEnumerable<T>> ToColumns<T>(this IEnumerable<T> source, int numberOfColumns)
which should be clear to whoever is implementing the method, and does not require multiple enumerations.
int[] arr = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
int i=0;
var result = arr.GroupBy(x => i++ % 3).Select(g => g.ToList()).ToList();

Categories

Resources