【问题标题】:How to create and manage a 2D array-like List object in C#?如何在 C# 中创建和管理类似二维数组的 List 对象?
【发布时间】:2014-05-20 17:54:55
【问题描述】:

我需要一个二维数组,它可以在所有方向上扩展并严格跟踪每个元素的整体定位,但读取效率最高。

我面临的用例如下: 我正在将 2D 构造板块相互碰撞和捣碎。当它们发生碰撞时,它们可以收缩、增长或两者都不会。每次迭代,所有元素都有可能被访问,因此读取时间非常重要。它们也必须能够在所有方面都可以增长/收缩,并且可以包含孔和凸结构。

内存不是一个大问题,但我想尽可能地保存它。我主要关心的是速度,因为旧的概念证明是松散地基于用 C++ 编写的,需要 15 分钟才能运行,而且我正在对原始概念进行大量添加。

我最初想使用带坐标的字典,但这带来了读取时间的问题;当请求他们没有密钥的内容时,字典会很慢,而且这种情况经常发生。

我现在正在考虑使用List<List<MyCLass>> 结构,对空位置使用空类对象。

我的另一个想法是使用代数数组 (y * stride + x),但宁愿避免其复杂性,而且它既难以构建又难以维护。

那么,本质上,拥有一个在 C# 中不断访问和频繁修改的超大型 2D 数据集的最佳方法是什么?

编辑: 按照要求; 每个阵列可能在 50x50 和 1000x1000 之间,并且在任何给定时间都会有 10-30 个。整个“世界”的大小为 512x512 到 4096x4096(在模拟开始时设置),最大重叠率约为 20%(不包括边缘情况)。但是,在笛卡尔坐标系中,每个 2D 板的多达 50% 将是空白空间,因此对于统一的大小,这意味着数组的实际大小将是其两倍,因此最多大约 20,132,659 个非空数组元素,并且比模拟中的 null 元素总数略少。

我可以接受这个程序最多需要几个小时才能完成一个模拟,但我担心这需要几天时间。这就是为什么我试图提出处理这些数据集的最佳方法。

【问题讨论】:

  • “字典在请求他们没有密钥的东西时很慢”......是吗?我原以为这主要会导致对空桶的查找。不会太慢。
  • 通用列表 对你有用吗?
  • 您是否按顺序遍历每个项目?还是从给定的键中读取或随机索引?
  • 你能发布你的数据是什么样子以及你是如何阅读它的吗?
  • 同时发布您希望存储在集合中的元素数量。

标签: c# arrays list


【解决方案1】:

如果您坚持使用可扩展的二维数组(矩阵),请考虑以下事项:

public class Matrix<T> : Collection<T>
{
    int row_count, col_count;
    List<T> _list; //reference to inner list
    T[] _items; //reference to inner array within inner list

    public Matrix(int row_count, int col_count)
        : this(row_count, col_count, new T[row_count*col_count])
    {
        if(row_count==0||col_count==0)
        {
            throw new NotSupportedException();
        }
    }

    Matrix(int row_count, int col_count, T[] values)
        : base(new List<T>(values))
    {
        // internal data arranged in 1D array, by rows.
        this._list=base.Items as List<T>;
        this.row_count=row_count;
        this.col_count=col_count;
        LinkInnerArray();
    }

    private void LinkInnerArray()
    {
        this._items=typeof(List<T>).GetField("_items",
            System.Reflection.BindingFlags.NonPublic
            |System.Reflection.BindingFlags.Instance).GetValue(base.Items) as T[];
    }

    public int RowCount { get { return row_count; } }
    public int ColCount { get { return col_count; } }
    public T[] Elements { get { return _list.ToArray(); } }

    public T this[int row, int col]
    {
        get { return base[col_count*row+col]; }
        set { base[col_count*row+col]=value; }
    }

    public T[] GetRow(int row)
    {
        if(row<0||row>=row_count) new IndexOutOfRangeException();
        T[] result=new T[col_count];
        lock(_items)
        {
            // fast array copy
            Array.Copy(_items, col_count*row, result, 0, result.Length);
        }
        return result;
    }
    public T[] GetColumn(int column)
    {
        if(column<0||column>=col_count) new IndexOutOfRangeException();
        T[] result=new T[row_count];
        lock(_items)
        {
            // No shortcut exists, only if C# was more like FORTRAN
            for(int i=0; i<row_count; i++)
            {
                result[i]=base[col_count*i+column];
            }
        }
        return result;
    }
    public void SetRow(int row, params T[] values)
    {
        if(values==null||values.Length==0) return;
        if(row<0||row>=row_count) new IndexOutOfRangeException();
        // fast array copy
        lock(_items)
        {
            Array.Copy(values, 0, _items, col_count*row, values.Length);
        }
    }
    public void SetColumn(int column, params T[] values)
    {
        if(values==null||values.Length==0) return;
        if(column<0||column>=col_count) new IndexOutOfRangeException();
        lock(_items)
        {
            // No shortcut exists, only if C# was more like FORTRAN
            for(int i=0; i<values.Length; i++)
            {
                base[col_count*i+column]=values[i];
            }
        }
    }

