web-dev-qa-db-fra.com

Comment peupler/instancier un tableau C # avec une seule valeur?

Je sais que les tableaux de types de valeurs instanciés en C # sont automatiquement renseignés avec la valeur default du type (par exemple, false pour bool, 0 pour int, etc.).

Existe-t-il un moyen de renseigner automatiquement un tableau avec une valeur de départ qui n'est pas la valeur par défaut? Soit lors de la création, soit après une méthode intégrée (comme Arrays.fill () de Java)? Disons que je voulais un tableau booléen qui était vrai par défaut, au lieu de faux. Existe-t-il un moyen intégré de procéder ou devez-vous simplement parcourir le tableau avec une boucle 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;
 }

Le fait de devoir parcourir le tableau et de "réinitialiser" chaque valeur sur true semble inefficace. Y a-t-il un moyen de contourner cela? Peut-être en retournant toutes les valeurs?

Après avoir tapé cette question et réfléchi, j’imagine que les valeurs par défaut sont simplement le résultat de la façon dont C # gère l’allocation de mémoire de ces objets en coulisse. J’imagine donc que c’est probablement impossible. Mais j'aimerais quand même savoir avec certitude!

162
patjbs

Je ne connais pas de méthode framework, mais vous pourriez écrire une aide rapide pour le faire à votre place.

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

Créez un nouveau tableau avec mille valeurs true:

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

De même, vous pouvez générer des séquences entières:

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

Pour les tableaux de grande taille ou de taille variable, vous devriez probablement utiliser:

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

Pour un petit tableau, vous pouvez utiliser la syntaxe d'initialisation de la collection en C # 3:

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

L'avantage de la syntaxe d'initialisation de la collection est qu'il n'est pas nécessaire d'utiliser la même valeur dans chaque emplacement et que vous pouvez utiliser des expressions ou des fonctions pour initialiser un emplacement. De plus, je pense que vous évitez le coût d’initialisation de l’emplacement de tableau sur la valeur par défaut. Donc, par exemple:

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

Si votre tableau est si grand, vous devriez utiliser BitArray. Il utilise 1 bit pour chaque bool au lieu d'un octet (comme dans un tableau de bools). Vous pouvez également définir tous les bits sur true avec des opérateurs de bits. Ou simplement initialiser sur true. Si vous n’avez besoin que de le faire une fois, cela ne vous coûtera que plus cher.

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);
21
MrFox

Après un peu plus de recherches et de lecture, j'ai trouvé ceci:

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

Ce qui est certainement plus proche de ce que je recherche. Mais je ne sais pas si c'est mieux que de parcourir le tableau d'origine dans une boucle for et de changer les valeurs. Après un test rapide en fait, il apparaît plus lent d'environ 5 fois. Ce n'est donc pas vraiment une bonne solution!

8
patjbs

malheureusement, je ne pense pas qu'il existe un moyen direct, mais je pense que vous pouvez écrire une méthode d'extension pour que la classe array le fasse.

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

Qu'en est-il d'une implémentation parallèle

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

Lors de l'initialisation d'un tableau, la puissance de ce code ne peut pas être vue, mais je pense que vous devez absolument oublier le "pur".

6
Petar Petrov

Ou ... vous pouvez simplement utiliser une logique inversée. Soit false moyenne true et vice versa.

Exemple de code

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

Le code ci-dessous combine une itération simple pour les petites copies et Array.Copy pour les grandes copies.

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

Les repères pour différentes longueurs de tableau utilisant un tableau int [] sont:

         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

La première colonne est la taille du tableau, suivie du temps de copie en utilisant une simple itération (implémentation @JaredPared). Le temps de cette méthode est après cela . Ce sont les points de repère utilisant un tableau d'une structure de quatre entiers

         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

cela fonctionne aussi ... mais pourrait être inutile

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

Vous pouvez utiliser Array.Fill dans .NET Core 2.0+ et .NET Standard 2.1+.

3
juFo

Si vous prévoyez de ne définir que quelques valeurs dans le tableau, mais souhaitez obtenir la valeur par défaut (personnalisée) la plupart du temps, vous pouvez essayer quelque chose comme:

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

Vous aurez probablement besoin d'implémenter d'autres interfaces pour le rendre utile, telles que celles de array lui-même.

