動的配列の件

解決


yTake  2022-07-31 12:19:34  No: 150317  IP: [192.*.*.*]

別のスレッドでMr.XRAYさんに
コメント頂けていたのにすみませんでした。
ここに引き継がせて頂きました。

動的配列は各行が独立した一次元の配列の集合という事の例示と受け止めています。

Mr.XRAYさんのサンプルに習い、試しに、次の様にFArrayの要素を代入してみました。
  k :=  1 ;
  for j := 0 to M - 1 do
      for i := 0 to N - 1 do
      begin
          FArray[ i, j ]  :=  k ;
          inc( k );
      end;
但し、M=3、N=4としています。

これをサンプル通りにLStreamへ書き込みました。

このLStreamからシーケンシャルに読みだした場合と、配列で読みだした場合とを比べてみよう思いましたが、配列でのアクセスでうまくゆきません。

先ず、LStreeamへFArrayを書き出した後では、ポインターの現在地が書き込み後の位置のままなので、Seekで先頭に戻す必要があると思います。
しかし、Seek( 0, TSeekOrigin.soBeginning )では何故か正しく要素にアクセスできません。
Seek( -cnt * N, soTSeekOrigin.soCurrent)とすると各要素にシーケンシャルにはアクセス出来ました。

ただ、配列として(行・列で)アクセスする場合、やはり先頭からのオフセットで考慮するので、soBeginningを使う方が良いと考えられますが、正しく要素にアクセスできていません。

シーケンシャルに配列要素へアクセスした場合の結果は、
buf1[0]   1
buf1[1]   5
buf1[2]   9
buf1[3]   2
buf1[4]   6
buf1[5]   10
buf1[6]   3
buf1[7]   7
buf1[8]   11
buf1[9]   4
buf1[10]   8
buf1[11]   12
となりますが、
配列として、アクセスした場合では、いずれも"1.7765824089018e-307"の様な値になっています。

試用したコードは以下の通り:
追加の変数
    buf1    : Array of Double;
    cnt     : Word;
    buf2    : Array of Array of Double;

主なコード
  M :=  3 ;
  N :=  4 ;
  SetLength(FArray, N, M);
  SetLength( buf1, N * M );
  SetLength( buf2, N, M );

  k :=  1 ;
  for j := 0 to M - 1 do
      for i := 0 to N - 1 do
      begin
          FArray[ i, j ]  :=  k ;
          inc( k );
      end;

   LStream := TMemoryStream.Create;
  try
    LStream.WriteData(N);
    LStream.WriteData(M);
    for LRowIdx := 0 to N - 1 do
    begin
        cnt :=  LStream.Write(FArray[LRowIdx][0], M * SizeOf(Double));
    end;

//シーケンシャルにアクセス
    LStream.Seek( -cnt * N, TSeekOrigin.soCurrent );

    for k := 0 to N * M - 1 do
        LStream.Read( buf1[ k ], SizeOf( Double ));

//配列として行・列でアクセス
    i :=  1 ; j :=  2 ;
    LStream.Seek( Sizeof( Double ) * i + M * j, TSeekOrigin.soBeginning );
    LStream.Read( buf2[ i, j ], Sizeof( Double ));

    i :=  3 ; j :=  0 ;
    LStream.Seek( Sizeof( Double ) * i + M * j, TSeekOrigin.soBeginning );
    LStream.Read( buf2[ i, j ], Sizeof( Double ));

    i :=  0 ; j :=  1 ;
    LStream.Seek( Sizeof( Double ) * i + M * j, TSeekOrigin.soBeginning );
    LStream.Read( buf2[ i, j ], Sizeof( Double ));

    i :=  2 ; j :=  1 ;
    LStream.Seek( Sizeof( Double ) * i + M * j, TSeekOrigin.soBeginning );
    LStream.Read( buf2[ i, j ], Sizeof( Double ));

です。

配列として正しくアクセスするにはどの様にするにでしょう?

編集    削除
Mr.XRAY  2022-07-31 19:20:01  No: 150319  IP: [192.*.*.*]

2 次元の動的配列は,各行が 1 次元の動的配列です.
動的配列はポインタです.つまり,2 次元の動的配列は,ポインタのポインタです.
つまり,各行は独立した配列です.
そのため,メモリストリムに書き込む時は行単位 (配列単位) でないと正しい値は書き込めません..

当然ながら,メモリストリームから配列に読み込む時も行単位 (配列単位) で読み込みます.
行単位 (配列単位) で読み込まないと正しい値にはなりません.