    public void AddRow(params T[] new_row)
    {
        lock(_list)
        {
            // add array to last row
            T[] row=new T[col_count];
            Array.Copy(new_row, 0, row, 0, new_row.Length);
            _list.AddRange(row);
            LinkInnerArray();
            this.row_count++;
        }
    }

    public void AddColumn(params T[] new_column)
    {
        lock(_list)
        {
            // go add an item on end of each row
            for(int i=row_count-1; i>=0; i--)
            {
                T item=i<new_column.Length?new_column[i]:default(T);
                _list.Insert(col_count*i+row_count-1, item);
            }
            LinkInnerArray();
            this.col_count++;
        }
    }

    public Matrix<R> Transform<R>(Func<T, R> operation)
    {
        R[] values=new R[row_count*col_count];
        for(int i=0; i<values.Length; i++)
        {
            values[i]=operation(base[i]);
        }
        return new Matrix<R>(row_count, col_count, values);
    }

    public Matrix<R> Combine<R>(Matrix<T> other, Func<T, T, R> operation)
    {
        R[] values=new R[row_count*col_count];
        for(int i=0; i<values.Length; i++)
        {
            values[i]=operation(base[i], other[i]);
        }
        return new Matrix<R>(row_count, col_count, values);
    }
}

class Program
{
    static void Main(string[] args)
    {
        int N=4;
        var A=new Matrix<int>(N, N);
        // initialize diagonal
        for(int i=0; i<N; i++)
        {
            A[i, i]=1;
        }

        // A = 
        // | 1  0  0  0 |
        // | 0  1  0  0 |
        // | 0  0  1  0 |
        // | 0  0  0  1 |

        A.AddRow(5, 4, 3, 2);
        A.AddColumn(1, 2, 3, 4, 5);

        // A = 
        // | 1  0  0  0  1 |
        // | 0  1  0  0  2 |
        // | 0  0  1  0  3 |
        // | 0  0  0  1  4 |
        // | 5  4  3  2  5 |

        var B=A.Transform(delegate(int x) { return 5-x; });
        // B = 
        // | 4  5  5  5  4 |
        // | 5  4  5  5  3 |
        // | 5  5  4  5  2 |
        // | 5  5  5  4  1 |
        // | 0  1  2  3  0 |

        var C=A.Combine(B, delegate(int x, int y) { return y-x; });
        // C = 
        // | 3  5  5  5  3 |
        // | 5  3  5  5  1 |
        // | 5  5  3  5 -1 |
        // | 5  5  5  3 -3 |
        // |-5 -3 -1  1 -5 |
    }
}

【讨论】:

  • 也许我应该直接用内部数组构建它,并使用Array.Resize() 函数。
  • 哇,这真是一流的课程,而且非常有用!我将把它再打开几天,看看我是否不能使用这种方法运行一些指标,但这看起来像我要找的!
【解决方案2】:

这取决于您如何阅读您的收藏。

例如,如果你循环遍历每个元素,那么 List 总是比字典快。

List<List<Item>> collection = new List<List<Items>>();
//add items
for(List l : collection){
 for(Item i : l){
   //do something with item
 }
}

如果您基于“键”值查找索引,那么字典总是更快(常量与线性)。

Dictionary<String,List<Item>> collection = new Dictionary<String,List<Items>>();
//add items
List<String> keysWeNeedToWorkOn = new List<String>();
//add keys we care about
for(String key : keysWeNeedToWorkOn){
   for(Item i : collection.get(key)){
     // do something with this item
  }
}

这个Big O Cheat sheet 可能有助于您准确地决定您需要什么。

【讨论】:

  • 哦,那张备忘单真的很有用!必须收藏/保存那个!但是我很好奇,如果我在查找arrayObject[x,y],那不会比dictObject[xyvalue] 快吗?我将始终能够根据它们的坐标查找项目,但这将是迭代集合中的每个项目并使用已知位置直接访问特定项目的混合。 Dict 节省内存,但是每次你请求一个不存在的项目时,它都会遍历列表 iirc 中的所有项目。
  • 如果您尝试访问不在字典中的项目,它不会遍历整个数组。 Map/Dictionarys 通过创建一个 HASHING 函数来应用于键值。当您添加一个项目时,它会散列密钥并将其存储在该内存位置。要执行查找,字典只需散列键并返回该内存位置的项目。超过 1 个密钥可以解析到同一个位置,但一个好的散列算法可以防止这种情况。 @spender 上面已经提到了这一点。 MAP/Dictionary 上的查找将始终保持不变,无论该键是否存在。
  • 我现在明白了。在对哈希表过程进行了进一步的教育之后,我现在看到了它是如何工作的。很抱歉我对此事的错误假设。假设 C# 的散列产生很少的冲突,查找时间或多或少是线性的,并且正如你所说,对于存在/不存在的项目不会改变。但是,与直接内存访问相比,哈希查找仍然存在固定开销。这篇文章证明了这一点:dotnetperls.com/array-dictionary-test
  • 尽管如此,您的建议还是帮助我指明了正确的方向!谢谢!
  • @Dreadicon 你知道什么比“谢谢”更好吗?将我的答案旁边的那个小复选标记变成绿色;)
猜你喜欢
  • 2019-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-18
  • 2018-08-18
相关资源
最近更新 更多