ネストされたループのより高速な代替手段?


85

数字の組み合わせのリストを作成する必要があります。数が非常に少ないので、byteではなく使用できますint。ただし、考えられるすべての組み合わせを取得するには、多くのネストされたループが必要です。私が求めていることを行うためのより効率的な方法があるかどうか疑問に思っています。これまでのコードは次のとおりです。

var data = new List<byte[]>();
for (byte a = 0; a < 2; a++)
for (byte b = 0; b < 3; b++)
for (byte c = 0; c < 4; c++)
for (byte d = 0; d < 3; d++)
for (byte e = 0; e < 4; e++)
for (byte f = 0; f < 3; f++)
for (byte g = 0; g < 3; g++)
for (byte h = 0; h < 4; h++)
for (byte i = 0; i < 2; i++)
for (byte j = 0; j < 4; j++)
for (byte k = 0; k < 4; k++)
for (byte l = 0; l < 3; l++)
for (byte m = 0; m < 4; m++)
{
    data.Add(new [] {a, b, c, d, e, f, g, h, i, j, k, l, m});
}

のようなものを使うことを考えていましたBitArrayが、どうやって組み込むのかわかりません。

任意の推奨事項をいただければ幸いです。あるいは、おそらくこれが私がやりたいことをする最も速い方法ですか?

いくつか の簡単なポイントを編集します(そして私がこれらを元の投稿に入れなかったことをお詫びします):

  • それらの数と順序(2、3、4、3、4、3、3など)は非常に重要であるため、LINQ使用した順列の生成などのソリューションを使用しても、各「列」の最大値は異なる
  • 私は数学者ではないので、「順列」や「組み合わせ」などの専門用語を正しく使用していない場合はお詫びします:)
  • 私はない、私はちょうど1をつかむことができないか、別のインデックスに基づいて-一度にこれらの組み合わせの全てを移入する必要があります
  • 使用byteは使用よりも速いですint、私それを保証します。また、メモリ使用量については、intではなく67m以上のバイト配列を使用する方がはるかに優れています。
  • ここでの私の最終的な目標は、ネストされたループのより高速な代替手段を探すことです。
  • 並列プログラミングの使用を検討しましたが、達成しようとしていることの反復性のため、(を使用してもConcurrentBag)それを成功させる方法を見つけることができませんでした-しかし、間違っていることが証明されてうれしいです:)

結論

Caramirielは、ループから少し時間を短縮する優れたマイクロ最適化を提供しているので、その答えを正解としてマークしました。エリックはまた、リストを事前に割り当てる方が速いと述べました。しかし、この段階では、ネストされたループが実際にこれを行うための可能な限り最速の方法であるように思われます(気のめいるように、私は知っています!)。

私がベンチマークしようとしていたことを正確に試したい場合はStopWatch、各ループで最大4つまでカウントする13のループを使用してください。これにより、リストに約67m以上の行が作成されます。私のマシン(i5-3320M 2.6GHz)では、最適化されたバージョンを実行するのに約2.2秒かかります。


1
linqを使用してみてください。マルチコアプロセッサを使用している場合は、Parrallel.for
Jalpesh Vadgama 2015

1
私が見るところによると、これらは順列ではありませんが、いくつかの非常に小さい(2〜4要素)セットの組み合わせは正しいですか、それとも1つのセットのすべて/一部の順列が本当に必要ですか?
カルステン

私はホードが検索を前提とbing.com/search?q=c%23+permutation+enumerableすでに、いくつかの理由(記事で言及されていない)のような既存の回答に対して決めstackoverflow.com/questions/4319049/...上場を検討し...あなたが見て、この質問をより良くするために反対することに決めたオプション。
Alexei Levenkov 2015

3
これがパフォーマンスに関するものである場合:リスト(コンストラクター)を事前に割り当てていくつかのループを展開することはできますが、これらの数値を事前に計算して保存することは別として、それについてだと思います。ループ(オーバーヘッド)は、体内での操作の量が少ないため、おそらくそれらすべての中で最もコストがかかります。
カラミリエル2015

5
@benpage:なぜすべての組み合わせを事前に生成する必要があるのですか?必要なときにインデックスから組み合わせを生成してみませんか?
Pieter Witvoet 2015

