Twitter: @grzegg
Kategoria: java, Tagi: - - - - - .

Java [28] – Łańcuchy znaków (String) oraz wyrażenia regularne

Z łańcuchami znaków spotykamy się niemal od początku kursu. Teraz zapoznamy się bliżej z klasą String i niektórymi możliwościami, które oferuje. Przy okazji wyjaśnię czym są wyrażenia regularne pokażę i ich niektóre możliwości.

Klasa String

Kiedy nasz program wykonywał na przykład taką instrukcję:
String gatunek = "Homo sapiens"
to tworzyła ona obiekt typu String, który przechowywał serię znaków a zmienna gatunek przechowywała do niego wskaźnik. Kiedy chcieliśmy porównać dwa ciągi znaków, wywoływaliśmy na tym obiekcie metody equals() lub equalsIgnoreCase(). Jest to niewielka część funkcjonalności udostępnianej przez klasę String, która oferuje spory zestaw metod przydatnych do pracy z łańcuchami. Kompletną listę, wraz z krótkim opisem znajdziesz, jak zwykle, w dokumentacji.

Poniżej znajdziesz przykłady niektórych z nich. Wklej zawartość poniższego programu do nowo utworzonej klasy Teksty, uruchom, i spróbuj wywnioskować po nazwie i skutkach działania, jaką funkcjonalność mają użyte metody. W razie problemów nie wahaj się zajrzeć do wspomnianej dokumentacji.

Być może to nie jest oczywiste na pierwszy rzut oka, ale zauważ, że obiekt tekst nie zmienia przechowywanego przez siebie łańcucha znaków a jedynie zwraca efekt takich modyfikacji, który możemy na przykład wydrukować, albo przypisać innemu obiektowi typu String. Sam łańcuch znaków nie ulega więc modyfikacji ale efektem działania stosowanych metod jest nowy obiekt typu String. Może to być czasem nieco niewygodne, zwłaszcza jeśli chcemy na przykład wykonać serię kolejnych zmian na danym łańcuchu znaków. Ograniczenie to można obejść, na przynajmniej dwa sposoby. Pierwszy z nich może wyglądać tak:


String lancuch = "aaaa";
lancuch = lancuch.toUpperCase();

Skutek jest taki, jakby łańcuch „aaaa” został zmieniony w „AAAA” ale tak naprawdę został stworzony nowy obiekt typu String przechowujący ciąg „AAAA” a wskaźnik do tego obiektu został przypisany do zmiennej lancuch.

Innym sposobem jest zaznajomienie się z klasą StringBuilder, która pozwala na modyfikacje przechowywanych łańcuchów znaków.

Wyrażenia regularne

Na razie, korzystając z powyższych przykładów, możemy na przykład sprawdzić, czy w podane przez użytkownika hasło jest dokładnie takie jak być powinno albo podanym tekście znajduje się wskazany łańcuch znaków. Ale czy można na przykład sprawdzić, czy w łańcuchu nukleotydów zapisanym w instancji klasy String znajduje się sekwencja „AGGGACAAC”, przy czym liczba zasad „G” w środku, może się wahać między 3 a 6? Albo czy w można znaleźć w tekście łańcuch składający się z dokładnie czterech cyfr, po których występują trzy wielkie litery, następnie znak „-” a na końcu występuje jakikolwiek znak, który nie jest spacją? W tego typu zadaniach nieocenioną pomoc oferują wyrażenia regularne (ang. regular expressions, regex).

Wyrażenia regularne są potężnym narzędziem przydatnym do pracy z tekstem oraz wszelkimi danymi zapisanymi w formie ciągów znaków. Pozwalają, za pomocą umownych znaków opisać z różnym stopniem ogólności inne ciągi znaków, czy ich kategorie. Można w ten sposób na przykład sprawdzić, czy dany ciąg znaków spełnia dane kryteria, znaleźć takie ciągi znaków w większej całości, wskazać ich położenie albo /usunąć/zmodyfikować/zamienić na inne.

