
	12.1. IMPLEMENTAREA OPERATIILOR CU MULTIMI FOARTE MARI

Propunem construirea unei unitati TURBO PASCAL care sa implementeze lucrul cu multimi orict de mari. Se stie ca implementarea TURBO permite lucrul cu multimi avnd maximum 256 de elemente.

Implementarea pe care o prezentam n continuare permite operarea cu multimi care pot avea maximum n elemente, unde n <= 65536. Multimile vor fi reprezentate prin functiile lor caracteristice (f(x) = 1 daca x apartine multimii si f(x) = 0 daca x nu apartine multimii). Presupunem ca elementele multimii de baza sunt numerotate 0, 1, ... n-1. 

Reprezentarea functiilor caracteristice o vom face pe biti. Deci reprezentarea unei multimi necesita [ (n+15) / 16 ] cuvinte, unde prin [.] am notat functia parte ntreraga.

	12.1.1. Obiectul MULTIME si unitatea MULTIMI

Textul sursa prin care se defineste obiectul MULTIME si unitatea MULTIMI este:

unit multimi;
interface

type multime = object

  nmax,           { Numarul maxim de elemente ale multimii }
  ncuv: word;   { Numarul de cuvinte necesar reprezentarii }
  pel: pointer;        { Pointer la reprezentarea multimii }

  constructor initv(n: word);      { Init cu multimea vida }
  constructor initc(s: multime);       { Init prin copiere }
  destructor  done;                 { Eliberarea spatiului }
  procedure adauga(s, d: word); { Adauga elementele s la d }
  procedure sterge(s, d: word); { Sterge elementele s la d }
  procedure complement;          { Complementeaza multimea }
  function apart(i: word): boolean;{ i apartine multimii ? }
