How to find the Mode in Array C#? [duplicate] - c#

This question already has answers here:
Find character with most occurrences in string?
(12 answers)
Closed 7 years ago.
I want to find the Mode in an Array. I know that I have to do nested loops to check each value and see how often the element in the array appears. Then I have to count the number of times the second element appears. The code below doesn't work, can anyone help me please.
for (int i = 0; i < x.length; i ++)
{
x[i]++;
int high = 0;
for (int i = 0; i < x.length; i++)
{
if (x[i] > high)
high = x[i];
}
}

Using nested loops is not a good way to solve this problem. It will have a run time of O(n^2) - much worse than the optimal O(n).
You can do it with LINQ by grouping identical values and then finding the group with the largest count:
int mode = x.GroupBy(v => v)
.OrderByDescending(g => g.Count())
.First()
.Key;
This is both simpler and faster. But note that (unlike LINQ to SQL) LINQ to Objects currently doesn't optimize the OrderByDescending when only the first result is needed. It fully sorts the entire result set which is an O(n log n) operation.
You might want this O(n) algorithm instead. It first iterates once through the groups to find the maximum count, and then once more to find the first corresponding key for that count:
var groups = x.GroupBy(v => v);
int maxCount = groups.Max(g => g.Count());
int mode = groups.First(g => g.Count() == maxCount).Key;
You could also use the MaxBy extension from MoreLINQ method to further improve the solution so that it only requires iterating through all elements once.

A non LINQ solution:
int[] x = new int[] { 1, 2, 1, 2, 4, 3, 2 };
Dictionary<int, int> counts = new Dictionary<int, int>();
foreach( int a in x ) {
if ( counts.ContainsKey(a) )
counts[a] = counts[a]+1
else
counts[a] = 1
}
int result = int.MinValue;
int max = int.MinValue;
foreach (int key in counts.Keys) {
if (counts[key] > max) {
max = counts[key];
result = key;
}
}
Console.WriteLine("The mode is: " + result);

As a beginner, this might not make too much sense, but it's worth providing a LINQ based solution.
x
.GroupBy(i => i) //place all identical values into groups
.OrderByDescending(g => g.Count()) //order groups by the size of the group desc
.Select(g => g.Key) //key of the group is representative of items in the group
.First() //first in the list is the most frequent (modal) value

Say, x array has items as below:
int[] x = { 1, 2, 6, 2, 3, 8, 2, 2, 3, 4, 5, 6, 4, 4, 4, 5, 39, 4, 5 };
a. Getting highest value:
int high = x.OrderByDescending(n => n).First();
b. Getting modal:
int mode = x.GroupBy(i => i) //Grouping same items
.OrderByDescending(g => g.Count()) //now getting frequency of a value
.Select(g => g.Key) //selecting key of the group
.FirstOrDefault(); //Finally, taking the most frequent value

Related

How to count how many times exist each number from int[] inside IEnumerable<int>?

I have array of ints(Call him A) and IEnumarable(Call him B):
B - 1,2,4,8,289
A - 2,2,56,2,4,33,4,1,8,
I need to count how many times exist each number from A inside B and sum the result.
For example:
B - 1,2,4,8,289
A - 2,2,56,2,4,33,4,1,8,
result = 1+3+2+1+0
What is elegant way to implement it?
With LINQ it is easy:
int count = A
.Where(x => B.Contains(x))
.Count();
Counts how many times elements from A are contained in B.
As Yuval Itzchakov points out, this can be simplified like this:
int count = A.Count(x => B.Contains(x));
I need to count how many times exist each number from A inside B and sum the result.
You can get both the count and sum as follows
List<int> b = new List<int>() { 1,2,4,8,289 };
List<int> a = new List<int>() { 2,2,56,2,4,33,4,1,8 };
var subset = a.Where(i => b.Contains(i));
var count = subset.Count(); // 7
var sum = subset.Sum(); // 23
Note that I reuse the same Linq expression to get both the count and the sum.
One might be tempted to use a HashSet<int> in place of a List<int> because the .Contains operation is faster. However, HashSet is a set, meaning if the same number is added multiple times, only one copy of that number will remain in the set.
sweet and simple.. one line solution
why dont you try it..
int sum = 0;
A.ToList().ForEach(a=>sum +=B.Count(b=>b==a));
Console.Write(sum);
you can sweap the A/B it will still work
With Linq you can do like this
var B = new List<int>{ 1, 2, 4, 8, 289 };
var A = new List<int> { 2, 2, 56, 2, 4, 33, 4, 1, 8 };
var repetitionSum = B.Select(b => A.Count(a => a == b)).Sum(); //result = 7
And if you want, you can get the individual repetition list like this
var repetition = B.Select(b => A.Count(a => a == b)).ToList();
// { 1, 3, 2, 1, 0 }
It is not clear if you want to know the occurrences of each number or the final count (your text and your example code differ). Here is the code to get the number of appearances of each number
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
int[] a = new []{1,2,3};
int[] b = new []{1,2,2,3};
Dictionary<int, int> aDictionary = a.ToDictionary(i=>i, i => 0);
foreach(int i in b)
{
if(aDictionary.ContainsKey(i))
{
aDictionary[i]++;
}
}
foreach(KeyValuePair<int, int> kvp in aDictionary)
{
Console.WriteLine(kvp.Key + ":" + kvp.Value);
}
}
}

