web-dev-qa-db-ja.com

2次元配列を「foreach」するにはどうすればよいですか?

2次元配列があります。

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

そして、このようにforeachを介してやりたい

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

しかし、私はエラーを受け取ります:

タイプ文字列をstring []に変換できません

私が望むものを達成する方法はありますか?つまり、イテレータ変数で配列の最初の次元を反復処理して、その行の1次元配列を返しますか?

44
Scott Langham

このように配列を定義する場合:

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

その後、foreachループを使用できます。

24

多次元配列は列挙できません。昔ながらの方法で繰り返します:

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

他の人が示唆したように、ネストされたforループを使用するか、多次元配列をギザギザとして再宣言することができます。

ただし、多次元配列are列挙可能であることを指摘する価値があると思いますが、あなたが望む方法ではありません。例えば:

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

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

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

[〜#〜] update [〜#〜]:少し時間がありましたので、このアイデアを具体化しました。コードについては以下を参照してください。


ちょっと変わった答えがあります:

あなたはcouldあなたが探していることを行います-基本的には2次元配列を行を持つテーブルとして扱います-静的メソッド(おそらく拡張メソッド)T[,]を取り、IEnumerable<T[]>を返します。ただし、基になるテーブルの各「行」を新しい配列にコピーする必要があります。

おそらくより複雑な(より複雑な)アプローチは、実際にIList<T>を2次元配列の単一の「行」のラッパーとして実装するクラスを作成することです(おそらくIsReadOnlyをtrueであり、this[int]プロパティとおそらくCountおよびGetEnumeratorのゲッターを実装します;他のすべてはNotSupportedExceptionをスローできます。次に、静的/拡張メソッドはIEnumerable<IList<T>>を返し、遅延実行を提供できます。

そうすれば、あなたが持っているものとほとんど同じようなコードを書くことができます:

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

ちょっとした考え。


実装の提案:

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

...今、私はStackOverflowに一日の休憩を与えるべきだと思う;)

15
Dan Tao

多次元配列をどのように定義するかによります。次の2つのオプションがあります。

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

以下は、各行をIEnumerable<T>として返す簡単な拡張メソッドです。これには、余分なメモリを使用しないという利点があります。

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

サンプル使用法:

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

LINQを使用すると、次のようにできます。

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) {
    // …
}

この方法では、インデックス1ではなくGroupByのIndex2を使用して、行ではなく列をループするためにforeachを使用することもできます。配列の次元がわからない場合は、GetLength()を使用する必要があります次元を決定し、商でその値を使用する方法。

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

多次元配列はテーブルのようなものであることを忘れないでください。あなたは持っていない バツ 要素と y 各エントリの要素。 (たとえば)table[1,2]に文字列があります。

したがって、各エントリはまだ1つの文字列にすぎず(この例では)、特定のx/y値のエントリにすぎません。したがって、両方のエントリをtable[1, x]で取得するには、ネストされたforループを実行します。次のようなもの(テストされていませんが、近いはずです)

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

メモリ使用量が関係しているため、私はこの方法の大ファンではありませんが、それが生成する配列を使用する場合、それはそんなに無駄ではありません。

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

実装:

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

私が見つけた他の解決策は、メモリをあまり使用しませんが、特に配列のエントリの次元が大きい場合は、より多くのCPUを使用します。

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

実装:

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

全体として、特にインデクサーによって生成された配列にアクセスする場合は、最初のオプションがより直感的であることがわかります。

結局のところ、これはすべて目を見張るようなものです。どちらの方法も、ソース配列に直接アクセスするために実際に使用されるべきではありません。

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

やってみます。お手伝いしたいと思います。で動作します

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