I got a list that contains items. They all got a 'Sort' column. The sort column is of type int, and it's unique.
Scenario:
sort 1; sort 2; sort 3;
If the user moves an item up (for example sort 3) in the list (for example to position 1, which would give the sort value 1), the items that are under the one that just got moved up, have to be shifted down in the list, and the sort number should be applied accordingly. In this case all shifted items sort - 1.
So the end state of the scenario looks like this:
sort 1 was sort 3; sort 3 was sort 2; sort 3 is now sort 1;
How can i do this with LINQ?
It's not just 3 items. It can be a lot more.
[Edit]
public ActionResult Up(int id)
{
var item = dataContext.item.FirstOrDefault(x => x.item == id);
return View(dataContext.items);
}
That might not be the easiest code to understand but I've tested it and it seems to work as intended.
Let's setup some data.
var array = new []
{
new { Sort = 1, Value = "foo1", },
new { Sort = 2, Value = "foo2", },
new { Sort = 3, Value = "foo3", },
new { Sort = 4, Value = "foo4", },
};
var oldSort = 1;
var newSort = 3;
First, query is split into three parts depending on positions of old and new indexes, so we can handle each case separately.
var q =
oldSort > newSort ?
array
.Where(x => x.Sort >= newSort && x.Sort < oldSort)
.Select(x => new { Sort = x.Sort + 1, Value = x.Value })
.Union(array.Where(x => x.Sort < newSort || x.Sort > oldSort))
.Union(array.Where(x => x.Sort == oldSort)
.Select(x => new { Sort = newSort, Value = x.Value }))
:
oldSort < newSort ?
array
.Where(x => x.Sort <= newSort && x.Sort > oldSort)
.Select(x => new { Sort = x.Sort - 1, Value = x.Value })
.Union(array.Where(x => x.Sort > newSort || x.Sort < oldSort))
.Union(array.Where(x => x.Sort == oldSort)
.Select(x => new { Sort = newSort, Value = x.Value }))
:
array;
Results for moving an item down (oldSort = 1, newSort = 3):
1 foo2
2 foo3
3 foo1
4 foo4
Results for moving an item up (oldSort = 4, newSort = 2):
1 foo1
2 foo4
3 foo2
4 foo3
UPDATE: The query works by splitting a sequence into three parts
Item with the old index becomes an item with the new index;
Items between the old and new indexes are shifted either up or down;
The rest keeps their indexes.
The result is the union of the parts.
UPDATE 2: The query works for any number of items and the absence of loops is intentional.
UPDATE 3: Here's one way to make the query work with LINQ-to-Entities.
using (var context = new TestDBEntities())
{
var array = context.TestTables;
var q =
oldSort > newSort ?
array
.Where(x => x.Sort >= newSort && x.Sort < oldSort)
.Select(x => new { Sort = x.Sort + 1, Value = x.Value })
.Union(array.Where(x => x.Sort < newSort || x.Sort > oldSort)
.Select(x => new { Sort = x.Sort, Value = x.Value }))
.Union(array.Where(x => x.Sort == oldSort)
.Select(x => new { Sort = newSort, Value = x.Value }))
:
oldSort < newSort ?
array
.Where(x => x.Sort <= newSort && x.Sort > oldSort)
.Select(x => new { Sort = x.Sort - 1, Value = x.Value })
.Union(array.Where(x => x.Sort > newSort || x.Sort < oldSort)
.Select(x => new { Sort = x.Sort, Value = x.Value }))
.Union(array.Where(x => x.Sort == oldSort)
.Select(x => new { Sort = newSort, Value = x.Value }))
:
array.Select(x => new { Sort = x.Sort, Value = x.Value });
}
The difference is that the types are now explicitly compatible.
The conditional operator is useful here:
var newitems = items.Select(x =>
new
{
Value = x.Value,
Sort = x.Sort == oldSort ? newSort :
x.Sort < oldSort && x.Sort >= newSort ? x.Sort + 1 :
x.Sort > oldSort && x.Sort < newSort ? x.Sort - 1 :
x.Sort
});
This is using Serge's setup:
var items = new []
{
new { Sort = 1, Value = "foo1", },
new { Sort = 2, Value = "foo2", },
new { Sort = 3, Value = "foo3", },
new { Sort = 4, Value = "foo4", },
};
var oldSort = 1;
var newSort = 3;
Its performance is decent (O(n) in all scenarios), plus it's concise and readable.
I know that you asked for a LINQ solution, but LINQ seems complicated to use in this situation, especially if you want to adjust the Sort column as well. I suggest a plain old approach using for loops and indexes. It performs the sort operation in-place and does not created a new list.
In order to make it reusable I create it as extension method for the IList interface, which makes it compatible to arrays too.
In order to make it generic, you need some way to access the Sort column. Exposing this column through an interface would restrict the solution to classes implementing this interface. Therefore I opted for accessors that you have to pass as delegates. They also work if the Sort column has another name like Order for instance.
public static class ListExtensions
{
public static void MoveItem<T>(this IList<T> list, int fromIndex, int toIndex,
Func<T, int> getSortKey, Action<T, int> setSortKey)
{
T temp = list[fromIndex];
int lastSortKey = getSortKey(temp);
setSortKey(temp, getSortKey(list[toIndex]));
if (fromIndex > toIndex) { // Move towards beginning of list (upwards).
for (int i = fromIndex; i > toIndex; i--) {
list[i] = list[i - 1];
int nextSortKey = getSortKey(list[i]);
setSortKey(list[i], lastSortKey);
lastSortKey = nextSortKey;
}
} else if (fromIndex < toIndex) { // Move towards end of list (downwards).
for (int i = fromIndex; i < toIndex; i++) {
list[i] = list[i + 1];
int nextSortKey = getSortKey(list[i]);
setSortKey(list[i], lastSortKey);
lastSortKey = nextSortKey;
}
}
list[toIndex] = temp;
}
}
You can use the method like this
list.MoveItem(3, 1, x => x.Sort, (x, i) => x.Sort = i);
Note that you have to pass the list indexes and not the sort values.
Here are the classes I used for the tests. Just set a breakpoint at the end of the two test methods in order to inspect the result in the locals window. Start the test in the Class View by right clicking on the Test class and choosing "Invoke Static Method".
public class SomeItem
{
public int Sort { get; set; }
public string Value { get; set; }
public override string ToString()
{
return String.Format("Sort = {0}, Value = {1}", Sort, Value);
}
}
public static class Test
{
public static void MoveUp()
{
List<SomeItem> list = InitializeList();
list.MoveItem(3, 1, x => x.Sort, (x, i) => x.Sort = i);
}
public static void MoveDown()
{
List<SomeItem> list = InitializeList();
list.MoveItem(1, 3, x => x.Sort, (x, i) => x.Sort = i);
}
private static List<SomeItem> InitializeList()
{
return new List<SomeItem> {
new SomeItem{ Sort = 1, Value = "foo1" },
new SomeItem{ Sort = 2, Value = "foo2" },
new SomeItem{ Sort = 3, Value = "foo3" },
new SomeItem{ Sort = 4, Value = "foo4" },
new SomeItem{ Sort = 5, Value = "foo5" }
};
}
}
UPDATE
A note on adjusting the sort key: The solution above works well if the sort keys are in-order and unique. If this is not always the case, a more robust solution would be to adjust the sort keys before storing the list back to the DB by simply setting the sort key equal to the list index. This would simplify the MoveItem method.
public static void MoveItem<T>(this IList<T> list, int fromIndex, int toIndex)
{
T temp = list[fromIndex];
if (fromIndex > toIndex) { // Move towards beginning of list (upwards).
for (int i = fromIndex; i > toIndex; i--) {
list[i] = list[i - 1];
}
} else if (fromIndex < toIndex) { // Move towards end of list (downwards).
for (int i = fromIndex; i < toIndex; i++) {
list[i] = list[i + 1];
}
}
list[toIndex] = temp;
}
public static void FixSortKeys<T>(this IList<T> list, Action<T, int> setSortKey)
{
for (int i = 0; i < list.Count; i++) {
setSortKey(list[i], i);
}
}
Related
Given 2 datasets (which are both a sequence of standard deviations away from a number, we are looking for the overlapping sections):
var list1 = new decimal[] { 357.06, 366.88, 376.70, 386.52, 406.15 };
var list2 = new decimal[] { 370.51, 375.62, 380.72, 385.82, 390.93 };
I would like to perform a merge with items from List2 being placed closest to items of List1, within a certain range, i.e. merge List2 element within 5.10 (standard deviation) of List1 element:
357.06
366.88 => 370.51
376.70 => 375.52, 380.72
386.52 => 390.93
406.15
The idea is to cluster values from List2 and count them, in this case element with value 376.70 would have the highest significance as it has 2 close neighbors of 375.52 and 380.72 (where as 366.88 and 386.52 have only 1 match, and the remaining none within range).
Which C# math/stats libraries could be used for this (or would there be a better way to combine statistically)?
If this is more of a computer science or stats question apologies in advance will close and reopen on relevant SO site.
Assuming that list2 is sorted (if not, put Array.Sort(list2);) you can try Binary Search:
Given:
var list1 = new decimal[] { 357.06m, 366.88m, 376.70m, 386.52m, 406.15m };
var list2 = new decimal[] { 370.51m, 375.62m, 380.72m, 385.82m, 390.93m };
decimal sd = 5.10m;
Code:
// Array.Sort(list2); // Uncomment, if list2 is not sorted
List<(decimal value, decimal[] list)> result = new List<(decimal value, decimal[] list)>();
foreach (decimal value in list1) {
int leftIndex = Array.BinarySearch<decimal>(list2, value - sd);
if (leftIndex < 0)
leftIndex = -leftIndex - 1;
else // edge case
for (; leftIndex >= 1 && list1[leftIndex - 1] == value - sd; --leftIndex) ;
int rightIndex = Array.BinarySearch<decimal>(list2, value + sd);
if (rightIndex < 0)
rightIndex = -rightIndex - 1;
else // edge case
for (; rightIndex < list1.Length - 1 && list1[rightIndex + 1] == value + sd; ++rightIndex) ;
result.Add((value, list2.Skip(leftIndex).Take(rightIndex - leftIndex).ToArray()));
}
Let's have a look:
string report = string.Join(Environment.NewLine, result
.Select(item => $"{item.value} => [{string.Join(", ", item.list)}]"));
Console.Write(report);
Outcome:
357.06 => []
366.88 => [370.51]
376.70 => [375.62, 380.72]
386.52 => [385.82, 390.93]
406.15 => []
Something like this should work
var list1 = new double[] { 357.06, 366.88, 376.70, 386.52, 406.15 };
var list2 = new double[] { 370.51, 375.62, 380.72, 385.82, 390.93 };
double dev = 5.1;
var result = new Dictionary<double, List<double>>();
foreach (var l in list2) {
var diffs = list1.Select(r => new { diff = Math.Abs(r - l), r })
.Where(d => d.diff <= dev)
.MinBy(r => r.diff)
.FirstOrDefault();
if (diffs == null) {
continue;
}
List<double> list;
if (! result.TryGetValue(diffs.r, out list)) {
list = new List<double>();
result.Add(diffs.r, list);
}
list.Add(l);
}
It uses MinBy from MoreLinq, but it is easy to modify to work without it.
In fact, you don't need extra libs or something else. You can use just LINQ for this.
internal class Program
{
private static void Main(string[] args)
{
var deviation = 5.1M;
var list1 = new decimal[] { 357.06M, 366.88M, 376.70M, 386.52M, 406.15M };
var list2 = new decimal[] { 370.51M, 375.62M, 380.72M, 385.82M, 390.93M };
var result = GetDistribution(list1.ToList(), list2.ToList(), deviation);
result.ForEach(x => Console.WriteLine($"{x.BaseValue} => {string.Join(", ", x.Destribution)} [{x.Weight}]"));
Console.ReadLine();
}
private static List<Distribution> GetDistribution(List<decimal> baseList, List<decimal> distrebutedList, decimal deviation)
{
return baseList.Select(x =>
new Distribution
{
BaseValue = x,
Destribution = distrebutedList.Where(y => x - deviation < y && y < x + deviation).ToList()
}).ToList();
}
}
internal class Distribution
{
public decimal BaseValue { get; set; }
public List<decimal> Destribution { get; set; }
public int Weight => Destribution.Count;
}
I hope it was useful for you.
I have a list with items, that have a Time property. If I want to select all items where Time is equal or bigger then some startTime, then I write something like this:
var newList = list.Where(i => (i.Time >= startTime));
But now I also want to get the last item, where the time is smaller than startTime. Is there a better way to implement this?
For example I have list where items have Time from this list:
[5:32, 5:46, 5:51, 6:07, 6:11, 6:36]
We specify a startTime as 6:00.
Now we want to get this times:
[5:51, 6:07, 6:11, 6:36]
Getting the whole List at once:
var newList = list
.OrderByDescending(i => i.Time)
.Take(list.Count(j => j.Time >= startTime) + 1)
.OrderBy(k => k.Time); //Optional
With Cognition's suggestion:
var newList = list
.OrderBy(i => i.Time)
.Skip(list.Count(j => j.Time < startTime - 1));
var result=list
.Where(i=>i.Time<startTime)
.OrderBy(i=>i.Time)
.Last()
.Concat(list
.OrderBy(i=>i.Time)
.Where(i=>i.Time>=startTime)
);
or
var result=list
.OrderBy(i=>i.Time)
.Last(i=>i.Time<startTime)
.Concat(list
.OrderBy(i=>i.Time)
.Where(i=>i.Time>=startTime)
);
var smallerThan = list
.Where(i => i.Time < startTime)
.OrderByDescending(o => o.Time)
.Take(1)
.Concat(list.Where(i => i.Time => startTime));
As your list is in order of the property you want to find, you can do something along the lines of
List<int> things = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8 };
int threshold = 4;
var newThings = things.Skip(things.FindIndex(x => x >= threshold) - 1);
Console.WriteLine(string.Join(", ", newThings));
Which outputs
3, 4, 5, 6, 7, 8
Extending it to use a class with a Time property which happens to be a TimeSpan:
class Z
{
public TimeSpan Time { get; set; }
};
class Program
{
static void Main(string[] args)
{
Random rand = new Random();
List<Z> zs = new List<Z>();
for (int i = 0; i < 10; i++)
{
zs.Add(new Z { Time = new TimeSpan(i, rand.Next(0,61), rand.Next(0,61)) });
}
TimeSpan threshold = new TimeSpan(4,0,0);
var newThings = zs.Skip(zs.FindIndex(x => x.Time >= threshold) - 1);
Console.WriteLine(string.Join(", ", newThings.Select(x => x.Time.ToString("c"))));
Console.ReadLine();
}
}
Sample output:
03:03:57, 04:09:37, 05:14:44, 06:58:55, 07:40:33, 08:37:06, 09:10:06
Many of the answers seem to require a descending orderby. But you can easily avoid this with a clean one liner and good efficiency:
var newList = list.Skip(list.Count(j => j.Time < startTime) - 1);
var newList = list
.Where(i => (i.Time >= startTime))
.ToList()
.Add(list
.Where(i => (i.Time < startTime))
.OrderByDescending(o => o.Time)
.FirstOrDefault()
)
int lastItemIndex = list.OrderBy(D => D.TimeOfDay).ToList()
.FindLastIndex(D => D.TimeOfDay < startTime);
var newList = list.Where(D => list.IndexOf(D) > lastItemIndex);
How do I return a list of the 3 lowest values in another list. For example, I want to get the 3 lowest values like this:
in_list = [2, 3, 4, 5, 6, 1]
To this:
out_list: [2, 3, n, n, n, 1]
Maybe a function like this:
out_list = function(in_list, 3)?
in_list and ouput list is declared like this:
List<string> in_list = new List<string>();
List<string> out_list = new List<string>();
Can you help me developing a C# code for this? Further explanation can be given.
If you really want those weird n, there's this simple solution:
public static List<string> Function(List<string> inputList, int max)
{
var inputIntegers = inputList
.Select(z => int.Parse(z))
.ToList();
var maxAuthorizedValue = inputIntegers
.OrderBy(z => z)
.Take(max)
.Last();
return inputIntegers
.Select(z => z <= maxAuthorizedValue ? z.ToString() : "n")
.ToList();
}
public static void Main(string[] args)
{
List<string> in_list = new List<string> { "2", "3", "4", "6", "1", "7" };
var res = Function(in_list, 3);
Console.Read();
}
For your new requirement about duplicates, you could limit the max number of integer your return:
public static List<string> Function(List<string> inputList, int max)
{
var inputIntegers = inputList.Select(z => int.Parse(z)).ToList();
var maxAuthorizedValue = inputIntegers
.OrderBy(z => z)
.Take(max)
.Last();
// I don't really like that kind of LINQ query (which modifies some variable
// inside the Select projection), so a good old for loop would probably
// be more appropriated
int returnedItems = 0;
return inputIntegers.Select(z =>
{
return (z <= maxAuthorizedValue && ++returnedItems <= max) ? z.ToString() : "n";
}).ToList();
}
You need two queries, one to determine the lowest items and one to fill the result-list. You can use a HashSet for faster loookups:
var lowest = new HashSet<String>(in_list
.Select(s => new { s, val = int.Parse(s) })
.OrderBy(x => x.val)
.Take(3)
.Select(x => x.s));
List<string> out_list = in_list.Select(s => lowest.Contains(s) ? s : "n").ToList();
If you actually only want 3 and duplicates are possible this is the best i've come up with:
var lowest = new HashSet<String>(in_list
.Select(s => new { s, val = int.Parse(s) })
.Distinct()
.OrderBy(x => x.val)
.Take(3)
.Select(x => x.s));
List<string> out_list = in_list
.Select((str, index) => new { str, index, value = int.Parse(str) })
.GroupBy(x => x.str)
.SelectMany(g => lowest.Contains(g.Key)
? g.Take(1).Concat(g.Skip(1).Select(x => new { str = "n", x.index, x.value }))
: g.Select(x => new { str = "n", x.index, x.value }))
.OrderBy(x => x.index)
.Select(x => x.str)
.ToList();
You could use Aggregate to grab a Dictionary of each element with its corresponding number of allowed occurrences which you could then use to grab your values from the input list:
public static List<string> GetList(List<string> in_list, int max)
{
Dictionary<string, int> occurrences = new Dictionary<string, int>();
int itemsAdded = 0;
in_list.OrderBy(x => x).Aggregate(occurrences, (list, aggr) =>
{
if (itemsAdded++ < max)
{
if (occurrences.ContainsKey(aggr))
occurrences[aggr]++;
else
occurrences.Add(aggr, 1);
}
return list;
});
//occurrences now contains only each required elements
//with the number of occurrences allowed of that element
List<string> out_list = in_list.Select(x =>
{
return (occurrences.ContainsKey(x) && occurrences[x]-- > 0 ? x : "n");
}).ToList();
return out_list;
}
I have two arrays and i am trying to get all possible sum of each element with other element of two array and index of each element
int[] width = new int[2] {10,20 };
int[] height = new int[2] {30,40 };
result should like this (value / indexes)
10 width0
10+20 width0+width1
10+30 width0+height0
10+40 width0+height1
10+20+30 width0+width1+height0
10+20+40 width0+width1+height1
10+20+30+40 width0+width1+height0+height1
And so for each element in two array
I tried using permutation but I get other output
It is more easy to get all combinations from one array than two arrays. And as we see, you need to store indices and array names along with the value of the elements in collections. So, in my opinion the best option is to combine these two arrays in one dictionary, where the key will be the value of the numbers and the value will be [ArrayName + Index of item] (f.e width0, height1 and so on....)
So, let's combine these arrays in one dictionary:
int[] width = new int[2] { 10, 20 };
int[] height = new int[2] { 30, 40 };
var widthDictionary = width.ToList().Select((number, index) => new { index, number })
.ToDictionary(key => key.number, value => string.Format("width{0}", value.index));
var heightDictionary = height.ToList().Select((number, index) => new { index, number })
.ToDictionary(key => key.number, value => string.Format("height{0}", value.index));
// And here is the final dictionary
var totalDictionary = widthDictionary.Union(heightDictionary);
Then add this method to your class: (source)
public static IEnumerable<IEnumerable<T>> GetPowerSet<T>(List<T> list)
{
return from m in Enumerable.Range(0, 1 << list.Count)
select
from i in Enumerable.Range(0, list.Count)
where (m & (1 << i)) != 0
select list[i];
}
Then send your dictionary as an argument to this method and project this collection as you want with the help of the Select() method:
var sumOfCombinations = GetPowerSet(totalDictionary.ToList())
.Where(x => x.Count() > 0)
.Select(x => new
{
Numbers = x.Select(pair => pair.Key).ToList(),
DisplayValues = x.Select(pair => pair.Value).ToList()
})
.ToList();
And at the end you can display expected result as this:
sumOfCombinations.ForEach(x =>
{
x.Numbers.ForEach(number => Console.Write("{0} ", number));
x.DisplayValues.ForEach(displayValue => Console.Write("{0} ", displayValue));
Console.WriteLine();
});
And, the result is:
This is a play off of #Farhad Jabiyev's answer.
Declares a class called IndexValuePair. and uses foreach on widthList and heightList. to populate the 'Index' property of item instance.
Note: Index is a string.
Class & Static Function
public class IndexValuePair
{
public string Index {get;set;}
public int Value {get;set;}
}
public static IEnumerable<IEnumerable<T>> GetPowerSet<T>(List<T> list)
{
return from m in Enumerable.Range(0, 1 << list.Count)
select
from i in Enumerable.Range(0, list.Count)
where (m & (1 << i)) != 0
select list[i];
}
Main (Console)
static void Main(string[] args)
{
int[] width = new int[2] { 10, 20 };
int[] height = new int[2] { 30, 40 };
var wholeList = width.Select(val => new IndexValuePair() { Index = "width", Value = val }).ToList();
var heightList = height.Select(val => new IndexValuePair() { Index = "height", Value = val }).ToList();
var iteration = 0;
wholeList.ForEach(ivp => { ivp.Index = ivp.Index + count; count = iteration + 1; });
iteration = 0;
heightList.ForEach(ipv => { ivp.Index = ivp.Index + count; count = iteration + 1; });
wholeList.AddRange(heightList);
var sumOfCombinations = GetPowerSet(wholeList).Where(x => x.Count() > 0)
.Select(x => new { Combination = x.ToList(), Sum = x.Sum(ivp => ivp.Value) }).ToList();
StringBuilder sb = new StringBuilder();
sumOfCombinations.ForEach(ivp =>
{
ivp.Combination.ForEach(pair => sb.Append(string.Format("{0} ", pair.Value)));
sb.Append(string.Format("= {0} = ", x.Sum));
ivp.Combination.ForEach(pair=> sb.Append(string.Format("{0} + ", pair.Index)));
sb.Length -= 3;
Console.WriteLine(sb);
sb.Clear();
});
var key = Console.ReadKey();
}
Is there a quick and nice way using linq?
How about:
var most = list.GroupBy(i=>i).OrderByDescending(grp=>grp.Count())
.Select(grp=>grp.Key).First();
or in query syntax:
var most = (from i in list
group i by i into grp
orderby grp.Count() descending
select grp.Key).First();
Of course, if you will use this repeatedly, you could add an extension method:
public static T MostCommon<T>(this IEnumerable<T> list)
{
return ... // previous code
}
Then you can use:
var most = list.MostCommon();
Not sure about the lambda expressions, but I would
Sort the list [O(n log n)]
Scan the list [O(n)] finding the longest run-length.
Scan it again [O(n)] reporting each number having that run-length.
This is because there could be more than one most-occurring number.
Taken from my answer here:
public static IEnumerable<T> Mode<T>(this IEnumerable<T> input)
{
var dict = input.ToLookup(x => x);
if (dict.Count == 0)
return Enumerable.Empty<T>();
var maxCount = dict.Max(x => x.Count());
return dict.Where(x => x.Count() == maxCount).Select(x => x.Key);
}
var modes = { }.Mode().ToArray(); //returns { }
var modes = { 1, 2, 3 }.Mode().ToArray(); //returns { 1, 2, 3 }
var modes = { 1, 1, 2, 3 }.Mode().ToArray(); //returns { 1 }
var modes = { 1, 2, 3, 1, 2 }.Mode().ToArray(); //returns { 1, 2 }
I went for a performance test between the above approach and David B's TakeWhile.
source = { }, iterations = 1000000
mine - 300 ms, David's - 930 ms
source = { 1 }, iterations = 1000000
mine - 1070 ms, David's - 1560 ms
source = 100+ ints with 2 duplicates, iterations = 10000
mine - 300 ms, David's - 500 ms
source = 10000 random ints with about 100+ duplicates, iterations = 1000
mine - 1280 ms, David's - 1400 ms
Here is another answer, which seems to be fast. I think Nawfal's answer is generally faster but this might shade it on long sequences.
public static IEnumerable<T> Mode<T>(
this IEnumerable<T> source,
IEqualityComparer<T> comparer = null)
{
var counts = source.GroupBy(t => t, comparer)
.Select(g => new { g.Key, Count = g.Count() })
.ToList();
if (counts.Count == 0)
{
return Enumerable.Empty<T>();
}
var maxes = new List<int>(5);
int maxCount = 1;
for (var i = 0; i < counts.Count; i++)
{
if (counts[i].Count < maxCount)
{
continue;
}
if (counts[i].Count > maxCount)
{
maxes.Clear();
maxCount = counts[i].Count;
}
maxes.Add(i);
}
return maxes.Select(i => counts[i].Key);
}
Someone asked for a solution where there's ties. Here's a stab at that:
int indicator = 0
var result =
list.GroupBy(i => i)
.Select(g => new {i = g.Key, count = g.Count()}
.OrderByDescending(x => x.count)
.TakeWhile(x =>
{
if (x.count == indicator || indicator == 0)
{
indicator = x.count;
return true;
}
return false;
})
.Select(x => x.i);
Here's a solution I've written for when there are multiple most common elements.
public static List<T> MostCommonP<T>(this IEnumerable<T> list)
{
return list.GroupBy(element => element)
.GroupBy(group => group.Count())
.MaxBy(groups => groups.Key)
.Select(group => group.Key)
.ToList();
}