web-dev-qa-db-ja.com

C / C ++で動的多次元配列を最適に処理するにはどうすればよいですか?

Cおよび/またはC++で動的(実行時まですべての次元がわからない)多次元配列を操作するために受け入れられている/最も一般的に使用されている方法は何ですか。

私はこのJavaコードが行うことを達成するための最もクリーンな方法を見つけようとしています:

public static void main(String[] args){
 Scanner sc=new Scanner(System.in);
 int rows=sc.nextInt();
 int cols=sc.nextInt();
 int[][] data=new int[rows][cols];
 manipulate(data);
}

public static void manipulate(int[][] data){
   for(int i=0;i<data.length;i++)
   for(int j=0;j<data[0].length.j++){
         System.out.print(data[i][j]);       
   }    
}

(std_inから読み取り、実行時までディメンションがわからないことを明確にします)。

編集:この質問はかなり古いものの、かなり人気があることに気づきました。私は実際にトップ投票の答えに同意しません。 Cの最良の選択は、Gugeが以下で述べたように、1次元配列を使用することだと思います。「行を割り当てることができますcols sizeof(int)そしてtable [row * cols + col]でアクセスできます。」 。

C++にはいくつかの選択肢があり、boostまたはstlが本当に好きな場合は、以下の答えが望ましいかもしれませんが、最も簡単でおそらく最速の選択肢は、Cのように1次元配列を使用することです。

[] []構文が必要な場合のCおよびC++でのもう1つの実行可能な選択肢は、下部にあるlillqの答えは、多数のmallocを使用して配列を手動で構築することです。

25
Ari Ronen

boost :: multi_arrayを使用します。

あなたの例のように、コンパイル時に知る必要があるのは次元の数だけです。これがドキュメントの最初の例です:

#include "boost/multi_array.hpp"
#include <cassert>

int 
main () {
  // Create a 3D array that is 3 x 4 x 2
  typedef boost::multi_array<double, 3> array_type;
  typedef array_type::index index;
  array_type A(boost::extents[3][4][2]);

  // Assign values to the elements
  int values = 0;
  for(index i = 0; i != 3; ++i) 
    for(index j = 0; j != 4; ++j)
      for(index k = 0; k != 2; ++k)
        A[i][j][k] = values++;

  // Verify values
  int verify = 0;
  for(index i = 0; i != 3; ++i) 
    for(index j = 0; j != 4; ++j)
      for(index k = 0; k != 2; ++k)
        assert(A[i][j][k] == verify++);

  return 0;
}

編集:コメントで示唆されているように、 これは「単純な」例です コンソール入力から要求して、実行時に多次元配列サイズを定義できるアプリケーション。これは、このサンプルアプリケーションの出力例です(3次元であるという定数でコンパイルされています):

Multi-Array test!
Please enter the size of the dimension 0 : 4

Please enter the size of the dimension 1 : 6

Please enter the size of the dimension 2 : 2

Text matrix with 3 dimensions of size (4,6,2) have been created.

Ready!
Type 'help' for the command list.

>read 0.0.0
Text at (0,0,0) :
  ""

>write 0.0.0 "This is a Nice test!"
Text "This is a Nice test!" written at position (0,0,0)

>read 0.0.0
Text at (0,0,0) :
  "This is a Nice test!"

>write 0,0,1 "What a Nice day!"
Text "What a Nice day!" written at position (0,0,1)

>read 0.0.0
Text at (0,0,0) :
  "This is a Nice test!"

>read 0.0.1
Text at (0,0,1) :
  "What a Nice day!"

>write 3,5,1 "This is the last text!"
Text "This is the last text!" written at position (3,5,1)

>read 3,5,1
Text at (3,5,1) :
  "This is the last text!"

>exit

コードの重要な部分は、ユーザーからディメンションを取得し、次のように配列を作成するメイン関数です。

const unsigned int DIMENSION_COUNT = 3; // dimension count for this test application, change it at will :)

// here is the type of the multi-dimensional (DIMENSION_COUNT dimensions here) array we want to use
// for this example, it own texts
typedef boost::multi_array< std::string , DIMENSION_COUNT > TextMatrix;

// this provide size/index based position for a TextMatrix entry.
typedef std::tr1::array<TextMatrix::index, DIMENSION_COUNT> Position; // note that it can be a boost::array or a simple array

