Related
I have a list of items (not sure they are even or odd number of items). What I wanna do is, pick up records in the pair of 5 (which actually is a list), create another list and insert these pair of 5 lists into that new list.
Thanks
I can create a group of items by doing this
MyList
.Zip(Enumerable.Range(0, MyList.Count()),
(s, r) => new {
Group = r / 5,
Item = s })
.GroupBy(i => i.Group,
g => g.Item)
.ToList();
But I want to generate a nested list.
Not sure I understand your aim correctly, but you can try to use Dictionary for it:
MyList.Zip(Enumerable.Range(0, MyList.Count()),
(s, r) => new { Group = r / 5, Item = s })
.GroupBy(i => i.Group, g => g.Item)
.ToDictionary(g => g.Key, g => g.ToList());
It looks like you want to batch elements in batches of 5 items each. The MoreLinq package already offers the Batch operator for this:
var items=Enumerable.Range(0,17);
var batches=items.Batch(5);
foreach(var batch in batches)
{
Console.WriteLine(String.Join(" - ",batch));
}
This produces :
0 - 1 - 2 - 3 - 4
5 - 6 - 7 - 8 - 9
10 - 11 - 12 - 13 - 14
15 - 16
This is far faster than grouping as it only iterates the collection once.
MoreLINQ has other operators too, like Window, WindowLeft and WindowRight that produce sliding windows of values. items.Window(5) would produce :
0 - 1 - 2 - 3 - 4
1 - 2 - 3 - 4 - 5
...
11 - 12 - 13 - 14 - 15
12 - 13 - 14 - 15 - 16
The implementation
The operator's implementation is simple enough that you can just copy it into your project:
public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(this IEnumerable<TSource> source, int size)
{
return Batch(source, size, x => x);
}
public static IEnumerable<TResult> Batch<TSource, TResult>( IEnumerable<TSource> source, int size,
Func<IEnumerable<TSource>, TResult> resultSelector)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size));
if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector));
return _(); IEnumerable<TResult> _()
{
TSource[] bucket = null;
var count = 0;
foreach (var item in source)
{
if (bucket == null)
{
bucket = new TSource[size];
}
bucket[count++] = item;
// The bucket is fully buffered before it's yielded
if (count != size)
{
continue;
}
yield return resultSelector(bucket);
bucket = null;
count = 0;
}
// Return the last bucket with all remaining elements
if (bucket != null && count > 0)
{
Array.Resize(ref bucket, count);
yield return resultSelector(bucket);
}
}
}
The code uses arrays for efficiency. If you really want to use mutable lists you can change the type of bucket to a List<T>, eg :
if (bucket == null)
{
bucket = new List<TSource>(size); //IMPORTANT: set the capacity to avoid reallocations
}
bucket.Add(item);
...
Why not just GroupBy?
using System.Linq;
...
int groupSize = 5;
var result = MyList
.Select((item, index) => new {
item,
index
})
.GroupBy(pair => pair.index / groupSize,
pair => pair.item)
.Select(group => group.ToList())
.ToList();
If you have a collection of items
var items = Enumerable.Range(1, 20);
And you want to take, say, 5 at a time
var setSize = 5;
You can iterate over the collection by index, and take that 5 at a time as a list, and put all those lists of 5 into one outer list
Enumerable.Range(0, items.Count() - setSize).Select(x => items.Skip(x).Take(setSize).ToList()).ToList()
The result (from C# interactive shell) looks like
List<List<int>>(15) {
List<int>(5) { 1, 2, 3, 4, 5 },
List<int>(5) { 2, 3, 4, 5, 6 },
List<int>(5) { 3, 4, 5, 6, 7 },
List<int>(5) { 4, 5, 6, 7, 8 },
List<int>(5) { 5, 6, 7, 8, 9 },
List<int>(5) { 6, 7, 8, 9, 10 },
List<int>(5) { 7, 8, 9, 10, 11 },
List<int>(5) { 8, 9, 10, 11, 12 },
List<int>(5) { 9, 10, 11, 12, 13 },
List<int>(5) { 10, 11, 12, 13, 14 },
List<int>(5) { 11, 12, 13, 14, 15 },
List<int>(5) { 12, 13, 14, 15, 16 },
List<int>(5) { 13, 14, 15, 16, 17 },
List<int>(5) { 14, 15, 16, 17, 18 },
List<int>(5) { 15, 16, 17, 18, 19 }
}
If you want each item to only show up once in each list, you can alter the above. Let's assume there's an odd number of elements:
var items = Enumerable.Range(1, 11);
You want to change the initial range used to index into your collection. Instead of taking 5 at a time on each index, it will jump the index up by 5 each iteration. The only tricky part is making sure to handle when the collection divides the number of elements you want to take; you don't want to end up with an empty list at the end. That is, this is incorrect:
Enumerable.Range(0, items.Count() / setSize).Select( // don't do this
The statement is then
Enumerable.Range(0, ((items.Count() - 1) / setSize) + 1).Select(x => items.Skip(setSize * x).Take(setSize).ToList()).ToList();
The result (from C# interactive shell) looks like
List<List<int>>(3) {
List<int>(5) { 1, 2, 3, 4, 5 },
List<int>(5) { 6, 7, 8, 9, 10 },
List<int>(1) { 11 }
}
I wonder if it is possible to implement a general Julia\Matlab alike View function in C# that would work for arrays of any dimensions (eg [,,] and [,,,]) as they do it in array slicer\mover view. So I wonder if there is a library that provides similar functionality for CSharp multidimentional arrays or how to implement it in C#?
The solution is twofold:
Use a wrapper class that holds a reference to the master array
Use the Array base class to make it polymorphic
Wrapper
class View<T>
{
private readonly Array array;
private readonly int dim;
private readonly int slice;
public View(Array array, int dim, int slice)
{
this.array = array;
this.dim = dim;
this.slice = slice;
}
public T this[params int[] indexes]
{
get { return (T)array.GetValue(BaseIndexesFor(indexes)); }
set { array.SetValue(value, BaseIndexesFor(indexes)); }
}
private int[] BaseIndexesFor(int[] indexes)
{
if (indexes.Length != array.Rank - 1) throw new ArgumentException("indexes");
int i_index = 0;
int[] baseIndexes = new int[array.Rank];
for (int i = 0; i < baseIndexes.Length; i++)
{
baseIndexes[i] = (i == dim) ? slice : indexes[i_index++];
}
return baseIndexes;
}
}
2D example
var A = new int[,]
{
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};
/* View(Array array, int dim, int slice)
*
* For 2 dimensional array:
* dim=0 -> rows
* dim=1 -> columns
*/
// From second dimension slice index 1
// Or simply, take column with index 1
var B = new View<int>(A, 1, 1);
B[2] = 0;
Console.WriteLine(A[2, 1]); // 0
3D examples
var C = new int[,,]
{
{
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
},
{
{ 11, 12, 13 },
{ 14, 15, 16 },
{ 17, 18, 19 }
},
{
{ 21, 22, 23 },
{ 24, 25, 26 },
{ 27, 28, 29 }
}
};
/* From first dimension slice index 2
* { 21, 22, 23 },
* { 24, 25, 26 },
* { 27, 28, 29 }
*/
var D = new View<int>(C, 0, 2);
D[1, 1] = 0;
Console.WriteLine(C[2, 1, 1]); // 0
/* From third dimension slice index 0
* { 1, 4, 7 },
* { 11, 14, 17 },
* { 21, 24, 27 }
*/
var E = new View<int>(C, 2, 0);
E[2, 0] = 0;
Console.WriteLine(C[2, 0, 0]); // 0
one a integer list and one a string list. The integer list's length will always be a multiple of 8. I would like to put the first 8 integers from my integer list into the first element of a string list, then loop and put the next 8 into the second element of the string list and so on. I have made an attempt, I currently have an error on the Add method as string doesn't have an add extension? Also I'm not sure if the way I have done it using loops is correct, any advice would be helpful.
List1 is my integer list
List2 is my string list
string x = "";
for (int i = 0; i < List1.Count/8; i++) {
for(int i2 = 0; i2 < i2+8; i2+=8)
{
x = Convert.ToString(List1[i2]);
List2[i].Add(h);
}
}
You can do that by using something like that
var list1 = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
var list2 = new List<string>();
for (int i = 0; i < list1.Count / 8; i++)
{
list2.Add(string.Concat(list1.Skip(i * 8).Take(8)));
}
// list2[0] = "12345678"
// list2[1] = "910111213141516"
A slightly more complicated approach, which only iterates once over list1 (would work with IEnumerable would be sth. like this:
var list1 = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }.AsEnumerable();
var list2 = new List<string>();
var i = 0;
var nextValue = new StringBuilder();
foreach (var integer in list1)
{
nextValue.Append(integer);
i++;
if (i != 0 && i % 8 == 0)
{
list2.Add(nextValue.ToString());
nextValue.Clear();
}
}
// could add remaining items if count of list1 is not a multiple of 8
// if (nextValue.Length > 0)
// {
// list2.Add(nextValue.ToString());
// }
For the fun of it, you can implement your own general purpose Batch extension method. Good practice to understand extension methods, enumerators, iterators, generics and c#'s local functions:
static IEnumerable<IEnumerable<T>> Batch<T>(
this IEnumerable<T> source,
int batchCount,
bool throwOnPartialBatch = false)
{
IEnumerable<T> nextBatch(IEnumerator<T> enumerator)
{
var counter = 0;
do
{
yield return enumerator.Current;
counter += 1;
} while (counter < batchCount && enumerator.MoveNext());
if (throwOnPartialBatch && counter != batchCount) //numers.Count % batchCount is not zero.
throw new InvalidOperationException("Invalid batch size.");
}
if (source == null)
throw new ArgumentNullException(nameof(source));
if (batchCount < 1)
throw new ArgumentOutOfRangeException(nameof(batchCount));
using (var e = source.GetEnumerator())
{
while (e.MoveNext())
{
yield return nextBatch(e);
}
}
}
Using it is rather trivial:
var ii = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
var ss = ii.Batch(4, true)
.Select(b => string.Join(", ", b))
And sure enough, the output is:
1, 2, 3, 4
5, 6, 7, 8
9, 10, 11, 12
while (listOfintergers.Count() > 0)
{
var first8elements = listOfintergers.ConvertAll(t=>t.ToString()).Take(8);
listOfStrings.Add(string.Concat(first8elements));
listOfintergers = listOfintergers.Skip(8).ToList();
}
to do it in linqI have an integer List and want to group these to a list of integer pairs.
var input = new[] {1, 24, 3, 2, 26, 11, 18, 13};
result should be: {{1, 24}, {3, 2}, {26, 11}, {18, 13}}
I tried:
List<int> src = new List<int> { 1, 24, 3, 2, 26, 11, 18, 13 };
var agr = src.Select((n, i) => new Tuple<int, int>(i++ % 2, n))
.GroupBy(t => t.Item1)
.ToList();
var wanted = agr[0].Zip(agr[1], (d, s) => new Tuple<int, int>(d.Item2, s.Item2));
Is there a better way to do it in linq?
Of course I can do it with a simple for-loop.
Edit:
I think I give MoreLinq a try. I also mark this as the answer even if it's an extension and not pure linq.
By the way - I think doing it with a for-loop is much more understandable.
You can use MoreLINQ Batch to split your input into a list of "length 2" lists. Or any other length you want.
List<int> src = new List<int> { 1, 24, 3, 2, 26, 11, 18, 13 };
List<IEnumerable<int>> wanted = src.Batch(2).ToList();
No need for MoreLINQ; Enumerate even- and odd-indexed values, and Zip
int[] input = new int[8] { 1, 24, 3, 2, 26, 11, 18, 13 };
var evenPositioned = input.Where((o, i) => i % 2 == 0);
var oddPositioned = input.Where((o, i) => i % 2 != 0);
var wanted = evenPositioned.Zip(oddPositioned, (even, odd) => new { even, odd }).ToList();
If you can garantee, that the length of the source can always be devided by 2:
List<int> src = new List<int> { 1, 24, 3, 2, 26, 11, 18, 13 };
var Tuple<int, int>[] wanted = new Tuple<int, int>[src.Count /2];
for(var i = 0; i < src.Count; i = i + 2)
wanted[i/2] = new Tuple<int, int>(src[i], src[i+1]);
a simple for loop is enough for this.Just start with 1 and increment it by 2
List<int> src = new List<int> { 1, 24, 3, 2, 26, 11, 18, 13 };
var list = new List<Tuple<int, int>>();
for(int i =1;i<src.Count;i=i+2)
{
list.Add(new Tuple<int, int>(src[i-1],src[i]));
}
In case of odd count last item will be skipped
Another simple loop solution featuring C# 7 tuples.
var input = new List<int> { 1, 24, 3, 2, 26, 11, 18, 13 };
var wanted = new List<(int, int)>();
for (var index = 0; index < input.Count; index += 2) {
wanted.Add((input[index], input[index + 1]));
}
I have a sequence. For example:
new [] { 10, 1, 1, 5, 25, 45, 45, 45, 40, 100, 1, 1, 2, 2, 3 }
Now I have to remove duplicated values without changing the overall order. For the sequence above:
new [] { 10, 1, 5, 25, 45, 40, 100, 1, 2, 3 }
How to do this with LINQ?
var list = new List<int> { 10, 1, 1, 5, 25, 45, 45, 45, 40, 100, 1, 1, 2, 2, 3 };
List<int> result = list.Where((x, index) =>
{
return index == 0 || x != list.ElementAt(index - 1) ? true : false;
}).ToList();
This returns what you want. Hope it helped.
var list = new List<int> { 10, 1, 1, 5, 25, 45, 45, 45, 40, 100, 1, 1, 2, 2, 3 };
var result = list.Where((item, index) => index == 0 || list[index - 1] != item);
It may be technically possible (though I don't think you can with a one-liner) to solve this with LINQ, but I think it's more elegant to write it yourself.
public static class ExtensionMethods
{
public static IEnumerable<T> PackGroups<T>(this IEnumerable<T> e)
{
T lastItem = default(T);
bool first = true;
foreach(T item in e)
{
if (!first && EqualityComparer<T>.Default.Equals(item, lastItem))
continue;
first = false;
yield return item;
lastItem = item;
}
}
}
You can use it like this:
int[] packed = myArray.PackGroups().ToArray();
It's unclear from the question what should be returned in the case of 1,1,2,3,3,1. Most answers given return 1,2,3, whereas mine returns 1,2,3,1.
You can use Contains and preserve order
List<int> newList = new List<int>();
foreach (int n in numbers)
if (newList.Count == 0 || newList.Last() != n)
newList.Add(n);
var newArray = newList.ToArray();
OUTPUT:
10, 1, 5, 25, 45, 40, 100, 1, 2, 3
Did you try Distinct?
var list = new [] { 10, 20, 20, 5, 25, 45, 45, 45, 40, 100, 1, 1, 2, 2, 3 };
list = list.Distinct();
Edit: Since you apparently only want to group items with the same values when consecutive, you could use the following:
var list = new[] { 10, 1, 1, 5, 25, 45, 45, 45, 40, 100, 1, 1, 2, 2, 3 };
List<int> result = new List<int>();
foreach (int item in list)
if (result.Any() == false || result.Last() != item)
result.Add(item);