web-dev-qa-db-fra.com

Comment «foreach» à travers un tableau à deux dimensions?

J'ai un tableau à deux dimensions,

string[,] table = {
                       { "aa", "aaa" },
                       { "bb", "bbb" }
                   };

Et je voudrais foreach à travers comme ça,

foreach (string[] row in table)
{
    Console.WriteLine(row[0] + " " + row[1]);
}

Mais, je reçois l'erreur:

Impossible de convertir la chaîne de type en chaîne []

Existe-t-il un moyen de réaliser ce que je veux, c'est-à-dire parcourir la première dimension du tableau avec la variable itérateur me renvoyant le tableau unidimensionnel pour cette ligne?

44
Scott Langham

Si vous définissez votre tableau comme ceci:

string[][] table = new string[][] { new string[] { "aa", "aaa" }, new string[]{ "bb", "bbb" } };

Ensuite, vous pouvez utiliser une boucle foreach dessus.

24
Radoslav Hristov

Les tableaux multidimensionnels ne sont pas énumérables. Répétez simplement la bonne vieille méthode:

for (int i = 0; i < table.GetLength(0); i++)
{
    Console.WriteLine(table[i, 0] + " " + table[i, 1]);
}
58
Marcelo Cantos

Comme d'autres l'ont suggéré, vous pouvez utiliser des boucles for imbriquées ou redéclarer votre tableau multidimensionnel comme un tableau irrégulier.

Cependant, je pense qu'il vaut la peine de souligner que les tableaux multidimensionnels sont énumérables, mais pas de la manière que vous souhaitez. Par exemple:

string[,] table = {
                      { "aa", "aaa" },
                      { "bb", "bbb" }
                  };

foreach (string s in table)
{
    Console.WriteLine(s);
}

/* Output is:
  aa
  aaa
  bb
  bbb
*/
25
Odrade

MISE À JOUR : J'ai eu un peu de temps, alors ... j'ai continué et j'ai développé cette idée. Voir ci-dessous pour le code.


Voici une réponse un peu folle:

Vous pourriez faire ce que vous cherchez - traiter essentiellement un tableau à deux dimensions comme un tableau avec des lignes - en écrivant une méthode statique (peut-être une méthode d'extension) qui prend un T[,] et renvoie un IEnumerable<T[]>. Cela nécessiterait cependant de copier chaque "ligne" de la table sous-jacente dans un nouveau tableau.

Une approche peut-être meilleure (mais plus impliquée) serait d'écrire réellement une classe qui implémente IList<T> comme un wrapper autour d'une seule "ligne" d'un tableau à deux dimensions (vous devez probablement définir IsReadOnly sur true et simplement implémenter le getter pour le this[int] propriété et probablement Count et GetEnumerator; tout le reste pourrait lancer un NotSupportedException). Votre méthode statique/d'extension pourrait alors renvoyer un IEnumerable<IList<T>> et fournir une exécution différée.

De cette façon, vous pourriez écrire du code à peu près comme ce que vous avez:

foreach (IList<string> row in table.GetRows()) // or something
{
    Console.WriteLine(row[0] + " " + row[1]);
}

Juste une pensée.


Suggestion de mise en œuvre:

public static class ArrayTableHelper {
    public static IEnumerable<IList<T>> GetRows<T>(this T[,] table) {
        for (int i = 0; i < table.GetLength(0); ++i)
            yield return new ArrayTableRow<T>(table, i);
    }

    private class ArrayTableRow<T> : IList<T> {
        private readonly T[,] _table;
        private readonly int _count;
        private readonly int _rowIndex;

        public ArrayTableRow(T[,] table, int rowIndex) {
            if (table == null)
                throw new ArgumentNullException("table");

            if (rowIndex < 0 || rowIndex >= table.GetLength(0))
                throw new ArgumentOutOfRangeException("rowIndex");

            _table = table;
            _count = _table.GetLength(1);
            _rowIndex = rowIndex;
        }

        // I didn't implement the setter below,
        // but you easily COULD (and then set IsReadOnly to false?)
        public T this[int index] {
            get { return _table[_rowIndex, index]; }
            set { throw new NotImplementedException(); }
        }

        public int Count {
            get { return _count; }
        }

        bool ICollection<T>.IsReadOnly {
            get { return true; }
        }

        public IEnumerator<T> GetEnumerator() {
            for (int i = 0; i < _count; ++i)
                yield return this[i];
        }

        // omitted remaining IList<T> members for brevity;
        // you actually could implement IndexOf, Contains, etc.
        // quite easily, though
    }
}

... maintenant je pense que je devrais donner une pause à StackOverflow pour le reste de la journée;)