回答:


61

構造体のプロパティを使用して、事前に構造体を割り当てることができます。以下のサンプルではいくつかのレベルを切り取っていますが、詳細を理解できると確信しています。オリジナル(リリースモード)の約5〜6倍の速度で実行されます。

ブロック:

struct ByteBlock
{
    public byte A;
    public byte B;
    public byte C;
    public byte D;
    public byte E;
}

ループ:

var data = new ByteBlock[2*3*4*3*4];
var counter = 0;

var bytes = new ByteBlock();

for (byte a = 0; a < 2; a++)
{
    bytes.A = a;
    for (byte b = 0; b < 3; b++)
    {
        bytes.B = b;
        for (byte c = 0; c < 4; c++)
        {
            bytes.C = c;
            for (byte d = 0; d < 3; d++)
            {
                bytes.D = d;
                for (byte e = 0; e < 4; e++)
                {
                    bytes.E = e;
                    data[counter++] = bytes;
                }
            }
        }
    }
}

リストに追加するたびに新しいリストが割り当てられるわけではないため、高速です。また、このリストを作成しているため、他のすべての値(a、b、c、d、e)への参照が必要です。各値はループ内で一度だけ変更されると想定できるため、そのように最適化できます(データの局所性)。

副作用についてのコメントも読んでください。

T[]代わりにを使用するように回答を編集しましたList<T>


1
その構造体なので、大丈夫です=)それらはすべてユニークです。List<T>.Addメソッドを呼び出すときにコピーされます。
カラミリエル2015

4
List()に容量を割り当てた場合はさらに高速になります
Eric

5
スタックに割り当てるオブジェクトが多すぎる場合は、stackoverflowの例外に注意してください。
アンドレイ・タタール

7
@アンドリュー私はあなたのポイントを取得しません。このコードは再帰的ではなく、スタックの使用量は最小限です。
CodesInChaos 2015

3
@Andrew:それはメモリ不足であり、stackoverflowではありません。これは、List<T>.Add()メソッドが保存できる範囲を超えているためです。これにより、サイズが2倍になり、2GBを超えるメモリになります。新しいList <ByteBlock>(maxPerLevel.Aggregate(1、(x、y)=> x * y))を使用して事前割り当てを試してください。ただし、このデータの完全な2GBブロックがメモリ内に必要であるということはすでに「ランダム」です。data.ToArray();にも注意してください。その時点でアイテムを2回メモリに保持するため、コストがかかります。[言い換え]
Caramiriel 2015

33

あなたがしているのは数えていることです(基数は可変ですが、それでも数えています)。

C#を使用しているので、コードを実際に最適化できる便利なメモリレイアウトやデータ構造を試してみたくないと思います。

だからここに私はあなたのケースに合わないかもしれない何か違うものを投稿しています、しかしそれは注目に値します:あなたが実際にまばらな方法でリストにアクセスする場合、ここでは線形時間でi番目の要素を計算することを可能にするクラス(むしろ他の答えのように指数関数的よりも)

class Counter
{
    public int[] Radices;

    public int[] this[int n]
    {
        get 
        { 
            int[] v = new int[Radices.Length];
            int i = Radices.Length - 1;

            while (n != 0 && i >= 0)
            {
                //Hope C# has an IL-opcode for div-and-reminder like x86 do
                v[i] = n % Radices[i];
                n /= Radices[i--];
            }
            return v;
        }
    }
}

このクラスはこのように使用できます

Counter c = new Counter();
c.Radices = new int[] { 2,3,4,3,4,3,3,4,2,4,4,3,4};

c[i]はあなたのリストと同じです、それに名前を付けてくださいll[i]

ご覧のとおり、Carry-Rippleカウンターを実装するだけなので、すべてのリスト全体を事前に計算した場合でも、これらすべてのループを簡単に回避できます:)。

カウンターは非常に研究された主題です、あなたが感じるならば、私はいくつかの文献を探すことを強く勧めます。


4
私はあなたの答えが好きですが、他のすべての答えが指数関数的であると言うのは真実ではありません。
ビスケット

1
カラミリエルの答えと比較して、これの速度はどれくらいですか?
ジョン・オドム

17
「C-kiddy-#」、本当に?それはまったく求められていないようです。
KChaloux 2015

