4

I have the following class

class Tile
{
    public int height;
    public int terrain;
}

And I have a 2D array of Tiles

Tile[,] area = new Tile[5,5];

How could I map my area from a Tile[,] to a int[,], where only the height is saved?

I tried doing this:

area.Select(tile => tile.height)

but apparently C# Multidimensional arrays do not implement IEnumerable.

How could I solve this problem?

3
  • 3
    sounds like you need a good old fashioned pair of nested for loops Commented Jan 16, 2018 at 20:46
  • This answer might provide a solution to your problem. stackoverflow.com/a/14030150/1181408 Commented Jan 16, 2018 at 21:00
  • This may help you in finding a solution: Enumerating on multi-dimention arrays Commented Jan 16, 2018 at 21:01

4 Answers 4

8

How could I solve this problem?

By writing code. There's no "select" that works, so make your own:

static class Extensions 
{
  public static R[,] Select<T, R>(this T[,] items, Func<T, R> f) 
  {
    int d0 = items.GetLength(0);
    int d1 = items.GetLength(1);
    R[,] result = new R[d0, d1];
    for (int i0 = 0; i0 < d0; i0 += 1)
      for (int i1 = 0; i1 < d1; i1 += 1)
        result[i0, i1] = f(items[i0, i1]);
    return result;
  } 
}

And now you have the extension method you want.

EXERCISES:

  • Which of the standard LINQ sequence operators make sense to adapt to multidimensional arrays, and which do not?
  • Are there operators you'd like to see on multidimensional arrays that are not standard LINQ operators but which you could implement as extension methods?
Sign up to request clarification or add additional context in comments.

2 Comments

@OP: If you intend to complete Eric Lippert's exercises, Jon Skeet's Edulinq will be an excellent resource.
Missing a } on the end there to close the static class
1

As there is no out of the box way to do this, you may try the workaround proposed here:

Extracted from the original post and all credit goes to original poster: Enumerating on Multi-dimentional arrays

public static class ArrayExtensions
{
    public static IEnumerable<T> ToEnumerable<T>(this Array target)
    {
    foreach (var item in target)
        yield return (T)item;
    }
}

Comments

1

If you really, reaaaallly, reaaaaaallly want, you could construct something like this:

public static void Main(string[] args)
{
    var area = new Tile[5, 5];

    for (var j = 0; j < 5; j++)
        for (var i = 0; i < 5; i++)
            area[i, j] = new Tile() { height = (j + 1) * (i + 1), terrain = 99 };

Your linq:

    // this copies the data over from your area-array into a new int[5,5] array using
    // IEnumerable.Aggregate(...) with an emtpy seeded int[5,5] array and
    // leverages Enumerable.Range() with integer division + modular to get
    // the indices right

    var onlyHeights = Enumerable
        .Range(0, 25)
        .Aggregate(new int[5, 5], (acc, i) =>
    {
        acc[i / 5, i % 5] = area[i / 5, i % 5].height;
        return acc;
    });

Test:

    for (var j = 0; j < 5; j++)
        for (var i = 0; i < 5; i++)
            Console.WriteLine($"area.height {area[i, j].height} => {onlyHeights[i, j]}");
    Console.ReadLine();
}

Output:

area.height 1 => 1
area.height 2 => 2
area.height 3 => 3
area.height 4 => 4
area.height 5 => 5
area.height 2 => 2
area.height 4 => 4
area.height 6 => 6
area.height 8 => 8
area.height 10 => 10
area.height 3 => 3
area.height 6 => 6
area.height 9 => 9
area.height 12 => 12
area.height 15 => 15
area.height 4 => 4
area.height 8 => 8
area.height 12 => 12
area.height 16 => 16
area.height 20 => 20
area.height 5 => 5
area.height 10 => 10
area.height 15 => 15
area.height 20 => 20
area.height 25 => 25

But thats just some nested for's in disguise.

2 Comments

I wouldn't say that was nested fors - just one for. If you wanted to emulate nested fors, you would do Enumerable.Range(0, 5).SelectMany(i => Enumerable.Range(0, 5).Select(j => (i, j))).Aggregate(.
@NetMage Instead of going 5 times to 5 it goes to 5*5 - so I see it as flattened (disguised) nested 5*5 loop. Yours is the real deal.
1

And if you want a more generic LINQ-like method accepting higher dimensional arrays.

public static class ArrayExtensions
{
    private static IEnumerable<int[]> CreatePermutations(int[] lengths, int pos = 0)
    {
        for (var i = 0; i < lengths[pos]; i++)
        {
            var newArray = (int[])lengths.Clone();
            newArray[pos] = i;
            if (pos + 1 >= lengths.Length)
            {
                yield return newArray;
                continue;
            }
            foreach (var next in CreatePermutations(newArray, pos + 1)) yield return next;
        }
    }
    public static Array Select<T,P>(this Array target, Func<T, P> func)
    {
        var dimensions = target.Rank;
        var lengths = Enumerable.Range(0, dimensions).Select(d => target.GetLength(d)).ToArray();
        var array = Array.CreateInstance(typeof(P), lengths);
        var permutations = CreatePermutations(lengths);
        foreach (var index in permutations)
        {
            array.SetValue(func((T)target.GetValue(index)), index);
        }
        return array;
    }
}

Which you can call like.

    var heightOnly = area.Select<Tile, int>(a => a.height);

1 Comment

Very neat, if one can wrap his head around the CreatePermutations +1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.