15
Dan Tao

Cela dépend de la façon dont vous définissez votre tableau multidimensionnel. Voici deux options:

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            // First
            string[,] arr1 = {
                       { "aa", "aaa" },
                       { "bb", "bbb" }
                   };

            // Second
            string[][] arr2 = new[] {
                new[] { "aa", "aaa" },
                new[] { "bb", "bbb" }
            };

            // Iterate through first
            for (int x = 0; x <= arr1.GetUpperBound(0); x++)
                for (int y = 0; y <= arr1.GetUpperBound(1); y++)
                    Console.Write(arr1[x, y] + "; ");

            Console.WriteLine(Environment.NewLine);

            // Iterate through second second
            foreach (string[] entry in arr2)
                foreach (string element in entry)
                    Console.Write(element + "; ");

            Console.WriteLine(Environment.NewLine);
            Console.WriteLine("Press any key to finish");
            Console.ReadKey();
        }
    }
}
4
Matthias
string[][] table = { ... };
3
Toby

Voici une méthode d'extension simple qui renvoie chaque ligne sous la forme d'un IEnumerable<T>. Cela a l'avantage de ne pas utiliser de mémoire supplémentaire:

public static class Array2dExt
{
    public static IEnumerable<IEnumerable<T>> Rows<T>(this T[,] array)
    {
        for (int r = array.GetLowerBound(0); r <= array.GetUpperBound(0); ++r)
            yield return row(array, r);
    }

    static IEnumerable<T> row<T>(T[,] array, int r)
    {
        for (int c = array.GetLowerBound(1); c <= array.GetUpperBound(1); ++c)
            yield return array[r, c];
    }
}

Exemple d'utilisation:

static void Main()
{
    string[,] siblings = { { "Mike", "Amy" }, { "Mary", "Albert" }, {"Fred", "Harry"} };

    foreach (var row in siblings.Rows())
        Console.WriteLine("{" + string.Join(", ", row) + "}");
}
2
Matthew Watson

En utilisant LINQ, vous pouvez le faire comme ceci:

var table_enum = table

    // Convert to IEnumerable<string>
    .OfType<string>()

    // Create anonymous type where Index1 and Index2
    // reflect the indices of the 2-dim. array
    .Select((_string, _index) => new {
        Index1 = (_index / 2),
        Index2 = (_index % 2), // ← I added this only for completeness
        Value = _string
    })

    // Group by Index1, which generates IEnmurable<string> for all Index1 values
    .GroupBy(v => v.Index1)

    // Convert all Groups of anonymous type to String-Arrays
    .Select(group => group.Select(v => v.Value).ToArray());

// Now you can use the foreach-Loop as you planned
foreach(string[] str_arr in table_enum) {
    // …
}

De cette façon, il est également possible d'utiliser foreach pour parcourir les colonnes au lieu des lignes en utilisant Index2 dans GroupBy au lieu de Index 1. Si vous ne connaissez pas la dimension de votre tableau, vous devez utiliser GetLength () pour déterminer la dimension et utiliser cette valeur dans le quotient.

2
Martini Bianco
string[][] languages = new string[2][];
            languages[0] = new string[2];
            languages[1] = new string[3];
// inserting data into double dimensional arrays.
            for (int i = 0; i < 2; i++)
            {
                languages[0][i] = "Jagged"+i.ToString();
            }
            for (int j = 0; j < 3; j++)
            {
                languages[1][j] = "Jag"+j.ToString();
            }

