Reputation: 448
Consider the following collection
var players = new[]{
new {Id = 1, Name = "A", Score = 70},
new {Id = 2, Name = "B", Score = 50},
new {Id = 3, Name = "C", Score = 100},
new {Id = 4, Name = "D", Score = 90}
};
If I wanted to return the position of specific player (say, Player with ID = 1) in above list ordered by score I could write a query like this:
var result = players.OrderByDescending(p => p.Score)
.Select((p, i) => new {player = p, Position = i})
.Where(x => x.player.Id == 1)
.First();
int position = result.Position;
var player = result.player;
Now how can I take this further and return the neighbouring items in addition to the actual player? Neighbouring items are the previous and next player and their respective positions when we order the list by score.
Here is the expected result of the query
var expectedResult = new[]{
new {Id = 2, Name = "B", Score = 50}, //Previous player
new {Id = 1, Name = "A", Score = 70},
new {Id = 4, Name = "D", Score = 90} //Next Player
};
Could the above result be achieved by a single LINQ expression? Any help would be appreciated.
Upvotes: 0
Views: 459
Reputation: 71945
I'd write something like this:
public static IEnumerable<IList<T>> GetOverlappingChunks<T>(
this IEnumerable<T> sequence, int chunkSize)
{
List<T> chunk = new List<T>(chunkSize);
foreach (var elt in sequence)
{
chunk.Add(elt);
if (chunk.Count > chunkSize)
chunk.RemoveAt(0);
if (chunk.Count == chunkSize)
yield return chunk.ToArray();
}
}
// ...
var result = players.OrderByDescending(p => p.Score)
.GetOverlappingChunks(3)
.Where(x => x[1].Id == 1);
(Too bad C# doesn't have a built-in deque type.)
If you need to handle cases where there are less than three players in the list, then you'd need to tweak GetOverlappingChunks
and the check slightly.
Upvotes: 1
Reputation: 5994
You can use the Zip operator defined in .NET 4.0, or use the Scan operator defined in the Rx extensions:
With zip:
var result = players.OrderByDescending(p => p.Score)
.Select((p, i) => new {Player = p, Position = i})
.ToList(); //forces evaluation
result.Zip(result.Skip(1), (i,j) => new {First= i, Second=j})
.Zip(result.Skip(2), (i,j) => new {First = i.First, Second = i.Second, Third=j})
.First(o => o.Second.player.Id == 1);
But this won't give you neighbors for the first and last player. If you want them too, you have to massage your collections (since all three ienumerable must have the same number of items)
Upvotes: 4