Sort list index by value

I have a list in C#
List<int> temp = new List<int>(0, 1, 2, 3, 4, 2, 8);
And I would like to make a list of items in temp that satisfy a bool expression, such as
List<int> results = temp.Sort(x > 2);
But .Sort() doesn't exist. The resulting list results would then contain
(3, 4, 6)
Which are the indexes of the values in the original list temp that are greater than 2.
Sorry if this is trivial or has been asked before; I'm new to coding and to SO. Thanks!
EDIT: As some of you have correctly pointed out, I'm don't actually want to sort the list, I just want to filter it. Thanks!
From the output it appears that you need Indices
List<int> temp = new List<int>{0, 1, 2, 3, 4, 2, 8};
var newList = temp.Select((r, i) => new { Index = i, Value = r })
.Where(r => r.Value > 2) //For your condition
.OrderBy(r => r.Value)
.Select(r => r.Index) //For output
.ToList();
This would return you (3, 4, 6)
EDIT: Since the question has been edited and pointed out that sorting the list based on value is not required, in that case OrderBy in the above statement can be left out.
var newList = temp.Select((r, i) => new { Index = i, Value = r })
.Where(r => r.Value > 2) //For your condition
.Select(r => r.Index) //For output
.ToList();
A simple, fast and easy to understand solution would be:
for (int i = 0; i < temp.Count; i++)
if(temp[i] > 2) results.Add(i);
I would like to make a list of items in temp that satisfy a bool expression,
That would be Where:
List<int> temp = new List<int>(0, 1, 2, 3, 4, 2, 8);
List<int> results = temp.Where(x => x > 2);
There are many variations of Select and Where that include the index of items that meet a criteria - if you can be more clear on what you want then you will get better examples.
You need to keep store the index before applying the where clause, or it will be lost.
List<int> temp = new List<int>{ 0, 1, 2, 3, 4, 2, 8 };
List<int> results = temp.Select((x,i) => new { Value = x, Index = i })
.Where(x => x.Value > 2)
.Select(x => x.Index)
.ToList();
You could try this one:
List<int> results = temp.Select((x, index) => new { Index = index, Number = x })
.Where(x=>x.Number > 2)
.Select(x=>x.Index);

VBA/Excel RANK in C#

I am working on creating calculations from a spreadsheet into C#, and I was wondering if C# has a similar method to Rank in Excel?
Rank in Excel
Returns the rank of a number in a list of numbers. The rank of a
number is its size relative to other values in a list. (If you were to
sort the list, the rank of the number would be its position.)
Syntax
RANK(number,ref,order)
Number is the number whose rank you want to find.
Ref is an array of, or a reference to, a list of numbers.
Nonnumeric values in ref are ignored.
Order is a number specifying how to rank number.
If order is 0 (zero) or omitted, Microsoft Excel ranks number as if
ref were a list sorted in descending order. If order is any nonzero
value, Microsoft Excel ranks number as if ref were a list sorted in
ascending order.
The same can be achieved through code, but I just wanted to check if there was anything I was missing first.
You can, sort of.
SortedList<int, object> list = new SortedList<int, object>();
// fill with unique ints, and then look for one
int rank = list.Keys.IndexOf(i);
Rank will be an ascending, zero-based position.
You could pretty it up by writing an extension method:
public static class Extensions
{
public static int Rank(this int[] array, int find)
{
SortedList<int, object> list = new SortedList<int, object>();
for (int i = 0; i < array.Length; i++)
{
list.Add(array[i], null);
}
if (list.ContainsKey(find))
{
return list.Keys.IndexOf(find);
}
else
{
return -1;
}
}
}
And use it like:
int[] ints = new int[] { 2, 7, 6, 3, 9, 12 };
int rank = ints.Rank(2);
...but I'm not convinced its the most sensible thing to do.
To get the equivalent of RANK you'll need to get the minimum index of each item when you group:
var ranks = list.OrderBy(x => x)
.Select((x, i) => new {x, i = i+1}) // get 1-based index of each item
.GroupBy(xi => xi.x) // group by the item
.Select(g => new {rank = g.Min(xi => xi.i), items = g}) // rank = min index of group
.SelectMany(g => g.items, (g, gg) => new {g.rank, gg.i}) ; // select rank and item
or if you'rs grouping by the property of a class:
var ranks = list.OrderBy(x => x.{some property})
.Select((x, i) => new {x, i = i+1}) // get 1-based index of each item
.GroupBy(xi => xi.x.{some property}) // group by the item's property
.Select(g => new {rank = g.Min(xi => xi.i), items = g}) // rank = min index of group
.SelectMany(g => g.items, (g, gg) => new {g.rank, gg.i}) ; // select rank and item
This works for me so far (and it is simpler)
public static int Rank<T>(T value, IEnumerable<T> data)
{
return data.OrderByDescending(x => x).ToList().IndexOf(value) + 1;
}
I used T so it can take all numeric types (int/double/decimal).
The usage is similar to Excel
int[] data = new[] { 3, 2, 2, 3, 4 };
int rank = Rank(3, data); // returns 2
I hope I didn't miss anything