2
そしてそれは:Math.DivRem
Caramiriel

1
ある程度は超えていると思いますが、最適化は使用の問題です。たとえば、すべてのアレイを1回だけ使用する場合は、集中的なメモリ割り当てを回避できます。これは、私の意見では重大なボトルネックです。さらに、すべての値を計算する場合は、除算を避けて単一の増分(つまり、+ 1の増分)を実行するという事実を利用する必要があります。これは、「箱から出してすぐに使える」回答またはプロトタイプとして意図されたものです。私は実際にスピードを上げようとはしませんでした。このように気に入っています:)

14

方法1

高速化する1つの方法はList<byte[]>、このように、を使い続ける予定がある場合は容量を指定することです。

var data = new List<byte[]>(2 * 3 * 4 * 3 * 4 * 3 * 3 * 4 * 2 * 4 * 4 * 3 * 4);

方法2

さらに、System.Array直接使用してより高速なアクセスを取得できます。すべての要素を事前にメモリに物理的に入力する必要がある場合は、このアプローチをお勧めします。

var data = new byte[2 * 3 * 4 * 3 * 4 * 3 * 3 * 4 * 2 * 4 * 4 * 3 * 4][];
int counter = 0;

for (byte a = 0; a < 2; a++)
    for (byte b = 0; b < 3; b++)
        for (byte c = 0; c < 4; c++)
            for (byte d = 0; d < 3; d++)
                for (byte e = 0; e < 4; e++)
                    for (byte f = 0; f < 3; f++)
                        for (byte g = 0; g < 3; g++)
                            for (byte h = 0; h < 4; h++)
                                for (byte i = 0; i < 2; i++)
                                    for (byte j = 0; j < 4; j++)
                                        for (byte k = 0; k < 4; k++)
                                            for (byte l = 0; l < 3; l++)
                                                for (byte m = 0; m < 4; m++)
                                                    data[counter++] = new[] { a, b, c, d, e, f, g, h, i, j, k, l, m };

これは私のコンピューターで完了するのに596ミリ秒かかります。これは、問題のコード(658ミリ秒かかる)よりも約10.4%高速です。

方法3

または、次の手法を使用して、まばらな方法でアクセスに適した低コストの初期化を行うこともできます。これは、一部の要素のみが必要であり、それらすべてを事前に決定する必要がないと考えられる場合に特に有利です。さらに、これらのような手法は、メモリが不足しているときに、より広大な要素を操作する場合の唯一の実行可能なオプションになる可能性があります。

この実装では、すべての要素は、アクセス時にオンザフライで遅延的に決定されるように残されています。当然、これにはアクセス中に発生する追加のCPUのコストがかかります。

class HypotheticalBytes
{
    private readonly int _c1, _c2, _c3, _c4, _c5, _c6, _c7, _c8, _c9, _c10, _c11, _c12;
    private readonly int _t0, _t1, _t2, _t3, _t4, _t5, _t6, _t7, _t8, _t9, _t10, _t11;

    public int Count
    {
        get { return _t0; }
    }

    public HypotheticalBytes(
        int c0, int c1, int c2, int c3, int c4, int c5, int c6, int c7, int c8, int c9, int c10, int c11, int c12)
    {
        _c1 = c1;
        _c2 = c2;
        _c3 = c3;
        _c4 = c4;
        _c5 = c5;
        _c6 = c6;
        _c7 = c7;
        _c8 = c8;
        _c9 = c9;
        _c10 = c10;
        _c11 = c11;
        _c12 = c12;
        _t11 = _c12 * c11;
        _t10 = _t11 * c10;
        _t9 = _t10 * c9;
        _t8 = _t9 * c8;
        _t7 = _t8 * c7;
        _t6 = _t7 * c6;
        _t5 = _t6 * c5;
        _t4 = _t5 * c4;
        _t3 = _t4 * c3;
        _t2 = _t3 * c2;
        _t1 = _t2 * c1;
        _t0 = _t1 * c0;
    }

