2010-12-07 67 views
10

J'implémente une matrice N x M (classe) avec un enregistrement et un tableau dynamique interne comme ci-dessous.Copie en profondeur d'un enregistrement avec R1: = R2, ou Y at-il un bon moyen d'implémenter la matrice NxM avec enregistrement?

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; 

Je choisis un dossier, parce que je ne veux pas créer/Free/Assigner à l'utiliser. Mais avec un tableau dynamique, les valeurs ne peuvent pas être copiées (en profondeur) avec M1: = M2, au lieu de M1.Assign (M2).

J'ai essayé de déclarer la méthode de conversion auto-implicite, mais elle ne peut pas être utilisée pour M1: = M2.

(Implicite (const pA: PMAT): TMAT et M1: = @ M2 fonctionne, mais il est assez laid et illisible ..)

Est-il possible de brancher la cession de l'enregistrement?

Ou est-il suggéré d'implémenter une matrice N x M avec des enregistrements?

Merci d'avance.

Edit:

Je mis en œuvre comme ci-dessous avec la méthode de Barry et a confirmé qu'il fonctionne correctement.

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; 

Je suis d'accord que ce n'est pas efficace. Utiliser simplement Assign avec un enregistrement pur est absolument plus rapide.

Mais il est très pratique et plus facile à lire. (Et intéressant. :-)

Je pense qu'il est utile pour le calcul de lumière ou le prototypage de pré-production. N'est-ce pas?

Edit2:

kibab donne la fonction d'obtenir le nombre de référence de tableau dynamique lui-même.

La solution de Barry est plus indépendante de l'impl interne, et fonctionne peut-être sur les compilateurs 64bit à venir sans aucune modification, mais dans ce cas, je préfère l'efficacité de kibab pour sa simplicité &. Merci.

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; 
+1

1 question intéressante. Je pense que je m'en tiendrai aux types de valeurs dans un enregistrement avec une surcharge de l'opérateur. –

Répondre

8

Vous pouvez utiliser une interface référence de champ à l'intérieur de votre enregistrement pour déterminer si votre tableau est partagé par plus d'un enregistrement: il suffit de vérifier le nombre de références sur l'objet derrière l'interface, et vous saurez que le d ata dans les tableaux est partagé.De cette façon, vous pouvez paresseusement copier sur modification, mais toujours utiliser le partage de données lorsque les matrices ne sont pas modifiées.

+0

+1 Fantastique! Je vais maintenant regarder mon code et voir si je peux le faire de cette façon! Rouler votre propre copie sur écrire! –

+0

Ah, copie paresseuse! Je vois! Il suffit d'insérer la routine Check'nCopy avant tout code auto-modifiable. Je vais essayer de mettre en œuvre avec cette technique. Merci beaucoup! – benok

+1

+1 pour une idée. Mais l'interface pour cela est IMHO pas nécessaire overhead (ou j'ai manqué sth. Ici?). Le nombre de Ref de n'importe quel tableau dynamique peut être lu directement comme ceci (si dyn-array est partagé alors il retournera la valeur> 1): function GetDynArrayRefCnt (const ADynArray): Longword; begin si Pointer (ADynArray) = nil alors Résultat: = 1 {ou 0, selon ce dont vous avez besoin} else Résultat: = PLongword (Mot entier (ADynArray) - 8) ^; fin; – kibab

4

Vous ne pouvez pas remplacer l'attribution d'enregistrement par des opérateurs implicites ou explicites. Le mieux que vous pouvez faire l'OMI est de ne pas utiliser l'attribution directe, en utilisant la méthode M.Assign à la place:

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; 

ex

M1.Assign(M2); 

au lieu de

M1:= M2; 
+5

S'il vous plaît noter que la copie ne fonctionne pas avec des tableaux multi-dim, http://qc.embarcadero.com/wc/qcmain.aspx?d=20086 Seulement première dimension est copiée, d'autres sont simplement référencées, et sous forme de tableaux dynamiques pas CopyOnWrite (comme les chaînes), donc en changeant dans un, change aussi dans d'autres (tableaux «copiés»). Similaire, vous trouverez probablement avec des enregistrements de copie. – kibab

+2

Il n'y a pas de tableaux dynamiques multidimensionnels; seulement des tableaux de tableaux, qui sont un concept différent (permet une forme irrégulière). Pourquoi Copy ferait-il une copie en profondeur des éléments du tableau qu'il copiait, mais seulement si ces éléments sont des tableaux? (Fondamentalement, je suggère que ce bug est très proche d'être "comme conçu".) –

+0

@Barry Kelly: Je ne suis pas d'accord. IMO l'implémentation actuelle de la procédure "Copier" devrait être améliorée pour inclure une copie profonde des tableaux dynamiques ou bien le concept entier de tableaux dynamiques dans Delphi devrait être changé (support CopyOnWrite). – kludg

2

Je viens de réaliser une raison pour laquelle cela ne peut être une bonne idée. Il est vrai que le code appelant devient beaucoup plus simple avec une surcharge de l'opérateur. Mais vous pouvez avoir des problèmes de performance.

Prenons, par exemple, le simple code A := A+B; et supposons que vous utilisez l'idée dans la réponse acceptée Barry. En utilisant la surcharge de l'opérateur, cette opération simple entraînera l'allocation d'un nouveau tableau dynamique. En réalité, vous voudriez effectuer cette opération en place.

De telles opérations en place sont très fréquents dans les algorithmes de la matrice d'algèbre linéaire pour la simple raison que vous ne voulez pas frapper le tas si vous pouvez l'éviter - il est cher. Pour les types de petite valeur (nombres complexes, matrices 3x3, etc.), la surcharge de l'opérateur à l'intérieur des enregistrements est efficace, mais je pense que si les performances sont importantes, la surcharge des matrices n'est pas la meilleure solution.

+0

Oui, je suis d'accord. La surcharge de l'opérateur est juste une syntaxe de sucre et pas efficace. Le prototypage rapide avec un moyen facile et ensuite l'optimisation de manière appropriée est ma stratégie préférée (mais dangereuse pour courir hors délai :-). – benok

+1

@benok: La surcharge de l'opérateur est efficace pour les petits types de valeur. Nous venons de convertir des matrices complexes, des matrices vectorielles 3x3 et 3x3 en surcharge d'opérateur, sans rien perdre en performance - le code objet est en réalité identique. Je ne pense tout simplement pas que cela fonctionnera bien avec les types de référence plutôt que les types de valeur. –