如何使用单个值填充/实例化C#数组?

pat*_*jbs 188 c# arrays default-value

我知道C#中实例化的值类型数组会自动填充类型默认值(例如,对于bool为false,对于int为0等).

有没有办法使用不是默认值的种子值自动填充数组?之后是创建还是内置方法(比如Java的Arrays.fill())?假设我想要一个默认为true的布尔数组,而不是false.是否有内置的方法来执行此操作,或者您只需要使用for循环遍历数组?

 // Example pseudo-code:
 bool[] abValues = new[1000000];
 Array.Populate(abValues, true);

 // Currently how I'm handling this:
 bool[] abValues = new[1000000];
 for (int i = 0; i < 1000000; i++)
 {
     abValues[i] = true;
 }
Run Code Online (Sandbox Code Playgroud)

必须遍历数组并将每个值"重置"为true似乎是无效的.有没有办法解决?也许通过翻转所有价值观?

在输出这个问题并思考之后,我猜测默认值只是C#如何在幕后处理这些对象的内存分配的结果,所以我想这可能不可能做到这一点.但我仍然想知道!

Ron*_*ony 182

Enumerable.Repeat(true, 1000000).ToArray();
Run Code Online (Sandbox Code Playgroud)

  • 虽然这有效,但它并不是一个很好的解决方案,因为它非常慢; 它实际上比使用for循环迭代慢大约4倍. (65认同)
  • 要查看一些真正的基准测试,请查看[C#Initialize Array](http://www.dotnetperls.com/initialize-array). (6认同)
  • `Enumerable.ToArray`不知道可枚举序列的大小,因此必须猜测数组大小.这意味着每次超过"ToArray"的缓冲区时,您将获得数组分配,并在最后为修剪添加一个分配.可枚举对象也涉及到开销. (4认同)
  • 只是注意,使用引用类型,这将填充整个数组,包含对同一单个对象的所有引用.如果这不是您想要的,并且您实际上想要为每个数组项生成不同的对象,请参阅/sf/answers/3145593741/. (4认同)
  • 是的,这是真的,当我们考虑性能时,for循环更快 (3认同)
  • 当我感到懒惰并且不关心小的性能影响时,用于填充小数组(&lt; 10 个元素)的好、简单的解决方案。 (2认同)
  • @EdwardBrey 这在 .NET Core 中不再适用。对 Linq to Objects 进行了许多优化。`Repeat` 现在传播元素的实际数量,而 `ToArray` 使用它直接分配正确大小的数组。 (2认同)

Jar*_*Par 136

不知道框架方法,但您可以编写一个快速帮助程序来为您完成.

public static void Populate<T>(this T[] arr, T value ) {
  for ( int i = 0; i < arr.Length;i++ ) {
    arr[i] = value;
  }
}
Run Code Online (Sandbox Code Playgroud)

  • @RobertDailey:这是一个编译器优化,不再是真的.我刚测试验证我的信念:如果i ++的返回值没有用于任何东西,那么编译器会自动将它编译为++ i.此外,即使我使用返回值,性能差异也很小,我需要做一个极端情况才能测量它.即便如此,它只导致了几个百分点的不同运行时间. (50认同)
  • i ++复制i,递增i,并返回原始值.++我只返回递增的值.因此++ i更快,这在我们在这里讨论的大循环中可能很重要. (22认同)
  • 我写了一个像这样的扩展方法,但我让它返回原始数组以允许方法链接,例如:`int [] arr = new int [16] .Populate(-1);` (6认同)
  • 将 `void` 更改为 `T[]` 然后你可以执行 `var a = new int[100].Polupate(1)` (5认同)
  • 好戏.在我看来,这应该是.Net的一部分. (3认同)
  • 如果你不需要副本,首选++ i而不是i ++. (3认同)

byt*_*der 68

创建一个包含一千个true值的新数组:

var items = Enumerable.Repeat<bool>(true, 1000).ToArray();  // Or ToList(), etc.
Run Code Online (Sandbox Code Playgroud)

同样,您可以生成整数序列:

var items = Enumerable.Range(0, 1000).ToArray();  // 0..999
Run Code Online (Sandbox Code Playgroud)

  • 不错,但它比for循环慢了大约4倍 (6认同)
  • @PetarPetrov 由于缓存抖动,这永远不会发生。我相当确定,由于 CPU 缓存的性质,在单个阵列上并行执行工作无论如何都会变慢,因为计算机期望同步工作并适当加载数据。 (2认同)

LBu*_*kin 23

对于大小可变的大型数组或数组,您应该使用:

Enumerable.Repeat(true, 1000000).ToArray();
Run Code Online (Sandbox Code Playgroud)

对于小数组,您可以使用C#3中的集合初始化语法:

bool[] vals = new bool[]{ false, false, false, false, false, false, false };
Run Code Online (Sandbox Code Playgroud)

集合初始化语法的好处是,您不必在每个插槽中使用相同的值,并且可以使用表达式或函数来初始化插槽.另外,我认为您可以避免将阵列插槽初始化为默认值的成本.所以,例如:

bool[] vals = new bool[]{ false, true, false, !(a ||b) && c, SomeBoolMethod() };
Run Code Online (Sandbox Code Playgroud)


MrF*_*Fox 23

如果你的数组太大,你应该使用BitArray.它对每个bool使用1位而不是一个字节(就像在bool数组中一样)也可以使用位运算符将所有位设置为true.或者只是初始化为true.如果你只需要做一次,它只会花费更多.

System.Collections.BitArray falses = new System.Collections.BitArray(100000, false);
System.Collections.BitArray trues = new System.Collections.BitArray(100000, true);

// Now both contain only true values.
falses.And(trues);
Run Code Online (Sandbox Code Playgroud)


Shu*_*rma 14

.NET Core 2.0及更高版本支持Array.Fill()方法。

这是示例代码。

var arr = new int[10];
int defaultValue = 2;
Array.Fill(arr,defaultValue);
Run Code Online (Sandbox Code Playgroud)

它还具有用于填充索引范围的重载方法。更多详情可在这找到。

  • 注意:相同的对象实例将用于所有值,这意味着如果它具有您想要稍后更新的字段(例如,“arr[0].Value = 2”),那么数组的所有值也将看到更新(例如,“arr[1].Value == 2”)。 (2认同)

bas*_*des 9

不幸的是我不认为有直接的方法,但我认为你可以为数组类编写一个扩展方法来做到这一点

class Program
{
    static void Main(string[] args)
    {
        int[] arr = new int[1000];
        arr.Init(10);
        Array.ForEach(arr, Console.WriteLine);
    }
}

public static class ArrayExtensions
{
    public static void Init<T>(this T[] array, T defaultVaue)
    {
        if (array == null)
            return;
        for (int i = 0; i < array.Length; i++)
        {
            array[i] = defaultVaue;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


pat*_*jbs 8

经过一番谷歌搜索和阅读后我发现了这个:

bool[] bPrimes = new bool[1000000];
bPrimes = Array.ConvertAll<bool, bool>(bPrimes, b=> b=true);
Run Code Online (Sandbox Code Playgroud)

这肯定更接近我正在寻找的东西.但是我不确定这是否比在for循环中迭代原始数组并且仅更改值更好.事实上经过快速测试后,它看起来慢了大约5倍.那么这不是一个好的解决方案!

  • 这类似于你想要做的,除了它为你的数组中的每个元素进行函数调用.它在语法上可能看起来更好,但它做了很多工作...... (4认同)

l33*_*33t 7

或者......你可以简单地使用倒置逻辑.让我们false意思true,反之亦然.

代码示例

// bool[] isVisible = Enumerable.Repeat(true, 1000000).ToArray();
bool[] isHidden = new bool[1000000]; // Crazy-fast initialization!

// if (isVisible.All(v => v))
if (isHidden.All(v => !v))
{
    // Do stuff!
}
Run Code Online (Sandbox Code Playgroud)

  • 人们的反应似乎是这是某种有趣的黑客行为。这是一种常见的优化技术。如果你幸运的话,编译器会为你做这件事。 (2认同)

juF*_*uFo 7

您可以Array.Fill在.NET Core 2.0+和.NET Standard 2.1+中使用。

  • 出色的!但请注意,这是一种相对较新的方法。它在 .NET Core 2.0+ 和 .NET Standard 2.1 中可用,但特别是在任何 .NET Framework 版本中“不可用”。(它将出现在 .NET 5.0 中,它将 .NET Framework 和 .NET Core 混合在一起)。 (5认同)
  • 例如 `Array.Fill(myArray, myDefaultValue);` (4认同)

Apo*_*ehn 7

如果您使用的是 .NET Core、.NET Standard >= 2.1,或者依赖 System.Memory 包,您也可以使用该Span<T>.Fill()方法:

var valueToFill = 165;
var data = new int[100];

data.AsSpan().Fill(valueToFill);

// print array content
for (int i = 0; i < data.Length; i++)
{
    Console.WriteLine(data[i]);
}
Run Code Online (Sandbox Code Playgroud)

https://dotnetfiddle.net/UsJ9bu


Jok*_*ang 7

只是一个基准:

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.997 (1909/November2018Update/19H2)
Intel Core i7-6700HQ CPU 2.60GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.302
  [Host]        : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT
  .NET Core 3.1 : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT

Job=.NET Core 3.1  Runtime=.NET Core 3.1

|           Method |     Mean |     Error |    StdDev |
|----------------- |---------:|----------:|----------:|
| EnumerableRepeat | 2.311 us | 0.0228 us | 0.0213 us |
|  NewArrayForEach | 2.007 us | 0.0392 us | 0.0348 us |
|        ArrayFill | 2.426 us | 0.0103 us | 0.0092 us |
Run Code Online (Sandbox Code Playgroud)
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.997 (1909/November2018Update/19H2)
Intel Core i7-6700HQ CPU 2.60GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.302
  [Host]        : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT
  .NET Core 3.1 : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT

Job=.NET Core 3.1  Runtime=.NET Core 3.1

|           Method |     Mean |     Error |    StdDev |
|----------------- |---------:|----------:|----------:|
| EnumerableRepeat | 2.311 us | 0.0228 us | 0.0213 us |
|  NewArrayForEach | 2.007 us | 0.0392 us | 0.0348 us |
|        ArrayFill | 2.426 us | 0.0103 us | 0.0092 us |
Run Code Online (Sandbox Code Playgroud)


Pet*_*rov 6

并行实现怎么样?

public static void InitializeArray<T>(T[] array, T value)
{
    var cores = Environment.ProcessorCount;

    ArraySegment<T>[] segments = new ArraySegment<T>[cores];

    var step = array.Length / cores;
    for (int i = 0; i < cores; i++)
    {
        segments[i] = new ArraySegment<T>(array, i * step, step);
    }
    var remaining = array.Length % cores;
    if (remaining != 0)
    {
        var lastIndex = segments.Length - 1;
        segments[lastIndex] = new ArraySegment<T>(array, lastIndex * step, array.Length - (lastIndex * step));
    }

    var initializers = new Task[cores];
    for (int i = 0; i < cores; i++)
    {
        var index = i;
        var t = new Task(() =>
        {
            var s = segments[index];
            for (int j = 0; j < s.Count; j++)
            {
                array[j + s.Offset] = value;
            }
        });
        initializers[i] = t;
        t.Start();
    }

    Task.WaitAll(initializers);
}
Run Code Online (Sandbox Code Playgroud)

当只初始化一个数组时,无法看到这段代码的力量,但我认为你绝对应该忘记"纯粹".


Pan*_*eof 6

下面的代码结合了小型副本的简单迭代和大型副本的Array.Copy

    public static void Populate<T>( T[] array, int startIndex, int count, T value ) {
        if ( array == null ) {
            throw new ArgumentNullException( "array" );
        }
        if ( (uint)startIndex >= array.Length ) {
            throw new ArgumentOutOfRangeException( "startIndex", "" );
        }
        if ( count < 0 || ( (uint)( startIndex + count ) > array.Length ) ) {
            throw new ArgumentOutOfRangeException( "count", "" );
        }
        const int Gap = 16;
        int i = startIndex;

        if ( count <= Gap * 2 ) {
            while ( count > 0 ) {
                array[ i ] = value;
                count--;
                i++;
            }
            return;
        }
        int aval = Gap;
        count -= Gap;

        do {
            array[ i ] = value;
            i++;
            --aval;
        } while ( aval > 0 );

        aval = Gap;
        while ( true ) {
            Array.Copy( array, startIndex, array, i, aval );
            i += aval;
            count -= aval;
            aval *= 2;
            if ( count <= aval ) {
                Array.Copy( array, startIndex, array, i, count );
                break;
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

使用int []数组的不同数组长度的基准是:

         2 Iterate:     1981 Populate:     2845
         4 Iterate:     2678 Populate:     3915
         8 Iterate:     4026 Populate:     6592
        16 Iterate:     6825 Populate:    10269
        32 Iterate:    16766 Populate:    18786
        64 Iterate:    27120 Populate:    35187
       128 Iterate:    49769 Populate:    53133
       256 Iterate:   100099 Populate:    71709
       512 Iterate:   184722 Populate:   107933
      1024 Iterate:   363727 Populate:   126389
      2048 Iterate:   710963 Populate:   220152
      4096 Iterate:  1419732 Populate:   291860
      8192 Iterate:  2854372 Populate:   685834
     16384 Iterate:  5703108 Populate:  1444185
     32768 Iterate: 11396999 Populate:  3210109
Run Code Online (Sandbox Code Playgroud)

第一列是数组大小,后面是使用简单迭代进行复制的时间(@JaredPared实现).此方法的时间就在此之后.这些是使用四个整数结构数组的基准

         2 Iterate:     2473 Populate:     4589
         4 Iterate:     3966 Populate:     6081
         8 Iterate:     7326 Populate:     9050
        16 Iterate:    14606 Populate:    16114
        32 Iterate:    29170 Populate:    31473
        64 Iterate:    57117 Populate:    52079
       128 Iterate:   112927 Populate:    75503
       256 Iterate:   226767 Populate:   133276
       512 Iterate:   447424 Populate:   165912
      1024 Iterate:   890158 Populate:   367087
      2048 Iterate:  1786918 Populate:   492909
      4096 Iterate:  3570919 Populate:  1623861
      8192 Iterate:  7136554 Populate:  2857678
     16384 Iterate: 14258354 Populate:  6437759
     32768 Iterate: 28351852 Populate: 12843259
Run Code Online (Sandbox Code Playgroud)


Eri*_* J. 6

此处提供的许多答案都归结为一次将数组初始化一个元素的循环,它没有利用旨在一次对内存块进行操作的 CPU 指令。

.Net Standard 2.1(在撰写本文时为预览版)提供Array.Fill(),这有助于在运行时库中实现高性能(尽管截至目前,.NET Core似乎没有利用这种可能性) .

对于早期平台上的那些,当数组大小很重要时,以下扩展方法的性能明显优于普通循环。当我的在线代码挑战解决方案超出分配的时间预算约 20% 时,我创建了它。它减少了大约 70% 的运行时间。在这种情况下,数组填充是在另一个循环内执行的。BLOCK_SIZE 是由直觉而不是实验设置的。一些优化是可能的(例如复制所有已设置为所需值的字节而不是固定大小的块)。

internal const int BLOCK_SIZE = 256;
public static void Fill<T>(this T[] array, T value)
{
    if (array.Length < 2 * BLOCK_SIZE)
    {
        for (int i = 0; i < array.Length; i++) array[i] = value;
    }
    else
    {
        int fullBlocks = array.Length / BLOCK_SIZE;
        // Initialize first block
        for (int j = 0; j < BLOCK_SIZE; j++) array[j] = value;
        // Copy successive full blocks
        for (int blk = 1; blk < fullBlocks; blk++)
        {
            Array.Copy(array, 0, array, blk * BLOCK_SIZE, BLOCK_SIZE);
        }

        for (int rem = fullBlocks * BLOCK_SIZE; rem < array.Length; rem++)
        {
            array[rem] = value;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)