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


205

我知道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;
 }

必须遍历数组并将每个值“重置”为true似乎没有效率。有没有办法解决?也许通过翻转所有值?

在输入完该问题并进行思考之后,我猜测默认值只是C#如何处理这些对象在后台的内存分配的结果,因此我想可能无法做到这一点。但我仍然想确定!


我通常将名称从is_found更改为is_still_hiding。尽管喜欢答案,但我需要在测试用例中对int数组进行类似的处理。(好问题)
ctrl-alt-delor

Answers:


146

不知道框架方法,但是您可以编写一个快速帮助程序来帮助您。

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

3
如果不需要副本,请首选++ i而不是i ++。
void.pointer 2011年

24
i ++复制i,递增i,然后返回原始值。++ i只返回增加的值。因此,++ i速度更快,这在像我们在这里讨论的大型循环中可能非常重要。
tenpn 2012年

57
@RobertDailey:这是编译器优化,不再适用。我刚刚进行测试以验证我的信念:如果i ++的返回值不用于任何东西,那么编译器将为您自动将其编译为++ i。同样,即使我确实使用了返回值,性能差异也是如此之小,我需要对这种情况进行极端的测量。即使那样,它也只导致百分之几的不同运行时。
爱德华·内德·哈维

8
我写了这样的扩展方法,但我让它返回了原始数组以允许方法链接,例如:int[] arr = new int[16].Populate(-1);
Gutblender 2014年

2
更改voidT[]然后您可以做var a = new int[100].Polupate(1)
orad

198
Enumerable.Repeat(true, 1000000).ToArray();

70
尽管此方法有效,但这并不是一个很好的解决方案,因为它非常慢。实际上,它比使用for循环慢大约4倍。
patjbs

4
是的,的确如此,当我们考虑到性能时,for循环更快
Rony

6
要查看一些实际的基准,请查看C#Initialize Array
theknut 2012年

4
Enumerable.ToArray不知道可枚举序列的大小,因此必须猜测数组的大小。这意味着您将在每次ToArray超过缓冲区时获得数组分配,并在末尾再进行一次分配。可枚举对象还涉及开销。
爱德华·布雷

5
请注意,对于引用类型,这将使​​用对同一单个对象的所有引用填充整个数组。如果这不是您想要的,并且您实际上想为每个数组项生成不同的对象,请参见stackoverflow.com/a/44937053/23715
Alex Che

74

创建一个具有一千个true值的新数组:

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

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

var items = Enumerable.Range(0, 1000).ToArray();  // 0..999

8
不错,但是它仍然比for循环慢大约4倍
patjbs

1
理论上,未来的patjbs Enumerable.Repeat将执行更快,因为它将使用并行实现。
Petar Petrov 2010年

1
@PetarPetrov这将永远不会由于缓存颠簸而发生。我可以肯定地说,由于CPU缓存的性质,无论如何在单个阵列上并行执行工作始终会变慢,因为计算机希望同步工作并适当地加载数据。
TernaryTopiary

预期的悲观化!=缺乏过早的优化。
Denis Gladkiy

24

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

Enumerable.Repeat(true, 1000000).ToArray();

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

bool[] vals = new bool[]{ false, false, false, false, false, false, false };

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

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

并初始化一个float []数组:float[] AlzCalDefault = new float[] {(float) 0.5, 18, 500, 1, 0};
Jim Lahman 2012年

FWIW数组的初始化可以在任何C#版本中完成,例如:bool[] vals = { false, true, false, !(a || b) && c, SomeBoolMethod() };
Peter van der Heijden,2015年

24

如果数组太大,则应使用BitArray。它为每个布尔使用1位而不是字节(就像在布尔数组中一样),您也可以使用位运算符将所有位设置为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);

17

您可以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融合在一起)。
亚伯

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;
        }
    }
}

我更喜欢这个扩展想法。有时,前期简单的解决方案确实是最好的!
patjbs

8

