VBA/Excel RANK in C# - 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

Related

Sorting array by frequency of elements in C#

Here is what i have so far
int[] numbers = { 3,5,4,3,8,8,5,3,2,1,9,5 };
int[] n = new int[12];
int[] k;
foreach (int number in numbers)
{
n[number]++;
}
Array.Sort(n);
Array.Reverse(n);
foreach (int value in n)
{
Console.WriteLine(value);
}
I know i am missing the part where i sort the frequency of the elements after i counted them and i just cant get my head around it. I'd appreciate some help, Thanks!
What's the problem with your solution ?
Whereas you correctly keep the frequencies of the numbers in the table called n in your code, which hereby I would call it frequencies, then you Sort this array. This action breaks your solution, since each frequency is associated with the corresponding index of its location in the array.
E.g. If an instance of this array is this [8,2,1,7,6]. When you call the Sort method on this array, this would have as a result the array to be sorted and the order of the elements of the array would be this [1,2,7,6,8]. Before calling sort, the first element of the array was indicating that the number 0 (the index of the first element is 0) has been found 8 times in our numbers. After sort, the first element is 1, which means now that the frequency of the number 0 is 1, which is apparently wrong.
If you want to keep it your way, then you could try something like this:
int[] numbers = { 1,2,2,9,1,2,5,5,5,5,2 };
int[] frequencies = new int[12];
int k = 3;
foreach (int number in numbers)
{
frequencies[number]++;
}
var mostFrequentNumbers = frequencies.Select((frequency, index) => new
{
Number = index,
Frequency = frequency
})
.OrderByDescending(item => item.Frequency)
.Select(item => item.Number)
.Take(k);
foreach (int mostFrequentNumber in mostFrequentNumbers)
{
Console.WriteLine(mostFrequentNumber);
}
Are there any other approaches ?
An easy way to do this is to use a data structure like a Dictionary, in which you would keep as keys the numbers and as the corresponding values the corresponding frequencies.
Then you can order by descending values the above data structure an keep the k most frequent numbers.
int[] numbers = { 1,2,2,9,1,2,5,5,5,5,2 };
int k = 3;
Dictionary<int, int> numberFrequencies = new Dictionary<int, int>();
foreach (int number in numbers)
{
if(numberFrequencies.ContainsKey(number))
{
numberFrequencies[number] += 1;
}
else
{
numberFrequencies.Add(number, 1);
}
}
var mostFrequentNumbers = numberFrequencies.OrderByDescending(numberFrequency => numberFrequency.Value)
.Take(k)
.Select(numberFrequency => numberFrequency.Key);
foreach (int mostFrequentNumber in mostFrequentNumbers)
{
Console.WriteLine(mostFrequentNumber);
}
You can also achieve the same thing by only using LINQ:
int[] numbers = { 1,2,2,9,1,2,5,5,5,5,2 };
int k = 3;
var mostFrequentNumbers = numbers.GroupBy(number => number)
.ToDictionary(gr => gr.Key, gr => gr.Count())
.OrderByDescending(keyValue => keyValue.Value)
.Take(k)
.Select(numberFrequency => numberFrequency.Key);
foreach (int mostFrequentNumber in mostFrequentNumbers)
{
Console.WriteLine(mostFrequentNumber);
}
You can just use Linq extensions:
using System.Linq;
using System.Collections.Generic;
...
private static IEnumerable<int> Solve(int[] numbers, int k) {
return numbers
.GroupBy(x => x)
.OrderByDescending(g => g.Count())
.Select(g => g.Key)
.Take(k);
}
Then you can call:
var numbers = new []{1,2,2,9,1,2,5,5,5,5,2};
var k = 3;
var result = Solve(numbers, k);
foreach (int n in result)
Console.WriteLine(n);
To be very terse:
var frequents = numbers.GroupBy(t => t)
.Where(grp => grp.Count() > 1)
.Select(t => t.Key)
.OrderByDescending(t => t)
.Take(k)
.ToList();

Find closest value in a list in C# with linq?

