开发者

How to return neighbouring items of an item in a LINQ query

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)
             .Selec开发者_运维知识库t((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.


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)


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