Linq: X objects in a row - c#

I need help with a linq query that will return true if the list contains x objects in a row when the list is ordered by date.
so like this:
myList.InARow(x => x.Correct, 3)
would return true if there are 3 in a row with the property correct == true.
Not sure how to do this.

Using a GroupAdjacent extension, you can do:
var hasThreeConsecutiveCorrect
= myList.GroupAdjacent(item => item.Correct)
.Any(group => group.Key && group.Count() >= 3);
Here's another way with a Rollup extension (a cross between Select and Aggregate) that's somewhat more space-efficient:
var hasThreeConsecutiveCorrect
= myList.Rollup(0, (item, sum) => item.Correct ? (sum + 1) : 0)
.Contains(3);

There is nothing built into linq that handles this case easily. But it is a relatively simple matter to create your own extension method.
public static class EnumerableExtensions {
public IEnumerable<T> InARow<T>(this IEnumerable<T> list,
Predicate<T> filter, int length) {
int run = 0;
foreach (T element in list) {
if (filter(element)) {
if (++run >= length) return true;
}
else {
run = 0;
}
}
return false;
}
}

Updated:
myList.Aggregate(0,
(result, x) => (result >= 3) ? result : (x.Correct ? result + 1 : 0),
result => result >= 3);
Generalized version:
myList.Aggregate(0,
(result, x) => (result >= length) ? result : (filter(x) ? result + 1 : 0),
result => result >= length);

Related

Check if a string is sorted

I have a string, simplified "12345" which is sorted. The string couild contain Digits (0-9) or letters (a-z). In case of a mixed use the natural sort order. I need a method to verify if this is true.
Attempt with linq technique:
string items1 = "2349"; //sorted
string items2 = "2476"; //not sorted, 6<>7
bool sorted1 = Enumerable.SequenceEqual(items1.OrderBy(x => x), items1); //true
bool sorted2 = Enumerable.SequenceEqual(items2.OrderBy(x => x), items2); //false
but there could be also a descending sort order.
Is there a better way then
string items3 = "4321";
bool sorted3 = Enumerable.SequenceEqual(items3.OrderBy(x => x), items3) || Enumerable.SequenceEqual(items3.OrderByDescending(x => x), items3);
to check if a string is sorted? Maybe some built in solution?
Your solution in fine and very readable. One problem with it is that it requires ordering the string which is O(n * log(n)), this can be solved by iterating the string without sorting it.
For example:
var firstDifs = items1.Zip(items1.Skip(1), (x, y) => y - x);
This Linq projects every 2 items in the first string to a number which indicates their difference, So if you have items1 = "1245" the output will be:
firstDifs: {1, 2, 1}
Now all you need to do is to validate that firstDifs is either ascending or descending:
bool firstSorted = firstDifs.All(x => x > 0) || firstDifs.All(x => x < 0); //true
Now:
Skip is O(1) since the amount of actions required to skip 1 cell is
constant.
Zip is O(n).
All is O(n).
So the whole solution is O(n).
Note that it will be more efficient with a simple loop, also if the first All has returned false because the 3487th item changes its direction (for example: 1234567891), the second All will run for no reason with the Zip running twice as well (Until where All require) - since there are two iterations of All and Linq evaluates them lazily.
It requires a reducer. In C#, it's Enumerable.Aggregate. It's O(n) algorithm.
var query = "123abc".Aggregate(new { asceding = true, descending = true, prev = (char?)null },
(result, currentChar) =>
new
{
asceding = result.prev == null || result.asceding && currentChar >= result.prev,
descending = result.prev == null || result.descending && currentChar <= result.prev,
prev = (char?)currentChar
}
);
Console.WriteLine(query.asceding || query.descending );
I once had to check something similar to your case but with huge data streams, so performance was important. I came up with this small extension class which performs very well:
public static bool IsOrdered<T>(this IEnumerable<T> enumerable) where T: IComparable<T>
{
using (var enumerator = enumerable.GetEnumerator())
{
if (!enumerator.MoveNext())
return true; //empty enumeration is ordered
var left = enumerator.Current;
int previousUnequalComparison = 0;
while (enumerator.MoveNext())
{
var right = enumerator.Current;
var currentComparison = left.CompareTo(right);
if (currentComparison != 0)
{
if (previousUnequalComparison != 0
&& currentComparison != previousUnequalComparison)
return false;
previousUnequalComparison = currentComparison;
left = right;
}
}
}
return true;
}
Using it is obviously very simple:
var items1 = "2349";
var items2 = "2476"; //not sorted, 6<>7
items1.IsOrdered(); //true
items2.IsOrdered(); //false
You can do much better than the accepted answer by not having to compare all of the elements:
var s = "2349";
var r = Enumerable.Range(1, s.Length - 1);
//var isAscending = r.All(i => s[i - 1] <= s[i]);
//var isDescending = r.All(i => s[i - 1] >= s[i]);
var isOrdered = r.All(i => s[i - 1] <= s[i]) || r.All(i => s[i - 1] >= s[i]);
var items = "4321";
var sortedItems = items.OrderBy(i => i); // Process the order once only
var sorted = sortedItems.SequenceEqual(items) || sortedItems.SequenceEqual(items.Reverse()); // Reverse using yield return
I would go for simple iteration over all elements:
string str = "whatever123";
Func<char, char, bool> pred;
bool? asc = str.TakeWhile((q, i) => i < str.Length - 1)
.Select((q, i) => str[i] == str[i+1] ? (bool?)null : str[i] < str[i+1])
.FirstOrDefault(q => q.HasValue);
if (!asc.HasValue)
return true; //all chars are the same
if (asc.Value)
pred = (c1, c2) => c1 <= c2;
else
pred = (c1, c2) => c1 >= c2;
for (int i = 0; i < str.Length - 1; ++i)
{
if (!pred(str[i], str[i + 1]))
return false;
}
return true;

