TListでの二次元配列クラスの作成

解決


maco  2004-05-21 23:02:56  No: 9069

いつもお世話になります。macoです。

レコードのポインタ型を格納したTListを、
更にTListに格納するクラスを作成しています。

イメージとして、
「値 := CellList.Items[x].Items[y].メンバ;」で値を取り出したいのです。

(補足)
  Itemsをdefaultプロパティに設定して、
  「値 := CellList[x][y].メンバ;」と値を管理したい。

しかし、レコードのポインタ型を格納する時点で、
無限ループが発生してしまい、上手く動作しません。

「Self」や「default」の使い方がまずいのかと思い、
色々試してはみたのですが、解決できませんでした。

どなたかお知恵を貸して下さい。宜しくお願いします。

次発言にコードをつけます。


maco  2004-05-21 23:03:15  No: 9070

type
  PRec = ^TRecord;
  TRec = record
    メンバ
end;

type
  TPList = class(TList) //PRecを格納するリスト
  private
    function GetP(Index: Integer): PRec;
    procedure PutP(Index: Integer; const Value: PRec);
  public
    property Items[Index : Integer] : PRec Read GetP Write PutP; default;
    // ↑ ItemsでPRecにアクセスする

type
  TCellList = Class(TList) //TPListを格納するリスト ■クラスの本体
  private
    XList : TPList;
  public
    constructor Create;
    property Items[Index : Integer] : TPList Read Get Write Put; default;
    // ↑ ItemsでPRecを格納したTPListにアクセスする
end;

implementation

//-------------{ TCellList }-----------------

constructor TCellList.Create;
var
  Rec : PRec;
  X,Y : Integer;
begin
  for X := 0 to 9 do begin
    XList := TPList.Create;
    XList.Count := 10;
    for Y := 0 to 9 do begin
      new(Rec);
      Rec^.メンバ := 値;
      XList.Items[j] := Rec; // (1).ここで(2)のPutPメソッドが実行される
    end;
  end;
end;

procedure TCellList.Put(Index: Integer; const Value: TPList);
begin
  TLA(Self).Items[Index] := Value;
end;

function TCellList.Get(Index: Integer): TPList;
begin
  Result := inherited Get(Index);
end;

//-------------{ TPList }-----------------

function TPList.GetP(Index: Integer): PRec;
begin
  Result := PLARec(inherited Get(Index));
end;

procedure TPList.PutP(Index: Integer; const Value: PRec);
begin
  TPList(Self).Items[Index] := Value; // (2).この文が無限ループになる
end;


maco  2004-05-21 23:08:53  No: 9071

申し訳ありません。

編集チェックが甘く元コードの変数名の個所がありました。
以下に編集後のものを書きます。

//-------------{ TCellList }-----------------

procedure TCellList.Put(Index: Integer; const Value: TPList);
begin
> TLA(Self).Items[Index] := Value;
>    ↓
  TCellList(Self).Items[Index] := Value;
end;

//-----------{ TPList }---------------

function TPList.GetP(Index: Integer): PRec;
begin
> Result := PLARec(inherited Get(Index));
>              ↓      
  Result := PRec(inherited Get(Index));
end;


jok  2004-05-21 23:33:41  No: 9072

回答ではありません。

もし、100行100列のポインタの二次元配列をつくるとして、その実装では
101個の TList 派生クラスをつくることになりますね。わたしだったら、
内部にポインタの二次元動的配列を保持して、Cells[iCol,iRow] のプロパティー
を作ります。そして、Cols と Rows のプロパティーにアクセスするときだけ
動的に TList のインスタンスを作ります。TStringGrid と TStrings の関係を
見習ってみてください。


maco  2004-05-22 00:22:28  No: 9073

一応、この書き込みに関しては自分で解決出来ました。
(1)でTPListにRecを格納するときにItemsメソッドが呼ばれ(2)に
(2)でItemsメソッドが呼ばれ再度(2)に……
  と言った感じにループになっていました。

なので、TCellList、TPListで定義したItemsメソッド名をどちらも変更し、
定義名を変更したメソッド内ではTList標準のItemsメソッドを利用することで正常に動作しました。

-----------------------------------------------------
jokさん
アドバイスありがとうございます。

jokさんがおっしゃる通り、100×100のポインタ二次元配列には
101個のTList派生クラスが必要になります。

もしよろしければ、jokさんの方法をもう少し具体的に教えていただけませんか?

jokさんの方法だと、私の方法でのポインタ管理用の100個のTListが不要になり、
代わり必要なときだけ、TListのインスタンスを作成するわけですか?
恥ずかしい話ですが、TList派生クラスでの二次元配列も結構苦労して考えたくらいですので、
正直実現方法に見当がつきません。

ヒントでも良いので教えて頂けないでしょうか?
(充分ヒントはもらっているのでしょうけど…)


jok  2004-05-22 10:12:16  No: 9074

>もしよろしければ、jokさんの方法をもう少し具体的に教えていただけませんか?