    public byte[] this[int index]
    {
        get
        {
            return new[]
            {
                (byte)(index / _t1),
                (byte)((index / _t2) % _c1),
                (byte)((index / _t3) % _c2),
                (byte)((index / _t4) % _c3),
                (byte)((index / _t5) % _c4),
                (byte)((index / _t6) % _c5),
                (byte)((index / _t7) % _c6),
                (byte)((index / _t8) % _c7),
                (byte)((index / _t9) % _c8),
                (byte)((index / _t10) % _c9),
                (byte)((index / _t11) % _c10),
                (byte)((index / _c12) % _c11),
                (byte)(index % _c12)
            };
        }
    }
}

これは私のコンピューターで完了するのに897ミリ秒かかります(方法2のArrayように作成して追加します)。これは問題のコード(658ミリ秒かかります)よりも約36.3%遅くなります


1
2番目の提案は、メモリ消費の観点からも大幅な節約です。(ただし、リストは変更されないことを前提としています)
Taemyr 2015

リスト全体を一度に作成する必要があります。リスト内のインデックスを参照できません。
benpage 2015

@Taemyrありがとう。それに応じて更新します。実装でリスト全体を事前に入力する必要があると本当に主張している場合、この3番目のオプションは明らかに機能しません。
ビスケット

3
@benpageなぜリストにデータを入力する必要があるのですか?
taemyr 2015

14

私のマシンでは、これにより、222ミリ秒と760ミリ秒(13個のforループ)の組み合わせが生成されます。

private static byte[,] GenerateCombinations(byte[] maxNumberPerLevel)
{
    var levels = maxNumberPerLevel.Length;

    var periodsPerLevel = new int[levels];
    var totalItems = 1;
    for (var i = 0; i < levels; i++)
    {
        periodsPerLevel[i] = totalItems;
        totalItems *= maxNumberPerLevel[i];
    }

    var results = new byte[totalItems, levels];

    Parallel.For(0, levels, level =>
    {
        var periodPerLevel = periodsPerLevel[level];
        var maxPerLevel = maxNumberPerLevel[level];
        for (var i = 0; i < totalItems; i++)
            results[i, level] = (byte)(i / periodPerLevel % maxPerLevel);
    });

    return results;
}

これは素晴らしい答えです!残念ながら、ネストされたループよりも実行速度が遅くなります。TPLを使用して編集できる可能性はありますか?
benpage 2015

残念ながら、それでもかなり遅いです。
benpage 2015

1
@benpage少なくとも2倍速くする簡単な方法があります。結果の型をint [、]に変更するだけです。これにより、1回の呼び出しで配列メモリ全体が割り当てられます。それがあなたのニーズにどのように適合するかはわかりません(戻り値の型を変更します)。
アンドレイ・タタール

8
var numbers = new[] { 2, 3, 4, 3, 4, 3, 3, 4, 2, 4, 4, 3, 4 };
var result = (numbers.Select(i => Enumerable.Range(0, i))).CartesianProduct();

http://ericlippert.com/2010/06/28/computing-a-cartesian-product-with-linq/で拡張メソッドを使用する

public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences)
{
    // base case: 
    IEnumerable<IEnumerable<T>> result =
        new[] { Enumerable.Empty<T>() };
    foreach (var sequence in sequences)
    {
        // don't close over the loop variable (fixed in C# 5 BTW)
        var s = sequence;
        // recursive case: use SelectMany to build 
        // the new product out of the old one 
        result =
            from seq in result
            from item in s
            select seq.Concat(new[] { item });
    }
    return result;
}

