2

I have numeric multidimensional arrays and I would like to perform basic math operations on them like scaling the values. The problem is I don't know the dimensions in advance so I create indices array on fly, recalculate linear, 1D index, into multidimensional indices. This is more work than actual computations themselves.

So I would like to treat multidimensional array as flat one (1D) and work element-wise from 0 index to "array length-1", and that's it (i.e. without recomputing 1D index into multidimensional one). The question is how to do it? Preferable without involving unsafe code.

Update 1: For the record, my computations are mutable -- i.e. I modify values in place.

Update 2: found the unsafe solution: Span and two dimensional Arrays

Update 3: finally I went unsafe way -- the loop and indexing is just dead-simple: How to get a pointer to memory of Array instance?

astrowalker
  • 3,123
  • 3
  • 21
  • 40
  • You can enumerate over all the values as a single sequence, do you intend to modify the values of the arrays? – Jeff Mercado Sep 14 '20 at 08:46
  • @JeffMercado, good question, yes, the operations are mutable. For the record, if they were not, how do you intend to put back enumeration into the array? – astrowalker Sep 14 '20 at 08:48
  • I don't know if there's a quick way to be able to modify the values as if it was a linear sequence, but you can enumerate over the values by simply casting the array to the item type. `arr.Cast()`. Any multidimensional array could be enumerated over that way. – Jeff Mercado Sep 14 '20 at 08:51
  • @JeffMercado wait - you're saying if i have a multidimmensional list/array and i call `.Cast` it will also flatten the collections? – sommmen Sep 14 '20 at 08:52
  • @sommmen, yes.. – Jeff Mercado Sep 14 '20 at 08:53
  • Could you post a sample set @astrowalker ? – sommmen Sep 14 '20 at 08:53
  • You cannot *mutate* the contents of an array via `IEnumerable.Cast` – Matthew Watson Sep 14 '20 at 09:05
  • @JeffMercado, I really doubt it -- on the first try the result is " Unable to cast object of type 'System.Double[,]' to type 'System.Double[]'". – astrowalker Sep 14 '20 at 09:05
  • @astrowalker, No, you cast to `IEnumerable`, that is something entirely different. – Jeff Mercado Sep 14 '20 at 09:10
  • Does this answer your question? [Combining array of arrays into single, distinct array using LINQ](https://stackoverflow.com/questions/5721360/combining-array-of-arrays-into-single-distinct-array-using-linq) – Fumeaux Sep 14 '20 at 09:10
  • @Fumeaux, no, I have already good, well, working, let's say 3D array. All I need is "look" at it as 1D. I found unsafe solution, but I prefer "safe" one, because unsafe code means I have to tag entire library as unsafe. – astrowalker Sep 14 '20 at 09:11
  • I don't think it would be possible to make a mutable one dimensional interface over a multidimensional array, without calculating the indices. Of course you have the option of making an adapter so you could access the array as if it was one dimensional, but I don't know if that would satisfy your reqs. – Jeff Mercado Sep 14 '20 at 09:16
  • _"I create indices array on fly [...] This is more work than actual computations themselves."_ Your question should include a code that demonstrates this. – Wyck Sep 14 '20 at 13:12
  • It is unclear as to what you're actually trying to do. Why are nested loops not sufficient? e.g.: `for (int i0 = 0; i0 < a.GetLength(0); ++i0) for (int i1 = 0; i1 < a.GetLength(1); ++i1) mutate(ref a[i0,i1]);` ? – Wyck Sep 14 '20 at 13:28
  • @Wyck, nested loops, like one loop per each dimension. How do you write single code for ANY array (i.e. 1D, 2D, 3D, 4D, 5D, and so forth)? – astrowalker Sep 15 '20 at 09:15
  • @astrowalker, I provided an example in an answer. – Wyck Sep 16 '20 at 18:13
  • "The problem is I don't know the dimensions in advance so I create indices array on fly, recalculate linear, 1D index, into multidimensional indices. This is more work than actual computations themselves." Can you show an example of one of your arrays, and how you'd like to manipulate it? What code issue specifically are you having with the multi-dimensional part? – Idle_Mind Sep 16 '20 at 18:49

2 Answers2

1

Impossible without unsafe in C # because you need pointers, you can create a function in C or C++ that meets your need and call it in C#.

With unsafe indeed you can handle a multidimensional arrays (Matrices) als 1D whith pointers - BUT you need to know at least the indentation and the count of all elements and it is valid only for arrays initialized like T[,,,], then you can take the example bellow:

    public static void AddFiveOnMatrixArray(int[,,] numbers, int count)
    {
        unsafe
        {
            fixed (int* ptr = &numbers[0, 0, 0])
            {
                for (int i = 0; i < count; i++)
                {
                    var x = *(ptr + i) + 5;

                    Console.Write($"{x} ");
                }

                Console.WriteLine();
            }
        }
    }

but the above can be done for jagged arrays too whith some changes as follow:

    public static void AddFiveOnJaggedArray(dynamic numbers)
    {
        if (numbers is Array && numbers[0] is Array)
        {
            for (int i = 0; i < numbers.Length; i++)
            {
                AddFiveOnJaggedArray(numbers[i]);
            }
        }
        else
        {
            unsafe
            {
                fixed (int* ptr = &((int[])numbers)[0])
                {
                    for (int i = 0; i < numbers.Length; i++)
                    {
                        var x = *(ptr + i) + 5;

                        Console.Write($"{x} ");
                    }
                }
            }
        }
    }

If you decide to use the second example, i dont know it may be make no sense to do your math operations whith pointers any more but it is up to you. If you have no info about the size of the array is probably better to iterate over it like above without pointers or anything and do your math over the elements instead of wrapping it into some other structure and then back again.

Hier is the Main method:

    public static void Main()
    {
        Console.WriteLine("Math operations over array[,,]:");
        AddFiveOnMatrixArray(new int[,,]
        {
            {
                { 10, 20, 30 },
                { 40, 50, 60 }
            },
            {
                { 70, 80, 90 },
                { 80, 70, 60 }
            },
            {
                { 50, 40, 30 },
                { 20, 10,  0 }
            }
        }, 18);

        Console.WriteLine("Math operations over array[][][]:");
        AddFiveOnJaggedArray(new int[][][]
        {
            new int[][]
            {
                new int[] { 10, 20, 30 },
                new int[] { 40, 50, 60, 50, 40 }
            },
            new int[][]
            {
                new int[] { 70, 80, 90, 80, 70 },
                new int[] { 80, 70, 60 }
            },
            new int[][]
            {
                new int[] { 50, 40, 30 },
                new int[] { 20, 10,  0, 10, 20 }
            }
        });
    }

and the output produced by both: enter image description here

spzvtbg
  • 964
  • 5
  • 13
0

I know you didn't want to compute indices, but since you claim to have an array of unknown dimension, I think this can be a flexible approach.

Here's an IndexGenerator class that can help you accomplish what you want.

It is enumerable, works with arrays of any type and any dimension, including dimensions with length zero. It yields arrays of indices suitable for use with Array.GetValue(params int[]) or Array.SetValue(object? value, params int[] indices).

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

public class IndexGenerator : IEnumerable<int[]>
{
    Array array;

    public IndexGenerator(Array value)
    {
        array = value;
    }

    public IEnumerator<int[]> GetEnumerator() => new Enumerator(this);
    IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);

    class Enumerator : IEnumerator<int[]>
    {
        int[] current;
        int[] lengths;
        int rank;

        public Enumerator(IndexGenerator gen)
        {
            rank = gen.array.Rank;
            lengths = Enumerable.Range(0, rank)
                                .Select(d => gen.array.GetLength(d))
                                .ToArray();
        }

        public int[] Current => current;
        object IEnumerator.Current => current;
        public void Dispose() { }

        public void Reset()
        {
            current = null;
        }

        public bool MoveNext()
        {
            if (current == null) {
                if (!lengths.All(l => l > 0)) return false;
                current = new int[rank];
                return true;
            }
            int d = rank;
            while (d > 0) {
                ++current[--d];
                if (current[d] < lengths[d]) return true;
                current[d] = 0;
            }
            return false;
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        string[,,] a = new string[,,] {
            {
                { "small white cat", "small white dog", "small white fly"},
                { "small black cat", "small black dog", "small black fly"}
            },
            {
                { "large white cat", "large white dog", "large white fly"},
                { "large black cat", "large black dog", "large black fly"}
            }
        };
        foreach (var i in new IndexGenerator(a))
            Console.WriteLine("a[{0}] = {1}", string.Join(",", i), a.GetValue(i));
    }
}

There are some interesting things you can do with this IndexGenerator class, like create your in-place transformer from a lambda function.

public static class ArrayExtensions
{
    public static Array Transform<A>(this Array me, Func<A, A> transformFn)
    {
        foreach (var i in new IndexGenerator(me))
            me.SetValue(transformFn((A)me.GetValue(i)), i);
        return me; // permit chaining
    }

    // This function is only used to display the elements.
    // see: https://stackoverflow.com/a/46997301/1563833
    public static IEnumerable<T> ToEnumerable<T>(this T[,] me)
    {
        foreach (var item in me)
            yield return item;
    }
}

class Program
{
    static void Main(string[] args)
    {
        int[,] array = new int[,] { { 1, 2 }, { 3, 4 } };
        Console.WriteLine(string.Join(",", array.ToEnumerable())); // 1,2,3,4

        array.Transform((int a) => a * 2); // double all the elements
        Console.WriteLine(string.Join(",", array.ToEnumerable())); // 2,4,6,8
    }
}

Important note

Sadly, this is probably just a stunt. While syntactically convenient, I don't think this is the most efficient way to do this (in terms of performance). Storing the data as a multidimensional array will likely prove to be a liability if you don't know the dimensions of the array, and this is due to reflection. You may want to re-think your choice of data structure or hand-roll a few nested-loop type-safe array access patterns for the dimensions that you actually do need to support.

Also, don't underestimate the speed of a copy operation rather than an in-place transform I think you'll find that's the fastest option. I welcome further insights from others about this.

Wyck
  • 10,311
  • 6
  • 39
  • 60
  • The code has to be fast, after all, it is just a piece of memory with some basic data (ints, doubles, etc) so I cannot roll out super machinery just to compute index. I went with unsafe and I have flat, super quick indexing, see here: https://stackoverflow.com/questions/63899222/how-to-get-a-pointer-to-memory-of-array-instance/63901832#63901832 – astrowalker Sep 17 '20 at 06:18