/*  This function will allow the user to manipulate the created array
    by managing it's commands.
    Returns true if the exit command have been called.
*/
bool process_command( const std::string& entry, TextMatrix& text_matrix );

/* Print the position values in the standard output. */
void display_position( const Position& position );

int main()
{
    std::cout << "Multi-Array test!" << std::endl;

    // get the dimension informations from the user
    Position dimensions; // this array will hold the size of each dimension 

    for( int dimension_idx = 0; dimension_idx < DIMENSION_COUNT; ++dimension_idx )
    {
        std::cout << "Please enter the size of the dimension "<< dimension_idx <<" : ";
        // note that here we should check the type of the entry, but it's a simple example so lets assume we take good numbers
        std::cin >> dimensions[dimension_idx]; 
        std::cout << std::endl;

    }

    // now create the multi-dimensional array with the previously collected informations
    TextMatrix text_matrix( dimensions );

    std::cout << "Text matrix with " << DIMENSION_COUNT << " dimensions of size ";
    display_position( dimensions );
    std::cout << " have been created."<< std::endl;
    std::cout << std::endl;
    std::cout << "Ready!" << std::endl;
    std::cout << "Type 'help' for the command list." << std::endl;
    std::cin.sync();


    // we can now play with it as long as we want
    bool wants_to_exit = false;
    while( !wants_to_exit )
    {
        std::cout << std::endl << ">" ;
        std::tr1::array< char, 256 > entry_buffer; 
        std::cin.getline(entry_buffer.data(), entry_buffer.size());

        const std::string entry( entry_buffer.data() );
        wants_to_exit = process_command( entry, text_matrix );
    }

    return 0;
}

そして、配列内の要素にアクセスするのは本当に簡単であることがわかります。次の関数のようにoperator()を使用するだけです。

void write_in_text_matrix( TextMatrix& text_matrix, const Position& position, const std::string& text )
{
    text_matrix( position ) = text;
    std::cout << "Text \"" << text << "\" written at position ";
    display_position( position );
    std::cout << std::endl;
}

void read_from_text_matrix( const TextMatrix& text_matrix, const Position& position )
{
    const std::string& text = text_matrix( position );
    std::cout << "Text at ";
    display_position(position);
    std::cout << " : "<< std::endl;
    std::cout << "  \"" << text << "\"" << std::endl;
}

注:このアプリケーションをVC9 + SP1でコンパイルしました-忘れられがちな警告がいくつか表示されました。

21
Klaim

C++で2次元配列を表すには2つの方法があります。一方は他方よりも柔軟性があります。

配列の配列

最初にポインタの配列を作成し、次に各ポインタを別の配列で初期化します。

// First dimension
int** array = new int*[3];
for( int i = 0; i < 3; ++i )
{
    // Second dimension
    array[i] = new int[4];
}

// You can then access your array data with
for( int i = 0; i < 3; ++i )
{
    for( int j = 0; j < 4; ++j )
    {
        std::cout << array[i][j];
    }
}

この方法の問題は、2番目の次元に多くの配列が割り当てられるため、メモリアロケータの作業が容易にならないことです。メモリが断片化され、パフォーマンスが低下する可能性があります。ただし、2次元の各配列のサイズが異なる可能性があるため、柔軟性が向上します。

すべての値を保持する大きな配列

ここでの秘訣は、必要なすべてのデータを保持するための大規模な配列を作成することです。難しいのは、array [i] [j]構文を使用してデータにアクセスできるようにする場合は、ポインターの最初の配列が必要になることです。

int* buffer = new int[3*4];   
int** array = new int*[3];

for( int i = 0; i < 3; ++i )
{
    array[i] = array + i * 4;
}

値の2次元座標からバッファー内のインデックスを計算することにより、バッファー内のデータに直接アクセスできるため、int *配列は必須ではありません。

// You can then access your array data with
for( int i = 0; i < 3; ++i )
{
    for( int j = 0; j < 4; ++j )
    {
        const int index = i * 4 + j;
        std::cout << buffer[index];
    }
}

覚えておくべきルール

コンピュータのメモリは線形であり、それでも長い間存在します。 2次元配列はコンピューターでネイティブにサポートされていないため、唯一の方法は配列を1次元配列に「線形化」することです。

8
Vincent Robert

