How to Find the Mode in Array C#

How do I find the mode (most frequent element) of an array?

Here's a LINQ solution:

int mode = myArray
.GroupBy(x => x)
.OrderByDescending(g => g.Count())
.First() // throws InvalidOperationException if myArray is empty
.Key;

This groups the elements of myArray by value, sorts the groups by the number of values in each group, and takes the value of the first group. If there is more than one mode, then this takes whichever one occurs first (has the lowest index) in myArray.

If there might be more than one mode and you need all of them, then you can use this variation instead:

var groups = myArray
.GroupBy(x => x)
.Select(g => new { Value = g.Key, Count = g.Count() })
.ToList(); // materialize the query to avoid evaluating it twice below
int maxCount = groups.Max(g => g.Count); // throws InvalidOperationException if myArray is empty
IEnumerable<int> modes = groups
.Where(g => g.Count == maxCount)
.Select(g => g.Value);

This groups the elements of myArray by value, finds the maximum number of values in any group, and takes the value of every group having that maximum number of values.

If myArray is very large, then the second version (which is O(n)) might be faster than the first version (which is O(n log n) due to the sort).

C# Finding the Mode

user DesertFox mentioned properly.
You are trying to print a list. Intead of that, as per your requirement, make it as a single string using String.Join.

Modify your test method like below

    static void Test()
{
try
{
int[] values = { 1, 6, 4, 7, 9, 2, 5, 7, 2, 6, 5, 7, 8, 1, 3, 8, 2 };
List<int> mode = new List<int>();

mode = Mode(values);
Console.WriteLine("Mode: {0}", String.Join(", ", mode));
Console.ReadLine();
}
catch(Exception ex)
{

}
}

Output

Sample Image

Most efficient way to find all modes in a List of randomly generated integers and how often they occured

Different code is more efficient for differing lengths, but as the length approaches 6 million, this approach seems fastest. In general, LINQ is not for improving the speed of code, but the understanding and maintainability, depending on how you feel about functional programming styles.

Your code is fairly fast, and beats the simple LINQ approaches using GroupBy. It gains a good advantage from using the fact that List.Sort is highly optimized, and my code uses that as well, but on a local copy of the list to avoid changing the source. My code is similar in approach to yours, but is designed around a single pass doing all the computation needed. It uses an extension method I re-optimized for this problem, called GroupByRuns, that returns an IEnumerable<IGrouping<T,T>>. It also is hand expanded rather than fall back on the generic GroupByRuns that takes extra arguments for key and result selection. Since .Net doesn't have an end user accessible IGrouping<,> implementation (!), I rolled my own that implements ICollection to optimize Count().

This code runs about 1.3x as fast as yours (after I slightly optimized yours by 5%).

First, the RunGrouping class to return a group of runs:

public class RunGrouping<T> : IGrouping<T, T>, ICollection<T> {
public T Key { get; }
int Count;

int ICollection<T>.Count => Count;
public bool IsReadOnly => true;

public RunGrouping(T key, int count) {
Key = key;
Count = count;
}

public IEnumerator<T> GetEnumerator() {
for (int j1 = 0; j1 < Count; ++j1)
yield return Key;
}

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public void Add(T item) => throw new NotImplementedException();
public void Clear() => throw new NotImplementedException();
public bool Contains(T item) => Count > 0 && EqualityComparer<T>.Default.Equals(Key, item);
public void CopyTo(T[] array, int arrayIndex) => throw new NotImplementedException();
public bool Remove(T item) => throw new NotImplementedException();
}

Second, the extension method on IEnumerable that groups the runs:

public static class IEnumerableExt {
public static IEnumerable<IGrouping<T, T>> GroupByRuns<T>(this IEnumerable<T> src) {
var cmp = EqualityComparer<T>.Default;
bool notAtEnd = true;
using (var e = src.GetEnumerator()) {
bool moveNext() {
return notAtEnd;
}
IGrouping<T, T> NextRun() {
var prev = e.Current;
var ct = 0;
while (notAtEnd && cmp.Equals(e.Current, prev)) {
++ct;
notAtEnd = e.MoveNext();
}
return new RunGrouping<T>(prev, ct);
}

notAtEnd = e.MoveNext();
while (notAtEnd)
yield return NextRun();
}
}
}

Finally, the extension method that finds the max count modes. Basically it goes through the runs and keeps a record of those int with the current longest run count.

public static class IEnumerableIntExt {
public static IEnumerable<KeyValuePair<int, int>> MostCommon(this IEnumerable<int> src) {
var mysrc = new List<int>(src);
mysrc.Sort();
var maxc = 0;
var maxmodes = new List<int>();
foreach (var g in mysrc.GroupByRuns()) {
var gc = g.Count();

if (gc > maxc) {
maxmodes.Clear();
maxmodes.Add(g.Key);
maxc = gc;
}
else if (gc == maxc)
maxmodes.Add(g.Key);
}

return maxmodes.Select(m => new KeyValuePair<int, int>(m, maxc));
}
}

Given an existing random list of integers rl, you can get the answer with:

var ans = rl.MostCommon();

Finding sample mode with C#

First you need to calculate the max in the first loop. Then in the second you can find the numbers that have a count equal to max and add the key, not the max to your result.

static List<int> Mode(int[] array)
{
if (array.Length == 0)
{
throw new ArgumentException("Sample can't be empty");
}
List<int> result = new List<int>();
var counts = new Dictionary<int, int>();
int max = 0;
foreach (int num in array)
{
if (counts.ContainsKey(num))
counts[num] = counts[num] + 1;
else
counts[num] = 1;
if(counts[num] > max)
max = counts[num];
}

foreach (var key in counts.Keys)
{
if (counts[key] == max)
{
result.Add(key);
}
}

return result;
}


Related Topics



Leave a reply



Submit