こんな感じです。ポインタの二次元配列を操作するクラスを TwoDArrayUnit.pas
につくりました。

--------------------------------------------

unit TwoDArrayUnit;

interface

uses
  SysUtils, Classes, Dialogs;

type
TPointerGrid = class(TObject)
private
  FPArray: array of array of pointer;
  FColCount: integer;
  FRowCount: integer;
  procedure SetColCount(const Value: integer);
  procedure SetRowCount(const Value: integer);
  function GetItems(iCol,iRow:integer): pointer;
  procedure SetItems(iCol,iRow:integer; const Value: pointer);
protected
public
  constructor Create(InitColCount,InitRowCount:integer);
  destructor Destroy;override;
  property ColCount:integer read FColCount write SetColCount;
  property RowCount:integer read FRowCount write SetRowCount;
  property Items[iCol,iRow:integer]:pointer read GetItems write SetItems;
end;

implementation

{ TPointerGrid }

constructor TPointerGrid.Create(InitColCount, InitRowCount: integer);
begin
  FColCount := InitColCount;
  FRowCount := InitRowCount;
  SetLength(FPArray,FColCount,FRowCount);
end;

destructor TPointerGrid.Destroy;
begin
  inherited;

end;

procedure TPointerGrid.SetColCount(const Value: integer);
begin
  FColCount := Value;
  SetLength(FPArray,FColCount,FRowCount);
end;

procedure TPointerGrid.SetRowCount(const Value: integer);
begin
  FRowCount := Value;
  SetLength(FPArray,FColCount,FRowCount);
end;

function TPointerGrid.GetItems(iCol,iRow:integer): pointer;
begin
  result := nil;
  if (iCol>=FColCount) or (iRow>=FRowCount) then
    ShowMessage('範囲外のアクセスです')
  else
    result := FPArray[iCol,iRow];
end;

procedure TPointerGrid.SetItems(iCol,iRow:integer; const Value: pointer);
begin
  if (iCol>=FColCount) or (iRow>=FRowCount) then
    ShowMessage('範囲外のアクセスです')
  else
    FPArray[iCol,iRow] := Value;
end;

end.

--------------------------------------------

まだ基本的な部分だけです。行・列の途中に行・列を挿入したり、削除したりの
メソッドは未実装です。property Cols:TList なども未実装ですが、感じがつかめ
るでしょう。これをテストするコードは、Button1 とStringGrid1をつかって

--------------------------------------------

unit Main;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
  Forms, Dialogs, TwoDArrayUnit, StdCtrls, Grids;

type
  TForm1 = class(TForm)
    StringGrid1: TStringGrid;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    PointerGrid:TPointerGrid;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
var
  iCol,iRow:integer;
begin
  PointerGrid := TPointerGrid.Create(5,5);

  for iRow := 0 to PointerGrid.RowCount-1 do
    for iCol := 0 to PointerGrid.ColCount-1 do
      PointerGrid.Items[iCol,iRow] := pointer(iCol*iRow);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  PointerGrid.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  iCol,iRow:integer;
begin
  for iRow := 0 to PointerGrid.RowCount-1 do
    for iCol := 0 to PointerGrid.ColCount-1 do
      StringGrid1.Cells[iCol,iRow] := IntToStr(integer(PointerGrid.Items[iCol,iRow]));
end;

end.

--------------------------------------------

うまくいっているようです。特定のレコードのポインタの配列の場合は、入出力の型を
pointer から PRec などに変えてください。


jok  2004-05-22 10:54:21  No: 9075

よく考えてみると、上の TPointerGrid クラスは二次元の動的配列へのアクセスを
簡単にラップしているだけですね。

> TStringGrid と TStrings の関係を見習ってみてください。

自分でこう書いておいて、ちっとも見習ってないですね。まとまった時間をかけて
実装コードを調べて改良してみます。


FODK  2004-05-23 16:33:10  No: 9076

TBitmapを使って二次元配列を実装してみるのはどうでしょう

var 
  ArrayBmp:TBitmap;
  X,Y:integer;//座標
  I:integer;//格納したい値
begin
  with ArraryBmp do
  begin
    ArraryBmp.Width := 100 //縦=Y
    ArraryBmp.Height := 100 //横=X
    Canvas.Pixels[X,Y] := I //格納
    I := Canvas.Pixels[X,Y];//取り出し
  end;
end;
これをプロパティにして高速にアクセスできるようにしてはどうでしょう
副産物としてbitmap形式でファイルに落とせます
さらにそのファイルは正式なファイルなので適当なツールで読み込めます


jok  2004-05-23 22:41:08  No: 9077

> TBitmapを使って二次元配列を実装してみるのはどうでしょう

おもしろいですね。いや、まったくおもしろい。発想が素晴らしい。
行・列を拡張・縮小するとき消えてしまわないようにバックアップするとか、
pf24bit 以上でないと Pixels[] の値がセットとゲットと一致しない
とかいろいろいろありますけど、発想の自由さに驚嘆しました。


