Using LINQ to Extract Specific Values from Multi-Dimensional Array
I am working with multi-dimentioned arrays of bool
, int
, and various struct
. The code loops through these arrays and performs some operation on specific values. For instance,
for (int x = 0; x < this.Size.Width; x++) {
for (int y = 0; y < this.Size.Height; y++) {
if (this.Map[x, y]) {
DrawTerrain(this.Tile[x, y].Location, Terrain.Water);
}
}
}
I can do simple LINQ stuff, but I can't do what I would like. What I would like to do is use LINQ. Maybe something like
from x in this.Map where x == true execute DrawTerrain(...)
But, I don't understand how I can get the x and y locations or how to call a method in the LINQ statement.
Also, it would be great if I can put this code into a function and be able to call it with a delegate or a predicate? I don't know if delegate or predicate are the correct words.
void Draw(Delegate draw, bool[,] map, struct[,] tiles)
from x in map where x == true draw(titles[x,y]).invo开发者_开发技巧ke;
}
The LINQ query syntax isn't really designed for working with 2-dimensional data structures, but you can write an extension method that will convert the 2D array into a sequence of values that contain the coordinates in the original 2D array and the value from the array. You'll need a helper type to store the data:
class CoordinateValue<T> {
public T Value { get; set; }
public int X { get; set; }
public int Y { get; set; }
}
Then you can write the extension method (for any 2D array) like this:
IEnumerable<CoordinateValue<T>> AsEnumerable(this T[,] arr) {
for (int i = 0; i < arr.GetLength(0); i++)
for (int j = 0; j < arr.GetLength(0); j++)
yield new CoordinateValue<T> { Value = arr[i, j]; X = i; Y = j; };
}
Now you can finally start using LINQ. To get coordinates of all elements from the 2D array such that the value stored in the element is true
, you can use the following:
var toDraw = from tile in this.Map.AsEnumerable()
where tile.Value == true
select tile;
// tile contains the coordinates (X, Y) and the original value (Value)
foreach(var tile in toDraw)
FillTile(tile.X, tile.Y);
This lets you specify some interesting conditions when filtering the tiles. For example if you wanted to get only diagonal elements, you could write:
var toDraw = from tile in this.Map.AsEnumerable()
where tile.Value == true && tile.X == tile.Y
select tile;
To answer your second question - if you want to encapsulate the behavior into the method, you'll probably need to use some Action<...>
delegate to represent the drawing function that will be passed to the method. It depends on the type signature of your drawing method though.
Well, you could do it with LINQ if you work hard enough, but it would be a pain. It sounds like your first bit of code is absolutely fine.
Having a version of that which is generalised to take an action seems very reasonable though:
public delegate void Drawer(int x, int y, Tile tile);
public void Draw(Drawer drawer, bool[,] map, Tile[,] tiles) {
for (int x = 0; x < this.Size.Width; x++) {
for (int y = 0; y < this.Size.Height; y++) {
if (this.Map[x, y]) {
drawer(x, y, tiles[x, y]);
}
}
}
}
If you really want the LINQ version, it would be something like:
var query = from x in Enumerable.Range(0, Size.Width)
from y in Enumerable.Range(0, Size.Height)
where Map[x, y]
select new { x, y };
foreach (var pair in query)
{
DoWhatever(pair.x, pair.y, tiles[pair.x, pair.y]);
}
You can do the following Linq query to get the x,y values then iterate over them to run your method.
var points = from x in Enumerable.Range(0, this.Size.Width - 1)
from y in Enumerable.Range(0, this.Size.Width - 1)
where this.Map[x, y]
select new { X = x, Y = y };
foreach (var p in points)
DrawTerrain(this.Tile[p.X, p.Y].Location, Terrain.Water);
精彩评论