tags:

views:

162

answers:

5

I have a list of strings in list. How do I use LINQ to get the last string in the list which has the character 'P' in the second position of the string. I would like to do this in a single statement using LINQ instead of doing a search in a conventional loop.

Example. The list contains these 3 strings:

Search a fox
APPLE
Going to school

The LINQ statement should return 2 which is the second string in the list which met the condition.

+1  A: 

How about

strings.Where(o => o.Length > 1 && o[1] == 'P').Last();
Jason Punyon
This returns the string and not the list index of that string
Tony_Henrich
but the answer is easy with LastIndexOf
Tony_Henrich
+1  A: 

Do you mean that you want the index of the string:

int lastIndex =
  list
  .Select((s,i) => new { Value = s, Index = i })
  .Where(o => o.Value.Length >= 2 && o.Value[1] == 'P')
  .Select(o => o.Index).Last();

or the string itself:

string lastString = list.Where(s => s.Length >= 2 && s[1] == 'P').Last();
Guffa
Won't work. s[2] is the third position of the string, and the Select((s,i)=>i) needs to come first, otherwise i is only the index within the results of the Where.
Zooba
@Zooba: I already changed the index. You are right that the Select will change the index, but simply swapping them doesn't work as you would lose the string instead.
Guffa
I'm well aware of that, but I figured I wouldn't need to explain all the details to you :)
Zooba
+7  A: 
var lastWithP = myList.Last(s => s.Length >= 2 && s[1] == 'P');
var lastIndex = myList.LastIndexOf(lastWithP);

Or alternatively:

var lastIndex = myList.Select((s, i) => new { S = s, I = i })
                      .Last(p => p.S.Length >= 2 && p.S[1] == 'P').I;

Both of these assume that none of the list elements are null, though they do check for at least two characters.

Performance-wise, benchmarking would be required, but my suspicion would be that the first may be quicker on a List<string>, since the LastIndexOf() will compare by reference. The second will do a lot more memory allocation because of the Select call, but on an expensive IEnumerable<string> (note that not all enumerables are necessarily expensive) will only require one enumeration.

Also, if there is no element in the list with 'P' in the second position, an exception will be thrown. LastOrDefault and a test for null may be used instead if desired.

Zooba
You can avoid memory allocation in the second scenario by using a value type (e.g. `KeyValuePair<int, string>`) rather than anonymous class.
Pavel Minaev
Ah, nice one. I like that.
Zooba
@Pavel: True, but if efficiency was the point of the question, we would not use LINQ at all for this...
Guffa
Yes, definitely. I felt the need to comment on this solely because Zooba mentioned "lot more memory allocation" in the text of the answer.
Pavel Minaev
+1  A: 

Using Query syntax:

int indexOfLast = (from index in Enumerable.Range(0, strings.Length -1)
                   where strings[index].Length >= 2 && strings[index][1] == 'P'
                   select index).Last();
trampster
+1  A: 
list.Aggregate(new {Cursor = -1, Pointer = -1}, (x, y) => new { Cursor = x.Cursor + 1, Pointer = y.Length > 1 && y[1] == 'P' ? x.Cursor + 1 : x.Pointer}).Pointer;