4it101»Reseni Caesarova Sifra

Reseni Caesarova Sifra

Caesarova šifra - řešení

Kroky řešení:

  1. vytvořil jsem si projekt sifra,
  2. vytvořil jsem rozhraní Sifra, nakopíroval jsem do něho předpřipravený kód rozhraní ze zadání,
  3. vytvořil jsem třídu CaesarovaSifra, doplnil jsem do ní hlavičky metod, aby bylo možno třídu přeložit,
  4. vytvořil jsem testovací třídu CaesarovaSifraTest a napsal testy,
  5. naprogramoval jsem metody třídy CaesarovaSifra

3. "Prázdná" třída CaesarovaSifra


public class CaesarovaSifra implements Sifra {

//== Datové atributy (statické i instancí)======================================

//== Konstruktory a tovární metody =============================================

    /***************************************************************************
     * Konstruktor pro Caesarovu šifru. Parametr posun udává, o kolik míst
     * v abecedě se má posunout zašifrovaný text.
     *
     * @param posun počet míst abecedy, o kolik se posune znak v zašifrovaném textu
     */

    public CaesarovaSifra(int posun) {
    }

//== Nesoukromé metody (instancí i třídy) ===============================================
    /**
     * Metoda zašifruje vstupní text za pomocí caesarovy šifry. Pokud je na vstupu neznámý znak,
     * vznikne výjimka IllegalArgumentException.
     *
     * @param otevrenyText   vstupní text k zašifrování
     * @return               zašifrovaný text
     */

        public String zasifruj(String otevrenyText) {
            return otevrenyText;
        }

    /**
     * Metoda dešifruje vstupní text za pomocí caesarovy šifry. Pokud je na vstupu neznámý znak,
     * vznikne výjimka IllegalArgumentException.
     *
     * @param sifrovyText   vstupní text k dešifrování
     * @return              otevřený text
     */

        public String desifruj(String sifrovyText) {
            return sifrovyText;
        }


//== Soukromé metody (instancí i třídy) ===========================================

}
 

4. Testy

Před psaním testů je potřeba si rozmyslet, které znaky jsou přípustné na vstupu, zda se provedou nějaké konverze (např. odstranění diakritiky, převod na velká písmena, odstranění nepřípustných znaků), jak by měl vypadat výstup. Já jsem se v řešení rozhodl pro následující:

  • ze vstupního textu se odstraní diakritika,
  • text se převede na velká písmena,
  • pokud bude po těchto konverzích ve vstupu jiný znak než písmeno velké anglické abecedy, číslice či mezera, tak se příslušný znak smaže,

Na základě toho jsem si připravil dvě testovací metody:


    public void testPosun3() {
        CaesarovaSifra caesarova = new CaesarovaSifra(5);
        assertEquals("F", caesarova.zasifruj("a"));
        assertEquals("A", caesarova.desifruj("F"));

        assertEquals("F", caesarova.zasifruj("A"));
        assertEquals("A", caesarova.desifruj("F"));

        assertEquals("F", caesarova.zasifruj("á"));
        assertEquals("A", caesarova.desifruj("F"));

        assertEquals("F", caesarova.zasifruj("ä"));
        assertEquals("A", caesarova.desifruj("F"));

        assertEquals("3", caesarova.zasifruj("z"));
        assertEquals("Z", caesarova.desifruj("3"));

        assertEquals("D", caesarova.zasifruj("8"));
        assertEquals("8", caesarova.desifruj("D"));

        assertEquals("XQT T", caesarova.zasifruj("slovo"));
        assertEquals("slovo", caesarova.desifruj("XQT T"));

        assertEquals("UT3NHJ46B6B9EAE", caesarova.zasifruj("pozice 16.16,49.59"));
        assertEquals("POZICE 16164959", caesarova.desifruj("UT3NHJ46B6B9EAE"));
    }

    public void testPosun17() {
        CaesarovaSifra caesarov1 = new CaesarovaSifra(17);
        assertEquals("R", caesarov1.zasifruj("a"));
        assertEquals("A", caesarov1.desifruj("R"));

        assertEquals("814B4", caesarov1.zasifruj("slovo"));
        assertEquals("slovo", caesarov1.desifruj("814B4"));
    }
 

5. Vlastní řešení

Datové atributy