Niektóre znaki w wyrażeniach regularnych odczytuje się dosłownie, inne mają znaczenie specjalne. Na przykład . (kropka) oznacza dowolny znak a ^ początek wiersza, przy czym znaczenie ich może się zmieniać zależnie od kontekstu. Szczególną wagę ma znak ukośnika, czyli \, który nadaje specjalne znaczenie kolejnemu znakowi. Na przykład litera d oznacza normalnie literę „d” ale \d oznacza dowolną cyfrę. Z kolei o ile, jak wspomniałem wyżej, . oznacza dowolny znak to \. oznacza po prostu kropkę.
Z tego powodu w kodzie powyżej (i poniżej) kiedy chciałem wydrukować za pomocą metody print() cudzysłów musiałem użyć sekwencji \", inaczej znak " zostałby odczytany jako koniec łańcucha znaków i powstałby błąd w kodzie. Ponieważ chcemy czasem wydrukować także znak \, albo kilka z nich, każdy musi być poprzedzony innym znakiem \. Także jeśli przekazujemy wyrażenie regularne zawierające ukośnik w formie łańcucha znaków, należy go poprzedzić ukośnikiem. Stąd się biorą takie potworki, w których czasem trudno się połapać:


System.out.println("split(\"o\\\\w+\\\");

W początkach przygody z wyrażeniami regularnymi może przydać się podręczna ściąga.

Poniżej znajduje się kod, który pokazuje jedynie niewielką część możliwości które mają wyrażenia regularne, wykorzystamy przy tym kolejne metody klasy String.

Jak zaznaczyłem powyższe przykłady pokazują jedynie niewielką cześć ogromnych możliwości wyrażeń regularnych. Jeśli kiedyś przyjdzie Ci pracować dużo z tekstem czy też danymi w formie plików tekstowych, warto zainwestować czas w ich głębsze poznanie. Z pewnością zwróci się. Niekoniecznie przy tym musisz programować w Javie. Składnia wyrażeń regularnych jest bardzo podobna lub w zasadzie taka sama w wielu językach programowania, albo innych narzędziach przeznaczonych do pracy z tekstem, nawet w lepszych edytorach.

Jeśli masz zamiar wykorzystywać wyrażenia regularne w Javie, warto zainteresować się pakietem java.util.regex i jej klasami Matcher i Pattern w czym pomoże Ci oczywiście dokumentacja. Należy je wykorzystać zwłaszcza w przypadku bardziej złożonych wyrażeń regularnych, te nie zawsze działają w powyżej opisany sposób.

Poniżej znajduje się krótki program, który pokazuje podstawowe zastosowanie wymienionych powyżej klas na przykładzie wyrażenia regularnego, który nie działa jeśli zastosujemy „łatwiejsze podejście”. Program sprawdza dwie sekwencje nukleotydów pod kątem występowania w nich znaków innych niż A, T, C lub G.

Zadanie

Stwórz program, który będzie „tłumaczył” sekwencję nukleotydów podaną w DNA lub RNA na ciąg aminokwasów i kodonów START i STOP. Znaczenie kodonów w kodzie genetycznym znajdziesz np. tu.
Użytkownik podaje, czy wpisze ciąg RNA czy DNA, następnie podaje sekwencję nukleotydów. Jeśli podany został ciąg DNA, program tłumaczy go na RNA (wypisuje go) i w końcu na ciąg aminokwasów. Zwróć uwagę na to, że użytkownik może podać sekwencję, niepodzielną przez trzy, rozwiąż ten problem.
Sekwencja aminokwasów powinna być wypisana w formie oznaczeń trójliterowych oraz jednoliterowych (np. Ala-His-Cys i AHC).
Program powinien być podzielony na klasy, kod odpowiedzialny za „tłumaczenie” powinien być wydzielony do osobnego pakietu, tak aby można go było wykorzystać w przyszłości do stworzenia programu z interfejsem graficznym.

Dodatkowo:
Dołącz opcję rozpoznawania czy wpisany został DNA czy RNA (w DNA występuje T, w RNA znajduje się U). Program powinien ponadto sprawdzić, czy nie podano błędnych nukleotydów (innych znaków niż oznaczające nukleotydy w danym kwasie nukleinowym).

9 komentarzy Java [28] – Łańcuchy znaków (String) oraz wyrażenia regularne

  • Radek

    Jeśli przykładowo mam sekwencję: ACTGCAGT i zacznę po kolei zamieniać zasady azotowe(DNA -> RNA):
    sekwencja.replaceAll(„T”,”A”);
    sekwencja.replaceAll(„A”,”U”);
    sekwencja.replaceAll(„G”,”C”);
    sekwencja.replaceAll(„C”,”G”);
    itd…
    to np. wszystkie T będą zamienione na A a potem wszystkie A łącznie z tymi nowopowstałymi zamienią się w U. W rezultacie wyjściowa sekwencja nie będzie zawierała żadnych A… a przecież powinny być w niej obecne wszystkie A powstałe z T.

    Ktoś wie jak to rozwiązać?

    • Grzegorz

      Słuszna uwaga. Dlatego najpierw można przetłumaczyć symbole nukleotydów na jakieś neutralne znaki, np. na cyfry a dopiero później na właściwe oznaczenia. Zobacz lekcję pod adresem http://ggoralski.pl/?p=3141

      • Radek

        Dziękuję za szybką odpowiedź.
        Kurs póki co jest bardzo dobry, wyjaśnia mnóstwo kwestii w przystępny sposób :)

        • Radek

          W sumie to mam jeszcze jeden problem: jak podzielić łańcuch znaków na łańcuchy 3-elementowe,czyli na poszczególne kodony?

          Konieczne jest chyba uzyskanie tablicy 3-znakownych Stringów, ale nie można tu raczej użyć metody split()…

          Wykorzystując ()substring i pętle można co prawda uzyskać mniejsze Stringi, przykładowo:

          String sekwencja = „ACTGATCTGAAT”;
          int b = sekwencja.length();
          while(sekwencja.length() > 3) {
          System.out.println(sekwencja.substring(0, 3));
          sekwencja = sekwencja.substring(3);
          }
          System.out.println(sekwencja);

          Z tym, że trzeba je później jakoś umieścić w tablicy, aby dokonać tłumaczenia każdego z nich po kolei.

          • Grzegorz

            Umieszczenie trójek znaków w tablicy to dobry pomysł ale można też tłumaczyć od razu w tej pętli. Skoro mamy już kodon to od razu można go przetłumaczyć na aminokwas i dodać do stringa w którym przechowujemy sekwencję aminokwasów. Nie sprawdzę teraz praktycznie czy to działa bo nie mam dostępu do komputera a w głowie też nie mam wszystkich rozwiązań ;-) ale powinno działać. Pozdrawiam!

  • Radek

    Metoda sprawdzająca czy wpisywana sekwencja to DNA czy RNA (wcześniej zastosowana została metoda wykluczająca znaki inne niż ACTGU, również z zastosowaniem pakietu regex).

    Metoda powinna zwracać „true” jeśli we wprowadzanym Stringu jest „U”. Problem jest taki, że zwraca „true” tylko wtedy jeśli w Stringu są conajmniej 2 „U”. Przykładowo jeśli wprowadzę „ACTU” metoda zwróci „false”, a jeśli „ACTUU” – „true”.

    public static boolean testujNaRNA (String sekwencja2){

    Pattern pat2 = Pattern.compile(„[U]”);
    Matcher mat2 = pat2.matcher(sekwencja2);
    mat2.find();

    return mat2.find();
    }

    Mam nadzieję, nie ma Pan mnie dosyć :)

Leave a Reply