【问题标题】:How can I efficiently assign a common initial value to a large array in C#?如何有效地将公共初始值分配给 C# 中的大型数组?
【发布时间】:2012-09-14 20:30:43
【问题描述】:

如何有效地为大型数组分配一个共同的初始值? 例如,如果我有一个 100 x 100 x 100 整数数组,其中所有初始值都应为零。

在 matlab 中我会简单地写:

array = zeros(100,100,100);

如何在 C# 中没有循环的情况下做到这一点?

【问题讨论】:

  • 在 C# 中,所有整数默认初始化为 0。
  • 好奇:是的,在他给出的例子中——但这不是问题的答案。问题是“How can I efficiently assign a common initial value to a large array?
  • @user1323995 请澄清:您是在问如何使您的代码尽可能短或尽可能快?

标签: c# arrays multidimensional-array initialization


【解决方案1】:

只是为了争论(我个人认为这不是一种好的编程风格),但它可以在 C# 中用一行代码完成,技术上无需循环但使用 LINQ

int[,,] cube = new int[A, B, C];
Enumerable.Range(0, A*B*C).Select(i => cube[i/(B*C), i%(B*C)/C, i%C] = 1).Count();

这里的Count()只需要让序列被枚举,它的结果被忽略。 这是为了实现 MATLAB 的 one() 函数。 要实现 zeros(),可以使用以下方法:

Array.Clear(cube, 0, A*B*C);

【讨论】:

    【解决方案2】:
    public void SetAllValues(int[,,] data, int value) {
        for (int x = 0; x < data.GetLength(0); x++) {
            for (int y = 0; y < data.GetLength(1); y++) {
                for (int z = 0; z < data.GetLength(2); z++) {
                    data[x, y, z] = value;
                }
            }
        }
    }
    

    【讨论】:

    • 他们特别说“在 C# 中没有循环我怎么能做到这一点?”
    • 听起来循环对我来说是正确的方法。这就像有人问我如何在不使用变量的情况下做到这一点。与其试图弄清楚这一点,我更愿意说服他们应该使用变量。
    • 那么我认为您应该指定为什么在答案中推荐循环,而不是仅仅将代码扔在那里。
    【解决方案3】:

    您可以像这样使用Enumerable.Repeat

    int[] zeros1Dim = Enumerable.Repeat(0, 100).ToArray();
    int[][] zeros2Dim = Enumerable.Repeat(zeros1Dim, 100).ToArray();
    int[][][] array = Enumerable.Repeat(zeros2Dim, 100).ToArray();
    

    【讨论】:

    • 可能不是最有效的,因为数组会比需要的大。 Enumerable.Repeat.ToArray 不知道序列有多大,因此需要将数组的大小连续加倍。这是与此问题相关的问题:stackoverflow.com/questions/10519275/…
    【解决方案4】:

    我不知道在 C# 中初始化数组的惯用、非循环方式,根据此处的答案,其他人似乎也不知道。不安全的内存复制可能需要更少的代码行,尽管它确实混淆了您正在做的事情的含义:它强调机制(将此值移动到此内存地址)而不是语义(使整个多维数组等于相同的值。)

    鉴于此,我的建议是咬紧牙关,使用循环,然后将其抽象为一个命名愉快的方法

    public static T[,,] FillArrayWithValue<T>(this T[,,] array, T value)
    {
        // Loops go here
    }
    

    将其粘贴在某个静态实用程序类中,然后您的调用站点变为:

    int[,,] myArray = new int[4, 5, 6];
    myArray.FillArrayWithValue(value);
    

    【讨论】:

      【解决方案5】:

      在 C 编程领域,通常使用诸如 memcpyCopyMemory 之类的 OS 函数来有效地将内存从一个地方移动到另一个地方。这比使用循环要好得多,因为操作系统将使用专用硬件 (DMA) 来执行操作。

      我不确定这对您的简单案例是否有用,但如果您愿意,可以从某些 DLL 导入这些函数。

      [DllImport("kernel32.dll")]
      static extern void CopyMemory(IntPtr destination, IntPtr source, uint length);
      
      static void Main(string[] args)
      {
          byte[] a = new byte[10]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
          byte[] b = new byte[10];
      
          IntPtr pa = Marshal.UnsafeAddrOfPinnedArrayElement(a, 0);
          IntPtr pb = Marshal.UnsafeAddrOfPinnedArrayElement(b, 0);
      
          CopyMemory(pb, pa, 10);
      }
      

      CopyMemory 函数仅适用于连续的内存块,因此请注意如何调用它。

      重要编辑:

      如果您查看下面的注释,您会注意到上面显示的这段代码远非“可用”,因为它可能会在您不知道的情况下破坏您的应用程序内存。更好的方法是使用Buffer.BlockCopy method,正如codesparkle 所建议的那样,它可能在内部做同样的事情,但以安全的方式:

      byte[] a = new byte[10]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
      byte[] b = new byte[10];
      Buffer.BlockCopy(a, 0, b, 0, 10);
      

      【讨论】:

      • 但这是不安全的,需要一个额外的数组。只有在性能非常重要时才使用 unsafe,然后使用 FillMemory()。
      • 我回答的主要目的是表明可以在不使用传统循环的情况下将内存从一个数组复制到另一个数组。有趣的是,我能够运行上面发布的代码,而无需在我的应用程序中标记 allow unsafe code。我猜本机 win32 函数被假定为 内存泄漏安全
      • 我不是 100% 确定:这是否真的将 b 复制到 a 或者您是否必须 UnMarshal 才能获得结果?
      • 如果您在 CopyMemory 调用之后放置断点,您可以看到值已成功复制。但是再想一想,我相信这只是因为数组对象值存储在一个连续的内存块中,该内存块恰好从 UnsafeAddrOfPinnedArrayElement 返回的地址开始
      • 连续的没问题,但我很惊讶它在没有实际固定数组的情况下工作,真正可怕的部分是你可以调用CopyMemory(pb, pa, 20); 并破坏堆......
      猜你喜欢
      • 2013-01-25
      • 2020-04-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多