Vytvářím dva datové atributy. Prvním je ABECEDA, což je neměnné statické pole znaků, které obsahuje znaky velké anglické abecedy, mezera a číslice (viz rozhodnutí udělané při psaní testů). Druhým datovým atributem je pole znaků posunuta, které bude obsahovat posunuté znaky a které později využiji při šifrování/dešifrování.


    // proměnná definuje přípustné znaky po normalizaci otevřeného textu
    // (a i v zašifrovaném textu)
    // používám písmena velké anglické abecedy, mezeru a číslice.
    private static final char[] ABECEDA= {
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
            'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
            ' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };

    // proměnná bude obsahovat posunutou abecedu, pomocí které se bude
    // šifrovat/dešifrovat
    private char[] posunuta;    
 

Konstruktor

V konstruktoru se provádí:

  • kontrola vstupního parametru - pokud je udaný posun menší než 1 nebo větší než velikost abecedy, tak vytvořím výjimku. Jiným řešením by bylo spočítat ze zadaného posunu zbytek po celočíselném dělení číslem 37 (délka abecedy) a výjimku vyvolat pouze v případě, že zbytek by byl 0 (tj. vůbec by se nešifrovalo).
  • naplnění pole "posunata" - na 7. řádku se vytvoří instance pole o délce pole ABECEDA. Poté v cyklu plněním jednotlivé položky pole posunuta - do konkrétní položky přesunu znak z pole ABECEDA s indexem o posun větším. Musím však ošetřit překročení rozsahu pole - při indexu i==30 a posunu 10 nechci přesouvat z neexistujícího indexu 40, ale z indexu 3. Tento index získám operací modulo - celočíselný zbytek po následujícím výpočtu (30 + 10)/37.
  1.  
  2.     public CaesarovaSifra(int posun) {
  3.         if ((posun < 1)  || (posun >= ABECEDA.length)) {
  4.             throw new IllegalArgumentException("posun musí být minimálně o jedno místo "
  5.                         + "a menší než velikost abecedy ("+ABECEDA.length+")");
  6.         }
  7.         posunuta = new char[ABECEDA.length];
  8.         for (int i=0; i<posunuta.length; i++) {
  9.             posunuta[i] = ABECEDA[(i+posun) % ABECEDA.length];
  10.         }
  11.     }
  12.  

Privátní metoda normalizuj

Cílem této metody je provést úpravy, které jsem si naplánoval při psaní testů:

  • ze vstupního textu se odstraní diakritika - používám třídu java.text.Normalizer, další informace k češtině můžete najít na stránkách http://www.macroware.cz/software/unicode/]]
  • text se převádí na velká písmena - metoda toUpperCase třídy String
  • pokud bude po těchto konverzích ve vstupu jiný znak než písmeno velké anglické abecedy, číslice či mezera, tak se příslušný znak smaže - používám metodu replaceAll třídy String a v ní regulární výrazy.
  1.  
  2.     private String normalizuj(String otevrenyText) {
  3.         // zrušení diakritiky - viz http://www.macroware.cz/software/unicode/
  4.         // v Java 1.5 volání a import vypadá odlišně,
  5.         String bezDiakritiky = Normalizer.normalize(otevrenyText,
  6.                           Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]","");
  7.         // převod na velká písmena
  8.         String velka = bezDiakritiky.toUpperCase();
  9.         // zrušení nevhodných znaků
  10.         return velka.replaceAll("[^A-Z0-9 ]","");
  11.     }
  12.  

Zašifrování otevřeného textu

Metoda na zašifrování otevřeného textu je nyní poměrně jednoduchá:

  • znormalizuji vstupní otevřený text,
  • připravím si proměnnou sifrovyText typu String, na začátku bude obsahovat prázdný řetězec,
  • v cyklu for nyní beru jednotlivá písmena z normalizovaného textu a pomocí soukromé metody zasifrujZnak každé písmeno převedu na zašifrované a připojím do řetězce sifrovyText
  • na konci vrátím zašifrovaný text,

        public String zasifruj(String otevrenyText) {
            // nejdříve pomocí metody normalizuj zruším diakritiku, převedu na velká písmena,
            String normalText = normalizuj(otevrenyText);
            // pro sifrovyText použiji pole znaků
            String sifrovyText = "";
            for (int i=0; i< normalText.length(); i++) {
                sifrovyText += zasifrujZnak(normalText.charAt(i));
            }
            return sifrovyText;
        }
 

Rychlejší a vhodnější by bylo zašifrované znaky ukládat do pole a to na konci převést na String. Ukáži to v metodě pro dešifrování.

Zašifrování jednoho znaku - soukromá metoda zasifrujZnak

Metoda je poměrně jednoduchá - znak zadaný jako parametr hledám v poli znaků ABECEDA - pokud ho najdu, tak ho vrátím odpovídající znak z pole posunuta. Pokud by se znak v poli ABECEDA nenašel, tak vyvolám výjimku.


    private char zasifrujZnak(char znak) {
        char vysl = '\00';
        for (int i = 0; i< ABECEDA.length; i++) {
            if (ABECEDA[i]==znak) {
                vysl = posunuta[i];
                break;
            }
        }
        if (vysl== '\00') {
            throw new IllegalArgumentException("neznámý znak ve vstupním poli - "+znak);
        }
        return vysl;
    }
 

Dešifrování textu

Dešifrování se velmi podobá zašifrování - odpadá normalizace. Při dešifrování znaků používám rychlejší řešení s použitím pole - samozřejmě to lze napsat i pomocí skládání řetězců, jako u metody na šifrování.

Následuje kód metody desifruj i pomocné privátní metody zasifrujZnak:


    public String desifruj(String sifrovyText) {
        // z důvodů rychlosti pro otevrenyText použiji pole znaků
        char [] otevrenyText = new char [sifrovyText.length()];
        for (int i=0; i< sifrovyText.length(); i++) {
            otevrenyText[i]=desifrujZnak(sifrovyText.charAt(i));
        }
        return String.valueOf(otevrenyText);
    }

    private char zasifrujZnak(char znak) {
        char vysl = '\00';
        for (int i = 0; i< ABECEDA.length; i++) {
            if (ABECEDA[i]==znak) {
                vysl = posunuta[i];
                break;
            }
        }
        if (vysl== '\00') {
            throw new IllegalArgumentException("neznámý znak ve vstupním poli - "+znak);
        }
        return vysl;
    }