经过更多的谷歌搜索和阅读后,我发现了这一点:

bool[] bPrimes = new bool[1000000];
bPrimes = Array.ConvertAll<bool, bool>(bPrimes, b=> b=true);

当然,这更接近我要寻找的东西。但是我不确定这是否比在for循环中遍历原始数组并更改值更好。实际上,经过快速测试后,它的速度似乎降低了约5倍。因此,这并不是一个很好的解决方案!


4
除了要对数组中的每个元素进行函数调用外,这与您尝试执行的操作相似。从语法上来说,它可能看起来更好,但是它需要做更多的工作……
Nader Shirazie 09年

是的,看起来就像一个简单的for循环几乎可以完成所有其他工作
patjbs

它创建一个新数组(不更改原始实例)。
Jeppe Stig Nielsen

7

并行实现呢

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);
}

仅初始化数组时,看不到此代码的功能,但我认为您绝对应该忘记“纯”的含义。


这冒着错误共享问题的风险,在错误共享中,不同的线程竞争CPU缓存行,因此与单线程实现相比会降低性能。是否发生这种情况取决于每个线程的内存块的大小和CPU架构。
Eric J.19年

7

下面的代码结合了用于小副本的简单迭代和用于大副本的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;
            }
        }
    }

使用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

第一列是数组大小,其次是使用简单迭代(@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

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!
}

有趣的解决方案,所有虽然这将是一个有很多比如整数困难,因为你失去的0
MrFox

1
如果您在变量名称上“反转逻辑”,那么这实际上是一个可行的选择:而不是bool[] isVisible让它成为bool[] isHidden
现实

1
人们似乎做出了这样的反应,这是一种有趣的hack。这是一种常见的优化技术。如果幸运的话,编译器将为您完成此操作。
l33t

4

这也可以...但是可能不必要

 bool[] abValues = new bool[1000];
 abValues = abValues.Select( n => n = true ).ToArray<bool>();

4

此处给出的许多答案都归结为一个循环,该循环一次将一个元素初始化数组,而这并没有利用旨在立即对一个内存块进行操作的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;
        }
    }
}

3

如果您打算仅在数组中设置一些值,但想在大多数时间获得(自定义)默认值,则可以尝试执行以下操作:

public class SparseArray<T>
{
    private Dictionary<int, T> values = new Dictionary<int, T>();

    private T defaultValue;

    public SparseArray(T defaultValue)
    {
        this.defaultValue = defaultValue;
    }

    public T this [int index]
    {
      set { values[index] = value; }
      get { return values.ContainsKey(index) ? values[index] ? defaultValue; }
    }
}

您可能需要实现其他接口以使其有用,例如数组本身上的接口。


3

不能将数组中的所有元素设置为单个操作,除非该值是元素类型的默认值。

例如,如果它是整数数组,则可以通过一次操作将它们全部设置为零,如下所示: Array.Clear(...)


2

我知道我参加晚会很晚,但这是个主意。编写一个包装器,该包装器具有与包装值之间的转换运算符,以便可以将其用作包装类型的替代。这实际上是受到@ l33t愚蠢的回答的启发。

首先(来自C ++),我意识到在C#中,构造数组的元素时不会调用默认的ctor。相反-即使存在用户定义的默认构造函数!-所有数组元素均被零初始化。这确实让我感到惊讶。

因此,只为默认ctor提供所需值的包装器类适用于C ++中的数组,但不适用于C#中的数组。一种解决方法是在转换时让包装器类型将0映射到所需的种子值。这样,出于所有实际目的,零初始化值似乎都使用种子进行了初始化:

public struct MyBool
{
    private bool _invertedValue;

    public MyBool(bool b) 
    {   
        _invertedValue = !b;
    }

    public static implicit operator MyBool(bool b)
    {
        return new MyBool(b);
    }

    public static implicit operator bool(MyBool mb)
    {
        return !mb._invertedValue;
    }

}

