Autor Téma: Ako pristupovať k Values v Dictionary, ak Values sú typu TArray<double>  (Přečteno 1255 krát)

Offline miroB

  • Guru
  • *****
  • Příspěvků: 516
  • Karma: 17
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
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?

« Poslední změna: 22-01-2019, 15:30:59 od miroB »

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 2588
  • Karma: 133
    • Verze Delphi: D2007, XE3, DX10
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  >:(

Offline miroB

  • Guru
  • *****
  • Příspěvků: 516
  • Karma: 17
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
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?

Offline miroB

  • Guru
  • *****
  • Příspěvků: 516
  • Karma: 17
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
..
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 );

Offline miroB

  • Guru
  • *****
  • Příspěvků: 516
  • Karma: 17
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
Ď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.

Offline miroB

  • Guru
  • *****
  • Příspěvků: 516
  • Karma: 17
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
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.
« Poslední změna: 23-01-2019, 11:35:44 od miroB »

Offline miroB

  • Guru
  • *****
  • Příspěvků: 516
  • Karma: 17
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
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.
« Poslední změna: 23-01-2019, 13:14:56 od miroB »

Offline miroB

  • Guru
  • *****
  • Příspěvků: 516
  • Karma: 17
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
..

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.

Offline vandrovnik

  • Guru
  • *****
  • Příspěvků: 766
  • Karma: 43
    • Verze Delphi: 10.3
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"?

Offline miroB

  • Guru
  • *****
  • Příspěvků: 516
  • Karma: 17
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
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
« Poslední změna: 23-01-2019, 15:03:53 od miroB »

Offline miroB

  • Guru
  • *****
  • Příspěvků: 516
  • Karma: 17
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
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.