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 }
}
Related
I'd like to know the best way to approach this. I have an integer array (say of 3, 4, 8, 10, 15, 24, 29, 30) and I want to sort it into 3 groups: 4 times table, 5 times table, and neither).
As the groups would suggest, it would sort the array into the 4 and 5 times table with another for items that aren't present in either.
What's the best way to approach this in C#? I'm currently using this:
int[] iArray = new int[]{3, 4, 8, 10, 15, 24, 29, 30};
var iE = iArray.GroupBy ((e) => {
if (e % 4 == 0) {
return "four";
} else if (e % 5 == 0) {
return "five";
} else {
return "other";
}
}).OrderBy (e => e.Count ());
Produces:
4
4
8
24
5
10
15
30
Other
3
29
int[] arr = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
If you want to get all multiples of 4 and all multiples of 5 (and have some overlap between the two) you can do this:
List<int> multiplesOf4 = (from i in arr where i % 4 == 0 select i).ToList();
List<int> multiplesOf5 = (from i in arr where i % 5 == 0 select i).ToList();
List<int> others = (from i in arr where i % 5 != 0 && i % 4 != 0 select i).ToList();
If you want no overlap, however, you need to pick which one will be dominant. I chose 4 here:
List<int> multiplesOf4 = new List<int>(),
multiplesOf5 = new List<int>(),
others = new List<int>();
foreach (int i in arr)
{
if (i % 4 == 0)
multiplesOf4.Add(i);
else if (i % 5 == 0)
multiplesOf5.Add(i);
else
others.Add(i);
}
Try this:
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 };
foreach (var g in numberGroupsTimes5)
{
string st = string.Format("Numbers with a remainder of {0} when divided by 5:" , g.Remainder);
MessageBox.Show("" + st);
foreach (var n in g.Numbers)
{
MessageBox.Show(""+n);
}
}
foreach (var g in numberGroupsTimes4)
{
string st = string.Format("Numbers with a remainder of {0} when divided by 4:", g.Remainder);
MessageBox.Show("" + st);
foreach (var n in g.Numbers)
{
MessageBox.Show("" + n);
}
}
You approach is correct. But you can do some small improvements to make it more readable and standard:
var iArray = new[] { 3, 4, 8, 10, 15, 24, 29, 30 };//Don't need to give type(int) explicitly
var iE = iArray.GroupBy(e => e % 4 == 0 ? "four" : e % 5 == 0 ? "five" : "other").OrderBy(e => e.Count());
It'll give the same results.
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 have a list of integer lists, like that:
A -> 10 10 1 1 1
B -> 10 9 9 7 6
...
I would like to sort them based on how many 10s they have, then on how many 9s, 8s, 7s, and so on untile the 1s
So in the example above A should be better than B because even if it has less total points, it has two 10s instead of only 1.
Code should be generic because I don't know how many numbers will be available for each case (sometimes 10, sometimes 5, or even only 3).
I developed something like that:
lists.OrderByDescending(a => a.Where(b => b == 10).Count()).
ThenByDescending(a => a.Where(b => b == 9).Count()).
and so on, but this is not generic...
I hope the question is clear... thank you very much!
You can create query which orders lists by count of 10s, then compose query by adding additional orderings for numbers from 9 to 1:
var query = lists.OrderByDescending(l => l.Count(x => x == 10));
for (int i = 9; i >= 1; i--)
query = query.ThenByDescending(l => l.Count(x => x == i));
For these sample lists:
var lists = new[] {
new[] { 10, 9, 9, 8, 7 },
new[] { 10, 9, 9, 7, 6 },
new[] { 10, 10, 1, 1, 1 }
};
Result will be:
[10, 10, 1, 1, 1]
[10, 9, 9, 8, 7]
[10, 9, 9, 7, 6]
It's simple, but not very efficient. If you need better performance, then consider creating custom comparer. Here is sample with comparer which uses zipped ordered sequences to check if all items in sequences are same, or get first item which is different:
public class CustomComparer : Comparer<IList<int>>
{
public override int Compare(IList<int> x, IList<int> y)
{
var comparisons = x.Zip(y, (a,b) => a.CompareTo(b));
foreach(var comparison in comparisons)
{
if (comparison != 0)
return comparison;
}
return x.Count.CompareTo(y.Count);
}
}
NOTE: If items in lists are not ordered, then you should sort them before zipping:
var comparisons =
x.OrderByDescending(i => i)
.Zip(y.OrderByDescending(i => i), (a,b) => a.CompareTo(b));
It works very simple. Consider two lists:
[10, 9, 9, 8, 7, 5]
[10, 9, 9, 7, 6]
It will create pairs of items in corresponding positions:
{10,10}, {9,9}, {9,9}, {8,7}, {7,6}
Then items in each pair will be compared one by one, until first mismatch will be found:
0, 0, 0, 1 (four comparisons only)
That means first list has more 8s than second one. Usage:
var query = lists.OrderByDescending(i => i, new CustomComparer());
Result is same.
The following comparer
public class Comparer : IComparer<IEnumerable<int>>
{
public int Compare(IEnumerable<int> a, IEnumerable<int> b)
{
var aOrdered = a.OrderByDescending(i => i).Concat(new[] { int.MinValue });
var bOrdered = b.OrderByDescending(i => i).Concat(new[] { int.MinValue });
return a.Zip(b, (i, j) => i.CompareTo(j)).FirstOrDefault(c => c != 0);
}
}
lets you order you lists of lists like so
var result = lists.OrderByDescending(i => i, new Comparer());
without iterating through each list ten times counting individual elements.
This compares the lists and returns conventional comparison result - 1, 0, or -1 is returned depending on whether one value is greater than, equal to, or less than the other.
static int CompareLists(List<int> a, List<int> b)
{
var grpA = a.GroupBy(p => p).ToDictionary(k=>k.Key,v=>v.Count());
var grpB = b.GroupBy(p => p).ToDictionary(k=>k.Key,v=>v.Count());
for (int i = 10; i >= 0; i--)
{
int countA = grpA.ContainsKey(i) ? grpA[i] : 0;
int countB = grpB.ContainsKey(i) ? grpB[i] : 0;
int comparison = countA.CompareTo(countB);
if (comparison != 0)
return comparison;
}
return 0;
}
First we convert the lists into dictionary of number->amount of occurences.
Then we iterate through numbers from 10 to 0 and compare the number of occurences. If the result is 0, then we go to another number.
If you have List<List<int>> to sort, just use list.Sort(CompareLists) as in:
List<int> d = new List<int> { 10, 6, 6 };
List<int> b = new List<int> { 10, 9, 9 };
List<int> a = new List<int> { 10, 10, 1, 1, 1 };
List<int> c = new List<int> { 10, 7, 7 };
List<int> e = new List<int> { 9, 3, 7 };
List<int> f = new List<int> { 9, 9, 7 };
List<List<int>> list = new List<List<int>>() { a, b, c, d, e, f };
list.Sort(CompareLists);
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);