static void Main(string[] args)
{
        MyBool mb = false; // should expose false.
        Console.Out.WriteLine("false init gives false: " 
                              + !mb);

        MyBool[] fakeBoolArray = new MyBool[100];

        Console.Out.WriteLine("Default array elems are true: " 
                              + fakeBoolArray.All(b => b) );

        fakeBoolArray[21] = false;
        Console.Out.WriteLine("Assigning false worked: " 
                              + !fakeBoolArray[21]);

        fakeBoolArray[21] = true;
        // Should define ToString() on a MyBool,
        // hence the !! to force bool
        Console.Out.WriteLine("Assigning true again worked: " 
                              + !!fakeBoolArray[21]);
}

此模式适用于所有值类型。如果需要用4初始化,则可以例如将0映射到4的整数等。

我很乐意像在C ++中那样做一个模板,提供种子值作为模板参数,但是我知道这在C#中是不可能的。还是我错过了什么?(当然,在C ++中根本不需要映射,因为可以提供一个默认的ctor,它将被数组元素调用。)

FWIW,这是C ++等效项:https : //ideone.com/wG8yEh


2

如果可以反转逻辑,则可以使用Array.Clear()方法将布尔数组设置为false。

        int upperLimit = 21;
        double optimizeMe = Math.Sqrt(upperLimit);

        bool[] seiveContainer = new bool[upperLimit];
        Array.Clear(seiveContainer, 0, upperLimit);

2

如果您使用的是.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]);
}

https://dotnetfiddle.net/UsJ9bu


2

这是Microsoft遗弃给我们的Framework用户的另一个版本。它是Panos Theof的解决方案以及Eric JPetar Petrov的并行解决方案的四倍Array.Clear和更快的速度。 -对于大型阵列,速度是其两倍。

首先,我想向您介绍函数的祖先,因为这样可以更轻松地理解代码。在性能方面,这几乎与Panos Theof的代码相当,并且对于某些事情可能已经足够了:

public static void Fill<T> (T[] array, int count, T value, int threshold = 32)
{
    if (threshold <= 0)
        throw new ArgumentException("threshold");

    int current_size = 0, keep_looping_up_to = Math.Min(count, threshold);

    while (current_size < keep_looping_up_to)
        array[current_size++] = value;

    for (int at_least_half = (count + 1) >> 1; current_size < at_least_half; current_size <<= 1)
        Array.Copy(array, 0, array, current_size, current_size);

    Array.Copy(array, 0, array, current_size, count - current_size);
}

如您所见,这是基于已初始化部分的重复加倍。这是简单而有效的,但与现代内存体系结构不符。因此诞生了一个版本,该版本仅使用加倍来创建对缓存友好的种子块,然后将该块迭代遍历目标区域:

const int ARRAY_COPY_THRESHOLD = 32;  // 16 ... 64 work equally well for all tested constellations
const int L1_CACHE_SIZE = 1 << 15;

public static void Fill<T> (T[] array, int count, T value, int element_size)
{
    int current_size = 0, keep_looping_up_to = Math.Min(count, ARRAY_COPY_THRESHOLD);

    while (current_size < keep_looping_up_to)
        array[current_size++] = value;

    int block_size = L1_CACHE_SIZE / element_size / 2;
    int keep_doubling_up_to = Math.Min(block_size, count >> 1);

    for ( ; current_size < keep_doubling_up_to; current_size <<= 1)
        Array.Copy(array, 0, array, current_size, current_size);

    for (int enough = count - block_size; current_size < enough; current_size += block_size)
        Array.Copy(array, 0, array, current_size, block_size);

    Array.Copy(array, 0, array, current_size, count - current_size);
}

注意:需要使用较早的代码(count + 1) >> 1作为加倍循环的限制,以确保最终的复制操作具有足够的草料以覆盖所有剩余的内容。如果count >> 1要使用奇数计数,则不是这种情况。对于当前版本,这无关紧要,因为线性复制循环会吸收任何松弛。

