Forum Delphi.cz

Delphi => Obecné => Téma založeno: miroB 22-01-2019, 15:22:13

Název: Ako pristupovať k Values v Dictionary, ak Values sú typu TArray<double>
Přispěvatel: miroB 22-01-2019, 15:22:13
Potreboval by som ukladať dynamický array typu double do TDictionary.
Array by mal byť schopný dynamicky rásť pomocou SetLength().
Riešenie potrebujem pre vytvorenie funkcie Median, ako UDF v SQLite.
Musí byť typu Aggregate - preto potrebujem pracovať s TDictionary.
Deklarácia :
Kód: Delphi [Vybrat]
  1.    
  2. uses
  3.   ..
  4.   System.Types;
  5. type
  6.     TmedPointer = ^TDoubleDynArray;  // TDoubleDynArray alias TArray<double>
  7.     T_medianCalc = class( TObject )
  8.     medDict     : TDictionary<Variant, TmedPointer>;  // pokus so smerníkom na Dynamic Array
  9.     medCount    : Integer;
  10.     end;
Využitie cca takto (prosím berte ako princíp. pozri Access violation):
Kód: Delphi [Vybrat]
  1. var
  2.   medLocal : T_medianCalc;
  3.   v      : Variant;
  4.   medArr : TArray<double>;
  5.   medPt : TmedPointer
  6. begin
  7.   ..
  8.   SetLength( medArr, 1000 ); // inicializácia. Neskôr sa prípadne podľa potreby zväčší.
  9.   medLocal.medDict.Add( v, @medArr[ 0 ] );
  10. ..
  11.   medPt := nil; // KONTROLA
  12.   if medDict.TryGetValue( v, medPt ) then // KONTROLA
  13.     begin           // KONTROLA - v nasledujúcom riadku príde k chybe
  14.     k := length( medPt^ );  // ACCESS VIOLATION !!!
  15.     end;    // neviem správne uložiť/vytiahnuť array of double z dictionary
  16. end;
  17.  
Dáta v databáze môžu teoreticky dosahovať desaťtisíce, či státisíce záznamov..
Je vôbec možné ukladať Dynamický Array ako hodnotu do Dictionary?
Ak áno, akú taktiku použiť? Je ten pointer OK?