maco  2004-05-24 23:53:11  No: 9078

jokさん

サンプルプログラムありがとうございます。
この方法であれば、
PRecを格納するTList(=TPList)も、TPListを格納するTListもどちらも不要になりますね。
プロパティの実装方法なども参考になりました。ありがとうございます。

> FPArray: array of array of pointer;
                                  ↓
  FPArray: array of array of PRec; というように変更

new(FPArray[i,j]);
FPArray[i,j]^.メンバ := 値;
  とすることでレコード型のポインタでも動作OKでした。

ところで、質問なのですが、
クラスの内部で、PRec(レコードのポインタ型)の二次元配列を保持するのと、
私のTListでの方法のどちらがメモリ消費上効率的なのでしょうか?

行・列数とも最大でも20程度ですので、メモリ消費を気にしなくても良いとは思いますが、
疑問に思ったので質問しました。

--------------------------------------------------------------------------
FODKさん

ご助言ありがとうございます。

今回の質問はStringGridの各セルの行列番号から補足情報を取り出す用途を
考えていました。もしかしたら、FODKさんの方法でも出来るのかも知れませんが、
TBitmapを扱ったことがないので、すぐには試せそうもありません。

また機会を作って試してみたいと思います。その時もし分からないことがありましたら、
質問させてください。

本当にありがとうございます。


jok  2004-05-25 02:44:45  No: 9079

> どちらがメモリ消費上効率的なのでしょうか?

クラスを動的に生成するのが少ない動的配列の方が効率的だと思います

> 今回の質問はStringGridの各セルの行列番号から補足情報を取り出す用途

Objects[i,j] は TObject 型ですからポインタと同じですけど


maco  2004-05-25 19:05:20  No: 9080

> クラスを動的に生成するのが少ない動的配列の方が効率的だと思います

やはりTListの動的生成が不要な分、動的配列での管理の方がスマートなのですね。
ありがとうございます。
今回は、レコードのポインタ型の二次配列を利用しクラスを作成してみます。

> Objects[i,j] は TObject 型ですからポインタと同じ

TBitmapもPixelsもTObjectを継承しているからでしょうか?

今回は数値しか扱わないので、他の人がプログラムを見たときに
TBitmapやPixelsを使っているとイメージが掴みにくくなるかなと思いましたので…。
(とうよりも私が良く理解できていません。申し訳ありません)


jok  2004-05-25 19:50:21  No: 9081

>> Objects[i,j] は TObject 型ですからポインタと同じ

> TBitmapもPixelsもTObjectを継承しているからでしょうか?

違います。StringGrid では行・列を指定して たとえば

StringGrid1.Cells[iCol,iRow] := 'Data';

というふうに文字列を設定したり、また取り出したりできますね。同様に

StringGrid1.Objects[iCol,iRow] := TObject(SomePointer);

というふうに、また、

SomePointer := Pointer(StringGrid1.Objects[iCol,iRow]);

というふうにポインタの値を設定取得可能です。

TBitmap ではたとえば pf32bit の PixelFormat では、1ピクセルあたりの
色データが32ビットの整数と等価です。また、ポインタも TObject などの
オブジェクト参照も 32bit の整数と等価のデータです。ですから互いにキャスト
して設定・取得ができます、という意味です。ビットマップは本質的に色の
二次元配列のデータ構造ですから、色が32bitのときは、キャストすると
ポインタの二次元配列として利用できます。この場合、ファイルとのアクセスは
標準のメソッドを使うことができて便利ですね。


maco  2004-05-25 20:15:32  No: 9082

TStringGrid の Objectsプロパティのことだったのですね。

> pf24bit 以上でないと Pixels[] の値がセットとゲットと一致しない
> TObject などのオブジェクト参照も 32bit の整数と等価のデータです。

pf32bitの件もようやく理解出来ました。

少し話しが違うかもしれませんが、
コンポーネントのTagプロパティに、Integer型にキャストしたポインタを格納するテクニックに雰囲気が似てますね。
(Tagのヘルプにも記述がありますが)

FODKさんのアドバイスは
TBitmapをベースにクラスを作成すれば、二次元配列を簡単に管理出来るし、
PixelFormatプロパティでpf32bitを指定すれば、Pixelsにポインタ型を格納出来るということだったのですね?


jok  2004-05-25 20:34:13  No: 9083

> Pixelsにポインタ型を格納出来るということだったのですね?

そうです。おもしろいでしょ?

> 今回の質問はStringGridの各セルの行列番号から補足情報を取り出す用途

このためには Objects[] を使用すると特にほかの配列は必要ないですね。


maco  2004-05-26 18:11:15  No: 9084

> そうです。おもしろいでしょ?

そうですね。本当に関心しました。
TBitmapという名前がついてる時点で、わたしにはそのような柔軟な考えは思いつかないと思います。

jokさん、FODKさん
この度はありがとうございました。とても勉強になりました。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加