数组单元的大小必须作为参数传递,因为-令人费解的是- sizeof除非泛型使用unmanaged了将来可能会或可能不再可用的约束(),否则不允许使用。错误的估计并不重要,但如果值准确,则性能最好,原因如下:

  • 低估元素大小可能导致块大小大于L1高速缓存的一半,因此增加了从L1逐出副本源数据并不得不从较慢的高速缓存级别重新获取副本源数据的可能性。

  • 高估元素大小会导致CPU的L1缓存利用率不足,这意味着与最佳利用率相比,线性块复制循环执行的频率更高。因此,比固定需要更多的固定循环/呼叫开销。

这是我的代码Array.Clear和前面提到的其他三个解决方案的基准。时序用于填充Int32[]给定大小的整数数组()。为了减少由高速缓存可变等导致的变化,每个测试被连续执行了两次,并为第二次执行计时。

array size   Array.Clear      Eric J.   Panos Theof  Petar Petrov   Darth Gizka
-------------------------------------------------------------------------------
     1000:       0,7 µs        0,2 µs        0,2 µs        6,8 µs       0,2 µs 
    10000:       8,0 µs        1,4 µs        1,2 µs        7,8 µs       0,9 µs 
   100000:      72,4 µs       12,4 µs        8,2 µs       33,6 µs       7,5 µs 
  1000000:     652,9 µs      135,8 µs      101,6 µs      197,7 µs      71,6 µs 
 10000000:    7182,6 µs     4174,9 µs     5193,3 µs     3691,5 µs    1658,1 µs 
100000000:   67142,3 µs    44853,3 µs    51372,5 µs    35195,5 µs   16585,1 µs 

如果此代码的性能不足,那么将有可能使线性复制循环(所有线程使用相同的源块)或我们的好朋友P / Invoke并行化。

注意:清除和填充块通常是通过运行时例程完成的,这些例程使用MMX / SSE指令分支为高度专业化的代码,而在任何体面的环境中,它们都只是简单地称呼各自的道德等值std::memset并确保达到专业的性能水平。IOW,按权利,库函数Array.Clear应该将我们所有的手动版本都抛在脑后。相反,它的事实说明了事情的真实程度。Fill<>首先,必须自己动手,因为它仍然仅存在于Core和Standard中,而不存在于Framework中。.NET已经存在了将近20年了,我们仍然必须左右P / Invoke以获得最基本的东西,或者自己开发。



0

这是System.Collections.BitArray具有这种构造函数的另一个方法。

bool[] result = new BitArray(1000000, true).Cast<bool>().ToArray();

要么

bool[] result = new bool[1000000];
new BitArray(1000000, true).CopyTo(result, 0);

0

在创建数组的内部创建一个私有类,并为其提供一个getter和setter方法。除非您需要数组中的每个位置都具有唯一性(例如随机性),否则请使用int吗?作为数组,如果位置等于null,则获取该位置,然后填充该位置并返回新的随机值。

IsVisibleHandler
{

  private bool[] b = new bool[10000];

  public bool GetIsVisible(int x)
  {
  return !b[x]
  }

  public void SetIsVisibleTrueAt(int x)
  {
  b[x] = false //!true
  }
}

或使用

public void SetIsVisibleAt(int x, bool isTrue)
{
b[x] = !isTrue;
}

作为二传手。


-2
Boolean[] data = new Boolean[25];

new Action<Boolean[]>((p) => { BitArray seed = new BitArray(p.Length, true); seed.CopyTo(p, 0); }).Invoke(data);

请使用更好的格式,并使用一些解释性的词,以便其他人可以更好地理解您的解决方案。
Gorgsenegger 2014年

1
您可以通过对目标数组进行分区并将种子复制到各个分区中来提高初始化性能。这仅是为了提供一个想法-这是我的第一个也是我有史以来的最后一个帖子。
ldsmithperrin 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.