252

C# の値型のインスタンス化された配列には、型の既定値が自動的に入力されることを私は知っています(たとえば、bool の場合は false、int の場合は 0 など)。

デフォルトではないシード値を配列に自動入力する方法はありますか? 作成時またはその後の組み込みメソッド (Java のArrays.fill() など)? false ではなく、デフォルトで true のブール配列が必要だとします。これを行う組み込みの方法はありますか、それとも 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# がこれらのオブジェクトのメモリ割り当てを舞台裏で処理する方法の結果であると推測しているため、おそらくこれを行うことは不可能だと思います。しかし、私はまだ確実に知りたいです!

4

26 に答える 26

244
Enumerable.Repeat(true, 1000000).ToArray();
于 2009-06-18T17:26:07.763 に答える
165

フレームワーク メソッドについてはわかりませんが、それを行うためのクイック ヘルパーを作成できます。

public static void Populate<T>(this T[] arr, T value ) {
  for ( int i = 0; i < arr.Length;i++ ) {
    arr[i] = value;
  }
}
于 2009-06-18T17:23:43.693 に答える
87

true千の値を持つ新しい配列を作成します。

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

同様に、整数シーケンスを生成できます。

var items = Enumerable.Range(0, 1000).ToArray();  // 0..999
于 2009-06-18T17:23:51.697 に答える
26

大きな配列または可変サイズの配列の場合は、おそらく次を使用する必要があります。

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() };
于 2009-06-18T17:39:23.593 に答える
25

配列が非常に大きい場合は、BitArray を使用する必要があります。バイトの代わりにすべてのブールに 1 ビットを使用します (ブールの配列のように)。ビット演算子を使用してすべてのビットを true に設定することもできます。または、true で初期化します。1回だけの場合は、費用が高くなります。

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);
于 2010-06-24T07:58:15.467 に答える
10

残念ながら、直接的な方法はないと思いますが、配列クラスの拡張メソッドを記述してこれを行うことができると思います

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;
        }
    }
}
于 2009-06-18T17:29:16.370 に答える
9

以下のコードは、小さなコピー用の単純な繰り返しと、大きなコピー用の 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 実装 ) を使用したコピーの時間が続きます。この方法の時間はその後です。これらは、4 つの整数の構造体の配列を使用したベンチマークです。

         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
于 2013-11-01T12:49:48.563 に答える
9

もう少しグーグルして読んだ後、私はこれを見つけました:

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

これは確かに私が探しているものに近いです。しかし、それが元の配列を for ループで反復処理して値を変更するよりも優れているかどうかはわかりません。実際に簡単なテストを行ったところ、約 5 倍遅いように見えました。

于 2009-06-18T17:25:40.947 に答える
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!
}
于 2012-02-06T14:43:47.507 に答える
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);
}

配列を初期化するだけでは、このコードの威力はわかりませんが、「純粋な」for については絶対に忘れるべきだと思います。

于 2010-06-24T08:44:50.700 に答える
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]);
}

https://dotnetfiddle.net/UsJ9bu

于 2019-12-16T08:48:32.123 に答える
6

ただのベンチマーク:

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 |
    [SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.NetCoreApp31)]
    public class InitializeArrayBenchmark {
        const int ArrayLength = 1600;

        [Benchmark]
        public double[] EnumerableRepeat() {
            return Enumerable.Repeat(double.PositiveInfinity, ArrayLength).ToArray();
        }

        [Benchmark]
        public double[] NewArrayForEach() {
            var array = new double[ArrayLength];

            for (int i = 0; i < array.Length; i++) {
                array[i] = double.PositiveInfinity;
            }

            return array;
        }

        [Benchmark]
        public double[] ArrayFill() {
            var array = new double[ArrayLength];
            Array.Fill(array, double.PositiveInfinity);

            return array;
        }
    }
于 2020-07-22T06:30:15.983 に答える
4

これも機能します...しかし不要かもしれません

 bool[] abValues = new bool[1000];
 abValues = abValues.Select( n => n = true ).ToArray<bool>();
于 2009-06-18T17:34:01.387 に答える
4

配列内のすべての要素を単一の操作として設定する方法はありません。例外として、その値は要素タイプのデフォルト値です。

たとえば、整数の配列の場合、次のように 1 回の操作ですべてをゼロに設定できます。 Array.Clear(...)

于 2014-12-15T23:05:38.127 に答える
4

これは、Microsoft によって放棄されたフレームワーク ユーザー向けの別のバージョンです。これは、 Panos Theof のソリューションEric JPetar Petrov の並列Array.Clearソリューションよりも 4 倍速く、大規模な配列では最大 2 倍高速です。

最初に、関数の祖先を示したいと思います。これにより、コードが理解しやすくなります。パフォーマンスに関しては、これは 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);
}

ご覧のとおり、これは既に初期化された部分の繰り返しの 2 倍化に基づいています。これは単純で効率的ですが、最新のメモリ アーキテクチャに反します。したがって、キャッシュに適したシード ブロックを作成するためだけに倍増を使用するバージョンが生まれました。このシード ブロックは、ターゲット エリアで繰り返しブラストされます。

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前述の他の 3 つのソリューションを比較するベンチマークです。Int32[]タイミングは、指定されたサイズの整数配列 ( ) を埋めるためのものです。キャッシュの変動などによる変動を減らすために、各テストを連続して 2 回実行し、タイミングは 2 回目の実行に合わせました。

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<>フレームワークではなく、コアとスタンダードのみにあるため、最初に自分でロールバックする必要がある場合も同様です。.NET が誕生してから 20 年近くが経ちますが、最も基本的なものを左右に P/Invoke するか、独自のものを作成する必要があります...

于 2019-12-28T14:31:21.960 に答える
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; }
    }
}

おそらく、配列自体のインターフェイスなど、他のインターフェイスを実装して便利にする必要があります。

于 2010-06-24T09:10:16.917 に答える
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 での初期化が必要な場合などは、int の 0 から 4 にマップできます。

テンプレート パラメーターとしてシード値を指定して、C++ で可能なようにテンプレートを作成したいと思いますが、C# では不可能であることは理解しています。または、何か不足していますか?(もちろん、C++ では、配列要素に対して呼び出されるデフォルトの ctor を提供できるため、マッピングはまったく必要ありません。)

FWIW、これに相当する C++ があります: https://ideone.com/wG8yEh

于 2015-03-26T19:54:34.287 に答える
2

ロジックを反転できる場合は、Array.Clear()メソッドを使用してブール配列を false に設定できます。

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

        bool[] seiveContainer = new bool[upperLimit];
        Array.Clear(seiveContainer, 0, upperLimit);
于 2016-05-26T23:25:19.253 に答える
1

この(重複?)質問には、さらにいくつかの回答があります:C#のmemsetに相当するものは何ですか?

誰かが代替案をベンチマークしました (安全でないバージョンが含まれていましたが、試していませんでしたmemset): http://techmikael.blogspot.co.uk/2009/12/filling-array-with-default-value.html

于 2012-09-01T08:24:29.190 に答える
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);
于 2016-07-27T12:21:54.837 に答える
-2
Boolean[] data = new Boolean[25];

new Action<Boolean[]>((p) => { BitArray seed = new BitArray(p.Length, true); seed.CopyTo(p, 0); }).Invoke(data);
于 2014-06-11T17:58:17.867 に答える