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?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Enrique Moreno Tent
  • 24,127
  • 34
  • 104
  • 189
  • 3
    sounds like you need a good old fashioned pair of nested for loops – pm100 Jan 16 '18 at 20:46
  • This answer might provide a solution to your problem. https://stackoverflow.com/a/14030150/1181408 – cgotberg Jan 16 '18 at 21:00
  • This may help you in finding a solution: [Enumerating on multi-dimention arrays](https://stackoverflow.com/questions/275073/why-do-c-sharp-multidimensional-arrays-not-implement-ienumerablet) – Diablo Jan 16 '18 at 21:01

4 Answers4

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?
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    @OP: If you intend to complete Eric Lippert's exercises, Jon Skeet's [Edulinq](https://codeblog.jonskeet.uk/category/edulinq/) will be an excellent resource. – Brian Jan 18 '18 at 19:21
  • Missing a } on the end there to close the static class – NotAPro Jul 18 '22 at 16:31
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.

Patrick Artner
  • 50,409
  • 9
  • 43
  • 69
  • 1
    I wouldn't say that was nested `for`s - just one `for`. If you wanted to emulate nested `for`s, you would do `Enumerable.Range(0, 5).SelectMany(i => Enumerable.Range(0, 5).Select(j => (i, j))).Aggregate(`. – NetMage Jan 16 '18 at 21:51
  • @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. – Patrick Artner Jan 17 '18 at 07:13
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;
    }
}
Diablo
  • 169
  • 1
  • 8
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);
geraphl
  • 305
  • 4
  • 10