how to count same elements in different arrays in C# - c#

I've a list that contains 4 sized arrays:
These arrays have 4 elements. I want to use another list that contains these arrays' first element's count. In addition, if their first elements are same, they should be summation. For example:
list[0] = {1,2,3,4}
list[1] = {1,1,5,3}
list[2] = {1,2,5,8}
list[3] = {2,2,3,3}
list[4] = {3,5,5,6}
list[5] = {4,4,4,4}
list[6] = {4,5,5,6}
So, anotherList should be:
anotherList = {3, 1, 1, 2}
How can I do this?
EDIT: Expected result is:

anotherList = list.Select(a => a[0]) // project each array to its first item
.GroupBy(x => x) // group first items by their value
.Select(g => g.Count()) // select count of same items
.ToList();
Output:
[ 3, 1, 1, 2 ]
NOTE: GroupBy internally uses Lookup which returns groups in same order as the are added, so it seems to be what you want.
UPDATE: Approach which does not depend on internal implementation of GroupBy
anotherList = list.Select((a,i) => new { Item = a[0], Index = i })
.GroupBy(x => x.Item)
.OrderBy(g => g.Min(x => x.Index))
.Select(g => g.Count())
.ToList();

Related

How to rank elements in c# especially when it has duplicates

I have a requirement to rank the array elements and the array has duplicate values. I tried following this Ranking items in a list with LINQ but this doesn't work when the array has duplicate values in it. Any easy way to do it in c#?
For Example :
input = [650,150,150,200]
output = [1,3,3,2]
For Example :
input = [650,200,200,150]
output = [1,2,2,3]
Update: The requirement is as below, what if I add one more element to the array
Ex: [650,150,150,200,100] output needs to be [1,3,3,2,5] instead of [1,3,3,2,4]
You can create a dictionary as rank-lookup source:
int[] array = new[] {650,150,150,200};
Dictionary<int, int> numRanks = array
.GroupBy(i => i)
.OrderByDescending(g => g.Key)
.Select((g, index) => (num:g.Key, rank:index+1))
.ToDictionary(x => x.num, x => x.rank);
int[] result = array.Select(i => numRanks[i]).ToArray();
For your updated requirement you could use a similar approach using a Lookup<TKey, TValue>:
var rankLookup = array
.OrderByDescending(i => i)
.Select((num, index) => (num, index))
.ToLookup(x => x.num, x => x.index + 1);
int[] result = array.Select(i => rankLookup[i].First()).ToArray();
The lookup is like a dictionary that allows duplicate keys. You need to use First here because you are just interested in the rank. If you'd use Count() you'd know how many duplicates it had.
You could create an array of items, distinct and in order, then use the indices to determine the rank of each item.
var ranks = input.Distinct().OrderByDescending(x => x).ToArray();
var ranked = input.Select(x => Array.IndexOf(ranks, x) + 1);
Working example
Update after comment
If rankings need to be skipped, just remove the Distinct:
var ranks = input.OrderByDescending(x => x).ToArray();
var ranked = input.Select(x => Array.IndexOf(ranks, x) + 1);
Array.IndexOf will take the first element when there are duplicates.
Working example

Get elements of an IEnumerator to a list