1
これは非常に遅く実行されます:(
benpage 2015

8

リストには、固定長の値を格納する配列が内部にあります。List.Addを呼び出すと、十分なスペースがあるかどうかがチェックされます。新しい要素を追加できない場合は、より大きなサイズの新しい配列を作成し、前のすべての要素をコピーしてから、新しい要素を追加します。これにはかなりのサイクルがかかります。

すでに要素の数がわかっているので、正しいサイズのリストを作成できます。これは、すでにはるかに高速であるはずです。

また、値にアクセスする方法はわかりませんが、これを作成してコードにイメージを保存することはできます(ディスクからのロードは、現在行っているよりも遅くなる可能性があります。これに対して何回読み取り/書き込みを行いますか事?


私は実際に通常の配列を事前に割り当ててみましたが、信じられないかもしれませんが、遅いです。上で述べたように、これはオンザフライで作成する必要があります。一度計算してそのままにしておくことはできません。
benpage 2015

本当に?うわー-あなたは最適化を有効にして実行していますか?(ただ尋ねる)
Carsten

ああ、それは別の問題です。通常の配列[x、y]は使いやすいですが、配列の配列の方が高速です。stackoverflow.com/questions/597720/…ILの
内部

5

これは、2つのループだけが必要な別の方法です。アイデアは、最初の要素を増やし、その数が超えた場合は次の要素を増やすことです。

データを表示する代わりに、currentValues.Cloneを使用して、その複製されたバージョンをリストに追加できます。私にとって、これはあなたのバージョンよりも速く実行されました。

byte[] maxValues = {2, 3, 4};
byte[] currentValues = {0, 0, 0};

do {
    Console.WriteLine("{0}, {1}, {2}", currentValues[0], currentValues[1], currentValues[2]);

    currentValues[0] += 1;

    for (int i = 0; i <= maxValues.Count - 2; i++) {
        if (currentValues[i] < maxValues[i]) {
            break;
        }

        currentValues[i] = 0;
        currentValues[i + 1] += 1;
    }

// Stop the whole thing if the last number is over
// } while (currentValues[currentValues.Length-1] < maxValues[maxValues.Length-1]);
} while (currentValues.Last() < maxValues.Last());
  • このコードが機能することを願って、私はそれをvbから変換しました

3

すべての数値はコンパイル時定数です。

すべてのループをリストに展開するのはどうですか(プログラムを使用してコードを記述します)。

data.Add(new [] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
data.Add(new [] {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
etc.

これにより、少なくともforループのオーバーヘッドが取り除かれるはずです(存在する場合)。

私はC#にあまり詳しくありませんが、オブジェクトをシリアル化する方法があるようです。そのリストを生成し、何らかの形式でシリアル化した場合はどうなりますか?ただし、逆シリアル化の方がリストを作成して要素を追加するよりも速いかどうかはわかりません。


シリアル化は、ボックスアプローチ以外では本当に素晴らしい考え方です。
Joel B

残念ながら、リストの最大値は動的であるため、静的に入力することはできません。でもいい考えです!
benpage 2015

2

結果を配列の配列にする必要がありますか?現在の設定では、内部配列の長さは固定されており、構造体に置き換えることができます。これにより、全体を1つの連続したメモリブロックとして予約できるようになり、要素へのアクセスが容易になります(後でこのことをどのように使用するかはわかりません)。

以下のアプローチははるかに高速です(私のボックスのオリジナルの1071msに対して41ms):

struct element {
    public byte a;
    public byte b;
    public byte c;
    public byte d;
    public byte e;
    public byte f;
    public byte g;
    public byte h;
    public byte i;
    public byte j;
    public byte k;
    public byte l;
    public byte m;
}

element[] WithStruct() {
    var t = new element[3981312];
    int z = 0;
    for (byte a = 0; a < 2; a++)
    for (byte b = 0; b < 3; b++)
    for (byte c = 0; c < 4; c++)
    for (byte d = 0; d < 3; d++)
    for (byte e = 0; e < 4; e++)
    for (byte f = 0; f < 3; f++)
    for (byte g = 0; g < 3; g++)
    for (byte h = 0; h < 4; h++)
    for (byte i = 0; i < 2; i++)
    for (byte j = 0; j < 4; j++)
    for (byte k = 0; k < 4; k++)
    for (byte l = 0; l < 3; l++)
    for (byte m = 0; m < 4; m++)
    {
        t[z].a = a;
        t[z].b = b;
        t[z].c = c;
        t[z].d = d;
        t[z].e = e;
        t[z].f = f;
        t[z].g = g;
        t[z].h = h;
        t[z].i = i;
        t[z].j = j;
        t[z].k = k;
        t[z].l = l;
        t[z].m = m;
        z++;
    }
    return t;
}

良いアイデア-実際、それは実際に私の実際のプロジェクトで行ったことです-単純さのために元のソリューションに入れなかっただけです。私は主に、ネストされたループのより良い代替手段を探していました。
benpage 2015

1

Parallel.For()それを実行するために使用するのはどうですか?(への構造体の最適化名声を@Caramiriel)。値を少し変更したので(aは2ではなく5)、結果に自信が持てるようになりました。

    var data = new ConcurrentStack<List<Bytes>>();
    var sw = new Stopwatch();

    sw.Start();

    Parallel.For(0, 5, () => new List<Bytes>(3*4*3*4*3*3*4*2*4*4*3*4),
      (a, loop, localList) => {
        var bytes = new Bytes();
        bytes.A = (byte) a;
        for (byte b = 0; b < 3; b++) {
          bytes.B = b;
          for (byte c = 0; c < 4; c++) {
            bytes.C = c; 
            for (byte d = 0; d < 3; d++) {
              bytes.D = d; 
              for (byte e = 0; e < 4; e++) {
                bytes.E = e; 
                for (byte f = 0; f < 3; f++) {
                  bytes.F = f; 
                  for (byte g = 0; g < 3; g++) {
                    bytes.G = g; 
                    for (byte h = 0; h < 4; h++) {
                      bytes.H = h; 
                      for (byte i = 0; i < 2; i++) {
                        bytes.I = i; 
                        for (byte j = 0; j < 4; j++) {
                          bytes.J = j; 
                          for (byte k = 0; k < 4; k++) {
                            bytes.K = k; 
                            for (byte l = 0; l < 3; l++) {
                              bytes.L = l;
                              for (byte m = 0; m < 4; m++) {
                                bytes.M = m;
                                localList.Add(bytes);
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }


        return localList;
      }, x => {
        data.Push(x);
    });

    var joinedData = _join(data);

_join() はプライベートメソッドであり、次のように定義されます。

private static IList<Bytes> _join(IEnumerable<IList<Bytes>> data) {
  var value = new List<Bytes>();
  foreach (var d in data) {
    value.AddRange(d);
  }
  return value;
}

私のシステムでは、このバージョンは約6倍高速に実行されます(1.718秒対0.266秒)。


1
これはあなたに偽共有を与えることをほぼ保証されており、おそらく何倍も遅くなるでしょう。
gjvdkamp 2015

悪くはありません-残念ながら、forループよりも実行速度遅くなります。FWIWALLで試してみましたParallel.For、VSがクラッシュしました!
benpage 2015

@gjvdkamp偽共有の問題が解消されると信じている並列バージョンで、回答を更新しました。
jdphenix 2015

0

あなたの数のいくつかは完全にビットの整数の数に収まるので、あなたはそれらを上位レベルの数で「詰める」ことができます:

for (byte lm = 0; lm < 12; lm++)
{
    ...
    t[z].l = (lm&12)>>2;
    t[z].m = lm&3;
    ...
}

もちろん、これによりコードが読みにくくなりますが、1つのループを節約できました。これは、数値の1つが2の累乗になるたびに実行できます。これは、あなたの場合は7倍です。


この答えについてもっと知りたいのですが、それについて詳しく教えていただけますか?
benpage 2015

回答が遅れ申し訳ございません。mは0から3になり、バイナリでは00から11になり、lは0から2になり、00から10になります。したがって、それらを別々に印刷すると、次のようになります。0000 00 01 00 10 00 11 01 00 .. .10 11これらを0000から1011までの4ビットの単一の数にマージし、マスクを使用して適切なビットを選択できます。lm&3はバイワイズになり、lmと(11)bの間でlm&12はlmと同じになります。 (1100)b次に、2ビットシフトして「実」数にします。ちなみに、この場合はlm >> 2で十分だと気づきました。
Fabien Dupont

0

これが別の解決策です。VSの外では、437.5ミリ秒の速度で実行されます。これは元のコード(私のコンピューターでは593.7)よりも26%高速です。

static List<byte[]> Combinations(byte[] maxs)
{
  int length = maxs.Length;
  int count = 1; // 3981312;
  Array.ForEach(maxs, m => count *= m);
  byte[][] data = new byte[count][];
  byte[] counters = new byte[length];

  for (int r = 0; r < count; r++)
  {
    byte[] row = new byte[length];
    for (int c = 0; c < length; c++)
      row[c] = counters[c];
    data[r] = row;

    for (int i = length - 1; i >= 0; i--)
    {
      counters[i]++;
      if (counters[i] == maxs[i])
        counters[i] = 0;
      else
        break;
    }
  }

  return data.ToList();
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.