Group by a predefined set of keys using LINQ

I've faced the following problem using LINQ. Say I have a collection of numbers in range from one to five: [1,2,4,5,0,3,1 ...]. There could be any number of those in that array. What I want is to transform that array into following structure: [{number:0, count:5},{number:1, count:3}, {number:2, count:0}....]. If I use GroupBy I miss entry for number 2. Is there any elegant and effective way of doing this using LINQ?
You need to perform an outer join between your collection and a "fixed" collection containing numbers 0 to 5 first. Then group that and do the counting.
var arrayOfNumbers = new int[] {1, 5, 4, 5, 1, 0, 2, 3, 4, 5, 1,1,1};
var result =
from n in arrayOfNumbers
group n by n into g
select new { Number = g.Key, Count = g.Count() };
foreach (var item in result)
{
Console.WriteLine(String.Format("Number {0}: count {1}", item.Number, item.Count));
}
List<KeyValuePair<string, int>> pairs = new List<KeyValuePair<string,int>>();
for(int i = 0; i < 5; i++)
{
pairs.Add(new KeyValuePair<string, int>("number:" + i, arrayOfNums.Select(x => x == i).Count()));
}
Where arrayOfNums is your array of values between 0 and 5.
The results are stored in a List<KeyValuePair>> in the format you want.
You could improve upon this, instead of using the hardcoded 5 in the loop, you could use find the highest value in the array.

How to search for a number in a list of arrays of numbers based on the first index of each array using LINQ?

I have a list of arrays of numbers. I am searching for the two arrays where my search number falls between the numbers positioned in index 0. Then return the number positioned in index 1 from the second array. (Assume numbers in index 0 are sorted already and there are no duplicates)
My wrong solution for LINQPad:
The value of 'found' should be 3 because 9 falls between 4 & 10 in second and third array. Then I take the second found array and return 3 which is in index 1 of that array.
List<int[]> list = new List<int[]> { new[] { 1, 5 }, new[] { 4, 6 }, new[] { 10, 3} , new[] { 15, 8} };
int searchFor = 9;
int found = list.Where(n => searchFor >= n[0] && searchFor <= n[0]).Select(i => i[1]).FirstOrDefault();
found.Dump(); //should be 3 instead of 0.
Try this :
int found = list.Zip(list.Skip(1), (x, y) => x[0]<=searchFor&&y[0]>=searchFor?y[1]:0).FirstOrDefault(o=>o!=0);
Well my logic is a little different, but get the result you want. I would recommend just using a Dictionary if you are doing key-pair-value stuff like this. It makes things simpler in my opinion and if you have no repeating key's this should work fine.
// Use dictionary instead of array's if just using two int values
var dic = new Dictionary<int, int>();
dic.Add(1, 5);
dic.Add(4, 6);
dic.Add(10, 3);
dic.Add(15, 8);
int searchFor = 9;
// Don't need to find this really
int low = (from l in dic
where l.Key <= searchFor
select l.Key).Max();
// Just need this
int found = (from h in dic
where h.Key >= searchFor
select h.Value).Min();
Console.WriteLine("Low: " + low);
Console.WriteLine("Found: " + found);
How about
var found = list.First(l => l[0] > searchFor)[1];
It should do the trick as I can assume that list is ordered by each first element.
If not, then
var found = list.Orderby(l=>l[0]).First(l => l[0] > searchFor)[1];
should also work.
The expression in the where statement filters for arrays having the first element less or equal and greater or equal than 9. Since it can't be less and greater at the same time it actually filters for all arrays that have 9 as first element.
For the given data this results in an empty sequence. FirstOrDefault therefore returns the default (0 for integers).
You actually have to look for the first element greater or equal than 9:
int[] result = list.FirstOrDefault(arr => arr[0] >= searchFor);
if (result == null)
{
Console.WriteLine("Not found!");
}
else
{
Console.WriteLine(result[1]);
}

Categories

Resources