8

したがってすべてのアレイが同じように作成されているわけではないことがわかります。多次元配列は、ゼロ以外の下限を持つことができます。たとえば、ExcelPIAのRange.Valueプロパティを参照してくださいobject[,] rectData = myRange.Value;

これらのデータをギザギザの配列に変換する必要があります。以下の私の最初の試みは、複雑さの匂いがします。それを最適化するための提案はありますか?下限がゼロでない可能性がある一般的なケースを処理する必要があります。

私はこのexメソッドを持っています:

    public static T[][] AsJagged<T>( this T[,] rect )
    {
        int row1 = rect.GetLowerBound(0);
        int rowN = rect.GetUpperBound(0);
        int col1 = rect.GetLowerBound(1);
        int colN = rect.GetUpperBound(1);

        int height = rowN - row1 + 1;
        int width = colN - col1 + 1;
        T[][] jagged = new T[height][];

        int k = 0;
        int l;
        for ( int i = row1; i < row1 + height; i++ )
        {
            l = 0;
            T[] temp = new T[width];
            for ( int j = col1; j < col1 + width; j++ )
                temp[l++] = rect[i, j];
            jagged[k++] = temp;
        }

        return jagged;
    }

このように使用されます:

    public void Foo()
    {
        int[,] iRect1 = { { 1, 1, 1, 1 }, { 1, 1, 1, 1 }, { 1, 1, 1, 1 }, { 1, 1, 1, 1 }, { 1, 1, 1, 1 }, { 1, 1, 1, 1 }, { 1, 1, 1, 1 }, { 1, 1, 1, 1 } };
        int[][] iJagged1 = iRect1.AsJagged();

        int[] lengths = { 3, 5 };
        int[] lowerBounds = { 7, 8 };
        int[,] iRect2 = (int[,])Array.CreateInstance(typeof(int), lengths, lowerBounds);
        int[][] iJagged2 = iRect2.AsJagged();

    }

Buffer.BlockCopy ()が機能するのか、それとも高速になるのか知りたいですか?

編集:AsJaggedは参照型を処理する必要があります。

編集:AsJagged()でバグが見つかりました。追加int l; col1 + width内側のループに追加されます。

4

2 に答える 2

7

ビューの警告/前提条件を前もって:

  • あなたはあなたのデータ型としてのみ使用しているようintです(または、少なくとも使用しても問題ないようですBuffer.BlockCopy。これは、一般的にプリミティブ型で生活できることを意味します)。
  • あなたが示したテストデータについては、多少まともなアプローチを使用しても大きな違いはないと思います。

そうは言っても、次の実装 ( を使用するため、特定のプリミティブ型 (ここintでは )に特化する必要がありますfixed) は、内側のループを使用するアプローチよりも約 10 倍高速です。

    unsafe public static int[][] AsJagged2(int[,] rect)
    {
        int row1 = rect.GetLowerBound(0);
        int rowN = rect.GetUpperBound(0);
        int col1 = rect.GetLowerBound(1);
        int colN = rect.GetUpperBound(1);

        int height = rowN - row1 + 1;
        int width = colN - col1 + 1;
        int[][] jagged = new int[height][];

        int k = 0;
        for (int i = row1; i < row1 + height; i++)
        {
            int[] temp = new int[width];

            fixed (int *dest = temp, src = &rect[i, col1])
            {
                MoveMemory(dest, src, rowN * sizeof(int));
            }

            jagged[k++] = temp;
        }

        return jagged;
    }

    [DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")]
    unsafe internal static extern void MoveMemory(void* dest, void* src, int length);

次の「テスト コード」を使用します。

    static void Main(string[] args)
    {
        Random rand = new Random();
        int[,] data = new int[100,1000];
        for (int i = 0; i < data.GetLength(0); i++)
        {
            for (int j = 0; j < data.GetLength(1); j++)
            {
                data[i, j] = rand.Next(0, 1000);
            }
        }

        Stopwatch sw = Stopwatch.StartNew();

        for (int i = 0; i < 100; i++)
        {
            int[][] dataJagged = AsJagged(data);
        }

        Console.WriteLine("AsJagged:  " + sw.Elapsed);

        sw = Stopwatch.StartNew();

        for (int i = 0; i < 100; i++)
        {
            int[][] dataJagged2 = AsJagged2(data);
        }

        Console.WriteLine("AsJagged2: " + sw.Elapsed);
    }

(最初のケース) が元の関数である場合AsJagged、次の出力が得られます。

AsJagged:  00:00:00.9504376
AsJagged2: 00:00:00.0860492

したがって、実際にはより高速な方法がありますが、テスト データのサイズ、実際にこの操作を実行する回数、および安全でないコードと P/Invoke コードを許可する意思があるかどうかによっては、おそらくそうするつもりはありません。それが必要です。

そうは言っても、実際に大きな違いを生んだ大規模なマトリックスdouble(たとえば 7000x10000 要素) を使用していました。

更新: Buffer.BlockCopy の使用について

何かのトリックを見落としているかもしれませんMarshalが、ここでは使用できないと思いBuffer.BlockCopyます。これは、ソース配列と宛先配列の両方がArray.

この例では、宛先は配列 (例: int[] temp = ...) ですが、ソースはそうではありません。プリミティブ型の 2 次元配列では、各「行」(つまり、最初の次元) がメモリ内の型の配列であることを「知っています」が、unsafeその配列を取得する安全な (のような) 方法はありません。最初にコピーするオーバーヘッドはありません。したがって、基本的には、単にメモリを処理し、その実際の内容を気にしない関数を使用する必要がありますMoveMemory。ところで、 の内部実装もBuffer.BlockCopy同様のことを行います。

于 2012-02-20T07:19:38.243 に答える
6

あなたの複雑さは O(N*M) N - 行数、M - 列数です。これは、N*M 値をコピーするときに得られる最高のものです...

Buffer.BlockCopy は内部の for ループよりも高速かもしれませんが、コンパイラがこのコードを適切に処理する方法を知っていて、それ以上速度が向上しない場合でも、私は驚かないでしょう。確認するためにテストする必要があります。

データをまったくコピーしないことで、パフォーマンスを向上させることができる場合があります(ルックアップが少し遅くなる可能性があります)。rectと行番号を保持し、正しい列にアクセスするインデクサーを提供する「配列行」クラスを作成すると、そのような行の配列を作成して、コピーを完全に節約できます。

このような「配列行」の配列を作成する複雑さは O(N) です。

編集: ArrayRow クラス、それは私を悩ませているからです...

ArrayRow は次のようになります。

class ArrayRow<T>
{
    private T[,] _source;
    private int _row;

    public ArrayRow(T[,] rect, int row)
    {
         _source = rect;
         _row = row;
    }

    public T this[int col] { get { return _source[_row, col]; } }
}

ここで、ArrayRows の配列を作成します。何もコピーしません。オプティマイザーは、行全体へのアクセスを順番に最適化する可能性が高くなります。

于 2012-02-20T06:45:37.527 に答える