3
Douglas

Si vous pouvez inverser votre logique, vous pouvez utiliser la méthode Array.Clear() pour définir le tableau booléen sur false.

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

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

Il n'y a aucun moyen de définir tous les éléments d'un tableau comme une seule opération, SAUF SI, cette valeur est la valeur par défaut des types d'élément. 

Par exemple, s'il s'agit d'un tableau d'entiers, vous pouvez les mettre tous à zéro en une seule opération, comme suit: Array.Clear(...)

2
James

La plupart des réponses présentées ici se résument à une boucle qui initialise le tableau élément par élément, sans tirer parti des instructions du processeur conçues pour fonctionner simultanément sur un bloc de mémoire.

.Net Standard 2.1 (dans l'aperçu au moment d'écrire ces lignes) fournit Array.Fill () , qui se prête à une implémentation hautes performances dans la bibliothèque d'exécution (à partir de maintenant, .NET Core - ne semble pas exploiter cette possibilité).

Pour les utilisateurs de plateformes antérieures, la méthode d’extension suivante surperforme une boucle triviale d’une marge substantielle lorsque la taille du tableau est importante. Je l'ai créé lorsque ma solution pour un défi de code en ligne dépassait de 20% le budget alloué. Cela a réduit le temps d'exécution d'environ 70%. Dans ce cas, le remplissage du tableau a été effectué dans une autre boucle. BLOCK_SIZE a été défini par les instincts plutôt que par l'expérience. Certaines optimisations sont possibles (par exemple, copier tous les octets déjà définis avec la valeur souhaitée plutôt qu’un bloc de taille fixe).

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.

Je me rends compte que je suis en retard à la fête mais voici une idée. Ecrivez un wrapper qui a des opérateurs de conversion vers et à partir de la valeur encapsulée afin qu’elle puisse être utilisée en tant que remplaçant du type encapsulé. Cela a été inspiré par la réponse stupide de @ l33t.

D'abord (venant de C++), j'ai réalisé qu'en C #, un ctor par défaut n'est pas appelé lorsque les éléments d'un tableau sont construits. Au lieu de cela - même en présence d'un constructeur par défaut défini par l'utilisateur! - tous les éléments du tableau sont initialisés à zéro. Cela m'a surpris.

Ainsi, une classe wrapper qui fournit simplement un ctor par défaut avec la valeur souhaitée fonctionnerait pour les tableaux en C++ mais pas en C #. Une solution consiste à laisser le type d’emballage mapper 0 sur la valeur de départ désirée lors de la conversion. De cette façon, les valeurs initialisées à zéro semblent être initialisées avec le germe à toutes fins pratiques:

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

Ce modèle est applicable à tous les types de valeur. On pourrait par exemple mapper 0 à 4 pour les ints si une initialisation avec 4 était souhaitée, etc.

J'adorerais en faire un modèle comme cela serait possible en C++, en fournissant la valeur de départ comme paramètre de modèle, mais je comprends que ce n'est pas possible en C #. Ou est-ce que je manque quelque chose? (Bien sûr, le mappage en C++ n'est pas du tout nécessaire car on peut fournir un ctor par défaut qui sera appelé pour les éléments de tableau.)

FWIW, voici un équivalent C++: https://ideone.com/wG8yEh .

1
Peter A. Schneider

Créez une classe privée à l'intérieur de laquelle vous créez le tableau, ainsi qu'un getter et un configurateur. Sauf si vous avez besoin que chaque position du tableau soit unique, comme aléatoire, utilisez int? sous forme de tableau, puis sur get si la position est égale à null, remplit cette position et renvoie la nouvelle valeur aléatoire.

IsVisibleHandler
{

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

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

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

Ou utiliser 

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

Comme poseur.

0
Peter J

Voici une autre approche avec System.Collections.BitArray qui a un tel constructeur.

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

ou 

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

Il existe quelques réponses supplémentaires à cette question (dupliquer?): Quel est l'équivalent de memset en C #?

Quelqu'un a comparé les solutions de rechange (il a inclus une version non sécurisée, mais n'a pas essayé memset): http://techmikael.blogspot.co.uk/2009/12/filling-array-with-default-value.html

0
Rich