Oblíbeným zadáním práce při studiu základů programování v Javě je mimo jiné Pascalův trojúhelník. Pro neznalce Javy (a programování všeobecně) bývá vytvoření takto jednoduchého úkolu problém (zejména z toho důvodu, že u Pascalova trojúhelníku dojde poměrně brzy k přetečení datového typu integer). Proto jsem se rozhodl podělit se s vámi o mé řešení.

Celé řešení se bude skládat pouze ze dvou metod. Ještě než začneme, budeme potřebovat několik proměnných:

    private static final int VELIKOST = 100;  // velikost trojúhelníku
    private static int[][] trojuhelnik = new int[VELIKOST][0]; // vytvoření pole pro trojúhelník
    private static int celkem = 0;  // proměnná pro zjištění konečného počtu řádků

První ze zmíněných metod je vyplnTrojuhelnik, která vrací dvoujrozměrné pole integer[][], které reprezentuje Pascalův trojúhelník:

    public static int[][] vyplnTrojuhelnik() {  // naplní trojúhelník čísly
        boolean ok = true;  // zjištění, zda nepřetekl integer

        for (int i = 0; i < trojuhelnik.length; i++) {  // pro každý řádek
            trojuhelnik[i] = new int[i + 1];  // vytvoř řádek o jeden prvek větší, než ten předchozí

            for (int o = 0; o < trojuhelnik[i].length; o++) {

                if (ok) { // pokud nepřetekl integer

                    if ((o == 0) || (o == i))  // pokud u prvního nebo posledního prvku
                        trojuhelnik[i][o] = 1;  // nastav výsledek na 1
                    else if (o < i) {  // jinak:
                        int prvek = (trojuhelnik[i - 1][o - 1] + trojuhelnik[i - 1][o]);  // prvek bude součtem dvou prvků nad ním ležících
                        if (prvek < 1) {  // pokud je prvek méně než 1 (přetekl integer)
                            celkem = i;  // nastavíme celkový počet řádků na i
                            ok = false; // indikujeme, že došlo k přetečení
                        }

                        trojuhelnik[i][o] = prvek;  // na pozici [i][o] vložíme prvek
                    }
                }
                else  // pokud přetekl integer
                    break; // konec 2. cyklu

            }
            if(!ok)  // pokud přetekl integer
                break; // konec 1. cyklu

        }

        return trojuhelnik;  // vrátit pole s trojúhelníkem
    }

Druhá metoda se bude nazývat vypisTrojuhelnik a jediné co bude mít na starosti, bude vypsání dvojrozměrného pole získaného z metody vyplnTrojuhelnik:

    public static void vypisTrojuhelnik() {
        for (int i = 0; i < celkem; i++) { // pro každý řádek trojúhelníku
            int[] radek = trojuhelnik[i];  // radek = pole prvků řádku

            System.out.print((i + 1) + "): ");  // vypsání aktuálního řádku (číselný seznam)

            if (i < 9) {  // pro čísla menší než 10 je potřeba přidat jednu mezeru kvůli formátování
                System.out.print(" ");
            }

            for (int o = 0; o < celkem - i; o++) {  // vypsání počtu mezer tak, aby vytvořili dojem trojúhelníku
                System.out.print(" ");
            }

            for (int prvek : radek) {  // pro každý prvek
                    System.out.print(prvek + " ");  // vypiš prvek a mezeru
            }

            System.out.println("\n");  // za každým řádkem odřádkuj
        }
    }

A to je vlastně vše. Pokud jste pořádně nepochopili, jak kontroluji přetečení, dělám to nejjednodušší možnou metodou a to tak, že zjišťuji, zda výsledek operace není menší než 0 (proč je číslo po přetečení zpravidla záporné se můžete dočíst např. zde).

 

Zde je kompletní funkční kód:

package Pascal;

public class PascaluvTrojuhelnik {

    private static final int VELIKOST = 100;  // velikost trojúhelníku
    private static int[][] trojuhelnik = new int[VELIKOST][0]; // vytvoření pole pro trojúhelník
    private static int celkem = 0;  // proměnná pro zjištění konečného počtu řádků

    public static void main(String[] args) {
        vyplnTrojuhelnik();
        vypisTrojuhelnik();
    }

