创建线性二维数组的子数组

Posted

技术标签:

【中文标题】创建线性二维数组的子数组【英文标题】:Creating sub-array of linearized 2d-array 【发布时间】:2021-06-09 09:23:59 【问题描述】:

我确信解决方案比我想象的要简单,但由于某种原因,我无法理解它。

我正在使用包含 2d 或 3d 数据的线性化(一维)数组。具体来说,我在 Unity DOTS 环境中使用 NativeArrays,但我确信有一个通用的解决方案。

这是我的伪代码用例:

origin[] = 
  3, 4, 5,
  9, 8, 7,
  2, 1, 6


originSize = (3, 3)

现在我想创建一个位置为 (1, 1) 的子数组,该数组的大小为 (2, 2)。 我希望得到

subarray[] = 
  8, 7,
  1, 6

我当前的代码如下所示:

for loop over subarray with index...

int x = index % subSize.x;
int y = index / subSize.x;
int tileX = x + subPos.x * subSize.x;
int tileY = y + subPos.y * subSize.y;
int originIndex = tileX + tileY * originSize.x
subArray[index] = originArray[inputIndex];

这不仅会遇到一些输入超出范围的问题,而且速度也很慢(它位于代码的性能关键部分)。我希望有一个 memcopy 替代方案,但由于数据在数组中是零散的,我唯一的想法是逐行进行,这似乎也不是很快。

我试图找到提取子数组的通用解决方案,但找不到任何不使用多维或锯齿数组的解决方案。该解决方案适用于线性阵列并且速度快,这一点很重要。

谢谢!

【问题讨论】:

index / subSize.x; 将始终为 0,因为 int 除法不是吗? 如果索引高于subSize.x(宽度)则不会。像这样的索引计算对于线性化二维数组来说是很正常的。索引介于 0 和 subSize.x * subSize.y 之间。 【参考方案1】:

我认为应该这样做

public static class ArrayExtensions

    public static T[] GetSubArray<T>( this T[] origin, int originDimensionsX, int originDimensionsY, int startPositionX, int startPositionY, int targetDimensionsX, int targetDimensionsY)
    
        // First of all some checks
        if(origin == null) return null;
        if(origin.Length == 0) return new T[0];
        if(originDimensionsX < 0 || originDimensionsY < 0 || originDimensionsX * originDimensionsY != origin.Length) throw new ArgumentOutOfRangeException($"Given dimensions originDimensionsX,originDimensionsY do not match the given array length origin.Length!");
        if(startPositionX < 0 || startPositionX > originDimensionsX - 1) throw new ArgumentOutOfRangeException($"Given startPosition startPositionX is outside of the origin dimensions originDimensionsX!");
        if(startPositionY < 0 || startPositionY > originDimensionsY - 1) throw new ArgumentOutOfRangeException($"Given startPosition startPositionY is outside of the origin dimensions originDimensionsY!");
        if(targetDimensionsX < 0 || targetDimensionsY < 0) throw new ArgumentOutOfRangeException ("TargetDimensions may not be negative!");
        if(startPositionX + targetDimensionsX > originDimensionsX || startPositionY + targetDimensionsY > originDimensionsY) throw new ArgumentOutOfRangeException ($"Given TargetDimensions targetDimensionsX,targetDimensionsY starting at startPositionX,startPositionY does not fit into originDimensions originDimensionsX,originDimensionsY!");

        var output = new T[targetDimensionsX * targetDimensionsY];

        var subIndex = 0;
        for(var y = startPositionY; y < startPositionY + targetDimensionsY; y++)
        
            for(var x = startPositionX; x < startPositionX + targetDimensionsX; x++)
            
                output[subIndex++] = origin[x + y * originDimensionsX];
            
        

        return output;
    

例子

var origin = new []
 
    0, 1, 2, 
    3, 4, 5, 
    6, 7, 8
;  

var subArray = origin.GetSubArray(3, 3, 1, 1, 2, 2);

