I have a List<bool>. I need to get the indexes of top n items where item value = true.
For example the following list items(bool)
10011001000
TopTrueIndexes(3) = The first 3 indexes where bits are true are 0, 3, 4
TopTrueIndexes(4) = The first 4 indexes where bits are true are 0, 3, 4, 7
How can I write a lambda for this?
Well, assuming you have some easily-identifiable condition, you can do something like this, which will work for any IEnumerable<T>:
var query = source.Select((value, index) => new { value, index })
.Where(x => x.value => Condition(value))
.Select(x => x.index)
.Take(n);
(Obviously fill in the appropriate bit of the Where clause. If it's just a List<bool> it may just be x => x.value.)
The important bits are that you use the overload of Select to get index/value pairs before the Where, and then another Select to get just the indexes after the Where... and use Take to only get the first n results.
There's an overload of Select where the lambda gets two parameters: the index and the element. So you can just take the indices where the value is true, supplying a sentinel (here, -1) for the ones you don't want. Then filter out the sentinels and take how many you want:
bool[] bools = ...;
var indices = bools.Select((val, ix) => val ? ix : -1).Where(i => i >= 0).Take(n);
This should probably do it.
IEnumerable<bool> GetItemsInList(IEnumerable<bool> list, int count) {
int ind = 0;
return list.Select(itm => new {i = ind++, v = itm}).Where(itm => itm.v).Take(count).Select(itm => itm.i);
}
Here how it goes:
Select source + its index
Add condition to the where clause (the source of the where clause now contains the original source + index)
Select the index (the index returned here is the original index from the original source)
var indexes = inputList.Select((input, index) => new { input, index }).Where(a => condition(a.input)).Select(a => a.index);
Related
I need return sum of elements with odd indexes in the array of doubles
This is my code:
public static double EvaluateSumOfElementsOddPositions(double[] inputData)
{
var sum = inputData
.Select((v, i) => new { Group = (i % 2 != 0), Value = v })
.GroupBy(x => x.Group)
.Select(g => g.Sum(y => y.Value));
return sum ;
}
But I have a mistake:
Can't implicitly convert IEnumerable to double.
I don't know how I can deal with that... Help, please!
What you are looking for is something like:
public static double EvaluateSumOfElementsOddPositions(double[] inputData)
{
return inputData
.Where((data, index) => index % 2 != 0)
.Sum();
}
You don't need to group the elements if you are not going to use elements at even indices.
Although it is a nice exercise to try to do this using LINQ, it is not very efficient.
GroupBy will create a Dictionary<Tkey, ICollection<TResult>>, or to be more precise: a Lookup Table. For every element, it will extract the key and the TResult. For ever element it will check if the Key is in the Lookup Table.
If not, it will put the TResult in a new ICollection<TResult> and add the Key and the collection to the table.
It the Key is in the table it will add the TResult to the end of the collection.
This is quite a lot of work, while in fact the only thing you want is:
public static IEnumerable<double> ToEveryOtherDouble(this IEnumerable<double> doubles)
{
bool everyOther = false;
// will return elements with index 1, 3, 5, ...
// start with true if you want 0, 2, 4, ...
foreach (double d in doubles)
{
if (everyOther)
yield return d;
everyOther = !everyOther;
}
Usage:
IEnumerable<double> inputData = ....
double sum = inputData.ToEveryOtherDouble().Sum();
If you insist on using LINQ, make two groups: a group containing doubles with the even indexes, and a group containing doubles with the odd indexes.
So Key of the group: i % 2
double sum = inputData.Select( (d, index) => new
{
Index = index,
Value = d,
})
.GroupBy(x => x.Index % 2, // keySelector: groups with key 0 and key 1
// parameter elementSelector: specify the elements of each group
x => x.Value) // as elements of the group, we only need the double
Result of the GroupBy: two groups. A group with key 0 and a group with key 1. The group with key 0 has as elements the doubles at even indexes, the group with key 1 has as elements the doubles at odd indexes.
Continuing the LINQ: if you want only the even indexes:
.Where(group => group.Key == 0).Sum();
Conclusion
The choice is yours: which one is easier to read, reuse, maintain and unit test:
double sum = inputData.Select( (d, index) => new
{
Index = index,
Value = d,
})
.GroupBy(x => x.Index % 2, x => x.Value)
.Where(group => group.Key == 0)
.Sum();
Or:
double sum = inputData.ToEveryOtherDouble().Sum();
I have a simple class:
class Balls
{
public int BallType;
}
And i have a really simple list:
var balls = new List<Balls>()
{
new Balls() { BallType = 1},
new Balls() { BallType = 1},
new Balls() { BallType = 1},
new Balls() { BallType = 2}
};
I've used GroupBy on this list and I want to get back the key which has the highest count/amount:
After I used x.GroupBy(q => q.BallType) I tried to use .Max(), but it returns 3 and I need the key which is 1.
I also tried to use Console.WriteLine(x.GroupBy(q => q.Balltype).Max().Key); but it throws System.ArgumentException.
Here's what I came up with:
var mostCommonBallType = balls
.GroupBy(k => k.BallType)
.OrderBy(g => g.Count())
.Last().Key
You group by the BallType, order by the count of items in the group, get the last value (since order by is in an ascending order, the most common value would be the last) and then return it's key
Some came up with the idea to order the sequence:
var mostCommonBallType = balls
.GroupBy(k => k.BallType)
.OrderBy(g => g.Count())
.Last().Key
Apart from that it is more efficient to OrderByDescending and then take the FirstOrDefault, you also get in trouble if your collection of Balls is empty.
If you use a different overload of GroupBy, you won't have these problems
var mostCommonBallType = balls.GroupBy(
// KeySelector:
k => k.BallType,
// ResultSelector:
(ballType, ballsWithThisBallType) => new
{
BallType = ballType,
Count = ballsWithThisBallType.Count(),
})
.OrderByDescending(group => group.Count)
.Select(group => group.BallType)
.FirstOrDefault();
This solves the previously mentioned problems. However, if you only need the 1st element, why would you order the 2nd and the 3rd element? Using Aggregate instead of OrderByDescending will enumerate only once:
Assuming your collection is not empty:
var result = ... GroupBy(...)
.Aggregate( (groupWithHighestBallCount, nextGroup) =>
(groupWithHighestBallCount.Count >= nextGroup.Count) ?
groupWithHighestBallCount : nextGroup)
.Select(...).FirstOrDefault();
Aggregate takes the first element of your non-empty sequence, and assigns it to groupWithHighestBallCount. Then it iterates over the rest of the sequence, and compare this nextGroup.Count with the groupWithHighestBallCount.Count. It keeps the one with the hightes value as the next groupWithHighestBallCount. The return value is the final groupWithHighestBallCount.
See that Aggregate only enumerates once?
Given:
class C
{
public string Field1;
public string Field2;
}
template = new [] { "str1", "str2", ... }.ToList() // presents allowed values for C.Field1 as well as order
list = new List<C> { ob1, ob2, ... }
Question:
How can I perform Linq's
list.OrderBy(x => x.Field1)
which will use template above for order (so objects with Field1 == "str1" come first, than objects with "str2" and so on)?
In LINQ to Object, use Array.IndexOf:
var ordered = list
.Select(x => new { Obj = x, Index = Array.IndexOf(template, x.Field1)})
.OrderBy(p => p.Index < 0 ? 1 : 0) // Items with missing text go to the end
.ThenBy(p => p.Index) // The actual ordering happens here
.Select(p => p.Obj); // Drop the index from the result
This wouldn't work in EF or LINQ to SQL, so you would need to bring objects into memory for sorting.
Note: The above assumes that the list is not exhaustive. If it is, a simpler query would be sufficient:
var ordered = list.OrderBy(x => Array.IndexOf(template, x.Field1));
I think IndexOf might work here:
list.OrderBy(_ => Array.IndexOf(template, _.Field1))
Please note that it will return -1 when object is not present at all, which means it will come first. You'll have to handle this case. If your field is guaranteed to be there, it's fine.
As others have said, Array.IndexOf should do the job just fine. However, if template is long and or list is long, it might be worthwhile transforming your template into a dictionary. Something like:
var templateDict = template.Select((item,idx) => new { item, idx })
.ToDictionary(k => k.item, v => v.idx);
(or you could just start by creating a dictionary instead of an array in the first place - it's more flexible when you need to reorder stuff)
This will give you a dictionary keyed off the string from template with the index in the original array as your value. Then you can sort like this:
var ordered = list.OrderBy(x => templateDict[x.Field1]);
Which, since lookups in a dictionary are O(1) will scale better as template and list grow.
Note: The above code assumes all values of Field1 are present in template. If they are not, you would have to handle the case where x.Field1 isn't in templateDict.
var orderedList = list.OrderBy(d => Array.IndexOf(template, d.MachingColumnFromTempalate) < 0 ? int.MaxValue : Array.IndexOf(template, d.MachingColumnFromTempalate)).ToList();
I've actually written a method to do this before. Here's the source:
public static IOrderedEnumerable<T> OrderToMatch<T, TKey>(this IEnumerable<T> source, Func<T, TKey> sortKeySelector, IEnumerable<TKey> ordering)
{
var orderLookup = ordering
.Select((x, i) => new { key = x, index = i })
.ToDictionary(k => k.key, v => v.index);
if (!orderLookup.Any())
{
throw new ArgumentException("Ordering collection cannot be empty.", nameof(ordering));
}
T[] sourceArray = source.ToArray();
return sourceArray
.OrderBy(x =>
{
int index;
if (orderLookup.TryGetValue(sortKeySelector(x), out index))
{
return index;
}
return Int32.MaxValue;
})
.ThenBy(x => Array.IndexOf(sourceArray, x));
}
You can use it like this:
var ordered = list.OrderToMatch(x => x.Field1, template);
If you want to see the source, the unit tests, or the library it lives in, you can find it on GitHub. It's also available as a NuGet package.
Is it possible to get an integer count of elements that appear only once in a list with LINQ without creating a temporary list of those values and then counting that list?
Obviously this doesn't work but something like:
int test = list.Count(s => s.id).Where(s.id.Count() == 1);
You have to group them and the project the items which have count of 1 and then count the number of groups:
int test = list.GroupBy(s => s.id).
.Where(g=> g.Count() == 1)
.Count();
You can use GroupBy():
int test = list.GroupBy(s => s.id).Count(g => g.Count() == 1);
I have a list as below. I would like to retrieve the indexes of all the items which have the value 1.
List<int> listFilter = new List<int>();
listFilter.Add(1);
listFilter.Add(0);
listFilter.Add(0);
listFilter.Add(1);
I should be getting 0 and 3 for the sample data above.
The code below gives me an object of [value, index] pair. How do I modify this to just output a list which has only the indexes.
var val = listFilter.Select((value, index) => new { Value = value, Index = index }).Where(item => item.Value == 1).ToList();
Thanks
Regards,
Balan Sinniah
The problem is that in the initial Select clause you returned an anonymous type. To get back out the value you need an additional Select later on to filter back to that value.
var val = listFilter
.Select((value, index) => new { Value = value, Index = index })
.Where(item => item.Value == 1)
.Select(item => item.Index)
.ToList();