Performing an action for each element in a 2D array - c#

I'm currently working on a game where the background is represented by a 2D array of tiles. Tile is a relatively simple class that contains graphic and traversability information. In my code, I find myself wanting to do something similar to the following often:
for (int x = 0; x < TileMap.GetLength(0); x++)
{
for (int y = 0; y < TileMap.GetLength(1); y++)
{
// do something
}
}
Obviously there's a better way to do that, right? I thought that maybe I could create an extension method that takes an Action parameter that iterates through the array and performs the specified action for each Tile, like so:
public static void PerformAction(this Tile[,] tileMap, Action<Tile> action)
{
for (int x = 0; x < tileMap.GetLength(0); x++)
{
for (int y = 0; y < tileMap.GetLength(1); y++)
{
action(tileMap[x, y]);
}
}
}
And then use it like this (just an example):
TileMap.PerformAction(t => t = new Tile());
This doesn't work, though. I can set a breakpoint in either the extension method or in the lambda expression and watch it get hit each iteration, but the actual Tile in the TileMap remains unchanged. For the example above, all Tiles are still null if they were null before. Am I doing something wrong? Is there a better way to do what I want to do?

From your description, it sounds like you want to be able to modify the element in the array itself, not just the object it references (or value it stores…it's not clear whether Tile is a class or a struct). Of course, invoking an Action<T> delegate, you're just passing the value from the array to the method, not a reference to the element of the array. The delegate has no way to modify the array itself in that scenario.
One way to fix this is to pass the element of the array by-reference instead of the default by-value. The built-in delegate types don't support by-reference parameter passing. But you can declare your own delegate (see this answer for more details):
delegate void ActionRef<T>(ref T t);
Then you can implement your extension method like this:
public static void PerformAction(this Tile[,] tileMap, ActionRef<Tile> action)
{
for (int x = 0; x < tileMap.GetLength(0); x++)
{
for (int y = 0; y < tileMap.GetLength(1); y++)
{
action(ref tileMap[x, y]);
}
}
}
Passing a delegate from a method like this:
void MyActionMethod(ref Tile tile)
{
// do something to tile
}
An alternative approach (and IMHO maybe somewhat cleaner) would be to have the action delegate return a new value if desired:
public static void PerformAction(this Tile[,] tileMap, Func<Tile, Tile> action)
{
for (int x = 0; x < tileMap.GetLength(0); x++)
{
for (int y = 0; y < tileMap.GetLength(1); y++)
{
tileMap[x, y] = action(tileMap[x, y]);
}
}
}
Then your method might look something like this:
Tile MyActionMethod(Tile tile)
{
// do something to tile
return tile;
}

Related

How can I make my own "foreach" or similar for my 2D array of custom objects?

I have a class (Biome) which contains a 2D array of custom objects (Level). I frequently want to loop through all (or some) of the levels and perform some actions, e.g. set their colour property.
Rather than writing the same nested for-loop over and over again, can I make my own method which will let me do something like: ForEachLevel(SetColour())?. Where SetColour() would be a method in my Biome class which just sets the Level's colour property to some random value or using some other logic based on factors within the Biome?
So instead of:
for (int r = 0; d < Rows; r++)
{
for (int c = 0; l < Cols; c++)
{
if (some logic here)
Levels[r, c].Colour = Color.Red;
else
Levels[r, c].Colour = Color.Green;
}
}
I could do something like:
ForEachLevel(SetColour(--what goes here?--));
void SetColour(Level level){
if (some logic here)
level.Colour = Color.Red;
else
level.Colour = Color.Green;
}
Or even better, I'd like to make something similar which only runs a function on, say, rows X through to Y.
As you can see from my example I don't even know how I'd get the context of each Level instance into the SetColour function.
I can keep copy/pasting my custom nested for-loops to achieve what I want, but I was hoping someone with more experience might be able to understand what I'm trying to do and point me to the right direction on how I can use better C# techniques.
Because I've been vague I understand if a specific answer cannot be given, but some key concepts for further research would be appreciated!! Thanks
EDIT2 actually that previous attempt doesnt work at all. I forgot I need to actually call ForEachLevel somewhere else. Still working on it.
private void SomeOtherMethod()
{
// where I want to actually use the ForEachLevel()
ForEachLevel(SetLevelColor(--Level??--, Color.red));
}
private void ForEachLevel(Action DoAThing)
{
for (int d = 0; d < Depths; d++)
{
for (int l = 0; l < Lanes; l++)
{
DoAThing();
}
}
}
private void SetLevelColor(Level level, Color color)
{
// trivial example
level.Color = color;
}

How to change the value of a primitive data type variable referred to by a foreach iterator

If I have a class Ball with a set accessor for its property radius, I can set the radius of a list of Ball objects using a foreach loop:
foreach(Ball ball in balls)
{
ball.radius = 1;
}
But if I am iterating through a collection of primitive data types, this does not work because the primitive does not have accessors. If I try to change the value of the primitive data type, I am actually performing the operation on the iterator, not to value itself, so the following does not work:
foreach(double radius in radii)
{
radius = 1;
}
I tried solving it with a pointer, like this:
double* p;
foreach(double radius in radii)
{
p = &radius;
*p = 1;
}
Unsurprisingly, this does not work because p refers to the address occupied by the iterator, not the variable.
In case it is important here, I will be more specific about what I am actually trying to do, and although I would still like an answer to my main question, it's quite possible that there is a much simpler way to do this with the methods of List<>, which I am not too familiar with. I have a List<List<double>>, which I want to alter every element of, specifically by dividing each by a normalisation constant normalisationFactor. The following works perfectly:
for (int i = 0; i < array.Count; i++)
{
for (int j = 0; j < array[0].Count; j++)
{
array[i][j] /= normalisationFactor;
}
}
However, I find the foreach syntax much more readable, and would like to to it that way if I can. For the reasons I have detailed above, the following does not work:
foreach (List<double> row in array)
{
foreach (double value in row)
{
value /= normalisationFactor;
}
}
So, how do I achieve what I want using a foreach loop?
primitives (structs) are immutable and passed around as values not pointers.
IEnumerables do not provide an means to update the data.
If you know the underlining data type (Array, Collection, List), just loop the position index and update the collection position
for (var i=0; i < radius.Count; i++)
radius[i] = newValue

How to have Column, Rows array (not vice versa)

Making game of life I need to a have a grid that is 30x20 (X * Y). The problem is (I had another question regarding to that) that the c# arrays are rows, columns. So when I use CursorPosition() to drawing I need to swap it because it wants column at first.
Is there any way how I can reverse it so I can use like this?
int [,] Array = new int[30,20];
Console.SetCursorPosition(29,19) // now its vice versa, I would need to use 19,29.
I believe that this is purely conceptual (c# arrays are neither row/col or col/row that is up to the developer) and comes down to iterating your array in either a depth-first or breadth-first manner e.g.
//Breadth-first
for(int x = 0; x < col.Length; x++)
{
for(int y = 0; y < row.Length; y++)
{
}
}
//Depth-first
for(int y = 0; y < row.Length; y++)
{
for(int x = 0; x < col.Length; x++)
{
}
}
At first I was inclined to answer no as the parameters to Console.SetCursorPosition is Positional parameters. But when I remember that C# have Named parameters too so something like this works.
int a = 10;
int b = 20;
Console.SetCursorPosition(top: a, left: b);
This is the closest you can get, if you want to know why, search for the terms above
What you need is a data structure to store date in relation with an x,y coordinate.
You do not have to use a multi-dimensional array for this. You could very easily create a class that hides the specific implementation from the other classes.
In fact this will make your design more robust.
You can store the data in a database, bitarray, single dimension array, etc.

Using Array.BinarySearch() to return first value <= lookup value?

I'm trying to create a "lookup" column that would return the index of the array value that is equal to or less than the value being looked up. So this is my attempt, which seems to work fine, but I was wondering if there is a cleaner way of doing it ?
// Sorted
float[] ranges = new float[]
{
0.8f,
1.1f,
2.7f,
3.9f,
4.5f,
5.1f,
};
private int GetIndex(float lookupValue)
{
int position = Array.BinarySearch(ranges, lookupValue);
if (position < 0)
{
// Find the highest available value that does not
// exceed the value being looked up.
position = ~position - 1;
}
// If position is still negative => all values in array
// are greater than lookupValue, return 0
return position < 0 ? 0 : position;
}
Thanks.
Nope, I think this is a pretty good approach.
The only thing I might change is to make it an extension method on arrays, instead of a private function referring to a class variable. Then it becomes general / not tied to one class, and the syntax is cleaner too: ranges.GetIndex(...)
Something like this:
public static class Extensions
{
public static int GetIndex<T>(this T[] ranges, T lookupValue)
{
// your code here
}
}
Of course, you'll have to remember this only works on sorted arrays...
You could use a normal for loop (assuming your data is ordered). Not sure if it's cleaner, but certainly not as effective on lots of data. Personally I would go for the BinarySearch you have.
int GetIndex(IList<float> ranges, float target)
{
for (int i = 0; i < ranges.Count; i++)
{
if(ranges[i] < target) continue;
if (ranges[i] >= target) return i;
}
return 0;
}

Trying to make a 2D array of lists

I'm trying to write a model containing digital organisms. Within the model i'd liek the environment to be a fixed 2-d array, but each cell needs to contain a list of the organisms in it. I tried using a jagged array, but as the number of occupied elements varies quite a bit throughout the programm run, i need to use something more flexible than an array. I've tried making a 2-D array of the type list, but im getting errors with it.
List<Creature>[,] theWorld;
public Environment()
{
List<Creature>[,] theWorld = new List<Creature>[100,100];
}
public void addCreature(Creature c)
{
for (int x = 0; x < 100; x++)
{
for (int y = 0; y < 100; y++)
{
theWorld[x, y].Add (c);
} } }
this is the segment where i'm trying to declare the array at the beginning, as a type that holds lists (of the organisms), and later i try to add a creature (c) to each of the lists in each element of the array.
when i run it i get the following error message-
"An unhandled exception of type 'System.NullReferenceException' occurred in HGT_sim_2.exe
Additional information: Object reference not set to an instance of an object."
and the line "World[x, y].Add (c);" is highlighted.
If anyone can tell me what i'm doing wrong, and even better, a way around the problem, it'd be amazing.
thank you ain advance!
All your array contains initially is a lot of nulls. You need to actually create the lists...
for(int x = 0 ; x < 100 ; x++)
for(int y = 0 ; y < 100 ; y++)
theWorld[x,y] = new List<Creature>();
Personally, though, I expect this will be a costly way to do things...
It depends in part on whether the data is "sparse" - i.e. are most of the cells usually taken? A simple (but possibly more efficient) approach, for example, would be to use something like multi-map; i.e.
Point pt = new Point(x,y);
theWorld.Add(pt, someCreature);
where theWorld could be something like EditableLookup<Point, Creature> (using EditableLookup<,> from "MiscUtil"). That way, you can still query it by co-ordinate, and have multiple creatures on a coordinate, but you don't have to allocate space for every cell. And because it functions as a dictionary it is still fast. Not as fast as a flat array, but it will scale to bigger (sparse) grids... of course, if the grid has creatures on every cell it could be more expensive! Hence the need to understand your data.
You need to initialize each member of your array, e.g.
for (int x = 0; x < 100; x++)
{
for (int y = 0; y < 100; y++)
{
theWorld[x, y] = new List<Creature>();
} }
Here's the fix:
List<Creature>[,] theWorld;
public Environment()
{
theWorld = new List<Creature>[100,100]; // Remove the type, you were creating a new list and throwing it away...
for(int x = 0 ; x < 100 ; x++)
for(int y = 0 ; y < 100 ; y++)
theWorld[x,y] = new List<Creature>();
}
public void addCreature(Creature c)
{
for (int x = 0; x < 100; x++)
{
for (int y = 0; y < 100; y++)
{
theWorld[x, y].Add (c);
} } }
When you do this:
List<Creature>[,] theWorld = new List<Creature>[100,100];
You're creating an array of List<Creature> references, but they are all empty (pointing to null, not a valid List). You need to initialize each individual element:
for (int x = 0; x < 100; x++) {
for (int y = 0; y < 100; y++) {
theWorld[i,j] = new List<Creature>();
}
}
Once you've done that, you'll be able to call .Add on the individual members.
You're doing it almost correct. Your variable is a 2D array of List<Creature>. Now, List<Creature> is a reference type, so the array is initialized to contain null's in all its members. Thus you get the NullReferenceException. The line
theWorld[x, y].Add (c);
is basically equivalent to
null.Add (c);
All you need to do is to initialize all the members to contain instances of List<Creature>. The best way to do this would be in the constructor. Just rewrite it like this:
public Environment()
{
theWorld = new List<Creature>[100,100];
for(int x = 0 ; x < 100 ; x++)
for(int y = 0 ; y < 100 ; y++)
theWorld[x,y] = new List<Creature>();
}
Now all the operations will work as expected.
Also note, that in your example you are creating a local variable with the same name as the class member. This way you don't initialize the class member at all - it stays null.
You have created the array object to hold your lists, but you haven't created the list itself. You will need to do the following in your constructor:
for (int x = 0; x < 100; x++)
for (int y = 0; y < 100; y++)
theWorld[x,y] = new List<Creature>();
Another problem: you were also defining theWorld as a local variable in your constructor, which means your theWorld field on Environment was never initialized, either.
However, 10,000 Lists may be overkill for what you really need. If your Environment really needs a Creature at every point, and some Creatures may move to other points (where there is more than one at a point, then it may make more sense to use a Dictionary<Point, IList<Creature>> as your model versus 10,000 lists.
public void Add(Creature c, Point at)
{
IList<Creature> list;
if (!theWorld.TryGetValue(at)) {
list = theWorld[at] = new List<Creature>();
}
list.Add(c);
}
You can then implement Move and Remove methods similarly. Also, note that you are adding the same Creature to every point, which (may) mean that there's one Creature at all points in your Environment. You will probably want to create a new Creature() for every point, if that's what you are actually modeling.

Categories