Why am I getting an IndexOutOfRangeException here?

I can't figure out why I'm getting it in the Where clause below.
using System;
using System.Linq;
public static class Extensions
{
/// <summary>
/// Removes consecutive characters,
/// e.g. "aaabcc" --> "abc"
/// </summary>
public static void RemoveDuplicates(this string s)
{
var arr = s.ToCharArray()
.Where((i,c) => (i > 0) ? (c != s[i - 1]) : true)
.ToArray();
s = new string(arr);
}
}
public class Program
{
public static void Main()
{
var str = "aaabcc";
str.RemoveDuplicates();
Console.WriteLine(str);
}
}
Also, is there a way to make this slightly more efficient and compact while still using LINQ?
You have the wrong order of parameters here:
.Where((i, c) => (i > 0) ? (c != s[i - 1]) : true)
should become:
.Where((c, i) => (i > 0) ? (c != s[i - 1]) : true)
The error is the (i,c ) in your where.
You are using the following Enumerable extension (See MSDN)
public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source,
Func<TSource, int, bool> predicate)
Note that the index in the Func is the second parameter.
I think the fastest method would not be using linq, but would be a string extension:
public static string RemoveDuplicates(this string s)
{
if (String.IsNullOrEmpty(s)) return String.Empty(); // optional: return null
var resultBuilder = new StringBuilder(s.Length);
resultBuilder.Append(s.First());
for (int i=1; i< s.Length; ++i)
{
if (s[i] != s[i-1])
resultBuilder.Append(s[i]);
}
return resultBuilder.ToString();
}
However, if you really want to use linq, for instance because you want to append other linq statements you can mimic the above behaviour as follows, while still using lazy loading:
public static string RemoveDuplicates(this string s)
{
if (String.IsNullOrEmpty(s)) return String.Empty();
s.AsEnumerable().Take(1)
.Concat(s.AsEnumerable().Skip(1)
.where( (c, i) => c != s[i]));
}
Note that because of the check that string is not null, I am certain there is a First().
Because of the Skip(1), index 0 in the where statement equals s[1], and index i equals s[i-1] for i>0.
You Probably need to read the Docs before using a Method or a special overload:
Type: System.Func<TSource, Int32, Boolean>
A function to test each source element for a condition; the second
parameter of the function represents the index of the source element
So your code should be something like this:
.Where((item, index) => (index > 0) ? (item != s[index - 1]) : true)
You could use Distinct method already instead of your own method for this purpose:
var str = "aaabcca";
var result = string.Join("",str.ToCharArray().Distinct());
Result:
"abc"
Edit:If you want to remove sequential duplicates you could try this code instead:
var removesequential = string.Join("",str.Where((c, i) => i == 0 || c != str[i - 1]));
Result:
"abca"