I'm new to c# so go easy on me. Anyways, I made a list of numbers
List<int> numbers = new List<int>();
and I want to make a list of each number and its count/frequency.
var grouped = numbers
.GroupBy(i => i)
.Select(i => new { Number = i.Key, Count = i.Count() });
In locals, I can see the group, which has an IEnumerator interface with all of the numbers and their count values image of what I'm talking about. So is there a way to make a list with the numbers and their frequency/count?
Thank you.
IEnumerable<T> is a sequence so it doesn't own a count. But Enumerable.Count is an extension method of IEnumerable<T>
That is, you don't necessarily need to convert an IEnumerable<T> into a List<T>:
var grouped = numbers
.GroupBy(i => i)
.Select(i => new { Number = i.Key, Count = i.Count() });
var groupedCount = grouped.Count();
// You may iterate grouped
foreach(var value in grouped)
{
Console.WriteLine($"{value.Number} {value.Count}");
}
If you really need List<T> semantics, you just need to call Enumerable.ToList:
var grouped = numbers
.GroupBy(i => i)
.Select(i => new { Number = i.Key, Count = i.Count() })
.ToList();
In the other hand, you may directly convert everything into a string as follows:
var groupText = string.Join("\n", numbers
.GroupBy(i => i)
.Select(i => $"Number: {i.Key} Count: {i.Count()}"))
To get a list, you just need to call ToList(), for example:
var grouped = numbers
.GroupBy(i => i)
.Select(i => new { Number = i.Key, Count = i.Count() })
.ToList();
However, you really don't need to do that, you can simply loop over the enumerable as it stands:
foreach(var item in grouped)
{
Console.WriteLine($"{item.Number} occurs {item.Count} times");
}
Sounds like you want ToDictionary with the number as key and the frequency as value:
var grouped = numbers
.GroupBy(i => i)
.Select(i => new { Number = i.Key, Count = i.Count() })
.ToDictionary(x => x.Number, x => x.Count);
Now you can easily print every number and its frequency by looping the dictionary.
In fact you donĀ“t even need neither ToDictionary nor your Select, as the IGrouping returned from GroupBy also derives from IEnumerable which is why you can iterate over it.
foreach(var g in grouped = numbers.GroupBy(i => i))
{
var number = g.Key;
var freq = g.Count();
}

Get max items in array of lists

I have array of lists (string typed):
List<string>[] nodesAtLevel = new List<string>[20];
e.g:
[0] - List: "Hi", "There"
[1] - List: "Hi", "There", "Someone"
[2] - List: "Hi"
I need to write a LINQ operation that would return the array index of the biggest list.
Regard the example above, the LINQ operation should return 1 (because it has 3 items).
I know I should use "Where" and "Max" functions but I can't figure out how.
Use this query. First, you want to create a collection of objects that holds information about index of a list in the array and count of its items. Then, order this new collection by Count, select the first or last (depending on how you ordered the collection) and take an index.
var result = nodesAtLevel.Select((l, i) => new { Count = l.Count, Index = i })
.OrderByDescending(x => x.Count)
.First()
.Select(x => x.Index);
my version:
var max = nodesAtLevel.Select((l, i) => new { index = i, list = l })
.OrderBy(x => x.list.Count)
.Last().index;

Return unique list ordered by repeated/duplicated item count

What would be the most efficient path to the result I'm looking for? I'm using ASP.NET 4.5 and C#.
Let say I have the following int list:
5,7,2,7,8,5,8,0,2,9,8,8,7
I want to remove duplicates from the list and order it by the number of appearances of each item. I know I can use LINQ's Distinct() to make the items unique, but how can I also make it order by the number of appearances? This is the desired result for the example above:
8,7,5,2,0,9
You could group the items and then order them by the count in each group:
var items = new[] { 5, 7, 2, 7, 8, 5, 8, 0, 2, 9, 8, 8, 7 };
var result = items
.GroupBy(x => x)
.OrderByDescending(x => x.Count())
.Select(x => x.Key);
var ordered = list
.GroupBy(x => x)
.OrderByDescending(g => g.Count())
.Select(g => g.Key);

How to select array index after Where clause using Linq?

Suppose I have the array string[] weekDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" }; , and I want to find out the index of array elements containing 's'. How can I do this using Linq ?
I've tried int[] indexOfDaysContainingS = weekDays.Where(day => day.Contains("s")).Select((day, index) => index).ToArray();, but this returns 0,1,2 as presumably it's getting the index of the filtered IEnumberable<string> after the Where() clause instead. If I put the Select() first, then all I have is the index and can't filter by the days.
What do I need to change to make it work and return 1,2,3 instead ?
You could do it this way:
weekDays.Select((day, index) => new { Day = day, Index = index })
.Where(x => x.Day.Contains("s"))
.Select(x => x.Index)
.ToArray();
Not sure if this is optimal..
Patko's answer is the way to go in the general case.
Here are 2 more options:
// Idea only works with collections that can be accessed quickly by index.
int[] indices = Enumerable.Range(0, weekDays.Length)
.Where(index => weekDays[index].Contains("s"))
.ToArray();
With MoreLinq:
// Similar to Patko's idea, except using a 'named' type.
int[] indices = weekDays.AsSmartEnumerable()
.Where(item => item.Value.Contains("s"))
.Select(item => item.Index)
.ToArray();
This should work:
weekDays.Where(a => a.Contains("s")).Select((a, i) => i).ToArray();

Categories

Resources