Getting last x consecutive items with LINQ - c#

My question is similar to this one: Finding Consecutive Items in List using Linq. Except, I'd like to get the last consecutive items that have no gaps. For example:
2, 4, 7, 8
output
7,8
Another example:
4,5,8,10,11,12
output
10,11,12
How can that be done?

I'm assuming in that you want the last consecutive sequence with more than one member... so from the sequence
{4, 5, 8, 10, 11, 12, 15}
you're expecting the sequence:
{10, 11, 12}
I've indicated the line to remove if the last sequence is permitted to have only a single member, giving a sequence of
{15}
Here's the linq:
new[] {4, 5, 8, 10, 11, 12, 15}
.Select((n,i) => new {n, i})
.GroupBy(x => x.n - x.i) //this line will group consecutive nums in the seq
.Where(g => g.Count() > 1) //remove this line if the seq {15} is expected
.Select(x => x.Select(xx => xx.n))
.LastOrDefault()
There's a hidden assumption here that the numbers of the sequence are in ascending order. If this isn't the case, it will be necessary to enroll the powers of microsoft's extension method for finding contiguous items in a sequence. Let me know if this is the case.

This works and is probably easier and more efficient than LINQ in this case:
var list = new[] { 2, 4, 7, 8 };
List<int> lastConsecutive = new List<int>();
for (int i = list.Length - 1; i > 0; i--)
{
lastConsecutive.Add(list[i]);
if (list[i] - 1 != list[i - 1])
break;
if(i==1 && list[i] - 1 == list[i - 1]) // needed since we're iterating just until 1
lastConsecutive.Add(list[0]);
}
lastConsecutive.Reverse();

I realise this is both late and wordy, but this is probably the fastest method here that still uses LINQ.
Test lists:
var list1 = new List<int> {2,4,7,8};
var list2 = new List<int> {4,5,8,10,11,12,15};
The method:
public List<int> LastConsecutive(List<int> list)
{
var rev = list.AsEnumerable().Reverse();
var res = rev.Zip(rev.Skip(1), (l, r) => new { left = l, right = r, diff = (l - r) })
.SkipWhile(x => x.diff != 1)
.TakeWhile(x => x.diff == 1);
return res.Take(1).Select(x => x.left)
.Concat(res.Select(x => x.right))
.Reverse().ToList();
}
This one goes from back to front and checks elements pairwise, only taking elements from when they start being consecutive (the SkipWhile) until they end being consecutive (the TakeWhile).
Then it does some work to pull the relevant pairwise numbers out (left number from the 'original' list and then all the right numbers), and reverses it back. Similar efficiency to the imperative version, but in my opinion simpler to read because of LINQ.

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.