行を割り当てることができますcols sizeof(int)そしてtable [row * cols + col]でアクセスできます。

5
Guge

ブーストを使用しない標準的な方法は、std :: vector:を使用することです。

_std::vector< std::vector<int> > v;
v.resize(rows, std::vector<int>(cols, 42)); // init value is 42
v[row][col] = ...;
_

これにより、必要なメモリが自動的に新規/削除されます。しかし、_std::vector_は主にそのように使用するように設計されていないため(_std::vector_を相互にネストする)、かなり低速です。たとえば、すべてのメモリが1つのブロックに割り当てられるのではなく、列ごとに個別に割り当てられます。また、行はすべて同じ幅である必要はありません。 Fasterは、法線ベクトルを使用し、_col_count * row + col_のようなインデックス計算を実行して、特定の行と列を取得します。

_std::vector<int> v(col_count * row_count, 42);
v[col_count * row + col) = ...;
_

ただし、これにより、_[x][y]_を使用してベクトルにインデックスを付ける機能が失われます。また、行と列の量をどこかに格納する必要がありますが、ネストされたソリューションを使用すると、v.size()を使用して行の量を取得し、v[0].size()を使用して列の量を取得できます。

ブーストを使用すると、_boost::multi_array_を使用できます。これは、希望どおりの動作をします(他の回答を参照)。


ネイティブC++配列を使用する生の方法もあります。これにはかなりの作業が必要であり、ネストされたベクトルソリューションよりも優れているわけではありません。

_int ** rows = new int*[row_count];
for(std::size_t i = 0; i < row_count; i++) {
    rows[i] = new int[cols_count];
    std::fill(rows[i], rows[i] + cols_count, 42);
}

// use it... rows[row][col] then free it...

for(std::size_t i = 0; i < row_count; i++) {
    delete[] rows[i];
}

delete[] rows;
_

ポインタから受け取ることができないため、作成した列と行の量をどこかに保存する必要があります。

これをCで行う簡単な方法は次のとおりです。

void manipulate(int rows, int cols, int (*data)[cols]) {
    for(int i=0; i < rows; i++) {
        for(int j=0; j < cols; j++) {
            printf("%d ", data[i][j]);       
        }
        printf("\n");
    }
}

int main() {
    int rows = ...;
    int cols = ...;
    int (*data)[cols] = malloc(rows*sizeof(*data));
    manipulate(rows, cols, data);
    free(data);
}

これはC99以降完全に有効ですが、標準のC++ではありません。C++では配列型のサイズをコンパイル時の定数にする必要があります。その点で、C++は現在Cより15年遅れています。そして、この状況はすぐに変わることはありません(C++ 17の可変長配列の提案はC99可変長配列の機能に近づきません)。

4
cmaster

CおよびC++の2DCスタイルの配列は、サイズがrows * columns * sizeof(datatype)バイトのメモリのブロックです。

実際の[行] [列]ディメンションは、コンパイル時に静的にのみ存在します。実行時に動的に何もありません!

だから、他の人が言ったように、あなたは実装することができます

  int array [ rows ] [ columns ];

なので:

 int  array [ rows * columns ]

またはとして:

 int * array = malloc ( rows * columns * sizeof(int) );

次へ:可変サイズの配列を宣言します。 Cの場合これは可能です:

int main( int argc, char ** argv )
{
  assert( argc > 2 );

  int rows    = atoi( argv[1] );
  int columns = atoi( argv[2] );

  assert(rows > 0 && columns > 0);
  int data [ rows ] [ columns ];  // Yes, legal!

  memset( &data, 0, sizeof(data) );

  print( rows, columns, data );
  manipulate( rows, columns, data );
  print( rows, columns, data );
}

Cの場合可変サイズの配列を非可変サイズの配列とほぼ同じように渡すことができます。

void manipulate( int theRows, int theColumns, int theData[theRows][theColumns] )
{
  for (   int r = 0; r < theRows;    r ++ )
    for ( int c = 0; c < theColumns; c ++  )
      theData[r][c] = r*10 + c;
}

ただし、C++ではそれは不可能です。動的割り当てを使用してアレイを割り当てる必要があります。例:

int *array = new int[rows * cols]();

またはできれば(自動メモリ管理を使用)

std::vector<int> array(rows * cols);

次に、1次元データを受け入れるように関数を変更する必要があります。