I've a list like this:
public List<Dictionary<int, int>> blanks { get; set; }
This keep some index values:
In addition I have also a variable named X. X can take any value. I want to find closest 'Key' value to X. For example:
If X is 1300, I want to take blanks index: 2 and Key: 1200.
How can I do this via linq? Or, is there any other solution?
Thanks in advance.
EDIT: What if it is not a Dictionary. What if it is a List like this:
List<List<int[]>> lastList = new List<List<int[]>>();
This time, I want to take first List's indexes and second List's index. For example, if X is 800, I want to take 0 and 0 (for index 0) and also take 1 and 1 (for index 1) How can I do this time?
var diffs = blanks.SelectMany((item, index) => item.Select(entry => new
{
ListIndex = index, // Index of the parent dictionary in the list
Key = entry.Key, // Key
Diff = Math.Abs(entry.Key - X) // Diff between key and X
}));
var closestDiff = diffs.Aggregate((agg, item) => (item.Diff < agg.Diff) ? item : agg);
Dictionary<int, int> closestKeyDict = blanks[closestKey.ListIndex];
int closestKey = closestDiff.Key;
int closestKeyValue = closestKeyDict[closestKey];
The SelectMany clause flattens all the dictionaries entries into a collection of { ListIndex, DictionaryKey, Difference } instances.
This flattened collection is then aggregated to retrieve the item with the minimum difference.
To answer your second questsion:
var diffs = blanks.SelectMany((list, listIndex) => list.
SelectMany((array, arrayIndex) => array.
Select((item, itemIndex) => new
{
ListIndex = listIndex,
ArrayIndex = arrayIndex,
ItemIndex = itemIndex,
Diff = Math.Abs(item - X)
})));
var closestDiff = diffs.Aggregate((agg, item) => (item.Diff < agg.Diff) ? item : agg);
Now in closestDiff you'll find the indices of the closes item (List index, array index and array item index)
This might not be the most optimized way but it should just work,
List<Dictionary<int, int>> blanks = new List<Dictionary<int, int>>
{
new Dictionary<int, int>{{100,200}},
new Dictionary<int, int>{{500,200}},
new Dictionary<int, int>{{700,200}},
new Dictionary<int, int>{{1200,200}},
new Dictionary<int, int>{{300,200}},
new Dictionary<int, int>{{200,200}},
new Dictionary<int, int>{{800,200}},
};
int x = 1300;
IEnumerable<int> keys = blanks.SelectMany(ints => ints.Keys);
var diff = keys.Select(i => Math.Abs(i - x)).ToList();
var index = diff.IndexOf(diff.Min());
var value = blanks[index].Keys.First();

Select indexes of all elements in sequence

Using LINQ, can I write a statement that will return an IEnumerable of the indexes of items.
Very simple instance:
{1,2,4,5,3}
would just return
{0,1,2,3,4}
and
{1,2,4,5,3}.Where(num => num == 4)
would return
{2}
It isn't exact code, but it should get the idea across.
var a = new[] {1, 2, 4, 5, 3};
//** First, generates a simple sequence with {0,1,2,3,4}
//** using the 2 parameter lambda select
var sequence1 = a.Select((_, index) => index);
//** Second, gets an array with all indexes where the value is 4.
// We need both value and index for the next line to work.
var sequence2 = a.Select((value, index) => new {value, index});
// Get all indexes where the value is 4
var indexArray = sequence2.Where(x => x.value == 4)
.Select(x => x.index).ToArray();
var numbers = Enumerable.Range(1,10).ToList();
int index = -1;
var indices = numbers.Select(x => i++).ToList();
If you're willing to change up your syntax a bit and use an extension method, the following will work. I'm not keen on it as it creates a new sequence for every call.
var sequence = new[] { 1, 2, 4, 5, 3 };
sequence.Indexer().Select(num => num.Item1); // returns {0,1,2,3,4}
sequence.Indexer().Where(num => num.Item2 == 4).Select(num => num.Item1); // returns {2}
private static IEnumerable<Tuple<int, T>> Indexer<T>(this IEnumerable<T> sequence)
{
return sequence.Select((x, y) => new Tuple<int, T>(y, x));
}
A better way would be to change up the way you're writing it altogether:
var sequence = new[] { 1, 2, 4, 5, 3 };
sequence.Select((num, index) => new { Num = num, Index = index }).Select(num => num.Index); // returns {0, 1,2,3,4}
sequence.Select((num, index) => new { Num = num, Index = index }).Where(num => num.Num == 4).Select(num => num.Index); // returns {2}
The full set of indices just depends on the number of items, not on the values, so you can do this:
IEnumerable<int> indices = Enumerable.Range(0, 5);
If you're dealing with an IEnumerable<T>, you could do the following to get the index of the item matching 4:
IEnumerable<int> values = new[] { 1, 2, 3, 4, 5 };
int indexOf4 = (
values.Select((v, i) => new {v, i})
.FirstOrDefault(vi => vi.v == 4) ?? new {v = 0, i = -1}).i;
This copes with the case where the value source doesn't contain a match (returning -1).
Of course, if you don't mind converting your IEnumerable<T> to a list then you can just call IndexOf:
int indexOf4a = values.ToList().IndexOf(4);
But, I suspect what the question is really looking for is a way to find all the indices for values that match a particular predicate. For example:
IEnumerable<int> big = values.Select((v, i) => new {v, i})
.Where(vi => vi.v > 3)
.Select (vi => vi.i);
which returns the indices of the values > 3: [3, 4].
If the predicate doesn't match any values then you'll get an empty enumerable as the result.
IEnumerable<int> seq = new[] { 1, 2, 4, 5, 3 };
// The indexes of all elements.
var indexes = Enumerable.Range(0, seq.Count());
// The index of the left-most element with value 4.
// NOTE: Will return seq.Count() if the element doesn't exist.
var index = seq.TakeWhile(x => x != 4).Count();
// The indexes of all the elements with value 4.
// NOTE: Be careful to enumerate only once.
int current_index = 0;
var all_indexes =
from e in (
from x in seq
select new { x, Index = current_index++ }
)
where e.x == 4
select e.Index;
You can do it like this:
public static IEnumerable<int> WhereIndices<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
return source.Select(Tuple.Create<T, int>)
.Where(z => predicate(z.Item1)).Select(z => z.Item2);
}
It's an extension method, so put it in a static non-nested class. Use it just like you use Where, that is:
.WhereIndices(num => num == 4)
This should do it. Not sure how efficient it is though..
List<int> list = new List<int>()
{
1,
2,
3,
4,
5
};
var indexes = list.Select(item => list.IndexOf(item));
var index = list.Where(item => item == 4).Select(item => list.IndexOf(item));

