web-dev-qa-db-ja.com

単一の値を使用してC#配列を生成/インスタンス化する方法は?

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#がこれらのオブジェクトのバックグラウンドでのメモリ割り当てを処理する方法の結果であると推測しているため、おそらくこれを行うことはできないと思います。しかし、私はまだ確実に知りたいです!

173
patjbs

フレームワークのメソッドを知らないが、あなたのためにそれをするクイックヘルパーを書くことができる。

public static void Populate<T>(this T[] arr, T value ) {
  for ( int i = 0; i < arr.Length;i++ ) {
    arr[i] = value;
  }
}
128
JaredPar
Enumerable.Repeat(true, 1000000).ToArray();
178
Rony

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

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

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

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

配列が非常に大きい場合は、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);
22
MrFox

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

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() };
22
LBushkin

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

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

確かに私が探しているものに近いです。しかし、forループで元の配列を繰り返し処理して値を変更するよりも良いかどうかはわかりません。実際に簡単なテストを行った結果、約5倍遅くなったように見えます。そのため、本当に良い解決策ではありません。

8
patjbs

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

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

または...単に反転ロジックを使用できます。 falsetrueを意味し、逆も同様です。

コードサンプル

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

並列実装はどうですか

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

配列を初期化するときのみ、このコードの威力は見えませんが、「純粋」を忘れる必要があると思います。

6
Petar Petrov

以下のコードは、小さなコピーの単純な反復と大きな​​コピーの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
6
Panos Theof

これも機能しますが、不要な場合があります

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

配列内のいくつかの値のみを設定することを計画しているが、ほとんどの場合(カスタム)デフォルト値を取得する場合は、次のようなものを試すことができます。

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

array 自体にあるような他のインターフェイスを実装する必要があるでしょう。

3
Douglas

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

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

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

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

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

2
James

私はパーティーに遅れていることに気づきましたが、ここにアイデアがあります。ラップされた値との間の変換演算子を持つラッパーを作成して、ラップされた型の代用として使用できるようにします。これは実際には、@ 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#では不可能であることを理解しています。それとも何か不足していますか? (もちろん、配列要素に対して呼び出されるデフォルトのctorを提供できるため、C++マッピングではまったく必要ありません。)

FWIW、これはC++に相当するものです。 https://ideone.com/wG8yEh .

2

ここで紹介する回答の多くは、一度に1要素ずつ配列を初期化するループに要約されます。これは、一度にメモリブロックを操作するように設計された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;
        }
    }
}
1
Eric J.
1
juFo

配列を作成する場所にプライベートクラスを作成し、そのゲッターとセッターを設定します。配列内の各位置をランダムなどの一意なものにする必要がない限り、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;
}

セッターとして。

0
Peter J

このようなコンストラクタを持つ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
fubo

この(重複?)質問には、さらにいくつかの回答があります。 C#のmemsetと同等のものは何ですか?

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

0
Rich