int21h

Abecední řazení

Úvod

Před více než deseti lety, když jsem začínal s pascalem, jsem se pokusil napsat funkce pro abecední třídění, které by dokázalo zpracovávat i české znaky.
Tehdy jsem to rozumně nedokázal napsat, tak jsem to po krátkém snažení zabalil. Od té doby jsem to vlastně nepotřeboval, takže k novému pokusu jsem se dostal až nedávno.

Tentokrát jsem ale záměr pojal velkoryseji - chtěl jsem, aby bylo možné přepínat mezi tříděními v různých normách češtiny a také aby se třídila azbuka, zkrátka, aby procedura byla jednoduše konfigurovatelná. Další požadavek je, aby se nedělal rozdíl mezi velkými a malými písmeny, t.j., aby
"auto" bylo zařazeno před "Zrno", i když ASCII(a)=97 a ASCII(Z)=90.

Úroveň 1

Postup byl vcelku jasný. V textu se může vyskytnout 256 různých znaků, takže si nadeklaruju pole o 256 bajtech, indexy do pole budou dány ASCII hodnotou znaků a hodnoty v poli budou určovat, kam budou dané ASCII znaky zařazeny.
Například pole[97{a}]=1, pole[65{A}]=1, pole[90{z}]=27 a podobně.
Spíš jde o to, jakým způsobem jednoduše takové pole nadeklarovat.
Nakonec jsem to udělal takhle:

trid_spol = '__!!##$$--00112233445566778899';
trid_czlat2 = trid_spol+'aA µbBcCĽ¬dDÔŇeE‚Ř·fFgGhHiI·ÖjJkKlLmMnNoO˘ŕpPq'+
                        'QrRýüsSçćtT¶›uUŁé…ŢvVxXyYěízZ§¦';

trid_czwin = trid_spol+'aAáÁbBcCčČdDďĎeEéÉěĚfFgGhHiIíÍjJkKlLmMnNoOóÓpPq'+
                       'QrRřŘsSšŠtTťŤuUúÚůŮvVxXyYýÝzZžŽ';

type PsortTable=^TSortTable;
TsortTable=array[0..255] of byte;

var _sysTridiciTbl:TSortTable;
    sysTridiciTbl:PSortTable;


Procedure PripravTridiciTabulku(t:string);
var a,b:byte;
begin
b:=Length(t);
if odd(b) then begin dec(b);delete(t,b,1);end; {delka retezce musi byt suda}
for a:=1 to b do sysTridiciTbl^[byte(t[a])]:=(a-1) div 2;
b:=b div 2;
for a:=0 to 255 do
    begin
    if Pos(char(a),t)=0 then
       begin
       sysTridiciTbl^[a]:=b;
       inc(b);
       if b>255 then
          b:=b;
       end;
    end;
end;

Takhle není nutno definovat všech 256 znaků, ale určím si jen ty, na kterých mi záleží.
V prvním kroku zpracuji definiční řetězec. Pozice každého znaku v definičním řetězci odpovídá abecední pozici. v třídícím poli.
Ještě před písmena jsem dal číslice a několik dalších znaků, jejichž výskyt běžně připadá do úvahy. Jelikož algoritmus očekává, že každý element má variantu velkého a malého písmene, tak musím jiné znaky než písmena uvádět dvojmo.
V druhém kroku pak přidělím abecední pozici zbylým znakům, t.j. těm, které nebyly uvedeny v definičním řetězci.
Pak už je to jednoduché. Umíme-li porovnat dva znaky a určit, který je v abecedě dál, tak umíme porovnat a utřídit i řetězce.

Function CmpChar(a,b:char):shortint;
var c,d:byte;
begin
c:=sysTridiciTbl^[byte(a)];
d:=sysTridiciTbl^[byte(b)];
if c<d then CmpChar:=1 else
if c>d then CmpChar:=-1 else CmpChar:=0;
end;


Function CmpString(var s1,s2:string):boolean;
{kdyz je dle tridici tabulky s1<=s2 vrati true, jinak false}
var a,b,c,d:byte;
    e:shortint;
begin
a:=Length(s1);
b:=Length(s2);

if a<b then c:=a else c:=b; {budeme porovnavat pocet znaku v kratsim retezci}

for d:=1 to c do
    begin
    e:=CmpChar(s1[d],s2[d]);
    if e<>0 then Break;
    end;

if e=0
   then CmpString:=b>=a {kdyz i po projiti celeho useku jsou retezce ident.}
   else CmpString:=e>0;
end;

Úroveň 2