Debug.Log(string.Join(",", subArray));

4, 5, 7, 8

由于对于 X 维度,我们总是为大型数组复制 连续 值,它可能比使用内部 for 循环更有效

Array.Copy

public static T[] GetSubArray(this int[] origin, int originDimensionsX, int originDimensionsY, int startPositionX, int startPositionY, int targetDimensionsX, int targetDimensionsY)
    
        // First of all some checks
        if(origin == null) return null;
        if(origin.Length == 0) return new T[0];
        if(originDimensionsX < 0 || originDimensionsY < 0 || originDimensionsX * originDimensionsY != origin.Length) throw new ArgumentOutOfRangeException($"Given dimensions originDimensionsX,originDimensionsY do not match the given array length origin.Length!");
        if(startPositionX < 0 || startPositionX > originDimensionsX - 1) throw new ArgumentOutOfRangeException($"Given startPosition startPositionX is outside of the origin dimensions originDimensionsX!");
        if(startPositionY < 0 || startPositionY > originDimensionsY - 1) throw new ArgumentOutOfRangeException($"Given startPosition startPositionY is outside of the origin dimensions originDimensionsY!");
        if(targetDimensionsX < 0 || targetDimensionsY < 0) throw new ArgumentOutOfRangeException ("TargetDimensions may not be negative!");
        if(startPositionX + targetDimensionsX > originDimensionsX || startPositionY + targetDimensionsY > originDimensionsY) throw new ArgumentOutOfRangeException ($"Given TargetDimensions targetDimensionsX,targetDimensionsY starting at startPositionX,startPositionY does not fit into originDimensions originDimensionsX,originDimensionsY!");

        var output = new T[targetDimensionsX * targetDimensionsY];

        var subIndex = 0;
        for(var y = startPositionY; y < startPositionY + targetDimensionsY; y++)
        
            Array.Copy(origin, startPositionX + y * originDimensionsX, output, subIndex, targetDimensionsX);
            subIndex += targetDimensionsX;
        

        return output;
    


或者你也可以下到字节级别,直接使用 Buffer.BlockCopy (假设现在是 int 或其他 const 大小的基本类型数组 - 不是通用的,你需要知道常量字节大小你的类型!)

const int SIZEOF_INT = sizeof(int);

public static int[] GetSubArray(this int[] origin, int originDimensionsX, int originDimensionsY, int startPositionX, int startPositionY, int targetDimensionsX, int targetDimensionsY)
    
        // First of all some checks
        if(origin == null) return null;
        if(origin.Length == 0) return new T[0];
        if(originDimensionsX < 0 || originDimensionsY < 0 || originDimensionsX * originDimensionsY != origin.Length) throw new ArgumentOutOfRangeException($"Given dimensions originDimensionsX,originDimensionsY do not match the given array length origin.Length!");
        if(startPositionX < 0 || startPositionX > originDimensionsX - 1) throw new ArgumentOutOfRangeException($"Given startPosition startPositionX is outside of the origin dimensions originDimensionsX!");
        if(startPositionY < 0 || startPositionY > originDimensionsY - 1) throw new ArgumentOutOfRangeException($"Given startPosition startPositionY is outside of the origin dimensions originDimensionsY!");
        if(targetDimensionsX < 0 || targetDimensionsY < 0) throw new ArgumentOutOfRangeException ("TargetDimensions may not be negative!");
        if(startPositionX + targetDimensionsX > originDimensionsX || startPositionY + targetDimensionsY > originDimensionsY) throw new ArgumentOutOfRangeException ($"Given TargetDimensions targetDimensionsX,targetDimensionsY starting at startPositionX,startPositionY does not fit into originDimensions originDimensionsX,originDimensionsY!");

        var output = new int[targetDimensionsX * targetDimensionsY];

        var subIndex = 0;
        for(var y = startPositionY; y < startPositionY + targetDimensionsY; y++)
        
            // In this case the indices are all referring to bytes instead of array index
            Buffer.BlockCopy(origin, (startPositionX + y * originDimensionsX) * SIZEOF_INT, output, subIndex * SIZEOF_INT, targetDimensionsX * SIZEOF_INT);
            subIndex += targetDimensionsX;
        

        return output;
    