Název: Re:Ako pristupovať k Values v Dictionary, ak Values sú typu TArray<double>
Přispěvatel: pf1957 22-01-2019, 17:55:45
Jeste dodam, ze bys takove chybe predesel v pripade zapnuti volby kompilace Typed @ operator
No jo, ale to by nesmela byt drtiva vetsina Delphi kodu 3. stran psana s jeho vypnutim. Ja to vzdycky mival zapnute a letos jsem rezignoval  >:(
Název: Re:Ako pristupovať k Values v Dictionary, ak Values sú typu TArray<double>
Přispěvatel: miroB 22-01-2019, 18:57:11
To ukladani pointeru je v poradku (samozrejme za predpokladu, ze budou referovana pole v dobe pristupu ze slovniku k nim existovat). Problem v Tvem kodu je v tom, ze vkladas do slovniku ukazatel na prvni element pole namisto pole samotneho:

Kód: Delphi [Vybrat]
  1. medLocal.medDict.Add( v, @medArr[ 0 ] );

nahrad za:

Kód: Delphi [Vybrat]
  1. medLocal.medDict.Add( v, @medArr );
OK to rád nahradím.
Ešte jeden dôležitý fakt:
Mám v úmysle redimenzovať pole podľa potreby.
Po kroku 1000, alebo 10 000. To ešte zvážim.
Teda začínam na 1000, pri tisícom zázname zvýšim dimenziu na 2000. Atď..
Pointer zostáva nezmenený? Alebo pre istotu použijem Delete a Add pre medDict?
Název: Re:Ako pristupovať k Values v Dictionary, ak Values sú typu TArray<double>
Přispěvatel: miroB 22-01-2019, 22:23:48
..
Adresa se s realokaci pameti pri zmene velikosti pole nezmeni, cili ten pointer bude stale ukazovat na stejnou promennou (pole). Jedinou komplikaci tady je, ze musi instance Tve tridy T_medianCalc v dobe pristupu do slovniku existovat. Pokud instanci uvolnis, vznikne tim ve slovniku tzv. dangling pointer (jenz je treba, idealne pri uvolnovani instance odstranit).
OK, funkcia už funguje ako celok.
Použil som nakoniec priamo Array a nie pointer na neho.
Deklarácie sú teraz takto:
Kód: Delphi [Vybrat]
  1.   Type
  2.     T_medianObject = class( TObject )
  3.       arr : TArray<double>;
  4.       Cnt : Integer;
  5.     end;
  6.  
  7.     TmedSQLiteDict  = TDictionary<psqlite3_context, TDictionary<Variant, T_medianObject >>;
Naplnenie:
Kód: Delphi [Vybrat]
  1. mo       := T_medianObject.Create;
  2. mo.arr   := TArray<double>.Create( AInputs[ 1 ].AsFloat );
  3. SetLength( mo.arr, 10000 );
  4. mo.Cnt   := 1;

Pamäť uvoľňujem v udalosti OnFinalize funkcie takto:
Pre položky slovníka:
Kód: Delphi [Vybrat]
  1. SetLength( mo.arr, 0 ); // for vKey in oRslt.Keys do begin mo := oRslt.Items[ vKey ];
  2. mo.Free;
  3. mo := nil;
Pre slovník v danej inštancii:
Kód: Delphi [Vybrat]
  1. // Premenná oRslt je TDictionary<Variant, T_medianObject >;
  2. oRslt.Clear; // medianFnDict.TryGetValue( StepCtx, oRslt )
a pre inštanciu funkcie:
Kód: Delphi [Vybrat]
  1. medianFnDict.Items[ StepCtx ].Clear; // medianFnDict := TmedSQLiteDict( AUserData );
Název: Re:Ako pristupovať k Values v Dictionary, ak Values sú typu TArray<double>
Přispěvatel: miroB 23-01-2019, 09:29:42
Ďakujem Delfin. Toto je presne, čo som potreboval. Ospravedlňujem sa, že som zabudol na "TObjectDictionary". Bol som týždeň na dovolenke, najazdil 1750 km a nejak mi to vyšumelo.. Idem to napraviť. To bude ideálne a univerzálne riešenie aj pre ostatné UDF.
Název: Re:Ako pristupovať k Values v Dictionary, ak Values sú typu TArray<double>
Přispěvatel: miroB 23-01-2019, 11:22:50
Ešte overenie mazania:
Pretože pre slovník postavený nad AUserData nie je možné využiť Free (okrem iného aj preto, že  onFinalize je navštívená viackrát, to závisí od počtu položiek, tak ako vyplynú z GROUP BY)
Spomínaný "TmedSQLiteDict" je typu TObjectDictionary<T>
a prvky sa pridávajú takto:
Kód: Delphi [Vybrat]
  1. TmedSQLiteDict( AUserData ).Add( AOutput.Handle, TObjectDictionary<Variant, T_medianObject> );

HLAVNÁ OTÁZKA:
Pri každom priechode cez onFinalize môžem použiť toto (Remove) ?
Kód: Delphi [Vybrat]
  1. TmedSQLiteDict( AUserData ).Remove( TObjectDictionary<Variant, T_medianObject> );
Bude to stačiť a okrem Remove nemusím už nič viac mazať/uvoľňovať?

Pre istotu v kontexte Remove výber z programu:
Kód: Delphi [Vybrat]
  1. procedure Tdm.fnMedianAggregateFinalize( AFunc: TSQLiteFunctionInstance; var AUserData: TObject );
  2. begin
  3. ..
  4. medFnDict := TmedSQLiteDict( AUserData );
  5. if medFnDict.TryGetValue( StepCtx, oRslt ) then
  6.   begin
  7.   ..
  8.   medFnDict.Remove( oRslt );  // Uvolnenie ako jediné. Ak by to teda stačilo
  9.   oRslt     := nil;
  10.   end;
Inak som dodržal všetky posledné pokyny z posledného príspevku od Delfin.
Název: Re:Ako pristupovať k Values v Dictionary, ak Values sú typu TArray<double>
Přispěvatel: miroB 23-01-2019, 13:05:41
Oprava odoberania z TObjectDictionary. OSPRAVEDLŇUJEM SA za predošlé znenie!!!
Funguje v tejto úprave:

HLAVNÁ OTÁZKA:
Pri každom priechode cez onFinalize môžem použiť toto (Remove) ?
Kód: Delphi [Vybrat]
  1. // Princíp použitia
  2. TmedSQLiteDict( AUserData ).Remove( sqlite3_aggregate_context( sqlite3_aggregate_context( AFunc.Output.Handle, 0 )^ ) );
  3. TObjectDictionary<Variant, T_medianObject>.Free;
  4. TObjectDictionary<Variant, T_medianObject> := nil;
Bude to stačiť a okrem Remove nemusím už nič viac mazať/uvoľňovať?
Principiálne takto, (viď kód hore) by stačilo uvoľnenie?

Pre istotu, reálny kontext Remove, výber z programu:
Kód: Delphi [Vybrat]
  1. procedure Tdm.fnMedianAggregateFinalize( AFunc: TSQLiteFunctionInstance; var AUserData: TObject );
  2. begin
  3. ..
  4. DataCtx                      := sqlite3_aggregate_context( AFunc.Output.Handle, 0 );
  5. if Assigned( DataCtx ) then
  6.   begin
  7.   StepCtx                    := psqlite3_context( DataCtx^ );
  8.   medFnDict := TmedSQLiteDict( AUserData );
  9.   if medFnDict.TryGetValue( StepCtx, oRslt ) then
  10.     begin
  11.     ..  // Tu prebehnú rôzne výpočty
  12.     medFnDict.Remove( StepCtx ); // Uvolnenie.. Ak by to teda stačilo
  13.     oRslt.Free;                 // Uvolnenie.. Ak by to teda stačilo
  14.     oRslt     := nil;
  15.     end;
Inak som dodržal všetky posledné pokyny z posledného príspevku od Delfin.
Poznámka:  Na záver by bolo jednoduché uvoľnenie hlavného slovníka, toho nad AUserData, ale to nie je možné využiť.
Pretože posledný priechod cez OnFinalize nie je známy.
Název: Re:Ako pristupovať k Values v Dictionary, ak Values sú typu TArray<double>
Přispěvatel: miroB 23-01-2019, 14:45:33
..

by byla pri vlastnictvi hodnot doOwnsValues zbytecna, ba co vic, chybna (protoze metodou Remove by se objekt uvolnil s naslednym manualnim pokusem o uvolneni). K uvolneni objektu TObjectDictionary<T> pri vlastnictvi hodnot doOwnsValues staci:

Kód: Delphi [Vybrat]
  1. medFnDict.Remove(StepCtx);
Mám pochybnosti..
Urobil som pokus. Objektu som pridal Destroy.
Ak ho nezruším "manuálne" pomocou free, program tade neprejde.
Zdá sa mi to podozrivé. Preto som to free pridal a nič podozrivé sa nedeje (program tam podľa mňa správne prechádza cez Destroy, ale inak nie je hlásená žiadna chyba).
Aj keď funkciu testujem na tabuľke o milión riadkoch.
Název: Re:Ako pristupovať k Values v Dictionary, ak Values sú typu TArray<double>
Přispěvatel: vandrovnik 23-01-2019, 14:58:29
Urobil som pokus. Objektu som pridal Destroy.
Ak ho nezruším "manuálne" pomocou free, program tade neprejde.

Jen pro jistotu, je u toho Destroy uvedeno "override"?
Název: Re:Ako pristupovať k Values v Dictionary, ak Values sú typu TArray<double>
Přispěvatel: miroB 23-01-2019, 15:02:00
Urobil som pokus. Objektu som pridal Destroy.
Ak ho nezruším "manuálne" pomocou free, program tade neprejde.
Jen pro jistotu, je u toho Destroy uvedeno "override"?
Override - Áno mal som ho tam od začiatku:)  Inak by mi tade program nešiel ani pri "manuálnom" Free
Název: Re:Ako pristupovať k Values v Dictionary, ak Values sú typu TArray<double>
Přispěvatel: miroB 23-01-2019, 16:36:11
Ak ho nezruším "manuálne" pomocou free, program tade neprejde.
A vlastni ty kolekce hodnoty? Vytvaris je s priznakem doOwnsValues.
Aha. Nie, myslel som, že TObjectDictionary to má automaticky ako Default.
Ďakujem. Už to funguje bez free.