Rozdział    7
          
                                                                                    ZBIORY
      
      
      
      
      7.1. Wprowadzenie
        Język Pascal umożliwia wykorzystywanie w programach zbiorów teorio- 
      mnogościowych, których elementy muszą należeć do pewnego określonego
      typu. 
      Typ zbiorowy definiujemy w sposób następujący:
        type nazwa = set of nazwa_typu ;
      gdzie nazwa jest nazwą typu zbiorowego, a nazwa_typu jest nazwą tzw.
      typu podstawowego, który stanowi elementy zbioru (może to być typ integer,
      char, boolean, wyliczeniowy lub okrojony). Wartościami zmiennych typu
      zbiorowego są zatem wszystkie zbiory, które można utworzyć z wartości
      typu podstawowego oraz zbiór pusty (pusty nie zawiera żadnego elementu). 
        Wartości zmiennym typu zbiorowego nadajemy przy pomocy tzw. konst-      
      ruktora zbioru, którym jest para nawiasów prostokątnych [], wewnątrz
      których wpisujemy wartości typu podstawowego.
      
      Przykład
      type
        abc = 'a'..'c';
        trzylitery = set of abc;
      var
        x: trzylitery;
      
        Utworzono typ zbiorowy o nazwie trzylitery (typem podstawowym są
      litery 'a','b','c') oraz zadeklarowano zmienną x typu trzylitery. 
      Wartościami zmiennej x mogą być wszystkie podzbiory zbioru { 'a','b','c' } 
      oraz zbiór pusty. Poniżej wypisano wszystkie możliwe przypisania wartości 
      zmiennej x przy pomocy konstruktora zbioru []. 
        x := [];
        x := ['a'];
        x := ['b'];
        x := ['c'];
        x := ['a','b'];
        x := ['a','c'];
        x := ['b','c'];
        x := ['a','b','c'];
      
        Zauważmy jeszcze, że definicję typu zbiorowego można zawrzeć bezpo-      
      średnio w deklaracji zmiennej x:
      var
        x: set of 'a'..'c';
                  
      Przykład
      type 
        podstawa = 1..10;
        zbiorliczb = set of podstawa;
      var
        pelny, x, pusty : zbiorliczb;
      
        Zadeklarowano zmienne pelny, x, pusty typu zbiorowego, których warto-      
      ściami mogą być dowolne podzbiory zbioru {1,...,10}. Wartości tym zmiennym
      przypiszemy w sposób następujący:
      pelny := [1..10];
      x := [3,5,7,9];
      pusty := [];
      
        Wartością zmiennej pelny jest zbiór liczb {1,...,10}, wartością zmiennej x
      jest zbiór liczb {3,5,7,9}, a  zmiennej pusty jest zbiór pusty, który nie 
      zawiera żadnego elementu.
            
        Powyższy przykład ilustruje między innymi definicję tak zwanego zbioru
      pełnego, czyli takiego, który zawiera wszystkie elementy ze zdefiniowanego
      typu podstawowego. Warto zwrócić uwagę, że zarówno zbiór pełny jak i
      zbiór pusty są szeroko wykorzystywane w wielu programach, w których
      operuje się na zbiorach.
      
      
      7.2. Operacje na zbiorach
        Język Pascal umożliwia wykonywanie następujących operacji na zbio-     
      rach:
      
      
Operator      Znaczenie
+      suma zbiorów
-      różnica zbiorów
*     część wspólna zbiorów
      
           Należy również zwrócić uwagę na operator in, który umożliwia stwier-      
      dzenie, czy dany element należy do zbioru. Można też wykorzystywać opera-      
      tory relacyjne:     
      
 