V dalším kroku jsem chtěl, aby se písmeno (respektive spřežka)CH chovalo tak, jak jsme my Češi zvyklí. T.j. aby se řadilo mezi H a I, místo toho, aby se chovalo jako písmeno C, t.j. mezi znaky B a D.
Napřed jsem uvažoval o tom, napsat rutinu na zpracování spřežek univerzálně, ale když jsem se tak díval na stránky unicode, tak jsem zjistil, že československá "CH-anomálie" je velký unikát a vlastně jsem žádné jiné abecedu měnící spřežky nenašel.
Tudíž stačí udělat rutinu na zpracování CH jednoúčelovou a jediná konfigurovatelná věc bude to, jestli rutina bude nebo nebude použita.
V prvé řadě je třeba rozšířit definiční řetězce:
const
trid_spol = '__!!##$$--00112233445566778899';
trid__ch = #255#255;
trid_czlat2 = trid_spol+'aA &#181;bBcCĽ&#172;dDÔŇeE&#8218;&#144;Ř&#183;fFgGhH'+trid__ch+'iI·ÖjJkKlLm'+
                        'MnNoO˘ŕpPqQrRýüsSçćtT¶&#8250;uUŁé&#8230;ŢvVxXyYěízZ§&#166;';

trid_czwin = trid_spol+'aAáÁbBcCčČdDďĎeEéÉěĚfFgGhH'+trid__ch+'iIíÍjJkKlLm'+
                       'MnNoOóÓpPqQrRřŘsSšŠtTťŤuUúÚůŮvVxXyYýÝzZžŽ';

trid_rudos = trid_spol+' &#8364;·&#129;˘&#8218;Ł&#131;¤&#8222;ˇ&#8230;ńđ&#166;&#8224;§&#8225;¨&#136;&#169;&#8240;ŞŠ&#171;&#8249;&#172;¦­Ť&#174;ŽŻ¬ŕ&#144;á&#8216;â&#8217;ă&#8220;ä&#8221;ĺ&#8226;ć&#8211;'+
                       'ç&#8212;č&#152;é&#8482;ęšë&#8250;ě¶íťîžďĽ'+
                       'aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVxXyYzZ';

type PsortTable=^TSortTable;
TsortTable=array[0..{!}256{!}] of byte;
Spřežka CH bude vložena na místo trid__ch. Do znakových sad, které CH neznají, prostě nebudeme trid__ch uvádět.
Algoritmus je jednoduchý.
Zaprvé se při vstupním zpracování definičního řetězce poznamená, jestli obsahuje "CH-anomálii". To je důvod, proč jsem rozšířil typ TSortTable z 256 na 257 bajtů. Na indexu [256] je uvedeno, jestli je přítomna CH-anomálie.
Zadruhé - rutina na porovnání znaků zůstane nezměněna.
Zatřetí - při porovnávání řetězců se ve všech spřežkách CH nahradí znak "C" znakem trid__ch[1]
Kód dotčených procedur je změněn takto.
Procedure PripravTridiciTabulku(t:string);
var a,b:byte;
begin
b:=Length(t);
if odd(b) then begin dec(b);delete(t,b,1);end; {delka retezce musi byt suda}
for a:=1 to b do sysTridiciTbl^[byte(t[a])]:=(a-1) div 2;

b:=b div 2;
for a:=0 to 255 do
    begin
    if Pos(char(a),t)=0 then
       begin
       sysTridiciTbl^[a]:=b;
       inc(b);
       if b>255 then
          b:=b;
       end;
    end;

if Pos(trid__ch,t)<>0 then
   sysTridiciTbl^[256]:=1 else sysTridiciTbl^[256]:=0;
end;


Function Nahrad_ch(var s:string;c:byte):boolean;
var a:byte;
    z:boolean;
begin
z:=false;
for a:=1 to c do
    if (s[a]='c') or (s[a]='C') then
       if (a<>c) and ((s[a+1]='h') or (s[a+1]='H')) then
          begin
          s[a]:=trid__ch[1];
          z:=true;
          end;
Nahrad_ch:=z;
end;


Function CmpString(var s1,s2:string):boolean;
{kdyz je dle tridici tabulky s1<=s2 vrati true, jinak false}
var a,b,c,d:byte;
    e:shortint;
    z1,z2:boolean;
    poms1:string;
    poms2:string;

begin
a:=Length(s1);
b:=Length(s2);

if a<b then c:=a else c:=b; {budeme porovnavat pocet znaku v kratsim retezci}

if sysTridiciTbl^[256]=1 then
   begin
   poms1:=s1;
   poms2:=s2;
   z1:=Nahrad_ch(s1,c);
   z2:=Nahrad_ch(s2,c);
   end;

for d:=1 to c do
    begin
    e:=CmpChar(s1[d],s2[d]);
    if e<>0 then Break;
    end;

if e=0
   then CmpString:=b>=a {kdyz i po projiti celeho useku jsou retezce ident.}
   else CmpString:=e>0;