// doing foreach through 2 dimensional arrays.
foreach (string[] s in languages)
            {
                foreach (string a in s)
                {
                    Console.WriteLine(a);
                }
            }
1
abhishek

N'oubliez pas qu'un tableau multidimensionnel est comme une table. Vous n'avez pas x élément et un y élément pour chaque entrée; vous avez une chaîne à (par exemple) table[1,2].

Ainsi, chaque entrée n'est toujours qu'une chaîne (dans votre exemple), c'est juste une entrée à une valeur x/y spécifique. Donc, pour obtenir les deux entrées à table[1, x], vous feriez une boucle imbriquée. Quelque chose comme ce qui suit (non testé, mais devrait être proche)

for (int x = 0; x < table.Length; x++)
{
    for (int y = 0; y < table.Length; y += 2)
    {
        Console.WriteLine("{0} {1}", table[x, y], table[x, y + 1]);
    }
}
1
AllenG

Je ne suis pas un grand fan de cette méthode en raison de l'utilisation de la mémoire impliquée, mais si vous utilisez les tableaux qu'elle produit, ce n'est pas un tel gaspillage.

public static void ForEachRow<T>(this T[,] list, Action<int, T[]> action)
{
    var len = list.GetLength(0);
    var sub = list.GetLength(1);

    T[] e;
    int i, j;

    for (i = 0; i < len; i++)
    {
        e = new T[sub];

        for (j = 0; j < sub; j++)
        {
            e[j] = list[i, j];
        }

        action(i, e);
    }
}

La mise en oeuvre:

var list = new[,]{0x0, 0x1, 0x2, 0x4, 0x8};

list.ForEachRow((i, row) =>
{
    for (var j = 0; j < row.Length; j++)
    {
        Console.WriteLine("[{0},{1}]: {2}", i, j, row[j]);
    }
});

L'autre solution que j'ai trouvée consomme moins de mémoire, mais utilisera plus de CPU, surtout lorsque les dimensions des entrées des tableaux sont plus grandes.

public static void ForEachRow<T>(this T[,] list, Action<int, IEnumerable<T>> action)
{
    var len = list.GetLength(0);
    var sub = list.GetLength(1);

    int i, j;
    IEnumerable<T> e;

    for (i = 0; i < len; i++)
    {
        e = Enumerable.Empty<T>();

        for (j = 0; j < sub; j++)
        {
            e = e.Concat(AsEnumerable(list[i, j]));
        }

        action(i, e);
    }
}

private static IEnumerable<T> AsEnumerable<T>(T add)
{
    yield return add;
}

La mise en oeuvre:

var list = new[,]{0x0, 0x1, 0x2, 0x4, 0x8};

list.ForEachRow((i, row) =>
{
    var j = 0;

    forrach (var o in row)
    {
        Console.WriteLine("[{0},{1}]: {2}", i, j, o);

        ++j;
    }
});

Dans l'ensemble, je trouve la première option plus intuitive, surtout si vous voulez accéder au tableau produit par son indexeur.

À la fin de la journée, tout cela n'est qu'un régal pour les yeux, aucune des méthodes ne devrait vraiment être utilisée en faveur d'un accès direct au tableau source;

for (var i = 0; i < list.GetLength(0); i++)
{
    foreach (var j = 0; j < list.GetLength(1); j++)
    {
        Console.WriteLine("[{0},{1}]: {2}", i, j, list[i, j]);
    }
}
0
Vorspire

J'essaye ça. J'espère vous aider. Ça marche avec

static void Main()
    {
        string[,] matrix = {
                               { "aa", "aaa" },
                               { "bb", "bbb" }
                           };
        int index = 0;
        foreach (string element in matrix)
        {
            if (index < matrix.GetLength(1))
            {
                Console.Write(element);
                if (index < (matrix.GetLength(1) - 1))
                {
                    Console.Write(" ");
                }
                index++;
            }
            if (index == matrix.GetLength(1))
            {
                Console.Write("\n");
                index = 0;
            }
        }
0
Matteo1010