end;
  pmultime = ^multime;      { Pointer la un obiect multime }
  function reuniune(a, b: multime): pmultime;      { a ( b }
  function intersec(a, b: multime): pmultime;      { a ( b }
implementation
{$I initv }
{$I initc }
{$I done }
{$I adauga }
{$I sterge }
{$I compleme }
{$I apart }
{$I reuniune }
{$I intersec }
end.

Obiectele de tip multime sunt formate din trei cmpuri: nmax care da numarul maxim de elemente, ncuv care da numarul maxim de cuvinte necesare pentru reprezentarea unei multimi si pel care este pointerul spre ncuv cuvinte n care se rprezinta multimea. 

Un astfel de obiect are doi constructori (initv, initc), un destructor (done) si nca patru metode (adauga, sterge, complement, apart).

Unitatea mai are doua functii (reuniune, intersec). Ambele primesc drept parametri doua obiecte de tip multime si dau ca rezultat un pointer spre obiectul de tip multime rezultat.

Sectiunile urmatoare descriu aceste noua proceduri si functii.

	12.1.2. Constructorul INITV

Textul sursa al acestui constructor este:

constructor multime.initv(n: word);
  var p: pointer;
  begin
    pel := nil;
    if n <= 0 then exit;
    nmax := n;         { Fixeaza numarul maxim de elemente }
    if n < $FFFF - 15
      then  n := (n + 15) div 16
      else  n := 4096;
    ncuv := n;       { Fixeaza numarul de cuvinte necesare }
    getmem(p, n * 2); { Aloca memorie pentru reprezentarea }
    pel := p;         {    elementelor multimii nou create }
    asm
      mov ax,0;   cld;   les di,p;    mov cx,n
      rep stosw        { Se umple reprezentarea cu zerouri }
    end
  end;

Parametrul n da numarul de elemente a multimii de baza. Din textul sursa rezulta clar cum functioneaza acest constructor. Trebuie totusi remarcate doua lucruri. 

Mai nti modul n care se face rotunjirea la cel mai mic multiplu de 16 care este superior lui n. Pentru n > 65520, daca i se mai aduna 15, atunci se depaseste capacitatea de reprezentare a lui n!

Apoi sa observam ca pentru a face zero toti bitii corespunzatori reprezentarii multimii, se folosesc patru instructiuni n limbaj de asamblare; primele trei pregatesc instructiunea STOSW, iar a patra o aplica n mod repetat.

	12.1.3. Constructorul INITC

Textul sursa al acestui constructor este:

constructor multime.initc(s: multime);
  var n: word;
      p, q: pointer;
  begin

    ncuv := s.ncuv;  nmax := s.nmax;  n := ncuv;
    getmem(p, n * 2);       { Aloca memorie pentru multime }
    pel := p;        q := s.pel;
    asm                 { Copiaza reprezentarea multimii s }
      push ds
      lds si,q;   les di,p;   cld;    mov cx,n
      rep movsw            { Se face copierea propriu-zisa }
      pop ds
    end
  end;

Acest constructor initializeaza multimea curenta ca fiind identica cu multimea s primita ca parametru. Dupa ce sunt initializate cele doua cmpuri numerice, se aloca spatiul de memorie necesar si apoi folosind instructiunea pe siruri de cuvinte MOVSW se copiaza definirea noii multimi.

	12.1.4. Destructorul DONE

Textul sursa al acestui destructor este:

destructor  multime.done;
  begin                       { Elibereaza memoria ocupata }
    freemem(pel, ncuv * 2)    {  de reprezentarea multimii }
  end;

	12.1.5. Metoda ADAUGA

Textul sursa al metodei este:

procedure multime.adauga(s, d: word);
  var  cuvs, bits, cuvd, bitd, mass, masd: word;
       ps, pd: pointer;
       citecuv: integer;
  begin
    if s < 0 then exit;
    if d < s then d := s;
    cuvs := s div 16;    { Primul cuvint unde se va adauga }
    bits := s mod 16;      ps := pel;
    cuvd := d div 16;   { Ultimul cuvint unde se va adauga }
    bitd := 15 - d mod 16; pd := pel;
    asm
      mov ax,$FFFF;         mov cx,bits;      shl ax,cl
      mov mass,ax             { Masca pentru primul cuvint }
      mov ax,$FFFF;         mov cx,bitd;      shr ax,cl
      mov masd,ax            { Masca pentru ultimul cuvint }
      mov ax,word ptr ps;   add ax,cuvs;      add ax,cuvs
      mov word ptr ps,ax        { Pointer la primul cuvint }
      mov ax,word ptr pd;   add ax,cuvd;      add ax,cuvd
      mov word ptr pd,ax       { Pointer la ultimul cuvint }
    end;
    citecuv := cuvd - cuvs; citecuv := citecuv - 1;
    if citecuv = -1 then begin   { Modifica doar un cuvint }
      mass := mass and masd;
      asm
        les di,ps;          mov ax,es:[di];   or ax,mass
        mov es:[di],ax     { Cuvintul cu adaugarile cerute }
      end
    end else begin       { Modifica cel putin doua cuvinte }
      asm
        les di,ps;          mov ax,es:[di];   or ax,mass
        mov es:[di],ax      { Elementele din primul cuvint }
        les di,pd;          mov ax,es:[di];   or ax,masd
        mov es:[di],ax     { Elementele din ultimul cuvint }
      end;
      if citecuv > 0 then asm     { Cel putin trei cuvinte }
        mov ax,$FFFF;       cld;              mov cx,citecuv
        les di,ps;          add di,2
        rep stosw     { Se umplu cu 1 cuvintele interioare }
      end
    end
  end;

Aceasta procedura adauga multimii curente elementele ale caror indici sunt cuprinsi ntre s si d. Mai nti se determina cuvntul si bitul din cadrul cuvntului care contine bitul s al multimii. De asemenea se determina cuvntul si bitul din cadrul cuvntului care contine bitul d al multimii.

Prima secventa n limbaj de asamblare pregateste masca mass care contine cifre binare 1 corespunzator bitilor cu rang mai mare sau egal celui corespunzator bitului s din multime. Tot aici se prepara masca masd care contine cifre binare 1 corespunzator bitilor cu rang mai sau egal celui corespunzator bitului d din multime. Aceste masti vor fi folosite pentru a actualiza primul si ultimul cuvnt care contin bitii dintre s si d din reprezentarea multimii.

n functie de cuvintele n care apar bitii s si d se disting trei cazuri:
	- bitii dintre s si d sunt apartin aceluiasi cuvnt;
	- bitii dintre s si d sunt n doua cuvinte consecutive;
	- bitii n cauza apartin cel putin la trei cuvinte.
Variabila citecuv diferentiaza cele trei cazuri. Cele trei secvente n limbaj de asamblare care urmeaza realizeaza adaugarea n cele trei cazuri amintite.

	12.1.6. Metoda STERGE

Aceasta metoda este aproape identica cu metoda ADAUGA prezentata n sectiunea precedenta. In esenta deosebirile constau n inversarea bitilor din cele doua masti masss, masd si inversarea ntre ele a operatiilor and si or. Textul ei sursa difera de cel al metodei ADAUGA n opt locuri, evidentiate n textul sursa din sectiunea precedenta. Acestea vor fi nlocuite dupa cum urmeaza:
	- numele metodei: sterge;
	- dupa fiecare dintre cele doua instructiuni subliniate  se va adauga cte o instructiune:

		NOT	AX

	- operatorul and va fi nlocuit cu operatorul OR;
	- cele trei aparitii ale instructiunii or vor fi nlocuite cu instructiunea AND;
	- constanta $FFF va fi nlocuita cu $0000.

	12.1.7. Metoda COMPLEMENT

Aceasta metoda inverseaza valorile tuturor bitilor care reprezinta multimea curenta. Prin aceasta se nlocuieste multimea curenta cu complementara ei fata de multimea de baza. Textul sursa al metodei este:

procedure multime.complement;
  var  p: pointer;
       n: word;
  begin
    p := pel;        n := ncuv;
    asm          { Se vor inversa toti bitii reprezentarii }
      push ds
      lds si,p;      les di,p;      cld;         mov cx,n
    @1: { Incarca cuvintul curent, inverseaza, rememoreaza }
      lodsw;         not ax;        stosw;       loop @1
      pop ds
  end   end;

	12.1.8. Metoda APART

Aceasta metoda este o functie cu rezultat boolean. Ea raspunde TRUE daca elementul de indice i, dat ca parametru, apartine multimii curente si raspunde FALSE n caz contrar. Textul sursa al metodei este:

function multime.apart(i: word): boolean;
  var  cuv, bit: word;
       p: pointer;
  begin
    apart := false;
    if (i < 0) or (i >= nmax) then exit;
    cuv := i div 16;           { Cuvintul in care se cauta }
    bit := i mod 16;             { Bitul cautat (in cuvint }
    p := pel;
    asm
      les di,p;      add di,cuv;      add di,cuv
      mov ax,es:[di]          { AX contine cuvintul curent }
      mov cx,bit;    shr ax,cl;       and ax,$0001
      mov cuv,ax                 { S-a izolat bitul curent }
    end;
    apart := cuv = 1              { Raspuns TRUE sau FALSE }
  end;
sursa al acestei functii este:

function reuniune(a, b: multime): pmultime;
  var  pb, pc: pointer;
       p: pmultime;
       n: word;
  begin
    reuniune := nil;
    if a.nmax <> b.nmax then exit;
    getmem(pc, sizeof(multime)); { Memorie pentru obiectul }
    p := pc;                     {   rezultat al operatiei }
    p^.initc(a);          { Initializeaza cu prima multime }
    reuniune := p;  pc := p^.pel;  pb := b.pel; n := b.nmax;
    asm
      push ds
      les bx,pb;        mov dx,es  { DX:BX si ES:BX --> b  }
      lds si,pc;        les di,pc  { DS:SI si ES:DI --> p^ }
      cld;               mov cx,n
    @1:
      lodsw { Incarca cuvintul din a identic cu cel din p^ }
      push es
      mov  es,dx                    { Schimbat temporar ES }
      or ax,es:[bx]           { Opereaza cu cuvintul din b }
      add bx,2           { Avans pentru iteratia urmatoare }
      pop es                         { Refacut ES original }
      stosw     { Memoreaza cuvintul rezultat al operatiei }
      loop @1
      pop ds
    end
  end;


Functia aloca mai nti spatiu pentru obiectul rezultat, reperat de pointerul p. Apoi initializeaza acest obiect cu multimea a prin constructorul INITC prezentat mai sus (12.1.3). Apoi secventa n limbaj de asamblare, folosind instructiunile LODSW si STOSW, parcurge n paralel cuvintele care reprezinta multimile a si b pe care le opereaza cu instructiunea OR, realiznd astfel reuniunea. Trebuie putina atentie la folosirea cu un dublu rol a registrului ES.

	12.1.9. Functia INTERSEC

Aceasta functie primeste ca parametri doua obiecte de tip multime a si b, iar ca rezultat da un element de tip pmultime (pointer spre tip multime) indicnd multimea a ( b. Textul acestei functii este aproape identic cu cel al functiei REUNIUNE. Singurele deosebiri sunt cele patru evidentiate n textul sursa de mai sus si anume:
	- cele trei aparitii ale numelui reuniune vor fi nlocuite cu numele INTERSEC;
	- instructiunea or din secventa n limbaj de asamblare va fi nlocuita cu instructiunea AND.

	12.1.10. Utilizarea unitatii MULTIMI

Programul t de mai jos executa o serie de operatii asupra a doua multimi m si n si obtine alte multimi, indicate prin pointeri.

program t;
uses multimi;
var  m, n: multime;
     i: integer;
     pncompl, pmreun, pmintn: pmultime;
begin
  m.initv(500);   m.adauga(30,38);
  n.initv(500);   n.adauga(35,43);
  getmem(pncompl, sizeof(multime));
  pncompl^.initc(n);        pncompl^.complement;
  pmreun := reuniune(m,n);  pmintn := intersec(m,n);
  writeln;
  for i := 29 to 45 do
    writeln(i:3, m.apart(i):7, n.apart(i):7,
                 pncompl^.apart(i):7,
                 pmreun^.apart(i):7, pmintn^.apart(i):7);
  m.done;        n.done;       pncompl^.done;
  pmreun^.done;  pmintn^.done;
  readln
end.

	12.1.11. Ciurul lui Eratostene

program ERATOSTENE;
{ Determina numerele prime pina la maximum 65533, folosind
  ciurul lui Eratostene. In acest scop se opereaza asupra
  unei singure multimi, "M". Rezultatele se afiseaza pe
  ecran si se scriu in fisierul "PRIME" }

uses   multimi;
const  s: string = 'Apasa o tasta ...';
var    M: multime;
       i, pas, maxim, limita: word;
       f: file of word;
begin
  maxim := $FFFD; { Aici se poate pune si un numar mai mic }
  M.initv(maxim);                M.adauga(2,maxim);
  limita := trunc(sqrt(maxim));  pas := 2;
  while pas <= limita do begin
    while not M.apart(pas) do inc(pas);
    i := pas + pas;
    while i <= maxim do begin
      M.sterge(i, i);
      if i < $FFFF - pas then i := i + pas  else  i := $FFFF
    end;
    inc(pas)
  end;
  pas := 0;  assign(f, 'PRIME');   rewrite(f);
  for i := 2 to maxim do
    if M.apart(i) then begin
      if pas mod 240 = 0 then begin
          writeln; write(s); readln end;
      if pas mod  10 = 0 then writeln;
      write(i:7);    inc(pas);    write(f, i)
    end;
  writeln;
  writeln('Exista ',pas,' numere prime intre 2 si ',maxim);
  write(s);     M.done;    close(f);    readln;
end.