if sysTridiciTbl^[256]=1 then
   begin
   if Z1 then s1:=poms1;
   if Z2 then s2:=poms2;
   end;
end;

Úroveň 3

Jako poslední věc jsem chtěl zavést věc, které se říká "přirozené řazení".
Jde o způsob, jak se řadí řetězce obsahující v sobě číslice.
Drtivá většina programů provede takovéto setřídění:
FOTO1.JPG
FOTO13.JPG
FOTO15.JPG
FOTO2.JPG
FOTO9.JPG
Jednou z mála světlých výjimek je průzkumník z windows XP, který názvy setřídí takto:
FOTO1.JPG
FOTO2.JPG
FOTO9.JPG
FOTO13.JPG
FOTO15.JPG
Mnohem lepší, ne?
Znovu je potřeba upravit proceduru CmpString, aby za určitých podmínek volala nikoliv CmpChar, ale CmpNum.
Procedura CmpNum není úplně triviální. Jde o to, že nelze jednoduše zavolat proceduru Val, protože za prvé může snadno být převáděné číslo větší než pascalovské číselné typy a za druhé budou proceduru Val mást případné další znaky za číslicemi. Také je potřeba počítat s tím, že čísla mohou být uvozena nulami.
Přes všechna uvedená rizika se mi ale zdá lepší proceduru Val využít než psát svoji rutinu úplně od nuly.
Výsledek je zde.
Function CmpNum(var s1,s2:string;var d:byte;c:byte):shortint;
{Bohuzel nelze snadno pouzit proceduru VAL - selhala by u cisel s vice rady
 nez pojme longint}
var a,b:byte;
    t1,t2:string;
    w1,w2:word;
    i1,i2:integer;
begin
a:=d;
repeat
t1:=Copy(s1,a,4);
t2:=Copy(s2,a,4);

Val(t1,w1,i1);
Val(t2,w2,i2);

if (i1=0) and (i2=0) then
   begin
   if w1>w2 then begin CmpNum:=-1;Exit;end;
   if w1<w2 then begin CmpNum:=1;Exit;end;
   end;

if i1=i2 then {zde uz vime, ze i1=i2<>0}
   begin
   t1:=Copy(s1,a,i1-1);
   t2:=Copy(s2,a,i2-1);
   Val(t1,w1,i1);
   Val(t2,w2,i2);
   if w1>w2 then begin CmpNum:=-1;Exit;end;
   if w1<w2 then begin CmpNum:=1;Exit;end;
   end;

if i1<>i2 then
   begin
   if i1<>0 then begin t1:=Copy(s1,a,i1-1);Val(t1,w1,i1);end;
   if i2<>0 then begin t2:=Copy(s2,a,i2-1);Val(t2,w2,i2);end;
   if w1>w2 then begin CmpNum:=-1;Exit;end;
   if w1<w2 then begin CmpNum:=1;Exit;end;

   CmpNum:=0;
   if i1<i2 then d:=i1 else d:=i2;
   Exit;
   end;
inc(a,4);
until 1=2;
end;


Function CmpString(var s1,s2:string):boolean;
{kdyz je dle tridici tabulky s1<=s2 vrati true, jinak false}
var a,b,c,d:byte;
    e:shortint;
    z1,z2:boolean;
    poms1:string;
    poms2:string;

begin
a:=Length(s1);
b:=Length(s2);

if a<b then c:=a else c:=b; {budeme porovnavat pocet znaku v kratsim retezci}

if sysTridiciTbl^[256]=1 then  {zna tridici sada anomalii s CH?}
   begin
   poms1:=s1;
   poms2:=s2;
   z1:=Nahrad_ch(s1,c);        {"C" v "CH" nahradi zastupnym znakem}
   z2:=Nahrad_ch(s2,c);        {to same i v druhem retezci}
   end;

d:=1;
while d<=c do
    begin
    if (byte(s1[d])>=byte('0')) and (byte(s1[d])<=byte('9')) and
       (byte(s2[d])>=byte('0')) and (byte(s2[d])<=byte('9'))
       then e:=CmpNum(s1,s2,d,c)
       else e:=CmpChar(s1[d],s2[d]);
    if e<>0 then Break;
    inc(d);
    end;  {while}

if e=0
   then CmpString:=b>=a {kdyz i po projiti celeho useku jsou retezce ident.}
   else CmpString:=e>0;

if sysTridiciTbl^[256]=1 then
   begin
   if Z1 then s1:=poms1;       {probehla zamena u S1? Tak to vrat zpatky}
   if Z2 then s2:=poms2;       {to same v druem retezci}
   end;
end;
2011-01-11 | Laaca
Reklamy:
„Vždy odpouštějme svým nepřátelům, nic je nedokáže víc rozzuřit.“ Oscar Wilde