I have a list of objects (i.e. integers) and I want to aggregate sub-lists with LINQ.
For example:
Original list: [ 1, 4, 5, 3, 4, 10, 4, 12 ]
Sub-lists: [ [1,4,5,3], [4,5,3,4], [5,3,4,10], [3,4,10,4], [4,10,4,12] ]
Result (Aggregated List): [ 5, 5, 10, 10, 12 ]
I want to create the maximum of a sub-list for each element containing itself and the following n = 3 elements. Is this possible with LINQ or do I need to create my own aggregation mechanism?
Thanks in advance,Christian
public IEnumerable<IEnumerable<int>> GetSubLists(int[] collection)
{
for(int i = 0; i< collection.Length - 3; i++)
yield return collection.Skip(i).Take(4);
}
GetSubLists(original).Select(l => l.Max());
Or in one line
int[] original = {1, 4, 5, 3, 4, 10, 4, 12 };
int chunkCount = 4;
Enumerable.Range(0, original.Length - chunkCount + 1).Select(i => original.Skip(i).Take(chunkCount))
.Select(l => l.Max());
var result = sublists.Select(sl => sl.Max());
// [5,5,10,10,12]
Creating sub-lists:
List<int> original = new List<int> { 1, 4, 5, 3, 4, 10, 4, 12 };
int sublistSize = 4;
// check if original size is greater than required sublistSize
var sublists = Enumerable.Range(0, original.Count - sublistSize + 1)
.Select(i => original.GetRange(i, sublistSize));
// [[1,4,5,3],[4,5,3,4],[5,3,4,10],[3,4,10,4],[4,10,4,12]]
IEnumerable<int[]> GetLists (int[] list, int size )
{
return Enumerable.Range(0, list.Length - size + 1).Select(x => list.Skip(x).Take(size).ToArray());
}
Sample:
var list = new[] {1, 4, 5, 3, 4, 10, 4, 12};
var max = GetLists(list, 4).Select(x => x.Max()).ToArray();
The Sub-lists intermediate result can be constructed with a "Sliding Window" function.
The desired Result then is the function Max() mapped over the windows with Select().
var originalList = new [] {1, 4, 5, 3, 4, 10, 4, 12};
var sublists = originalList.Window(4); // [ [1,4,5,3], [4,5,3,4], ... ]
var result = sublists.Select(Enumerable.Max); // [ 5, 5, 10, 10, 12 ]
Efficient Window function:
public static IEnumerable<IEnumerable<T>> Window<T>(this IEnumerable<T> source,
int windowSize)
{
if(windowSize < 1) throw new ArgumentException("windowSize must positive", "windowSize");
var q = new Queue<T>(windowSize);
foreach(var e in source)
{
q.Enqueue(e);
if(q.Count < windowSize) continue; // queue not full yet
yield return q;
q.Dequeue();
}
}
Related
For example I have this as List<List<int>>:
[2,4,4,2,5]
[1,3,6,3,8]
[0,3,9,0,0]
Should return the sum but only taking cells assuming that the cell count is always the same:
[3, 10, 19, 5, 13]
I am trying to find an easy way to solve this using Linq if it is possible because I am doing this with a lot of for loops and if conditions and I am complicating myself.
Is there a possible way to achieve this using Linq?
Linq approach
List<List<int>> items = new List<List<int>>() {
new List<int> { 2, 4, 4, 2, 5 },
new List<int> { 1, 3, 6, 3, 8 },
new List<int> { 0, 3, 9, 0, 0 } };
List<int> result = Enumerable.Range(0, items.Min(x => x.Count)).Select(x => items.Sum(y => y[x])).ToList();
var xx = new List<List<int>>() {
new List<int>() { 2, 4, 4, 2, 5 },
new List<int>() { 1, 3, 6, 3, 8 },
new List<int>() { 0, 3, 9, 0, 0 },
};
var y = xx.Aggregate((r, x) => r.Zip(x).Select(p => p.First + p.Second).ToList());
I am doing this with a lot of for loops and if conditions and I am complicating myself.
You can accomplish it by using a single for loop.
Two possible approaches to achieve that are:
Approach 1
Creating an array with a capacity equal to the size of either of the lists in the original list collection
Filling the array with 0s
Looping through all lists in the original list collection, aggregating the sum for each index
Approach 2
Creating a list based on the first list in the original list collection
Looping through all subsequent lists in the original list collection, aggregating the sum for each index
Both approaches benefit from the assumption given in the question post:
[...] assuming that the cell count is always the same
If your original list collection is defined as a List<List<int>>:
List<List<int>> valuesCollection = new()
{
new() { 2, 4, 4, 2, 5 },
new() { 1, 3, 6, 3, 8 },
new() { 0, 3, 9, 0, 0 },
};
, the two approaches may be implemented as follows:
Approach 1
var indexCount = valuesCollection[0].Count;
var sums = new int[indexCount];
Array.Fill(sums, 0);
foreach (var values in valuesCollection)
{
for (var i = 0; i < sums.Length; i++)
{
sums[i] += values[i];
}
}
Approach 2
Note: Uses namespace System.Linq
var sums = valuesCollection[0].ToList();
foreach (var values in valuesCollection.Skip(1))
{
for (var i = 0; i < sums.Count; i++)
{
sums[i] += values[i];
}
}
Using either approach, sums's resulting content will be { 3, 10, 19, 5, 13 }.
Example fiddle here.
There is a list of short. The values of it doesn't matter like:
List<short> resultTemp = new List<short>{1,2,3,4,5,6,7,8,9...};
This code should reduse the result list count by removing each Nth item from it.
Example 1:
List<short>{1,2,3,4,5,6,7,8,9,10}.Count == 10;
var targetItemsCount = 5;
result should be {1,3,5,7,9} and result.Count should be == 5
Example 2:
List<short>{1,2,3,4,5,6,7,8,9}.Count == 9;
var targetItemsCo:nt = 3;
result should be {1,4,7} and result.Count should be == 3
But it should stop to remove it, somewhere for make result count equal targetItemsCount (42 in this code, but its value else doesn't matter).
The code is:
var currentItemsCount = resultTemp.Count;
var result = new List<short>();
var targetItemsCount = 42;
var counter = 0;
var counterResettable = 0;
if (targetItemsCount < currentItemsCount)
{
var reduceIndex = (double)currentItemsCount / targetItemsCount;
foreach (var item in resultTemp)
{
if (counterResettable < reduceIndex ||
result.Count + 1 == currentItemsCount - counter)
{
result.Add(item);
counterResettable++;
}
else
{
counterResettable = 0;
}
counter++;
}
}
And the resault.Count in this example equals 41, but should be == targetItemsCount == 42;
Ho do I remove each N item in List untill List.Count more then target value with C#?
If my understanding is correct:
public static void run()
{
var inputs =
new List<Input>{
new Input{
Value = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 },`
TargetCount = 5, ExpectedOutput= new List<int>{1,3,5,7,9}
},
new Input{
Value = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 },
TargetCount = 3, ExpectedOutput= new List<int>{1,4,7}
},
};
foreach (var testInput in inputs)
{
Console.WriteLine($"# Input = [{string.Join(", ", testInput.Value)}]");
var result = Reduce(testInput.Value, testInput.TargetCount);
Console.WriteLine($"# Computed Result = [{string.Join(", ", result)} ]\n");
}
}
static List<int> Reduce(List<int> input, int targetItemsCount)
{
while (input.Count() > targetItemsCount)
{
var nIndex = input.Count() / targetItemsCount;
input = input.Where((x, i) => i % nIndex == 0).ToList();
}
return input;
}
class Input
{
public List<int> ExpectedOutput;
public List<int> Value;
public int TargetCount;
}
Result :
Input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Computed Result = [1, 3, 5, 7, 9 ]
Input = [1, 2, 3, 4, 5, 6, 7, 8, 9]
Computed Result = [1, 4, 7 ]
To guarantee you get the expected number of selected items:
double increment = Convert.ToDouble(resultTemp.Count) / targetItemsCount;
List<short> result = Enumerable.Range(0, targetItemsCount).
Select(x => resultTemp[(int)(x * increment)]).
ToList();
Note that in the following case
List<short>{1,2,3,4,5,6,7,8,9}.Count == 9;
var targetItemsCount = 6;
The result will be [1, 2, 4, 5, 7, 8], i.e. rounding the index down when needed
Also, you'll need to add validation (targetItemsCount > 0, targetItemsCount < resultTemp.Count...)
Link to Fiddle
Give this a try:
var resultTemp = Enumerable.Range(1, 9).ToList();
var targetItemsCount = 3;
var roundingError = resultTemp.Count % targetItemsCount;
var reduceIndex = (resultTemp.Count - roundingError) / targetItemsCount;
List<int> result;
if (reduceIndex <= 1)
result = resultTemp.Take(targetItemsCount).ToList();
else
result = resultTemp.Where((a, index) => index % reduceIndex == 0).Take(targetItemsCount).ToList();
Tried it with your given example, also gave 42 a spin with a list of 1 to 100 it will remove every 2nd item till it reaches 42, so the last entry in the list would be 83.
As I said, give it a try and let me know if it fits your requirement.
I need to find a way to return the longest match found in number of sets/lists (values returns only once) when the order of items is important.
the list is not cyclic.
A match is a sequence of values that exists in all the lists and maintains the same order of elements in all the lists.
e.g. 1:
List<int> list1 = new List<int> { 1, 2, 3, 4, 7, 9 };
List<int> list2 = new List<int> { 1, 2, 5, 6, 3, 4, 7, 9 };
List<int> list3 = new List<int> { 1, 2, 3, 6, 8, 9 };
List<int> list4 = new List<int> { 1, 2, 5, 6, 8, 9 };
result { 1, 2 }
e.g. 2:
List<int> list1 = new List<int> { 2, 3, 6, 8, 1, 18 };
List<int> list2 = new List<int> { 2, 3, 4, 6, 8, 1, 18, 19, 17, 14 };
List<int> list3 = new List<int> { 2, 5, 6, 8, 1, 18, 16, 13, 14 };
List<int> list4 = new List<int> { 2, 6, 8, 1, 18, 19, 17, 14 };
result { 6, 8, 1, 18 }
The match doesn't have to be found at the beginning or at the end and can be on any part of any list.
I hope that I explained my problem good enough :)
Thanks!
You can build a map from pairs of ints to a count of how many of the lists they appear adjacent in.
Pseudo-code:
For each list L {
For each adjacent pair (x, y) in L {
Counts[x, y] += 1
}
}
Now you can iterate through the first list (or the shortest list), and find the longest run such that each adjacent pair (x, y) in the run with Counts[x, y] showing that the pair appears in every list.
Pseudo-code:
run = []
best_run = []
For x in L[0] {
if len(run) is zero or Counts[run[len(run)-1], x] == number of lists {
run = run + x
} else {
run = [x]
}
if run is longer than best_run {
best_run = run
}
}
This works given the assumption in the question that no integer appears twice in the same list.
This algorithm runs in O(N) time, where N is the sum of the lengths of all the lists.
Here's my approach.
First I need a way to compare lists:
public class ListCompare<T> : IEqualityComparer<List<T>>
{
public bool Equals(List<T> left, List<T> right)
{
return left.SequenceEqual(right);
}
public int GetHashCode(List<T> list)
{
return list.Aggregate(0, (a, t) => a ^ t.GetHashCode());
}
}
Next a method to produce all subsequences of a source list:
Func<List<int>, IEnumerable<List<int>>> subsequences = xs =>
from s in Enumerable.Range(0, xs.Count)
from t in Enumerable.Range(1, xs.Count - s)
select xs.Skip(s).Take(t).ToList();
Now I can create a list of lists:
var lists = new [] { list1, list2, list3, list4, };
Finally a query that pulls it all together:
var answer =
lists
.Skip(1)
.Aggregate(
subsequences(lists.First()),
(a, l) => a.Intersect(subsequences(l), new ListCompare<int>()))
.OrderByDescending(x => x.Count)
.FirstOrDefault();
Given the sample data provided in the question this produces the expected results.
First generate an ordered combination of int from the shortest list
Compare the lists other than shortest list with the combination. For easy comparison of lists I just convert to string and use string.Contains()
Return immediately if find the match as the items left are next order or the shorter one.
public static List<int> GetLongestMatch(params List<int>[] all)
{
var shortest = all.Where(i => i.Count == all.Select(j => j.Count).Min()).First();
var permutations = (from length in Enumerable.Range(1, shortest.Count)
orderby length descending
from count in Enumerable.Range(1, shortest.Count - length + 1)
select shortest.Skip(count - 1).Take(length).ToList())
.ToList();
Func<List<int>, string> stringfy = (list) => { return string.Join(",", list.Select(i => i.ToString()).ToArray()); };
foreach (var item in permutations)
{
Debug.WriteLine(string.Join(", ", item.Select(i => i.ToString()).ToArray()));
if (all.All(list => stringfy(list).Contains(stringfy(item))))
{
Debug.WriteLine("Matched, skip process and return");
return item;
}
}
return new List<int>();
}
Usage
var result = GetLongestMatch(list1, list2, list3, list4);
Result
2, 3, 6, 8, 1, 18
2, 3, 6, 8, 1
3, 6, 8, 1, 18
2, 3, 6, 8
3, 6, 8, 1
6, 8, 1, 18
Matched, skip process and return
I need to get the indices of all the largest elements who are equal to each other in a sorted list of ints.
So given this list
elements: {1 , 1, 2, 3, 4, 4, 5, 6, 7, 7, 8, 9, 10, 11, 11, 12, 13, 13, 13}
index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
^ ^ ^
I will get this output
{16,17,18}
So far I've got
list.Select((x, i) => new {x, i})
To get the indices, but I can't use OrderBy() with First() or Single() because I need all the max elements' indices, not just the very top one.
Is there an elegant way to accomplish this (with LINQ or otherwise)?
Thus items are sorted, you only need to get index of first item with max value (that item will have exactly same value as last item), and then create range of indexes starting from this index to the end of list:
var items = new List<int> {1,1,2,3,4,4,5,6,7,7,8,9,10,11,11,12,13,13,13};
int startIndex = items.IndexOf(items[items.Count - 1]);
var indexes = Enumerable.Range(startIndex, items.Count - startIndex);
The easy/lazy way:
var a = new[] {1, 1, 2, 3, 4, 4, 5, 6, 7, 7, 8, 9, 10, 11, 11, 12, 13, 13, 13};
var b = new List<int>();
var max = a.Max();
for (var i = 0; i < a.Length; i++)
{
if (a[i] == max) b.Add(i);
}
I wouldn't use LINQ since it is a simple foreach over the collection.
//var data = new[] {1, 1, 13, 2, 3, 4, 4, 5, 6, 7, 7, 8, 9, 10, 11, 11, 12, 13, 13, 13};
var data = new[] {1, 1, 2, 3, 4, 4, 5, 6, 7, 7, 8, 9, 10, 11, 11, 12, 13, 13, 13};
var largest = int.MinValue;
var indices = new List<int>();
foreach (var x in data.Select((value, idx) => new {value, idx}))
{
if (x.value > largest)
{
indices.Clear();
largest = x.value;
}
// if unsorted
//if (x.value == largest) indices.Add(x.idx);
// if sorted you don't need to check against largest
indices.Add(x.idx);
}
Console.WriteLine("largest = {0}; indices = {1}", largest, string.Join(", ", indices));
Though if you must use LINQ, you can use this option instead of the foreach:
data.Select((value, idx) => new {value, idx})
.Aggregate(indices, (idxs, n) =>
{
if (n.value > largest)
{
idxs.Clear();
largest = n.value;
}
//unsorted
if (n.value == largest) idxs.Add(n.idx);
//sorted
//idxs.Add(n.idx);
return idxs;
});
This will get you a result of all the elements that have duplicates with their indices:
var result = elements.Select((value, index) => new { value, index })
.Where(g => elements.FindAll(v => v == g.value).Count > 1)
.GroupBy((a) => a.value).OrderByDescending((g) => g.Key).Take(3);
//I placed Take(3) as example since you said you need to find
//elements who are equal to each other,so only those that are
// not distinct(have duplicates) get into the collection.
//this will loop through the results and display the value(the item
//on the list) and its respective index.
foreach (var item in result.SelectMany(g => g))
{
string outcome = item.value + " - " + item.index;
Console.WriteLine(outcome);
}
Is there a linq command that will filter out duplicates that appear in a sequence?
Example with '4':
Original { 1 2 3 4 4 4 5 6 7 4 4 4 8 9 4 4 4 }
Filtered { 1 2 3 4 5 6 7 4 8 9 4 }
Thanks.
Not really. I'd write this:
public static IEnumerable<T> RemoveDuplicates(this IEnumerable<T> sequence)
{
bool init = false;
T current = default(T);
foreach (var x in sequence)
{
if (!init || !object.Equals(current, x))
yield return x;
current = x;
init = true;
}
}
Yes there is! One-line code and one loop of the array.
int[] source = new int[] { 1, 2, 3, 4, 4, 4, 5, 6, 7, 4, 4, 4, 8, 9, 4, 4, 4 };
var result = source.Where((item, index) => index + 1 == source.Length
|| item != source[index + 1]);
And according to #Hogan's advice, it can be better:
var result = source.Where((item, index) => index == 0
|| item != source[index - 1]);
More readable now i think. It means "choose the first element, and those which isn't equal to the previous one".
Similar to svick's answer, except with side effects to avoid the cons and reverse:
int[] source = new int[] { 1, 2, 3, 4, 4, 4, 5, 6, 7, 4, 4, 4, 8, 9, 4, 4, 4 };
List<int> result = new List<int> { source.First() };
source.Aggregate((acc, c) =>
{
if (acc != c)
result.Add(c);
return c;
});
Edit: No longer needs the source.First() as per mquander's concern:
int[] source = new int[] { 1, 2, 3, 4, 4, 4, 5, 6, 7, 4, 4, 4, 8, 9, 4, 4, 4 };
List<int> result = new List<int>();
result.Add(
source.Aggregate((acc, c) =>
{
if (acc != c)
result.Add(acc);
return c;
})
);
I think I still like Danny's solution the most.
You can use Aggregate() (although I'm not sure whether it's better than the non-LINQ solution):
var ints = new[] { 1, 2, 3, 4, 4, 4, 5, 6, 7, 4, 4, 4, 8, 9, 4, 4, 4 };
var result = ints.Aggregate(
Enumerable.Empty<int>(),
(list, i) =>
list.Any() && list.First() == i
? list
: new[] { i }.Concat(list)).Reverse();
I think it's O(n), but I'm not completely sure.
If you're using .NET 4 then you can do this using the built-in Zip method, although I'd probably prefer to use a custom extension method like the one shown in mquander's answer.
// replace "new int[1]" below with "new T[1]" depending on the type of element
var filtered = original.Zip(new int[1].Concat(original),
(l, r) => new { L = l, R = r })
.Where((x, i) => (i == 0) || !object.Equals(x.L, x.R))
.Select(x => x.L);