Operator      Znaczenie
=      równość zbiorów
<>      nierówność  zbiorów
<= oraz =>     zawieranie się zbiorów
        
        Obecnie dla przypomnienia podamy definicję sumy, różnicy i iloczynu
      zbiorów. Sumą zbiorów A oraz B jest zbiór, którego elementy należą do
      zbioru A lub do zbioru B. Iloczynem zbiorów A oraz B jest zbiór, którego
      elementy należą do zbioru A i do zbioru B. Natomiast różnicą zbiorów A
      oraz B jest zbiór, którego  elementy należą do zbioru A, ale nie należą do
      zbioru B. Wszystkie zdefiniowane operacje na zbiorach ilustruje poniższy
      przykład.
      
      Przykład
      Rozważmy następujący zbiór artykułów sportowych:
      { narty, kijki, buty_nar, łyżwy, sanki, rakiety, piłki, płetwy, okulary_pływ,
      rowery, motocykle} 
      Następnie przypuśćmy, że sklep A sprzedaje następujące artykuły:
        { narty, kijki,buty_nar,rakiety,piłki }
      a sklep B sprzedaje poniżej wymienione artykuły:
        { rakiety,piłki,płetwy,okulary_pływ }
        Powyższe dane możemy w programie zapamiętać przy pomocy struktury
      danych postaci:
      type
        artyk = (narty,kijki,buty_narc,lyzwy,sanki,rakiety,
             pilki,pletwy,okul_plyw,rowery,motocykle);
        zbiorartyk = set of artyk;
      var
        sklep_A,     { artykuły sprzedawane przez sklep A }
        sklep_B,     { artykuły sprzedawane przez sklep B }
        wszystkie,   { wszystkie artykuły } 
        x            { zmienna robocza wykorzystywana do
                    przechowywania informacji o obu sklepach }
             : zbiorartyk;
      
      Zmiennym wszystkie, sklep_A, sklep_B powinny być nadane następujące
      wartości:
        wszystkie := [narty..motocykle];
        sklep_A := [narty..pilki];
        sklep_B := [rakiety..okul_plyw];
      
      Przy pomocy tych zmiennych oraz poszczególnych operatorów możemy
      otrzymywać informacje o artykułach sportowych, co ilustruje poniższa
      tabela:

             wyrażenie                                                                  wartość zmiennej x

            1)  x := sklep_A + sklep_B                                          [narty..okul_plyw]
            2)  x := sklep_A * sklep_B                                          [rakiety,pilki]

            3)  x := sklep_A - sklep_B                                          [narty..sanki]
            4)  x := sklep_B - sklep_A                                          [pletwy,okul_plyw]
            5)  x := wszystkie - sklep_A                                        [pletwy..motocykle]
            6)  x := wszystkie - sklep_B                                        [narty..sanki,rowery,motocykle]
            7)  x := wszystkie - sklep_A - sklep_B                        [rowery,motocykle]

      Znaczenie wartości poszczególnych wyrażeń jest następujące:
      1) artykuły dostępne w jednym lub drugim sklepie
      2) artykuły dostępne w obu sklepach
      3) artykuły dostępne w sklepie A i niedostępne w sklepie B
      4) artykuły dostępne w sklepie B i niedostępne w sklepie A
      5) artykuły niedostępne w sklepie A
      6) artykuły niedostępne w sklepie B
      7) artykuły niedostępne w żadnym sklepie
        Program ilustrujący przetwarzanie informacji o artykułach sprzedawa-      
      nych w dwóch sklepach sportowych zostanie zamieszczony w następnym
      podrozdziale.
         
      7.3. Podstawowe algorytmy
         W niniejszym podrozdziale podamy podstawowe algorytmy, które umoż-      
      liwiają wykonywanie operacji na zbiorach. 
        Przede wszystkim zilustrujemy podstawowe operacje, a mianowicie
      wprowadzanie i wyprowadzanie danych ze zmiennych typu zbiorowego. 
        Jeżeli typem podstawowym nie jest typ wyliczeniowy, to wczytanie i
      wydruk wartości zmiennej typu zbiorowego odbywa się względnie prosto.
      Zilustrujemy to na przykładzie zmiennych o poniższej deklaracji:
      type
        zbiorliczb = set of 1..100;
      var
        x,y: zbiorliczb;
      
        Przypomnijmy, że przy takiej deklaracji wartościami zmiennych x i y
      mogą być wszystkie podzbiory zbioru { 1,...,100 } łącznie ze zbiorem pustym.    
      Procedura CzytajZbior pozwala na wczytanie dowolnej wartości zmiennej
      typu zbiorliczb. Algorytm wykorzystany w tej procedurze jest następujący:
      
      Algorytm 7.1.
      przypisz zmiennej a typu zbiorowego zbiór pusty
      podczas gdy wczytana liczba jest różna od zera to
        jeśli wczytana liczba mieści się w zakresie typu podstawowego to
           {
           dodaj do zmiennej a zbiór złożony z wczytanej liczby
           czytaj liczbę
           }
      
      A oto treść procedury:
      
      procedure CzytajZbior(var a:zbiorliczb;ograniczenie1,
                                ograniczenie2: integer);
      { Procedura wczytywania wartości zmiennej typu zbiorowego }
      var
        k: integer;
      begin
        writeln('Podaj wartość zmiennej zbiorowej ( 0 kończy )');
        read(k);
        a := [];
        while k <> 0 do
           begin
             if k in [ ograniczenie1..ograniczenie2] then 
                a := a + [k];
             read(k)
           end
      end;
      
        Wydruk wartości zmiennej typu zbiorliczb umożliwia procedura
      DrukujZbior. Zanim podamy jej treść przedstawimy algorytm wykorzystany
      w tej procedurze.
      
      Algorytm 7.2.
      dla i przebiegającej przez wszystkie wartości typu podstawowego 
        wykonuj
        jeśli i należy do wartości zmiennej a to 
           drukuj i
      
      
      procedure DrukujZbior(a:zbiorliczb;ograniczenie1,
                                                                                      ograniczenie2: integer);
      {Procedura drukowania wartości zmiennej typu zbiorowego}
      var
        i: integer;
      begin
        writeln('Wartość zmiennej zbiorowej');
      
        for i := ograniczenie1 to ograniczenie2 do
           if i in a then writeln(i)
      end;
      
        Zamieszczony poniżej program testuje wykonanie procedur CzytajZbior i
      DrukujZbior.
      
      Program 7.1
      program Test;
      type
        zbiorliczb = set of 1..100;
      var
        x,y: zbiorliczb;
      
      procedure CzytajZbior(var a:zbiorliczb;ograniczenie1,
                                                                                      ograniczenie2: integer);
      {Procedura wczytywania wartości zmiennej typu zbiorowego}
      begin 
        { Treść procedury zamieszczono wcześniej }
      end;
      
      procedure DrukujZbior(a:zbiorliczb;ograniczenie1,
                                                                                      ograniczenie2: integer);
      { Procedura drukowania wartości zmiennej typu zbiorowego }
      begin 
        { Treść procedury zamieszczono wcześniej }
      end;
      
      begin
        CzytajZbior(x,1,100);
        CzytajZbior(y,1,100);
        DrukujZbior(x,1,100);
        DrukujZbior(y,1,100)
      end.
      
      Jeśli wczytamy następujące ciągi liczb:
      3 4 3 4 4 9 0
      55 67 55 55 88 0
      
      to wartością zmiennej x będzie [3,4,9], a wartością zmiennej y [55,67,88].
        Warto podkreślić, że podane wyżej procedury mają charakter ogólny,
      mogą być wykorzystywane dla wszystkich typów zbiorowych, w których
      typem podstawowym są liczby. Dla innych typów podstawowych należy
      dokonać drobnej modyfikacji tych procedur.
        Sytuacja znacznie się komplikuje, jeśli typem podstawowym, z którego
      tworzy się elementy zbioru, jest typ wyliczeniowy. W poprzednim podroz-      
      dziale rozważaliśmy przykład, w którym były wyznaczane informacje doty-      
      czące artykułów sprzedawanych w dwóch sklepach. Przypomnijmy, że typem
      podstawowym, z którego tworzyliśmy wartości zmiennych typu zbiorowego
      był następujący typ wyliczeniowy:
      artyk = ( narty, kijki, buty_narc,lyzwy,sanki,rakiety,pilki,pletwy,
                  okul_plyw, rowery, motocykle );
      
      a typem zbiorowym typ:
        zbiorartyk = set of artyk;
      
      W tym przypadku do wczytywania i wyprowadzania wartości zmiennych
      typu zbiorowego należy zaprojektować procedury ukierunkowane na ten
      konkretny typ wyliczeniowy (dla innego typu wyliczeniowego procedury te
      będą miały inną postać).
        Algorytm wykorzystany w procedurze drukowania i treść tej procedury
      są przedstawione poniżej:
      
      Algorytm 7.3.
      dla i przebiegającej wszystkie wartości typu wyliczeniowego wykonuj
        jeśli i zawiera się w a to
           wyprowadź odpowiedni tekst w zależności od  i
      
      procedure DrukujWylicz(a:zbiorartyk);
      { Procedura drukowania wartości zmiennej typu zbiorowego,
      którego typ podstawowy jest typem wyliczeniowym }
      var
        i: artyk; 
      begin
        for i := narty to motocykle do
           if i in a then
             case i of
                narty: writeln('narty');
                kijki: writeln('kijki');
                buty_narc: writeln('buty_narc');
                lyzwy: writeln('lyzwy');
                sanki: writeln('sanki');
                rakiety:writeln('rakiety');
                pilki: writeln('pilki');
                pletwy: writeln('pletwy');
                okul_plyw: writeln('okul_plyw');
                rowery: writeln('rowery');
                motocykle: writeln('motocykle')
             end
      end;
      
        Algorytm wykorzystany w procedurze czytania i treść procedury są
      podane poniżej.
      
      Algorytm 7.4.
      przypisz zmiennej a typu zbiorowego zbiór pusty
      podczas gdy wczytana liczba jest różna od zera to
        jeśli wczytana liczba mieści się w liczbie elementów typu
           wyliczeniowego to
           {
           dodaj do zmiennej a element złożony z odpowiedniej wartości
           typu wyliczeniowego                                                             poprzez przyporządkowanie liczbie wartości
           czytaj liczbę
           }
      
      procedure CzytajWylicz(var a:zbiorartyk; liczbaelemen: integer);
      { Procedura wczytywania wartości zmiennej typu zbiorowego,
      którego typ podstawowy jest typem wyliczeniowym }
      var
        k: integer;
      begin
        writeln('Podaj liczby odpowiadające wartościom',
             ' typu wyliczeniowego'); 
        read(k);
        a := [];
        while k <> 0 do
           begin
             if ( 1 <= k ) and ( k <= liczbaelemen ) then 
                case k of
                  1: a := a + [narty];
                  2: a := a + [kijki];
                  3: a := a + [buty_narc];
                  4: a := a + [lyzwy];
                  5: a := a + [sanki];
                  6: a := a + [rakiety];
                  7: a := a + [pilki];
                  8: a := a + [pletwy];
                  9: a := a + [okul_plyw];
                  10: a := a + [rowery];
                  11: a := a + [motocykle]
                end;
             read(k)
           end
      end;
      
        Program ilustrujący wykorzystanie procedury CzytajWylicz i
      DrukujWylicz jest przedstawiony poniżej.
      
      Program 7.2
      program Testowy;
      { Program ilustrujący wykorzystanie procedur CzytajWylicz
      i DrukujWylicz }
      type
        artyk = (narty,kijki,buty_narc,lyzwy,sanki,rakiety,
               pilki,pletwy,okul_plyw,rowery,motocykle);
        zbiorartyk = set of artyk;
      var
        x  { zmienna do wprowadzania i wyprowadzania wartości }
             : zbiorartyk;
      
      procedure CzytajWylicz(var a:zbiorartyk; liczbaelemen:
                                                                                       integer);
      { Procedura wczytywania wartości zmiennej typu zbiorowego,
      którego typ podstawowy jest typem wyliczeniowym }
      begin
        { Treść procedury zamieszczono wcześniej }
      end;
      
      procedure DrukujWylicz(a:zbiorartyk);
      { Procedura drukowania wartości zmiennej typu zbiorowego,
      którego typ podstawowy jest typem wyliczeniowym }
      begin 
        { Treść procedury zamieszczono wcześniej }
      end;
      
      begin
        CzytajWylicz(x,11);
        DrukujWylicz(x)
      end.
      
      Jeżeli wprowadzimy następujące dane:
      1   5   1   4   6  4   1  0
      
      to uzyskamy wydruk postaci:
      narty 
      lyzwy
      sanki
      rakiety
      
        Posiadając umiejętność wczytywania i wyprowadzania wartości, możemy
      przystąpić do wykorzystania informacji zawartych w zmiennych typu zbioro-      
      wego. Obecnie zaprojektujemy program, który umożliwia po wprowadzeniu
      wykazu artykułów sportowych uzyskanie następujących informacji: 
      1) wykaz artykułów (spośród wprowadzonych), których nie można zakupić w
      żadnym sklepie
      2) wykaz artykułów, które musimy zakupić w sklepie A
      3) wykaz artykułów, które musimy zakupić w sklepie B
      Zmienną, w której będziemy przechowywali wprowadzony wykaz artykułów,
      nazwiemy wykaz. Podobnie jak w poprzednim podrozdziale do wyznaczenia
      żądanych danych wykorzystamy wyrażenia logiczne. Informację pierwszą
      możemy wyznaczyć poprzez wyrażenie postaci
        ( wszystkie - sklep_A - sklep_B ) * wykaz
      Informację drugą wyznaczamy obliczając następujące wyrażenie:
        ( sklep_A - sklep_B ) * wykaz
      A informację trzecią wyznaczamy przy pomocy wyrażenia:
        ( sklep_B - sklep_A ) * wykaz
      gdzie wartością zmiennej wszystkie jest zbiór pełny, wartością zmiennej
      sklep_A zbiór artykułów sprzedawanych w sklepie A oraz wartością zmien-     
      nej sklep_B zbiór artykułów sprzedawanych w sklepie B. Wartości wymie-      
      nionym zmiennym należy nadać na początku programu. Unikniemy w ten
      sposób wypisywania w programie odpowiednich składowych poprzez zastą-      
      pienie ich nazwą zmiennej i tak np. zamiast zapisu [narty..motocykle]
      użyjemy zmiennej wszystkie. Przypisanie tych wartości może się odbywać
      przy pomocy poniższej procedury o nazwie Stale:
      procedure Stale(var wszystkie,sklep_A,sklep_B:                                                      zbiorartyk);
      { nadanie zmiennym stałych wartości }
      begin
        wszystkie := [narty..motocykle];
        sklep_A := [narty..sanki];
        sklep_B := [rakiety..okul_plyw]
      end;
      
      Program realizujący wyprowadzenie żądanej informacji jest podany poniżej:
      
      Program 7.3
      program Sportowy;
      type
        artyk = (narty,kijki,buty_narc,lyzwy,sanki,rakiety,
               pilki,pletwy,okul_plyw,rowery,motocykle);
        zbiorartyk = set of artyk;
      var
        sklep_A,    { wykaz artykułów dostępnych w sklepie A }
        sklep_B,    { wykaz artykułów dostępnych w sklepie B }
        wszystkie,  { wykaz wszystkich artykułów }
        wykaz,      { wprowadzony wykaz artykułów }
        wynik       { wartość wynikowa }
                : zbiorartyk;
      
      procedure Stale(var wszystkie,sklep_A,sklep_B: zbiorartyk);
      { nadanie zmiennym stałych wartości }
      begin 
        { Treść procedury zamieszczono wcześniej }
      end;
      
      procedure CzytajWylicz(var a:zbiorartyk; liczbaelemen:                                                         integer);
      { Procedura wczytywania wartości zmiennej typu zbiorowego,
      którego typ podstawowy jest typem wyliczeniowym }
      begin
        { Treść procedury zamieszczono wcześniej }
      end;
      
      procedure DrukujWylicz(a:zbiorartyk);
      { Procedura drukowania wartości zmiennej typu zbiorowego,
      którego typ podstawowy jest typem wyliczeniowym }
      begin 
        { Treść procedury zamieszczono wcześniej }
      end;
      
      begin
        Stale(wszystkie,sklep_A,sklep_B);
        CzytajWylicz(wykaz,11);
        writeln('Artykuły z wczytanego wykazu');
        DrukujWylicz(wykaz);
      
        writeln('Artykuły z wykazu, których nie ma w żadnym sklepie');
        wynik := ( wszystkie - sklep_A - sklep_B ) * wykaz;
        DrukujWylicz(wynik);
      
        writeln('Artykuły z wykazu, które można kupić tylko w sklepie A');
        wynik := ( sklep_A - sklep_B ) * wykaz;
        DrukujWylicz(wynik);
      
        writeln('Artykuły z wykazu, które można kupić tylko w sklepie B');
        wynik := ( sklep_B - sklep_A ) * wykaz;
        DrukujWylicz(wynik)
      end.
      
        Przedstawione wyżej procedury wprowadzania i wyprowadzania wartości 
      zmiennych typu zbiorowego wykorzystywały następujące dwa ogólne algorytmy: 
      
      Algorytm 7.5.
      dla wszystkich wartości typu podstawowego wykonuj
        jeśli wartość ta zawiera się w pewnym zbiorze to
           przetwarzaj tę wartość
      
      
      Algorytm 7.6.
      przypisz zmiennej x wartość zbioru pustego
      podczas gdy istnieją wartości do przetworzenia wykonuj
        {
        czytaj wartość a
        jeśli a zawiera się w zbiorze pełnym to
           x := x + [a]
        }
      
      Algorytm 7.5 był wykorzystywany przy wyprowadzaniu wartości zmiennej
      typu zbiorowego, a algorytm 7.6 przy wprowadzaniu tej wartości. Równie
      często jest stosowany następujący algorytm:
      
      Algorytm 7.7.
      przypisz zmiennej x wartość zbioru pełnego
      podczas gdy istnieją wartości do przetworzenia wykonuj
        {
        czytaj wartość a
        jeśli  zawiera się w zbiorze pełnym to
           x := x - [a]
        }
      
        Algorytm 7.7 jest stosowany do eliminacji pewnych wartości ze zbioru
      pełnego. Wykorzystanie wszystkich trzech podanych algorytmów ilustruje
      kolejny przykład.
      
      Przykład
      W przykładzie rozważymy następujący problem. W tekście umieszczonym w
      pliku tekstowym należy znaleźć znaki nieobecne w tym tekście i jednocześ-      
      nie występujące w pierwszym zbiorze znaków oraz znaki obecne w tym
      tekście i jednocześnie występujące w drugim zbiorze znaków. 
        Pierwszy zbiór znaków będziemy przechowywać w zmiennej typu zbioro-      
      wego o nazwie nieobecne, a drugi zbiór znaków w zmiennej o nazwie obecne.
      Do wczytania i drukowania wartości tych zmiennych wykorzystamy dwie
      procedury o nazwach CzytajZbior oraz DrukujZbior. Procedury te są zapro-      
      jektowane analogicznie jak procedury o tych samych nazwach podane w
      poprzednim podrozdziale. Różnią się tylko typem parametrów i zmiennych
      wewnętrznych.
        Zbiór znaków obecnych w tekście i jednocześnie występujących w zmien-      
      nej obecne będziemy przechowywać w zmiennej t_obecne, natomiast zbiór
      znaków nieobecnych w tekście i jednocześnie występujących w zmiennej
      nieobecne będziemy przechowywać w zmiennej t_nieobecne. Wartość począt-      
      kowa zmiennej t_obecne powinna być równa [], a wartość początkowa zmien-      
      nej t_nieobecne powinna być równa wartości zmiennej nieobecne. Dla war-      
      tości zmiennej obecne:
        [X,Y,P]  
      i wartości zmiennej nieobecne:
        [a,b,c,d,e]
      oraz tekstu 
         Program = algorytm + struktura danych 
      wartość zmiennej t_obecne powinna wynosić:
        [P]
      a wartość zmiennej t_nieobecne powinna być równa:
        [b,e]
        Możemy teraz przystąpić do zaprojektowania procedury wyznaczającej
      wartości zmiennych t_obecne oraz t_nieobecne. Algorytm przetwarzania
      tekstu wykorzystuje algorytmy 7.6 oraz 7.7 i jest następujący:
      
      Algorytm 7.8
      otwórz żądany plik
      podczas gdy w pliku występują jeszcze symbole wykonuj
        {
        czytaj znak z pliku
        jeśli znak zawiera się w wartości zmiennej obecne to 
           dodaj do wartości zmiennej t_obecne zbiór złożony ze znaku
        jeśli znak zawiera się w wartości zmiennej nieobecne to
           odejmij od wartości zmiennej t_nieobecne zbiór złożony ze znaku
      
      Tekst procedury jest podany poniżej:
      procedure Przetwarzaj(obecne,nieobecne:znaki;
      var t_obecne:znaki; var t_nieobecne:znaki; var plik:text);
      { Procedura przetwarzania znaków z tekstu }
      var
        znak: char;
      begin
        assign(plik,'ala');     
        reset(plik);            
        while not eof(plik) do
           begin
             read(plik,znak);
             if znak in obecne then
                t_obecne := t_obecne + [znak];
             if znak in nieobecne then
                t_nieobecne := t_nieobecne - [znak]
           end
      end;
      
        Możemy już teraz podać tekst programu sprawdzającego przynależność
      znaków. Zrobimy to przy założeniu, że wzorcowe zbiory znaków (zmienne
      obecne oraz nieobecne) mogą składać się z dużych i małych liter. Ogranicze-      
      nia typu podstawowego przy wywołaniu procedur CzytajZbior i DrukujZbior
      są zatem następujące: 'A' ograniczenie dolne i 'z' ograniczenie górne. Przy
      innym typie podstawowym należałoby zmienić te ograniczenia.
      
      Program 7.4
      program Tekst;
      { Program sprawdzania przynależności znaków z tekstu }
      type
        znaki = set of char;
      var
        obecne, nieobecne,        { wzorcowe zbiory znaków }
        t_obecne, t_nieobecne: { wyznaczane zbiory znaków }
                  znaki;
        plik: text;     { plik zawierający sprawdzany tekst }
      
      procedure CzytajZbior(var a:znaki;
      ograniczenie1,ograniczenie2,koniec: char);
      { Procedura wczytywania zbioru znaków }
      var
        k: char;
      begin
        writeln('Podaj wartość zmiennej zbiorowej: ');
        read(k);
        a := [];
        while k <> koniec do
           begin
             if (ograniczenie1 <= k) and
                (k <= ograniczenie2) then 
                a := a + [k];
             read(k)
           end
      end;
      
      procedure DrukujZbior(a:znaki; ograniczenie1,ograniczenie2: char);
      { Procedura drukowania zbioru znaków }
      var
        i: char;
      begin
        writeln('Wartość zmiennej zbiorowej');
        for i := ograniczenie1 to ograniczenie2 do
           if i in a then writeln(i)
      end;
      
      procedure Przetwarzaj(obecne,nieobecne: znaki; 
        var t_obecne,t_nieobecne: znaki; var plik: text);
      { Procedura przetwarzania znaków z tekstu }
      begin  
      end;
      
      begin
        writeln('Podaj pierwszy wykaz znaków ');
        CzytajZbior(obecne,'A','z','?');
        writeln('Wykaz znaków, których szukamy znaków',
             'obecnych w tekście:');
        DrukujZbior(obecne,'A','z');
        
        writeln('Podaj drugi wykaz znaków ');
        CzytajZbior(nieobecne,'A','z','?');
        writeln('Wykaz znaków, których szukamy znaków',
                      ' nieobecnych w tekście:');
        DrukujZbior(nieobecne,'A','z');
      
        t_obecne := [];
        t_nieobecne := nieobecne;
      
        Przetwarzaj(obecne,nieobecne,t_obecne,t_nieobecne, plik);
      
        writeln('Znaki obecne we wczytanym tekście');
        DrukujZbior(t_obecne,'A','z');
      
        writeln('Znaki nieobecne we wczytanym tekście');
        DrukujZbior(t_nieobecne,'A','z')
      end.
      
        Na zakończenie zauważmy, że procedury wczytywania i drukowania
      wzorcowych zbiorów znaków zostały tak zaprojektowane, aby wczytywany
      był każdy znak należący do zbioru. Oczywiście możliwe jest takie zaprojek-      
      towanie tych procedur, aby wczytywane było tylko ograniczenie dolne i
      górne znaków należących do zbioru. Wykonanie tego zadania pozostawiamy
      jako ćwiczenie Czytelnikowi.