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]++);
Related
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
Let say you have list of items and you want to partition them, make operation on one partition and concatenate partitions back into list.
For example there is list of numbers and I want to partition them by parity, then reverse odds and concatenate with evens. [1,2,3,4,5,6,7,8] -> [7,5,3,1,2,4,6,8]
Sounds simple, but I've got stuck on merging back two groups. How would you do it with LINQ?
IEnumerable<int> result = Enumerable.Range(0, 1000)
.GroupBy(i => i % 2)
.Select(p => p.Key == 1 ? p.Reverse() : p)
.??? // need to concatenate
Edit
[1,2,3] is the representation of array which I want to get as the result, not output, sorry if I confused you by that.
The GroupBy method returns an IEnumerable<IGrouping<TKey, TSource>>. As IGrouping implements IEnumerable, you can use SelectMany to concatenate multiple IEnumerable<T> instances into one.
Enumerable.Range(0, 1000)
.GroupBy(i => i % 2)
.Select(p => p.Key == 1 ? p.Reverse() : p)
.OrderByDescending(p => p.Key)
.SelectMany(p => p);
There are a few ways to achieve this,
so if we start with your function
Enumerable.Range(0, 1000)
.GroupBy(i => i % 2)
.Select(p => p.Key == 1 ? p.Reverse() : p)
you could then use an Aggregate
.Aggregate((aggrgate,enumerable)=>aggrgate.Concat(enumerable))
this will then go though your list of results and concat them all into a collection and return it, you just need to make sure that aggrgate and enumerable are the same type in this case a IEnumerable<int>
another would be to call SelectMany()
.SelectMany(enumerable=>enumerable)
this then likewise pulls all the enumerables together into a single enumerable, again you need to ensure the types are IEnumerable<int>
other options would be to hard code the keys as Tim is suggesting or pull out of linq and use a loop
You could use this approach using a Lookup<TKey, TElement>:
var evenOddLookup = numbers.ToLookup(i => i % 2);
string result = String.Join(",", evenOddLookup[1].Reverse().Concat(evenOddLookup[0]));
If you don't want a string but an int[] as result:
int[] result = evenOddLookup[1].Reverse().Concat(evenOddLookup[0]).ToArray();
You could do something like this.
var number = string.Join(",",
Enumerable.Range(0, 1000)
.GroupBy(i => i % 2) // Separate even/odd numbers
.OrderByDescending(x=>x.Key) // Sort to bring odd numbers first.
.SelectMany(x=> x.Key ==1? // Sort elements based on even or odd.
x.OrderByDescending(s=>s)
: x.Where(s=> s!=0).OrderBy(s=>s))
.ToArray());
string output = string.Format("[{0}]", number);
Check this Demo
Just use OrderBy like this:
List<int> arr = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8 };
var result = arr.OrderBy(i => i % 2 == 0 ? 1 : 0)
.ThenBy(i => i % 2 == 0 ? i : int.MaxValue)
.ThenByDescending(i => i);
This should give you your desired result as you want:
[1,2,3,4,5,6,7,8] will be converted into [7,5,3,1,2,4,6,8]
I have this array :
Point[] arr = samples.pointsArray;
I using this row to retrieve all elements that satisfy condition:
var maxXCol = arr.Where( p => maxX.X == p.X );
Any idea how to modify row above, to get only the indexes of these elements?
Thank you in advance!
EDIT
Use the version of Select that takes both the index and the object and create an anonymous object with the object and index inside it. It would look like this:
someEnumerable.Select((obj, idx) => new {Item = obj, Index = idx})
You'll need to do this before you use Where so that the original index remains intact after the filter operation.
In the following operations you can use the item like so:
x => x.Item
and the index like so:
x => x.Index
var maxXCol = arr.Select((p, inx) => new { p,inx})
.Where(y => maxX.X == y.p.X)
.Select(z => z.inx);
You may select the value first with the index in anonymous type, later you filter it with your condition and then select the index.
var result = arr.Select((g, index) => new { g, index })
.Where(r => maxX.X == r.X)
.Select(t => t.index);
You can use Select overload which takes an index, and project that index together with the original row. Then take only the index for the result collection.
var maxXCol = arr
.Select((p, index) => new { Item = p, Index = index })
.Where(p => maxX.X == p.Item.X)
.Select(x => x.Index);
Try this:
arr.Select((e,i)=>new{index=i, value=e}).Where(ei=>ei.value.X==maxX.X).Select(ei=>ei.index);
var maxXCol = arr
.Select((a, b) => new { b, a })
.Where(p => maxX.X == p.a.X)
.Select(i=>i.b);
Assuming I have the following list:
IList<string> list = new List<string>();
list.Add("Mouse");
list.Add("Dinner");
list.Add("House");
list.Add("Out");
list.Add("Phone");
list.Add("Hat");
list.Add("Ounce");
Using LINQ how would I select the words containing "ou" and sort the selection such that the words beginning with "ou" are listed at the start and then the words containing but not starting with "ou" are subsequently listed. The list I'm trying to create would be:
Ounce
Out
House
Mouse
I came up with the following but it is not working:
list.Where(x => x.Contains("ou"))
.OrderBy(x => x.StartsWith("ou"))
.Select(x => x);
You're getting a case-sensitive comparison, and also you need OrderByDescending(). A quick and dirty way to achieve the case-insensitivity is ToLowerInvariant():
var result = list.Where(x => x.ToLowerInvariant().Contains("ou"))
.OrderByDescending(x => x.ToLowerInvariant().StartsWith("ou"))
.Select(x => x);
Live example: http://rextester.com/GUR97180
This previous answer shows the correct way to do a case insensitive comparison (ie, dont use my example above, its bad)
Your first mistake is not comparing strings in a case-insensitive way; "Out" and "Ounce" have capital Os and would not return "true" when you use Contains("ou"). The solution is to use ToLower() when checking letters.
list.Where(x => x.ToLower().Contains("ou"))
.OrderByDescending(x => x.ToLower.StartsWith("ou")) //true is greater than false.
.Select(x => x);
Three problems:
You need to assign the result to something, otherwise it is simply discarded.
You need to use OrderByDescending because true sorts after false if you use OrderBy.
You need to use a case-insensitive compare.
Try this:
var needle = "ou";
var stringComparison = StringComparison.OrdinalIgnoreCase;
var query =
from word in list
let index = word.IndexOf(needle, stringComparison)
where index != -1
orderby index
select word;
This will append an empty space to the beginning of words that start with "OU".
var result = list.Where(x => x.ToLowerInvariant().Contains("ou"))
.OrderBy(x => x.ToLowerInvariant()
.StartsWith("ou") ? " " + x : x.Trim());
list = list.Where(x => x.ToLower().Contains("ou"))
.OrderBy(x => !x.ToLower().StartsWith("ou")).ToList();
Or by using the methods of List (changing it from IList to List):
list.RemoveAll(x => !x.ToLower().Contains("ou"));
list.Sort((s1, s2) => -1 * 1.ToLower().StartsWith("ou")
.CompareTo(s2.ToLower().StartsWith("ou")));
I think this is what you're looking for:
list = list.Where(x => x.IndexOf("ou", StringComparison.OrdinalIgnoreCase) >= 0)
.OrderByDescending(x => x.StartsWith("ou", StringComparison.OrdinalIgnoreCase))
.ThenBy(x => x)
.ToList();
Note that instead of converting the strings ToLower (or upper), I use a StringComparison enum (currently OrdinalIgnoreCase). This ensures that it works consistently as expected in any culture. Choose the right case-insensitive comparison depending on your circumstance.
If you prefer the LINQ query syntax that's:
list = (from x in list
where x.IndexOf("ou", StringComparison.OrdinalIgnoreCase) >= 0
orderby x.StartsWith("ou", StringComparison.OrdinalIgnoreCase) descending, x
select x).ToList();
var bla = "ou";
var list = new List<string>{
"Mouse",
"Dinner",
"House",
"Out",
"Phone",
"Hat",
"Ounce"};
var groupa = list.GroupBy(x =>x.ToLower().Contains(bla));
groupa.First().ToList().OrderByDescending(x => x.ToLower().StartsWith(bla));
You can simply call the list.Sort method by passing in an instance of a custom comparer as follows:
public class MyCustomStringComparer: IComparer<string>
{
public int Compare(Entity x, Entity y)
{
int result = 0;
if (x.ToLower().StartsWith("ou") && y.ToLower().StartsWith("ou"))
result = x.Compare(y);
else if (x.ToLower().StartsWith("ou") && !y.ToLower().StartsWith("ou"))
result = -1;
else if (!x.ToLower().StartsWith("ou") && y.ToLower().StartsWith("ou"))
result = 1;
else
result = x.Compare(y);
return (result);
}
}
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();