静的配列は,全てのデータがメモリ上で連続しています.
1 行目の最後の要素のデータの次は 2 行目の最初のデータです.
  

編集    削除
Mr.XRAY  2022-08-01 07:18:51  No: 150320  IP: [192.*.*.*]

> 当然ながら,メモリストリームから配列に読み込む時も行単位 (配列単位) で読み込みます. 

これは適切な表現ではありませんでした.
しかし,多くの場合,配列の全ての要素を取得してから各種の演算に使用することを考えると,
行単位で処理してしまう方がコーディングもスッキリします.

> つまり,各行は独立した配列です. 

ですから,実際には指定要素だけの取得もできます.

    // 主なコード
    LMemStream.Position := 0;
    LMemStream.ReadData(LRowCount);
    LMemStream.ReadData(LColCount);
    
    // 配列の行数と列数を設定 (配列用のメモリの確保)
    SetLength(LDstDblArray, LRowCount, LColCount);

    // メモリストリームからの読み出し位置
    LRow := 1;
    LCol := 2;
    LMemStream.Position := SizeOf(Double) * (LColCount * LRow + LCol)
                         + SizeOf(Integer) * 2;
                         
    // メモリストリームのデータを配列要素に読み込む
    LMemStream.Read(LDstDblArray[LRow][LCol], SizeOf(Double));
   

編集    削除
yTake  2022-08-01 19:19:48  No: 150321  IP: [192.*.*.*]

解説をありがとうございます。

考え方は分かった様に思いますが、何故か配列としてアクセスできていません。

分からない点は、”ストリームからの読み出し位置”の後半部分で”+ SizeOf(Integer) * 2”のところです。
前半部分は配列要素の位置(LRow,LCol)(行,列)を示している様に思います。(前半部分のみで良い様に思えます)

例えば、3行1列目のデータへは、
    j := 3;
    i := 1;
    LStream.Position := SizeOf( Double ) * ( N * j + i )
                         + SizeOf( Integer ) * 2;

    LStream.Read( LDstDblArray[ j ][ i ], SizeOf( Double ));
この様にアクセスすると思いますが、入力した値が格納されていません。

何か、私の解釈が根本的にズレている様に思います。
ストリームへ正しく書き出せていなければ、正しくアクセス出来ていても値は異なってしまいます。

編集    削除
Mr.XRAY  2022-08-01 20:15:40  No: 150322  IP: [192.*.*.*]

> 分からない点は、”ストリームからの読み出し位置”の後半部分で”+ SizeOf(Integer) * 2”のところです。 

最初の書き込みで提示されたコードでは,
最初に,メモリストリームから,行数と例数の値を読み取っています.
配列のデータはその後にあります.
したがって,その分のオフセットです.
行数と列数を格納する型を Integer とした場合は SizeOf(Integer) * 2 となります.

> 例えば、3行1列目のデータへは、
>     j := 3;
>     i := 1; 

3 行目の 1 列目は,2, 0 です.
動的配列の要素番号は,常に 0 から始まります ( 0 ベースと言います).
  

編集    削除
Mr.XRAY  2022-08-02 21:59:31  No: 150324  IP: [192.*.*.*]

> 配列のデータはその後にあります.
> したがって,その分のオフセットです.

配列のデータを読み出すには,その分,読み出し開始位置を調整する必要があります.
そのような値のことをオフセットと言います.
そのオフセットの値のことです.

編集    削除
AAAAA  2022-08-03 16:51:55  No: 150331  IP: [192.*.*.*]

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
    TD = Record
      B1: Integer;
      B2: Double;
      B3: Byte;
    End;
type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;
  P: Pointer;

implementation

{$R *.dfm}

//保存
procedure TForm1.Button1Click(Sender: TObject);
var
    I,J,K: Integer;
    D: array of array of array of TD;
    FileStream: TFileStream;
begin
  SetLength(D, 10, 10, 10 );

  for I:=0 to 9 do
  begin
    for J:=0 to 9 do
    begin
      for K:=0 to 9 do
      begin
        D[I,J,K].B1 := I;
        D[I,J,K].B2 := J + 0.1;
        D[I,J,K].B3 := K;
      end;
    end;
  end;

  FileStream := TFileStream.Create('AA.TXT',fmCreate);

  for I:=0 to 9 do
  begin
    for J:=0 to 9 do
    begin
      for K:=0 to 9 do
      begin
        FileStream.WriteData(D[I,J,K]);
      end;
    end;
  end;

  FileStream.Free