How to check if contents of a List<String> is alphabetical [duplicate]

I am doing some unit tests and I want to know if there's any way to test if a list is ordered by a property of the objects it contains.
Right now I am doing it this way but I don't like it, I want a better way. Can somebody help me please?
// (fill the list)
List<StudyFeedItem> studyFeeds =
Feeds.GetStudyFeeds(2120, DateTime.Today.AddDays(-200), 20);
StudyFeedItem previous = studyFeeds.First();
foreach (StudyFeedItem item in studyFeeds)
{
if (item != previous)
{
Assert.IsTrue(previous.Date > item.Date);
}
previous = item;
}
If you are using MSTest, you may want to take a look at CollectionAssert.AreEqual.
Enumerable.SequenceEqual may be another useful API to use in an assertion.
In both cases you should prepare a list that holds the expected list in the expected order, and then compare that list to the result.
Here's an example:
var studyFeeds = Feeds.GetStudyFeeds(2120, DateTime.Today.AddDays(-200), 20);
var expectedList = studyFeeds.OrderByDescending(x => x.Date);
Assert.IsTrue(expectedList.SequenceEqual(studyFeeds));
A .NET 4.0 way would be to use the Enumerable.Zip method to zip the list with itself offset by one, which pairs each item with the subsequent item in the list. You can then check that the condition holds true for each pair, e.g.
var ordered = studyFeeds.Zip(studyFeeds.Skip(1), (a, b) => new { a, b })
.All(p => p.a.Date < p.b.Date);
If you're on an earlier version of the framework you can write your own Zip method without too much trouble, something like the following (argument validation and disposal of the enumerators if applicable is left to the reader):
public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(
this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> selector)
{
var e1 = first.GetEnumerator();
var e2 = second.GetEnumerator();
while (e1.MoveNext() & e2.MoveNext()) // one & is important
yield return selector(e1.Current, e2.Current);
}
Nunit 2.5 introduced CollectionOrderedContraint and a nice syntax for verifying the order of a collection:
Assert.That(collection, Is.Ordered.By("PropertyName"));
No need to manually order and compare.
If your unit testing framework has helper methods to assert equality of collections, you should be able do something like this (NUnit flavored):
var sorted = studyFeeds.OrderBy(s => s.Date);
CollectionAssert.AreEqual(sorted.ToList(), studyFeeds.ToList());
The assert method works with any IEnumerable, but when both collections are of type IList or "array of something", the error message thrown when the assert fails will contain the index of the first out-of-place element.
The solutions posted involving sorting the list are expensive - determining if a list IS sorted can be done in O(N). Here's an extension method which will check:
public static bool IsOrdered<T>(this IList<T> list, IComparer<T> comparer = null)
{
if (comparer == null)
{
comparer = Comparer<T>.Default;
}
if (list.Count > 1)
{
for (int i = 1; i < list.Count; i++)
{
if (comparer.Compare(list[i - 1], list[i]) > 0)
{
return false;
}
}
}
return true;
}
A corresponding IsOrderedDescending could be implemented easily by changing > 0 to < 0.
Greg Beech answer, although excellent, can be simplified further by performing the test in the Zip itself. So instead of:
var ordered = studyFeeds.Zip(studyFeeds.Skip(1), (a, b) => new { a, b })
.All(p => p.a.Date <= p.b.Date);
You can simply do:
var ordered = !studyFeeds.Zip(studyFeeds.Skip(1), (a, b) => a.Date <= b.Date)
.Contains(false);
Which saves you one lambda expression and one anonymous type.
(In my opinion removing the anonymous type also makes it easier to read.)
if(studyFeeds.Length < 2)
return;
for(int i = 1; i < studyFeeds.Length;i++)
Assert.IsTrue(studyFeeds[i-1].Date > studyFeeds[i].Date);
for isn't dead just quite yet!
How about:
var list = items.ToList();
for(int i = 1; i < list.Count; i++) {
Assert.IsTrue(yourComparer.Compare(list[i - 1], list[i]) <= 0);
}
where yourComparer is an instance of YourComparer which implements IComparer<YourBusinessObject>. This ensures that every element is less than the next element in the enumeration.
Linq based answer is:
You can use SequenceEqual method to check if the original and ordered one is same or not.
var isOrderedAscending = lJobsList.SequenceEqual(lJobsList.OrderBy(x => x));
var isOrderedDescending = lJobsList.SequenceEqual(lJobsList.OrderByDescending(x => x));
Don't forget to import System.Linq namespace.
Additionally:
I am repeating that this answer is Linq based, you can achieve more efficiency by creating your custom extension method.
Also, if somebody still wants to use Linq and check if the sequence both is ordered in ascending or descending order, then you can achieve a little bit more efficiency like that:
var orderedSequence = lJobsList.OrderBy(x => x)
.ToList();
var reversedOrderSequence = orderedSequence.AsEnumerable()
.Reverse();
if (lJobsList.SequenceEqual(orderedSequence))
{
// Ordered in ascending
}
else (lJobsList.SequenceEqual(reversedOrderSequence))
{
// Ordered in descending
}
You could use an extension method like this:
public static System.ComponentModel.ListSortDirection? SortDirection<T>(this IEnumerable<T> items, Comparer<T> comparer = null)
{
if (items == null) throw new ArgumentNullException("items");
if (comparer == null) comparer = Comparer<T>.Default;
bool ascendingOrder = true; bool descendingOrder = true;
using (var e = items.GetEnumerator())
{
if (e.MoveNext())
{
T last = e.Current; // first item
while (e.MoveNext())
{
int diff = comparer.Compare(last, e.Current);
if (diff > 0)
ascendingOrder = false;
else if (diff < 0)
descendingOrder = false;
if (!ascendingOrder && !descendingOrder)
break;
last = e.Current;
}
}
}
if (ascendingOrder)
return System.ComponentModel.ListSortDirection.Ascending;
else if (descendingOrder)
return System.ComponentModel.ListSortDirection.Descending;
else
return null;
}
It enables to check if the sequence is sorted and also determines the direction:
var items = new[] { 3, 2, 1, 1, 0 };
var sort = items.SortDirection();
Console.WriteLine("Is sorted? {0}, Direction: {1}", sort.HasValue, sort);
//Is sorted? True, Direction: Descending
Here's how I do it with Linq and I comparable, might not be the best but works for me and it's test framework independent.
So the call looks like this:
myList.IsOrderedBy(a => a.StartDate)
This works for anything that implements IComparable, so numbers strings and anything that inherit from IComparable:
public static bool IsOrderedBy<T, TProperty>(this List<T> list, Expression<Func<T, TProperty>> propertyExpression) where TProperty : IComparable<TProperty>
{
var member = (MemberExpression) propertyExpression.Body;
var propertyInfo = (PropertyInfo) member.Member;
IComparable<TProperty> previousValue = null;
for (int i = 0; i < list.Count(); i++)
{
var currentValue = (TProperty)propertyInfo.GetValue(list[i], null);
if (previousValue == null)
{
previousValue = currentValue;
continue;
}
if(previousValue.CompareTo(currentValue) > 0) return false;
previousValue = currentValue;
}
return true;
}
Hope this helps, took me ages to work this one out.
Checking a sequence can have four different outcomes. Same means that all elements in the sequence are the same (or the sequence is empty):
enum Sort {
Unsorted,
Same,
SortedAscending,
SortedDescending
}
Here is a way to check the sorting of a sequence:
Sort GetSort<T>(IEnumerable<T> source, IComparer<T> comparer = null) {
if (source == null)
throw new ArgumentNullException(nameof(source));
if (comparer == null)
comparer = Comparer<T>.Default;
using (var enumerator = source.GetEnumerator()) {
if (!enumerator.MoveNext())
return Sort.Same;
Sort? result = null;
var previousItem = enumerator.Current;
while (enumerator.MoveNext()) {
var nextItem = enumerator.Current;
var comparison = comparer.Compare(previousItem, nextItem);
if (comparison < 0) {
if (result == Sort.SortedDescending)
return Sort.Unsorted;
result = Sort.SortedAscending;
}
else if (comparison > 0) {
if (result == Sort.SortedAscending)
return Sort.Unsorted;
result = Sort.SortedDescending;
}
}
return result ?? Sort.Same;
}
}
I'm using the enumerator directly instead of a foreach loop because I need to examine the elements of the sequence as pairs. It makes the code more complex but is also more efficient.
Something LINQ-y would be to use a separate sorted query...
var sorted = from item in items
orderby item.Priority
select item;
Assert.IsTrue(items.SequenceEquals(sorted));
Type inference means you'd need a
where T : IHasPriority
However, if you have multiple items of the same priority, then for a unit test assertion you're probably best off just looping with the list index as Jason suggested.
One way or another you're going to have to walk the list and ensure that the items are in the order you want. Since the item comparison is custom, you could look into creating a generic method for this and passing in a comparison function - the same way that sorting the list uses comparison functions.
You can create an ordered and an unordered version of the list first:
var asc = jobs.OrderBy(x => x);
var desc = jobs.OrderByDescending(x => x);
Now compare the original list with both:
if (jobs.SequenceEqual(asc) || jobs.SequenceEquals(desc)) // ...
var studyFeeds = Feeds.GetStudyFeeds(2120, DateTime.Today.AddDays(-200), 20);
var orderedFeeds = studyFeeds.OrderBy(f => f.Date);
for (int i = 0; i < studyFeeds.Count; i++)
{
Assert.AreEqual(orderedFeeds[i].Date, studyFeeds[i].Date);
}
What about something like this, without sorting the list
public static bool IsAscendingOrder<T>(this IEnumerable<T> seq) where T : IComparable
{
var seqArray = seq as T[] ?? seq.ToArray();
return !seqArray.Where((e, i) =>
i < seqArray.Count() - 1 &&
e.CompareTo(seqArray.ElementAt(i + 1)) >= 0).Any();
}
Microsoft.VisualStudio.TestTools.UnitTesting.CollectionAssert.AreEqual(
mylist.OrderBy((a) => a.SomeProperty).ToList(),
mylist,
"Not sorted.");
Here's a more lightweight generic version. To test for descending order, change the >= 0 comparison to <= 0.
public static bool IsAscendingOrder<T>(this IEnumerable<T> seq) where T : IComparable<T>
{
var predecessor = default(T);
var hasPredecessor = false;
foreach(var x in seq)
{
if (hasPredecessor && predecessor.CompareTo(x) >= 0) return false;
predecessor = x;
hasPredecessor = true;
}
return true;
}
Tests:
new int[] { }.IsAscendingOrder() returns true
new int[] { 1 }.IsAscendingOrder() returns true
new int[] { 1,2 }.IsAscendingOrder() returns true
new int[] { 1,2,0 }.IsAscendingOrder() returns false
While AnorZaken's and Greg Beech's answers are very nice, as they don't require using an extension method, it can be good to avoid Zip() sometimes, as some enumerables can be expensive to enumerate in this way.
A solution can be found in Aggregate()
double[] score1 = new double[] { 12.2, 13.3, 5, 17.2, 2.2, 4.5 };
double[] score2 = new double[] { 2.2, 4.5, 5, 12.2, 13.3, 17.2 };
bool isordered1 = score1.Aggregate(double.MinValue,(accum,elem)=>elem>=accum?elem:double.MaxValue) < double.MaxValue;
bool isordered2 = score2.Aggregate(double.MinValue,(accum,elem)=>elem>=accum?elem:double.MaxValue) < double.MaxValue;
Console.WriteLine ("isordered1 {0}",isordered1);
Console.WriteLine ("isordered2 {0}",isordered2);
One thing a little ugly about the above solution, is the double less-than comparisons. Floating comparisons like this make me queasy as it is almost like a floating point equality comparison. But it seems to work for double here. Integer values would be fine, also.
The floating point comparison can be avoided by using nullable types, but then the code becomes a bit harder to read.
double[] score3 = new double[] { 12.2, 13.3, 5, 17.2, 2.2, 4.5 };
double[] score4 = new double[] { 2.2, 4.5, 5, 12.2, 13.3, 17.2 };
bool isordered3 = score3.Aggregate((double?)double.MinValue,(accum,elem)=>(elem>(accum??(double?)double.MaxValue).Value)?(double?)elem:(double?)null) !=null;
bool isordered4 = score4.Aggregate((double?)double.MinValue,(accum,elem)=>(elem>(accum??(double?)double.MaxValue).Value)?(double?)elem:(double?)null) !=null;
Console.WriteLine ("isordered3 {0}",isordered3);
Console.WriteLine ("isordered4 {0}",isordered4);
You can use lambda in extension:
public static bool IsAscending<T>(this IEnumerable<T> self, Func<T, T, int> compareTo) {
var list = self as IList<T> ?? self.ToList();
if (list.Count < 2) {
return true;
}
T a = list[0];
for (int i = 1; i < list.Count; i++) {
T b = list[i];
if (compareTo(a, b) > 0) {
return false;
}
a = b;
}
return true;
}
Using:
bool result1 = Enumerable.Range(2, 10).IsAscending((a, b) => a.CompareTo(b));
more:
var lst = new List<(int, string)> { (1, "b"), (2, "a"), (3, "s1"), (3, "s") };
bool result2 = lst.IsAscending((a, b) => {
var cmp = a.Item1.CompareTo(b.Item1);
if (cmp != 0) {
return cmp;
} else {
return a.Item2.CompareTo(b.Item2);
}
});
var expectedList = resultA.ToArray();
var actualList = resultB.ToArray();
var i = 0;
foreach (var item in expectedList)
{
Assert.True(expectedList[i].id == actualList[i].id);
i++;
}