Reforming integer range to a smaller integer range (Unity (C#))

So, i have a set of integers in an list
public List<int> numbers = new List<int>() { 3, 7, 6, 9, 8, 10, 11 }
what i am wanting to do is change those numbers so they are ordered between 0 and 6, to set as siblingindexs.
and then would be changed to become
public List<int> newArrangedNumbers = new List<int>() {0, 2, 1, 4, 3, 5, 6}
But im really not sure how to do this... Anyone know?
P.S. i cant rearrange the numbers because then i would lose track of the game objects since the numbers themselves aren't actually in an array, but i have gameobjects in an array, and i find the "SortIndex" of each gameobject, which are the numbers from above, the order of the numbers in the array is actually the order of GameObjects in the array, which i need to keep the same.
Edit: i also cannot change them to float values because for some reason, when using SetSiblingIndex(int), you have to integers, you cant use floats
Edit 2: i am NOT trying to sort the numbers, i am trying to CONVERT the numbers from 3-11 into 0-6 in ORDER
From:
{3, 7, 6, 9, 8, 10, 11}
To:
{0, 2, 1, 4, 3, 5, 6}
Edit 3: Here is my script for testing
List<int> Indexs = new List<int>() { 4, 7, 56, 9, 65, 67, 8, 3, 6 };
var sorted = Indexs.Select((x, i) => new KeyValuePair<int, int>(x, i)).OrderBy(x => x.Key).ToList();
List<int> newArrangedNumbers = sorted.Select(x => x.Value).ToList();
for(int i = 0; i < newArrangedNumbers.Count; i++)
{
Debug.Log(Indexs[i] + " : " + newArrangedNumbers[i]);
}
When i only had 7 (0-6) indexs in the "Indexs" List it worked fine, but when i added any more, it started giving me the incorrect numbers
This is what it gives with this
Here is a good method to achieve your desired output from this stack form C# Sort list while also returning the original index positions?
The modified code for your solution is below.
//The original list
List<int> numbers = new List<int>() { 3, 7, 6, 9, 8, 10, 11 };
var sorted = numbers
.Select((x, i) => new KeyValuePair<int, int>(x, i))
.OrderBy(x => x.Key)
.ToList();
//The sorted list
List<int> numbersSorted = sorted.Select(x => x.Key).ToList();
//List of indexes sorted based on the list above
List<int> newArrangedNumbers = sorted.Select(x => x.Value).ToList();
Edit
Since you sort the list, but also retrieve the sorted indexes based on the list you just sorted, you aren't going to have any mixup with your game objects.
It sounds like you want to find positions of each element in a sorted list of the same items.
So sort and find where element is and assign the index. Code sample below assumes unique numbers:
var sorted = numbers.OrderBy(x=>x).ToArray();
var result = new int[numbers.Count];
for (var i = 0; i < numbers.Count; i++)
{
var index = number.IndexOf(sorted[i]);
result[index] = i;
}
Notes
for anything about 5-10 items I'd use dictionary instead of IndexOf if you want to stick with this code.
if numbers are not unique or performance is critical you need to use solution by Aaron Jones that eventually will track original indexes even if it is harder to understand.

Is there a way to do an operation on all but one member of an array?

I have been working through the daily coding problems and came to this one.
Given an array of integers, return a new array such that each element
at index i of the new array is the product of all the numbers in the
original array except the one at i.
For example, if our input was [1, 2, 3, 4, 5], the expected output
would be [120, 60, 40, 30, 24]. If our input was [3, 2, 1], the
expected output would be [2, 3, 6].
Follow-up: what if you can't use division?
So the easy way to do this would be just to multiply all the elements in the array and then just divide by [i] but that gives the problem that if I = 0 you are going to get an exception error.
I'm aware of the aggregate function that does an operation on all members of an array but is there a way to modify aggregate so that it does it to all members but one, or is there some other function/method that gives this functionality?
If source is small, you can skip index with a help of Where, e.g.
int[] source = new int[] { 1, 2, 3, 4, 5 };
int[] result = Enumerable
.Range(0, source.Length)
.Select(i => source
.Where((value, index) => index != i) // all items except i-th
.Aggregate((s, a) => s * a)) // should be multiplied
.ToArray();
Console.Write(string.Join(", ", result));
Outcome:
120, 60, 40, 30, 24
Edit: However, the solution has O(N**2) time complexity; in case the initial source array is large we can implement a more efficient O(N) code (and yes, we should mind zeroes):
int[] source = ...
int[] result;
int zeroCount = source.Count(item => item == 0);
if (zeroCount >= 2) // All zeroes case
result = new int[source.Length];
else if (zeroCount == 1) // All zeroes save one value case
result = source
.Select(v => v == 0
? source.Where(item => item != 0).Aggregate((s, a) => s * a)
: 0)
.ToArray();
else { // No zeroes case
// long, 1L: to prevent integer overflow, e.g. for {1000000, 1000000} input
long total = source.Aggregate(1L, (s, a) => s * a);
result = source
.Select(v => (int)(total / v)) // yes, it's a division...
.ToArray();
}
There are no built-in functions that aggregate on all except a single specified member (would you specify it by value or by index?)
However, a loop would be very straightforward, and Linq gives you the Where method where you can create whatever predicate you want and can then apply aggregations on the filtered result.
So to sum all numbers of an array instead of the third one, for example, you could do something like:
array.Where((x,i) => i != 2).Sum(); // use 2 since the index is 0-based
There's also not a built-in Linq method for Product, but I'm certain there's one out there, or again you could easily roll-your-own.

Finding mode in List of integers [duplicate]

This question already has answers here:
How to find the Mode in Array C#? [duplicate]
(4 answers)
Closed 7 years ago.
How can I find the mode of a list of numbers? I know the logic of it (I think) but I don't know how to implement that logic or convert what my brain thinks into workable code.
This is what I know:
I need to have a loop that goes through the list one time to see how many times a number is repeated and an array to save the times a number is repeated. I also need to tell my program to discard the lesser amount once a larger one is found.
A linq approach, more concise but almost certainly less efficient than Yeldar Kurmangaliyev's:
int FindMode(IEnumerable<int> data)
{
return data
.GroupBy(n => n)
.Select(x => new { x.Key, Count = x.Count() })
.OrderByDescending(a => a.Count)
.First()
.Key;
}
This does not handle the case where data is empty, nor where there are two or more data points with the same frequency in the data set.
Yes, you are right:
Let we have a list of numbers:
List<int> myValues = new List<int>(new int[] { 1, 3, 3, 3, 7, 7 } );
You need to have a loop that goes through the list one time:
foreach (var val in myValues)
{
}
to see how many times a number is repeated in array to save the times a number is repeated:
Dictionary<int, int> repetitions = new Dictionary<int, int>();
foreach (var val in myValues)
{
if (repetitions.ContainsKey(val))
repetitions[val]++; // Met it one more time
else
repetitions.Add(val, 1); // Met it once, because it is not in dict.
}
Now, your dictionary repetitions stores how many (exactly value) times key value repeated.
Then, you need to find the record of mode (i.e. record with the highest time of repetitions (i.e. highest value)) and take this one. LINQ will help us - let's sort the array by value and take the last one...or sort it descending and take the first one. Actually, that's the same in terms of result and productivity.
var modeRecord = repetitions.OrderByDescending(x => x.Value).First();
// or
var modeRecord = repetitions.OrderBy(x => x.Value).Last();
Here it is! Here we have a mode:
List<int> myValues = new List<int>(new int[] { 1, 3, 3, 3, 7, 7 } );
Dictionary<int, int> repetitions = new Dictionary<int, int>();
foreach (var val in myValues)
{
if (repetitions.ContainsKey(val))
repetitions[val]++; // Met it one more time
else
repetitions.Add(val, 1); // Met it once, because it is not in dict.
}
var modeRecord = repetitions.OrderByDescending(x => x.Value).First();
Console.WriteLine("Mode is {0}. It meets {1} times in an list", modeRecord.Key, modeRecord.Value);
Your mode calculation logic is good. All you need is following your own instructions in a code :)
Here's an alternative LINQ approach:
var values = new int[] { 1, 3, 3, 3, 7, 7 };
var mode =
values
.Aggregate(
new { best = 0, best_length = 0, current = 0, current_length = 0 },
(a, n) =>
{
var current_length = 1 + (a.current == n ? a.current_length : 0);
var is_longer = current_length > a.best_length;
return new
{
best = is_longer ? n : a.best,
best_length = is_longer ? current_length : a.best_length,
current = n,
current_length,
};
}).best;

Print out only odd elements from an IEnumerable?

I am having problems with an array where I for example want to printout the odd numbers in the list.
int[] numbers = new int[]{ 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
Console.WriteLine(numbers.Where(n => n % 2 == 1).ToArray());
The ToString method does not seem to work? I do not want to loop through the elements. What can I do?
You need to call String.Join to create a string with the contents of the sequence.
For example:
Console.WriteLine(String.Join(", ", numbers.Where(n => n % 2 == 1));
This uses the new overload which takes an IEnumerable<T>.
In .Net 3.5, you'll need to use the older version, which only takes a string[]:
Console.WriteLine(String.Join(
", ",
numbers.Where(n => n % 2 == 1)
.Select(n => n.ToString())
.ToArray()
)
);
In addition to the other answers which point out that you can't just print out an array, I note that this doesn't print out all the odd numbers in the list because your test for oddness is incorrect. Do you see why?
Hint: try testing it with negative numbers. Did you get the expected result? Why not?
You can use ForEach():
numbers.ToList().ForEach(
x=>
{if(x % 2 == 1)
Console.WriteLine(x);
});

Categories

Resources