注意:在智能手机上输入 -> 未经测试,但我希望思路清晰

【讨论】:

我认为这可能是迄今为止最好的答案,非常感谢。我需要检查我是否可以做BlockCopy 或与NativeArray 类似的事情,但上述解决方案已经非常好并且易​​于并行化(通过将工作分成块,因为它只依赖于子索引)。 哦,对于本机数组,您也可以只使用NativeArray.Copy,这又是通用的,您不需要知道字节大小 谢谢,我完全错过了Copy 方法。我主要使用CopyFromCopyTo 方法,它们没有变体来指定要复制的部分。这很有帮助!【参考方案2】:

这是我尝试并为我工作的代码 sn-p。让我知道它是否有帮助: `

        // Size for original array
        int originSizeX = 4;
        int originSizeY = 4;

        // Size for sub array
        int subArrSizeX = 3;
        int subArrSizeY = 3;

        // Starting positions of sub-array in original array
        int startingPosX = 0;
        int StartingPosY = 1;

        // Create original array
        int[] arr = CreateArr(originSizeX, originSizeY);

        // Get sub array
        int[] subArr = GetSubArr(arr, subArrSizeX, subArrSizeY, startingPosX, StartingPosY, originSizeX, originSizeY);


    private static int[] GetSubArr(int[] arr, int subArrSizeX, int subArrSizeY, int startingPosX, int startingPosY, int originArrSizeX, int originArrSizeY)
    
        int[] subArr = new int[subArrSizeX * subArrSizeY];

        int subArrCurrentIndexX = 0;
        int subArrCurrentIndexY = 0;

        for (int originArrCurrentIndexY = startingPosY; originArrCurrentIndexY < startingPosY + subArrSizeY; originArrCurrentIndexY++)
        
            for (int originArrCurrentIndexX = startingPosX; originArrCurrentIndexX < startingPosX + subArrSizeX; originArrCurrentIndexX++)
            
                // Transform indices from 2D to 1D
                int originArrIndex = (originArrCurrentIndexY ) * originArrSizeX + originArrCurrentIndexX ;
                int subArrIndex = (subArrCurrentIndexY ) * subArrSizeX + subArrCurrentIndexX ;

                subArr[subArrIndex] = arr[originArrIndex];

                subArrCurrentIndexX++;

                if (subArrCurrentIndexX == subArrSizeX)
                
                    subArrCurrentIndexX = 0;
                    subArrCurrentIndexY++;
                
            
        

        return subArr;
    

    private static int[] CreateArr(int originSizeX, int originSizeY)
    
        int[] arr = new int[originSizeX * originSizeY];

        for (int i = 0; i < arr.Length; i++)
        
            arr[i] = i;
        

        return arr;
    

`

【讨论】:

谢谢你,这行得通,但不幸的是速度很慢。出于这个原因,我试图避免嵌套循环。如果没有其他方法,我将不得不处理它,但我仍然希望有更好的表现。我会尝试将您的算法调整为在使用 Unity JobSystem 的并行线程中效果更好的东西(在输出数据上具有连续索引的单循环),也许这已经提供了更好的性能。仍然非常感谢!

以上是关于创建线性二维数组的子数组的主要内容,如果未能解决你的问题,请参考以下文章

C 语言数组 ( 验证二维数组内存是线性的 | 打印二维数组 | 以一维数组方式打印二维数组 | 打印二维数组值和地址 )

在二维数组中创建线性渐变

怎么用java取出出指定条件二维数组的子数组

返回一个二维整形数组的子数组的最大和

如何将二维数组赋值给vector

数据结构_线性结构(稀疏数组)