Linq - Get all items between 2 matching elements

Provided a list, I want to select all items between the 2 given. (including the begin and end params)
My current solution is as follows:
private IEnumerable<string> GetAllBetween(IEnumerable<string> list, string begin, string end)
{
bool isBetween = false;
foreach (string item in list)
{
if (item == begin)
{
isBetween = true;
}
if (item == end)
{
yield return item;
yield break;
}
if (isBetween)
{
yield return item;
}
}
}
But surely there must be a pretty linq query that accomplishes the same thing?
You can nearly use SkipWhile and TakeWhile, but you want the last item as well - you want the functionality of TakeUntil from MoreLINQ. You can then use:
var query = source.SkipWhile(x => x != begin)
.TakeUntil(x => x == end);
static IEnumerable<T> GetAllBetween<T>( this List<T> list, T a, T b )
{
var aOffset = list.IndexOf( a );
var bOffset = list.IndexOf( b );
// what to do if one or all items not found?
if( -1 == aOffset || -1 == bOffset )
{
// for this example I will return an empty array
return new T[] { };
}
// what to do if a comes after b?
if( aOffset > bOffset )
{
// for this example i'll simply swap them
int temp = aOffset;
aOffset = bOffset;
bOffset = temp;
}
return list.GetRange( aOffset, bOffset - aOffset );
}
I think a simple Skip, Take should do it. I normally take it for paging ASP.NET resultsites.
var startIndex = list.IndexOf(begin);
var endIndex = list.IndexOf(end);
var result = list.Skip(startIndex + 1).Take(endIndex - 1 - startIndex);