    public static int[][] vyplnTrojuhelnik() {  // naplní trojúhelník čísly
        boolean ok = true;  // zjištění, zda nepřetekl integer

        for (int i = 0; i < trojuhelnik.length; i++) {  // pro každý řádek
            trojuhelnik[i] = new int[i + 1];  // vytvoř řádek o jeden prvek větší, než ten předchozí

            for (int o = 0; o < trojuhelnik[i].length; o++) {

                if (ok) { // pokud nepřetekl integer

                    if ((o == 0) || (o == i))  // pokud u prvního nebo posledního prvku
                        trojuhelnik[i][o] = 1;  // nastav výsledek na 1
                    else if (o < i) {  // jinak:
                        int prvek = (trojuhelnik[i - 1][o - 1] + trojuhelnik[i - 1][o]);  // prvek bude součtem dvou prvků nad ním ležících
                        if (prvek < 1) {  // pokud je prvek méně než 1 (přetekl integer)
                            celkem = i;  // nastavíme celkový počet řádků na i
                            ok = false; // indikujeme, že došlo k přetečení
                        }

                        trojuhelnik[i][o] = prvek;  // na pozici [i][o] vložíme prvek
                    }
                }
                else  // pokud přetekl integer
                    break; // konec 2. cyklu

            }
            if(!ok)  // pokud přetekl integer
                break; // konec 1. cyklu

        }

        return trojuhelnik;  // vrátit pole s trojúhelníkem
    }

    public static void vypisTrojuhelnik() {
        for (int i = 0; i < celkem; i++) { // pro každý řádek trojúhelníku
            int[] radek = trojuhelnik[i];  // radek = pole prvků řádku

            System.out.print((i + 1) + "): ");  // vypsání aktuálního řádku (číselný seznam)

            if (i < 9) {  // pro čísla menší než 10 je potřeba přidat jednu mezeru kvůli formátování
                System.out.print(" ");
            }

            for (int o = 0; o < celkem - i; o++) {  // vypsání počtu mezer tak, aby vytvořili dojem trojúhelníku
                System.out.print(" ");
            }

            for (int prvek : radek) {  // pro každý prvek
                    System.out.print(prvek + " ");  // vypiš prvek a mezeru
            }

            System.out.println("\n");  // za každým řádkem odřádkuj
        }
    }
}

Poznámka:
Pokud byste nahradili všechny výskyty int např. za long, “dopočítáte se” bez přetečení dál.

Možná stejně jako já před nedávnou dobou řešíte problémy s připojením na XMPP server pomocí SASL. Pokud studujete oficiální dokument na xmpp.org (nebo RFC), patrně jste se bez větších obtíží dostali až k části, ve které musíte odpovědět serveru na jeho challenge. V tuto chvíli narazíte nejspíš na problém, protože výpočet odpovědi na challenge není jednoduchý (resp. jednoduše pouze vypadá).

Problém je totiž v tom, že výpočty nutné k vytvoření odpovědi, nesmíte nijak upravovat. Nesmíte je převádět na ASCII, ani UTF-8. Musíte s nimi pracovat v jejich nejsurovější formě, ke které se dostanete – v Hexadecimální soustavě. Pouze při práci v hex se vám podaří vytvořit korektní odpověď.

Proč někdo v XMPP vymyslel tuto zbytečnost, nechápu (u jiných druhů SASL připojení toto vůbec nutné není), nicméně není žádný problém se tomu v Javě přízpůsobit.

Nejdříve napíšeme metodu byteArrayToHex, která jako parametr obdrží byte[] pole, které zpracuje a v hexa vrátí jako String:

private static String byteArrayToHex(byte[] array) {  // returns hex representation of byte[] array
    String resultStr = "";
    for (int i=0; i < array.length; i++) {
        resultStr += Integer.toString( ( array[i] & 0xff ) + 0x100, 16).substring( 1 );
    }
    return resultStr;
}

Další potřebnou metodou bude spojení dvou byte[] polí do jednoho (zde jsem byl líný a udělal jsem metodu pouze pro dvě vstupní pole, ačkoli budeme ke konci potřebovat kombinovat pole tři — bude ale stačit metodu zavolat dvakrát). Nazveme ji combineByteArrays, metoda bude pracovat se dvěma vstupními parametry typu pole byte[] a vracet bude také pole byte[]:

private static byte[] combineByteArrays(byte[] a, byte[] b) { // combines two byte[] arrays
    byte[] result = new byte[a.length + b.length];
    System.arraycopy(a, 0, result, 0, a.length);
    for (int i = a.length; i < result.length; i++) {
        result[i] = b[i-a.length];
    }
    return result;
}