Order List of Arrays by the information in the arrays?

What i want to do is reorder a list of arrays by the information that is in the arrays. For example my list might have 20 Long[] arrays that i want to order by Highest Totallaps and Lowest TotalTime Have tried several things though having trouble coming up with anything. Any Help would be greatly appreciated.
public List<long[]> Reorderedlist()
{
_timeKeeper._timeKeeperControls controls = new _timeKeeper._timeKeeperControls();
List<long[]> returnList = new List<long[]>();
List<long[]> listToReOrder = new List<long[]>();
listToReOrder = controls.teamInfoInClass("1",ContactlessTimer.Properties.Settings.Default.currentRaceID);
//newlist contains list of long[] arrays
//each array contains
//long[0] = id1 (eg 33)
//long[1] = id2 (eg 34)
//long[2] = totalLaps (eg 10)
//long[3] = total time (eg 340000 in miliseconds)
foreach (long[] Arr in listToReOrder)
{
foreach (long info in Arr)
{
//order
}
}
return returnList;
}
Use LINQ methods: OrderByDescending and ThenBy:
List<long[]> returnList = listToReOrder.OrderByDescending(x => x[2])
.ThenBy(x => x[3])
.ToList();

how to find no of duplicate values in arraylist

I have a arraylist which has values some of them are repeated. I need the count of the repeated values. Is this possible in c#?
here is a great post how to do it with LINQ
var query =
from c in arrayList
group c by c into g
where g.Count() > 1
select new { Item = g.Key, ItemCount = g.Count()};
foreach (var item in query)
{
Console.WriteLine("Country {0} has {1} cities", item.Item , item.ItemCount );
}
If your object has equals method correctly overrided just call Distinct() from System.Linq namespace on it
It requires the ArrayList to be homogeneous and calling Cast<YourType>() before Distinct().
Then subtract the length of arrayList from the Distinct sequence.
arraList.Count - arraList.Cast<YourType>().Distinct().Count()
it will throw exception if your items in arrayList is not of type YourType, and if you use OfType<YourType> it filters items to objects of type YourType.
but if you want the count of each repeated item, this is not your answer.
public Dictionary<T,int> CountOccurences<T>(IEnumerable<T> items) {
var occurences = new Dictionary<T,int>();
foreach(T item in items) {
if(occurences.ContainsKey(item)) {
occurences[item]++;
} else {
occurences.Add(item, 1);
}
}
return occurences;
}
myList.GroupBy(i => i).Count(g => g.Count() > 1)
and if you specifically need ArrayList
ArrayList arrayList = new ArrayList(new[] { 1, 1, 2, 3, 4, 4 });
Console.WriteLine(arrayList.ToArray().GroupBy(i => i).Count(g => g.Count() > 1));
Based on comments by poster
ArrayList arrayList = new ArrayList(new[] { 1, 1, 2, 3, 4, 4 });
Console.WriteLine(arrayList.ToArray().Count(i => i == 4));
int countDup = ArrayList1.Count - ArrayList1.OfType<object>().Distinct().Count();
var items = arrayList.Cast<object>()
.GroupBy(o => o)
.Select(g => new { Item = g, Count = g.Count() })
.ToList();
each item of result list will have two properties:
Item - source item
Count - count in source list
You can acomplish this many ways. The first that comes to me would be to group by the values within your array list, and only return the grouping counts that are over 1.
ArrayList al = new ArrayList();
al.Add("a");
al.Add("b");
al.Add("c");
al.Add("f");
al.Add("a");
al.Add("f");
int count = al.ToArray().GroupBy(q => q).Count(q=>q.Count()>1);
count will return the value of 2 as a and f are duplicated.
You could sort it, then it becomes very easy.
Edit: sorting becomes a moot point when done this way.
Arraylist myList = new ArrayList();
myList = someStuff;
Dictionary<object, int> counts = new Dictionary<object,int>();
foreach (object item in myList)
{
if (!counts.ContainsKey(item))
{
counts.Add(item,1);
}
else
{
counts[item]++;
}
}
Edit:
Some minor things might vary (not certain about some of my square braces, I'm a little rusty with c#) but the concept should withstand scrutiny.

Categories

Resources