end;

//読み込み
procedure TForm1.Button2Click(Sender: TObject);
var
    I,J,K: Integer;
    D: array of array of array of TD;
    FileStream: TFileStream;
    S: String;
begin
  SetLength(D, 10, 10, 10 );

  for I:=0 to 9 do
  begin
    for J:=0 to 9 do
    begin
      for K:=0 to 9 do
      begin
        D[I,J,K].B1 := 0;
        D[I,J,K].B2 := 0;
        D[I,J,K].B3 := 0;
      end;
    end;
  end;

  FileStream := TFileStream.Create('AA.TXT',fmOpenRead);

  for I:=0 to 9 do
  begin
    for J:=0 to 9 do
    begin
      for K:=0 to 9 do
      begin
        FileStream.ReadData(D[I,J,K]);
      end;
    end;
  end;

  FileStream.Free;

  for I:=0 to 9 do
  begin
    for J:=0 to 9 do
    begin
      S := '';
      for K:=0 to 9 do
      begin
        S := S + IntToStr(D[I,J,K].B1) + FloatToStr(D[I,J,K].B2) + IntToStr(D[I,J,K].B3) + ' ';
      end;
      Memo1.Lines.Add(S);
    end;
  end;

end;

end.

編集    削除
yTake  2022-08-03 19:57:28  No: 150334  IP: [192.*.*.*]

AAAAAさん、ありがとうございます。

試させて頂きました。
しかしながら、
[dcc32 エラー] Unit1.pas(108): E2250 指定された引数で呼び出すことのできるオーバーロード関数 'ReadData' が定義されていません
が生じます。
WriteData、ReadDataはいずれも引数を二つとる様で、各々

        FileStream.WriteData( D[ I, J, K ], Sizeof( TD ));
        FileStream.ReadData( D[ I, J, K ], Sizeof( TD ));

とさせて頂きましたが、依然として同じエラーが出てしまいます。

なお、こちらはFMXで試しています。FMXではFileSteramの使い方が違うのでしょうか?


一見、問題なさそうに思えるのですが、エラーは解消されません。

編集    削除
AAAAA  2022-08-04 01:56:20  No: 150335  IP: [192.*.*.*]

FMXでも 
FileStream.ReadData(D[I,J,K]);
FileStream.WriteData(D[I,J,K]);
でいけるね

FileStream.ReadData(D[I,J,K],SizeOf(TD));
FileStream.WriteData(D[I,J,K],SizeOf(TD));
でもいけたけど

Delphi 11 だけど
XE6 だと無理なのか

同じエラーって何?



        FileStream.Read(D[I,J,K],SizeOf(TD));

編集    削除
AAAAA  2022-08-05 03:03:48  No: 150338  IP: [192.*.*.*]

XE6だと

FileStream.Write(D[I,J,K],SizeOf(TD));
FileStream.Read(D[I,J,K],SizeOf(TD));

かな

編集    削除
yTake  2023-11-10 10:49:48  No: 151257  IP: [192.*.*.*]

本件、宙ぶらりんとなっていました。すみません。

趣旨が少し変わってしまうかも知れませんが、
fs  :  TFileStream;
ptr2 : PWord;
ptr4 : PSingle;
i, j, k, x, y, z  :  Word;
cnt, size :  LongWord;
begin
  x  :=  マトリックスのXサイズ;
  y  :=  マトリックスのYサイズ;
  z  :=  マトリックスのZサイズ;
  if  データ型=Word  then
  begin
    size := Sizeof(Word)*x*y*z:
    GetMem(ptr2,size);
    cnt := fs.Read(ptr2^,size);
    for  k := 0  to  z -1  do
      for  j := 0  to  y -1  do
        for  i := 0  to  x -1  do
        begin
          mtx[i,j,k].data2  :=  ptr2^;
          inc(ptr2);
        end; 
  end
  else if データ型=Single  then
  begin
    size := Sizeof(Single)*x*y*z:
    GetMem(ptr4,size);
    cnt := fs.Read(ptr4^,size);
    for  k := 0  to  z -1  do
      for  j := 0  to  y -1  do
        for  i := 0  to  x -1  do
        begin
          mtx[i,j,k].data4  :=  ptr4^;
          inc(ptr4);
        end; 
  end;
end;

で、読み出せています。(別件でこの項を示唆頂けた内容を少し含んでいます)

この項、締めさせて頂きます。

編集    削除