How to select array index after Where clause using Linq? - c#

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();

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

Is it possible to implement a "Smaller Numbers than Current" using a single LINQ query?

Was doing this problem https://leetcode.com/problems/how-many-numbers-are-smaller-than-the-current-number/submissions/
Input: nums = [8,1,2,2,3]
Output: [4,0,1,1,3]
Explanation: For
nums[0]=8 there exist four smaller numbers than it (1, 2, 2 and 3).
For nums[1]=1 does not exist any smaller number than it. For nums[2]=2
there exist one smaller number than it (1). For nums[3]=2 there exist
one smaller number than it (1). For nums[4]=3 there exist three
smaller numbers than it (1, 2 and 2).
as LINQ-y as possible and came up with a solution which is only half LINQ :(
public class Solution {
public int[] SmallerNumbersThanCurrent(int[] nums) {
var groups = nums
.Select((val, index) => new { index, val })
.GroupBy(x => x.val)
.OrderBy(g => g.Key)
.Select(g => g.Select(x => x.index).ToArray());
var arr = new int[nums.Length];
int numSmaller = 0;
foreach (var indices in groups)
{
foreach (var index in indices)
{
arr[index] = numSmaller;
}
numSmaller += indices.Length;
}
return arr;
}
}
Is anyone here clever enough to help me figure out a way to LINQ-ify the second half of the solution? Preferably O(n log n) as code I have.
I hope I understood your question. You could do the following.
public int[] SmallerNumbersThanCurrent(int[] nums)
{
return nums.Select(x=> nums.Count(c=> c<x)).ToArray();
}
Though I don't think using one LINQ is a good idea here, it is possible to get rid of the foreach you have like this, assuming approximate nlog(n) is required:
nums.Select((num, index) => new { num, index })
// order number
.OrderBy(x => x.num)
// select number with their original index in nums and
// their order in the ordered collection
.Select((x, order) => new { x.num, x.index, order })
// Group the result by number
.GroupBy(x => x.num)
// Consolidate order in the ordered collection by selecting the minimum
// possible order
.Select(g => new
{
numWithOrder = g.Select(_ => new
{
num = _,
minOrder = g.First().order
})
})
// Flatten the collection
.SelectMany(g => g.numWithOrder)
// There should be minOrder number of results in the original collection
// are smaller than the number
.Select(x => new { x.num.index, result = x.minOrder })
// Restore as per original index
.OrderBy(x => x.index)
// Select final result
.Select(x => x.result)
As you might have seen, LINQ kills the readability of the code.
Here is another solution. It uses the Scan extension method from the System.Interactive package, for counting by accumulation the numbers that are smaller than the numbers of the current group.
public int[] SmallerNumbersThanCurrent(int[] nums)
{
return nums
.Select((x, i) => (Item: x, Index: i))
.GroupBy(x => x.Item, x => x.Index)
.OrderBy(g => g.Key)
.Scan(seed: (Indices: Enumerable.Empty<int>(), Counter: 0),
accumulator: (acc, x) => (x, acc.Counter + acc.Indices.Count()))
.SelectMany(acc => acc.Indices,
(acc, element) => (Index: element, CountOfSmallerNumbers: acc.Counter))
.OrderBy(x => x.Index)
.Select(x => x.CountOfSmallerNumbers)
.ToArray();
}
This solution is arguably even more obscure and unreadable than weichch's solution. 😃
The signature of the Scan extension method:
public static IEnumerable<TAccumulate> Scan<TSource, TAccumulate>(
this IEnumerable<TSource> source, TAccumulate seed,
Func<TAccumulate, TSource, TAccumulate> accumulator);
Generates a sequence of accumulated values by scanning the source sequence and applying an accumulator function.
public int[] SmallerNumbersThanCurrentShorter(int[] nums)
{
return (from x in nums select (from y in nums where y < x select y).Count()).ToArray();
}
just do this!!

how to count same elements in different arrays in 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();

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;

Does LINQ work on Index?

Lets say i have an array
byte[] myarr = {1,4,3,4,1,2,1,2,4,3,1,4,2};
myarr will be of length 13 (0-12 Index) which will also be the length of int[] val.
int[] val = new int[13];
I want to check index of myarr where its value is 4 i.e. 1,3,8,11.
Then i want
val[1]++;
val[3]++;
val[8]++;
val[11]++;
One way of doing this is using for loop
for(int i=0; i<myarr.length; i++)
{
if(myarr[i] == 4)
val[i]++;
}
We can use Array.indexof but it returns the first index of that value meaning that value has to be unique and my myarr has lots of same values.
Can this be done using linq?
This is what I ended up doing in LINQ (update included):
myarr.Select((b, i) => b == 4 ? i : -1)
.Where(i => i != -1)
.ToList().ForEach(i => val[i]++);
Your non-LINQ version is obviously much more succinct and readable, so I think you should use that.
You can, but it won't be simpler. LINQ will only help you with the query part, the update part has to be done in a foo loop, but since the array contains value types you need to get indexes from your LINQ-query and not the actual values and you have won nothing.
You can use an anonymous type to store the index:
int[] newIndices = myarr.Select((i, index) => new { Index = index, Value = i })
.Where (x => x.Value == 4)
.Select(x => x.Index + 1)
.ToArray();
Edit: Finally i've understood your question ;)
myarr.Select((i, index) => new { Index = index, Value = i })
.Where(x => x.Value == 4)
.Select(x => x.Index)
.ForEach(i => myarr[i]++);

Categories

Resources