Poslední, patrně nejjednodušší (nicméně nejpotřebnější) částí bude metoda computeResponse, která jako vstupní parametry obdrží veškeré potřebné údaje pro XMPP challenge response (username, password, reals, nonce, qop, cnonce, digest_uri a nc — kde je získat se dozvíte na konci článku) a to jako String. Vracet bude response připravenou k odeslání XMPP serveru, také jako String:

private static String computeResponse(String username, String password, String realm, String nonce, String qop, String cnonce, String digest_uri, String nc) throws NoSuchAlgorithmException {  // computes response for challenge query of XMPP server
    MessageDigest md5 = MessageDigest.getInstance("MD5");
    final byte[] part1 = md5.digest(combineByteArrays(md5.digest((username + ":" + realm + ":" + password).getBytes()), (":" + nonce + ":" + cnonce).getBytes()));
    final byte[] part2 = md5.digest(combineByteArrays("AUTHENTICATE:".getBytes(), digest_uri.getBytes()));
    final byte[] temp = combineByteArrays(byteArrayToHex(part1).getBytes(), (":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":").getBytes());
    final byte[] part3  = md5.digest(combineByteArrays(temp, byteArrayToHex(part2).getBytes()));
    return byteArrayToHex(part3);
}

 

Pro jistotu uvádím ještě kompletní kód (včetně ukázky použití):

private static byte[] combineByteArrays(byte[] a, byte[] b) { // combines two byte[] arrays
    byte[] result = new byte[a.length + b.length];
    System.arraycopy(a, 0, result, 0, a.length);
    for (int i = a.length; i < result.length; i++) {
        result[i] = b[i-a.length];
    }
    return result;
}

private static String byteArrayToHex(byte[] array) {  // returns hex representation of byte[] array
    String resultStr = "";
    for (int i=0; i < array.length; i++) {
        resultStr += Integer.toString( ( array[i] & 0xff ) + 0x100, 16).substring( 1 );
    }
    return resultStr;
}

private static String computeResponse(String username, String password, String realm, String nonce, String qop, String cnonce, String digest_uri, String nc) throws NoSuchAlgorithmException {  // computes response for challenge query of XMPP server
    MessageDigest md5 = MessageDigest.getInstance("MD5");
    final byte[] part1 = md5.digest(combineByteArrays(md5.digest((username + ":" + realm + ":" + password).getBytes()), (":" + nonce + ":" + cnonce).getBytes()));
    final byte[] part2 = md5.digest(combineByteArrays("AUTHENTICATE:".getBytes(), digest_uri.getBytes()));
    final byte[] temp = combineByteArrays(byteArrayToHex(part1).getBytes(), (":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":").getBytes());
    final byte[] part3  = md5.digest(combineByteArrays(temp, byteArrayToHex(part2).getBytes()));
    return byteArrayToHex(part3);
}

public static void main(String[] args) throws NoSuchAlgorithmException {

    String username = "test";
    String password = "secret";
    String realm = "osXstream.local";
    String nonce = "392616736";
    String qop = "auth";
    String cnonce = "05E0A6E7-0B7B-4430-9549-0FE1C244ABAB";
    String digest_uri = "xmpp/osXstream.local";
    String nc = "00000001";        

    // prints out "37991b870e0f6cc757ec74c47877472b"
    System.out.println(computeResponse(username, password, realm, nonce, qop, cnonce, digest_uri, nc));
}

 

Doufám, že vám tento návod byl k něčemu dobrý, a hlavně že vám ušetřil několik hodin hledání odpovědí na Google.

 

Poznámka:
jak celý proces výpočtu response funguje, jsem se dozvěděl zde: http://deusty.blogspot.com/2007/09/example-please.html. Na zmíněném webu se dozvíte i jak rozkódovat challenge a další užitečné rady.

Příhoda

Další slohovou prací, kterou Vám přináším je přes rok staré vypravování na téma Moje nejzajímavější příhoda tohoto školního roku. Jedná se o kratší příběh, ve kterém líčím svoji strastiplnou cestu do divadla…

Byl čtvrtek pozdě odpoledne a já seděl v metru, které právě vjíždělo na dobře známou stanici Palmovka. Šel jsem do Divadla pod Palmovkou na představení Caligula v hlavní roli s Jiřím Langmajerem. Do začátku inscenace zbývalo více než dvacet minut. Jelikož jsem věděl, že divadlo je pár kroků od zastávky metra, nikam jsem nespěchal a v klidu si vykračoval.

Cestu jsem měl dobře nastudovanou z mapy, takže jsem se nebál, že bych se ztratil. Poté, co jsem minul prodavače Strážní věže a Nového prostoru, jsem zamířil jediným logickým směrem – tedy rovně. To ovšem byla osudová chyba. Nedošlo mi, že ulice, která ústí nad povrchem výstupu z metra, tedy v úrovni autobusových zastávek, není ta, do které jsem právě zamířil. Ceduli se jménem ulice jsem přehlédl a s naprosto neomylnou jistotou jsem se vydal vstříc kýženému cíli. Po třech minutách chůze mě zaskočil konec chodníku, ze kterého jsem si nic nedělal a odbočil do nejbližší ulice po mé pravé ruce. Po chvíli mi došlo, že tady něco nehraje. Divadlo, které mělo být zhruba dvě minuty chůze od metra, se ani po deseti minutách poklusu nikde neobjevovalo, místo toho jsem vběhl na jakousi hlavní silnici. Díky svému geniálnímu orientačnímu smyslu jsem odbočil doleva, tedy směrem, kterým jsem se znovu přiblížil výchozímu bodu putování.

Osud mě však postavil před další – pro mě těžko řešitelný problém – dorazil jsem totiž na křižovatku. Dva směry jsem okamžitě vyloučil a rozhodoval se, zda se vydám doleva, či doprava. Kdybych si ráno pořádně přečetl horoskop, dozvěděl bych se, že nemám podléhat na první pohled zřejmým věcem. Ten jsem ale nečetl, proto jsem začal pronásledovat skupinku mužů a žen ve večerních šatech. Po nervózním pohledu na hodinky jsem zjistil, že z dvaceti minut času mi zbylo pouhých osm. Skupinka mezitím přešla silnici, proto jsem se vydal za nimi. Uběhly další tři minuty mého drahocenného času, když jsem zaslechl jednu z žen přede mnou, jak říká: „Bylo to ale velmi zajímavé představení, že?“ Muž opodál odvětil: „Yes, it was wonderful!“ Nyní mi došlo, že tento hlouček nejde do divadla, ale z divadla. Ladnou piruetou jsem se otočil a změnil směr o 180°.

Přešel jsem na druhou stranu ulice, abych se přesvědčil, že divadlo není ani tam, a že jsem ho pouze nepřehlédl. Mezitím se setmělo a já zjistil, že jsem vešel do jakéhosi parčíku plného stromů a keřů. Cesta vedla dál, tak jsem se neznepokojoval. Stresující a naprosto šokující bylo zjištění, že tento malebný parčík končí zídkou. Zhodnotil jsem situaci a došel k závěru, že obcházením bych ztratil zbytek času a na návrat také nebylo ani pomyšlení. Prudkým švihem pravé nohy jsem se vyhoupl na zeď a elegantním skokem, který jsem si procvičoval na hodinách tělesné výchovy, dopadl na druhé straně. Hodinky mi ukázaly, že odtikávají poslední čtyři minuty. Akceleroval jsem na obdivuhodnou rychlost, až jsem doběhl zpět na onu osudovou křižovatku. V dálce jsem konečně uviděl nápis Zenklova ulice, tedy ulice, ve které mělo stát divadlo.

Ještě jsem ale neměl vyhráno. Na semaforech svítila červená a na protější straně ulice stálo policejní auto, o které byli opřeni dva strážníci, kteří mě bedlivě pozorovali. Na přecházení na červenou nebylo ani pomyšlení, proto jsem musel vyčkat, až padne zelená. Nadzvukovou rychlostí jsem přeběhl silnici, dochvátal do ulice Zenklova a zjistil, že Divadlo pod Palmovkou je přímo před mým nosem.

Představení jsem stihl, dokonce jsem ani nebyl poslední. Vypadá to, že přímá úměrnost mezi vzdáleností a časem v tomto případě neplatila. Potvrdilo se, že Murphyho zákon „co se může pokazit, to se taky pokazí“ platí vždy a všude!

Až na pár faktů není téměř žádný údaj pravdivý. Pokud jste tedy viděli někoho v obleku, jak přelézá zídku, nebyl jsem to já ;)