List<T>.Any(); How to get index of matched item?

mI am comparing Listview items with Generic List items with List.Any Method like this:
foreach (ListViewItem itemRow in lstviewAddsheets.Items)
{
if (InvalidSheets.Any(x => x != null && x.FilePath == itemRow.Tag.ToString()))
{
//Math found
}
}
Please tell me, how to get InvalidSheets list index which was matched with itemRow.Tag.ToString().
Since there seems some debate about how much faster it would be to use List.FindIndex() instead of Linq to find the index, I wrote a test program.
This assumes that you only care about finding the index of the first matching item in a list. It doesn't handle multiple matching items.
Also note that this test is worst-case in that the matching item is at the very end of the list.
My results for an x86 release build (run on Windows 8 x64, quad core processor):
Calling Via FindIndex() 100 times took 00:00:00.9326057
Calling Via Linq 100 times took 00:00:04.0014677
Calling Via FindIndex() 100 times took 00:00:00.8994282
Calling Via Linq 100 times took 00:00:03.9179414
Calling Via FindIndex() 100 times took 00:00:00.8971618
Calling Via Linq 100 times took 00:00:03.9134804
Calling Via FindIndex() 100 times took 00:00:00.8963758
showing that List.FindIndex() is roughly four times faster than using Linq.
Here's the test code:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Demo
{
class Test
{
public string FilePath;
}
class Program
{
private void run()
{
int count = 1000000;
List<Test> list = new List<Test>(count);
for (int i = 0; i < count; ++i)
list.Add(new Test{ FilePath = i.ToString()});
string target = (count-1).ToString();
for (int trial = 0; trial < 4; ++trial)
{
Action viaFindIndex =
(
() =>
{
int index = list.FindIndex(x => (x != null) && (x.FilePath == target));
}
);
Action viaLinq =
(
() =>
{
int index = list.Select((x, i) => new { Item = x, Index = i })
.First(x => (x != null) && (x.Item.FilePath == target))
.Index;
}
);
viaFindIndex.TimeThis("Via FindIndex()", 100);
viaLinq.TimeThis("Via Linq", 100);
}
}
private static void Main()
{
new Program().run();
}
}
static class DemoUtil
{
public static void TimeThis(this Action action, string title, int count = 1)
{
var sw = Stopwatch.StartNew();
for (int i = 0; i < count; ++i)
action();
Console.WriteLine("Calling {0} {1} times took {2}", title, count, sw.Elapsed);
}
}
}
So given that List.FindIndex() is both much faster AND much easier to read than using the Linq, I can see no reason to use Linq to solve this particular problem.
int index = list.FindIndex(x => (x != null) && (x.FilePath == target));
versus
int index = list.Select((x, i) => new { Item = x, Index = i })
.First(x => (x != null) && (x.Item.FilePath == target))
.Index;
The first version wins on all counts IMO.
You can do this
int index = InvalidSheets.FindIndex(x => x != null && x.FilePath == itemRow.Tag.ToString());
if you want to get the object directly then do this
var matchedObject = InvalidSheets.FirstOrDefault(x => x != null && x.FilePath == itemRow.Tag.ToString());
Here is how you can get the index:
var index = InvalidSheets.Select((x, i) => new {Item = x, Index = i})
.First(x => x.Item != null && x.Item.FilePath == itemRow.Tag.ToString())
.Index;
However you might want to refactor this with FirstOrDefault like this:
foreach (ListViewItem itemRow in lstviewAddsheets.Items)
{
var sheet = InvalidSheets.Select((x, i) => new {Item = x, Index = i})
.FirstOrDefault(x => x.Item != null && x.Item.FilePath == itemRow.Tag.ToString());
if (sheet != null)
{
var index = sheet.Index;
}
}
Try this:
InvalidSheets.IndexOf(InvalidSheets.First(x => x != null && x.FilePath == itemRow.Tag.ToString()))
It will get index of first invalid sheet matching the predicate
You can project the index with the overload, therefore you need to select an anonymous type:
var invalids = InvalidSheets.Select((s, i) => { Sheet=s, Index=i })
.Where(x => x.Sheet != null && x.Sheet.FilePath == itemRow.Tag.ToString()));
bool anyInvalid = invalids.Any(); // is any invalid
IEnumerable<int> indices = invalids.Select(x => x.Index);// if you need all indices

Categories