12

以下のように、レコードと内部動的配列を使用して N x M マトリックス (クラス) を実装しています。

TMat = record
public     
  // contents
  _Elem: array of array of Double;

  //
  procedure SetSize(Row, Col: Integer);

  procedure Add(const M: TMat);
  procedure Subtract(const M: TMat);
  function Multiply(const M: TMat): TMat;
  //..
  class operator Add(A, B: TMat): TMat;
  class operator Subtract(A, B: TMat): TMat;
  //..
  class operator Implicit(A: TMat): TMat; // call assign inside proc.
                                          // <--Self Implicit(which isn't be used in D2007, got compilation error in DelphiXE)

  procedure Assign(const M: TMat); // copy _Elem inside proc.
                                   // <-- I don't want to use it explicitly.
end;

レコードを作成/解放/割り当てて使用したくないので、レコードを選択します。

しかし、動的配列では、値は M1.Assign(M2) の代わりに M1 := M2 で (ディープ) コピーできません。

自己暗黙的な変換メソッドを宣言しようとしましたが、M1:=M2 には使用できません。

(Implicit(const pA: PMat): TMat と M1:=@M2 は機能しますが、かなり醜くて判読できません..)

レコードの割り当てをフックする方法はありますか?

または、レコードで N x M マトリックスを実装する提案はありますか?

前もって感謝します。

編集:

Barry の方法で以下のように実装し、正常に動作することを確認しました。

type
  TDDArray = array of array of Double;

  TMat = record
  private
     procedure CopyElementsIfOthersRefer;
  public
    _Elem: TDDArray;
    _FRefCounter: IInterface;
   ..
  end;

procedure TMat.SetSize(const RowSize, ColSize: Integer);
begin
  SetLength(_Elem, RowSize, ColSize);

  if not Assigned(_FRefCounter) then
    _FRefCounter := TInterfacedObject.Create;
end;

procedure TMat.Assign(const Source: TMat);
var
  I: Integer;
  SrcElem: TDDArray;
begin
  SrcElem := Source._Elem; // Allows self assign

  SetLength(Self._Elem, 0, 0);
  SetLength(Self._Elem, Length(SrcElem));

  for I := 0 to Length(SrcElem) - 1 do
  begin
    SetLength(Self._Elem[I], Length(SrcElem[I]));
    Self._Elem[I] := Copy(SrcElem[I]);
  end;
end;

procedure TMat.CopyElementsIfOthersRefer;
begin
  if (_FRefCounter as TInterfacedObject).RefCount > 1 then
  begin
    Self.Assign(Self); // Self Copy
  end;
end;

効率的ではないことに同意します。純粋なレコードで割り当てを使用するだけで、絶対に高速になります。

しかし、それはかなり便利で読みやすいです.(そして興味深い. :-)

軽い計算や試作前の試作に重宝すると思います。ではない ?

編集2:

kibabは、動的配列自体の参照カウントを取得する関数を提供します。

Barry のソリューションは、内部 impl からより独立しており、今後の 64 ビット コンパイラでも変更なしで動作する可能性がありますが、この場合、シンプルで効率的な kibab を好みます。ありがとう。

  TMat = record
  private
     procedure CopyElementsIfOthersRefer;
  public
    _Elem: TDDArray;
   ..
  end;

procedure TMat.SetSize(const RowSize, ColSize: Integer);
begin
  SetLength(_Elem, RowSize, ColSize);
end;    

function GetDynArrayRefCnt(const ADynArray): Longword;
begin
  if Pointer(ADynArray) = nil then
    Result := 1 {or 0, depending what you need}
  else
    Result := PLongword(Longword(ADynArray) - 8)^;
end;

procedure TMat.CopyElementsIfOthersRefer;
begin
  if GetDynArrayRefCnt(_Elem) > 1 then
    Self.Assign(Self);
end;
4

3 に答える 3

9

レコード内のインターフェイス フィールド参照を使用して、配列が複数のレコードで共有されているかどうかを確認できます。インターフェイスの背後にあるオブジェクトの参照カウントを確認するだけで、配列内のデータが共有されていることがわかります。そうすれば、変更時に遅延コピーできますが、マトリックスが変更されていない場合でもデータ共有を使用できます。

于 2010-12-07T18:26:55.607 に答える
4

Implicit または Explicit 演算子によるレコードの割り当てを上書きすることはできません。IMO でできる最善の方法は、直接代入を使用せず、代わりに M.Assign メソッドを使用することです。

procedure TMat.Assign(const M: TMat);
begin
// "Copy" currently only copies the first dimension,
//  bug report is open - see comment by kibab 
//  _Elem:= Copy(M._Elem);
  ..
end;

M1.Assign(M2);

それ以外の

M1:= M2;
于 2010-12-07T11:13:38.647 に答える
3

これがそれほど素晴らしいアイデアではない理由がわかりました。演算子のオーバーロードにより、呼び出しコードがはるかに単純になることは事実です。ただし、パフォーマンスの問題が発生する可能性があります。

たとえば、単純なコードA := A+B;を考えて、バリーの受け入れられた回答でアイデアを使用するとします。この単純な操作をオーバーロードする演算子を使用すると、新しい動的配列が割り当てられます。実際には、この操作を適切に実行する必要があります。

このようなインプレース操作は、回避できる場合はヒープにヒットしたくないという単純な理由で、線形代数行列アルゴリズムで非常に一般的です-それは高価です。

小さな値の型 (例: 複素数、3x3 行列など) の場合、レコード内での演算子のオーバーロードは効率的ですが、パフォーマンスが重要な場合、大きな行列の演算子のオーバーロードは最善の解決策ではないと思います。

于 2010-12-08T20:24:09.800 に答える