void manipulate( int theRows, int theColumns, int *theData )
{
  for (   int r = 0; r < theRows;    r ++ )
    for ( int c = 0; c < theColumns; c ++  )
      theData[r * theColumns + c] = r*10 + c;
}
3
Mr.Ree

C++の代わりにCを使用している場合は、Dave HansonのライブラリのArray_T抽象化を確認することをお勧めします Cインターフェイスと実装 。それは非常にきれいで、よく設計されています。演習として、生徒に2次元バージョンを実行してもらいます。あなたはそれをするか、単にインデックスマッピングを行う追加の関数を書くことができます、例えば、

void *Array_get_2d(Array_T a, int width, int height, int i, int j) {
    return Array_get(a, j * width, i, j);
}

幅、高さ、および要素へのポインタを格納する別個の構造を持つ方が少しクリーンです。

2
Norman Ramsey

私は最近、同様の問題に遭遇しました。 Boostを利用できませんでした。ベクトルのベクトルは、単純な配列と比較してかなり遅いことが判明しました。ポインターの配列があると、初期化が非常に面倒になります。これは、すべての次元を反復処理してポインターを初期化する必要があるためです。プロセス内にかなり扱いにくいカスケード型があり、typedefが多数ある可能性があります。

免責事項:これはあなたの質問の一部にしか答えないため、これを回答として投稿する必要があるかどうかはわかりませんでした。以下についてお詫び申し上げます。

  • 他のコメンテーターが述べたように、標準入力から寸法を読み取る方法については説明しませんでした。
  • これは主にC++用です。
  • このソリューションは2次元でのみコーディングしました。

とにかくこれを投稿することにしました。C++の多次元配列に関する質問に答えて、ベクトルのベクトルが頻繁に表示されるのを見て、パフォーマンスの側面については誰も言及していません(気になる場合)。

また、この質問の中心的な問題は、質問のJavaの例と同じように簡単に、つまり面倒なことなく使用できる動的な多次元配列を取得する方法に関するものであると解釈しました。疑似多次元1次元配列を使用してインデックスを計算する必要があります。

静的境界の場合と同じように動的境界を持つ多次元配列を宣言するためにGCC/G ++によって提供されるもののように、他の回答で言及されているコンパイラ拡張は見られませんでした。私が理解していることから、この質問は回答を標準のC/C++に限定するものではありません。 ISO C99は明らかにそれらをサポートしていますが、C++およびそれ以前のバージョンのCでは、コンパイラ固有の拡張機能であるように見えます。この質問を参照してください: mallocなしのCの動的配列?

コードが小さく、組み込みの静的多次元配列が使いやすく、同じくらい高速であるため、人々がC++を好む方法を思いつきました。

template <typename T> 
class Array2D {
private:
    std::unique_ptr<T> managed_array_;
    T* array_;
    size_t x_, y_;

public:
    Array2D(size_t x, size_t y) {
        managed_array_.reset(new T[x * y]);
        array_ = managed_array_.get();
        y_ = y;
    }
    T* operator[](size_t x) const {
        return &array_[x * y_];
    }
};

このように使えます。寸法はありません

auto a = Array2D<int>(x, y);
a[xi][yi] = 42;

少なくとも最後の次元を除くすべてにアサーションを追加し、アイデアを3つ以上の次元に拡張できます。多次元配列を取得する別の方法についてブログに投稿しました。また、相対的なパフォーマンスとコーディングの取り組みについても、より具体的に説明します。

C++での動的多次元配列のパフォーマンス

1
user2880576

これを実現するためにmallocを使用しても、通常のarray [] []平均を介してアクセスできるようにすることができます。これは、array [rows * cols + cols]メソッドとは異なります。

main()
{
   int i;
   int rows;
   int cols;
   int **array = NULL;

   array = malloc(sizeof(int*) * rows);
   if (array == NULL)
       return 0;  // check for malloc fail

   for (i = 0; i < rows; i++)
   {
       array[i] = malloc(sizeof(int) * cols)
       if (array[i] == NULL)
           return 0;  // check for malloc fail
   }

   // and now you have a dynamically sized array
}
0
lillq

C++では特定の配列の長さを決定する方法はありません。おそらく最良の方法は、配列の各次元の長さを渡し、配列自体の.lengthプロパティの代わりにそれを使用することです。

0
Andy