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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
package tekst; import java.util.Arrays; public class Tekst { public static void main(String[] args) { String tekst = "Chromosom"; System.out.println("Tekst : "+tekst); // testy, porównania System.out.println("equals(\"chromosom\") : "+tekst.equals("chromosom")); System.out.println("equalsIgnoreCase(\"chromosom\") : "+tekst.equalsIgnoreCase("chromosom")); System.out.println("contains(\"omo\") : "+tekst.contains("omo")); System.out.println("startsWith(\"Chro\") : "+tekst.startsWith("Chro")); System.out.println("endsWith(\"Chro\") : "+tekst.endsWith("Chro")); System.out.println("regionMatches(2,\"BROMEK\",1,3) : " + tekst.regionMatches(2, "BROMEK",1 , 3)); System.out.println("regionMatches(true,2,\"BROMEK\",1,3) : " + tekst.regionMatches(true, 2, "BROMEK",1 , 3)); System.out.println("length() : "+tekst.length()); // modyfikacje System.out.println("toLowerCase() : "+tekst.toLowerCase()); System.out.println("toUpperCase() : "+tekst.toUpperCase()); System.out.println("replace(\"osom\", \"atyda\") : "+tekst.replace("osom", "atyda")); System.out.println("replaceFirst(\"o\", \"i\") : "+tekst.replaceFirst("o", "i")); System.out.println("replaceAll(\"o\", \"i\") : "+tekst.replaceAll("o", "i")); System.out.println("concat(\"owy\") : "+tekst.concat("owy")); // wycinanie i dzielenie System.out.println("substring(4) : "+tekst.substring(4)); System.out.println("substring(3,6) : "+tekst.substring(3,6)); String[] teksty = tekst.split("o"); System.out.println("split(\"o\") : " +Arrays.toString(teksty)); String[] teksty2 = tekst.split("o",2); System.out.println("split(\"o\",2) : " +Arrays.toString(teksty2) ); tekst = " "+tekst+" "; System.out.println("tekst = \" \"+tekst+\" \" : "+tekst); System.out.println("trim() : "+tekst.trim()); } } |
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
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
package wyrazeniaregularne; import java.util.Arrays; public class WyrazeniaRegularne { public static void main(String[] args) { String tekst = "Chromosom"; System.out.println("tekst : "+tekst); // testy // Jedna nie cyfra System.out.println("matches(\"\\\\D\") : "+tekst.matches("\\D")); // Jedna lub więcej nie cyfr System.out.println("matches(\"\\\\D+\") : "+tekst.matches("\\D+")); // Jedna lub więcej cyfr System.out.println("matches(\"\\\\d+\") : "+tekst.matches("\\d+")); // Jedna lub więcej nie cyfr a potem zero lub więcej cyfr System.out.println("matches(\"\\\\D+\\\\d*\") : "+tekst.matches("\\D+\\d*")); // Jedna lub więcej nie cyfr a potem jedna lub więcej cyfr System.out.println("matches(\"\\\\D+\\\\d+\") : "+tekst.matches("\\D+\\d+")); // Jedna lub więcej nie cyfr a potem zero lub jedna cyfra System.out.println("matches(\"\\\\D+\\\\d?\") : "+tekst.matches("\\D+\\d?")); String tekst2 = "Chromosom2"; System.out.println("tekst2 : "+tekst2); // Jedna lub więcej nie cyfr a potem jedna lub więcej cyfr System.out.println("matches(\"\\\\D+\\\\d+\") : "+tekst2.matches("\\D+\\d+")); // Jedna lub więcej nie cyfr a potem zero lub jedna cyfra System.out.println("matches(\"\\\\D+\\\\d?\") : "+tekst.matches("\\D+\\d?")); // Jeden lub więcej znaków które mogą być częścią słowa (litery, cyfry i znak _) System.out.println("matches(\"\\\\w+\") : "+tekst2.matches("\\w+")); // Jeden lub więcej znaków które nie mogą być częścią słowa (nie litery, cyfry i znak _) System.out.println("matches(\"\\\\W+\") : "+tekst2.matches("\\W+")); // Jeden lub więcej białych znaków (spacje, tabulatory) System.out.println("matches(\"\\\\s+\") : "+tekst2.matches("\\s+")); // Jeden lub więcej nie białych znaków (nie spacje, tabulatory) System.out.println("matches(\"\\\\S+\") : "+tekst2.matches("\\S+")); // Kropka oznacza jakikolwiek znak System.out.println("matches(\"Chr.m.s.m2\") : "+tekst2.matches("Chr.m.s.m2")); // .+ oznacza jeden lub więcej jakikolwiek znaków System.out.println("matches(\"Chr.+m2\") : "+tekst2.matches("Chr.+m2")); // {2,9} oznacza od dwu do dziewięciu elementów wskazanych wcześniej - tu dowolnych znaków System.out.println("matches(\"Chr.{2,9}m2\") : "+tekst2.matches("Chr.{2,9}m2")); // {2,9} oznacza conajmniej dwa elementy wskazane wcześniej - tu dowolnych znaków System.out.println("matches(\"Chr.{2,}m2\") : "+tekst2.matches("Chr.{2,}m2")); // {2,9} oznacza dwa elementy wskazane wcześniej - tu dowolnych znaków System.out.println("matches(\"Chr.{2}m2\") : "+tekst2.matches("Chr.{2}m2")); String tekst3 = "chromosom to nie jest ciało które tworzy chrom"; System.out.println(tekst3); // ^ oznacza początek linii System.out.println("replaceAll(\"^chrom\", \"CHROM\") : "+tekst3.replaceAll("^chrom", "CHROM")); // $ oznacza koniec linii System.out.println("replaceAll(\"chrom$\", \"CHROM\") : "+tekst3.replaceAll("chrom$", "CHROM")); // Dzielimy łańcuch po literce "o" po której następuje jeden znak słowa // uzyskujemy tablicę String[] : "); String[] teksty = tekst3.split("o\\w"); System.out.println("split(\"o\\\\w+\\\") : "+Arrays.toString(teksty)); // Dzielimy łańcuch po literce "o" przed którą występuje litera "r" lub "s" // uzyskujemy tablicę String[] teksty = tekst3.split("(r|s)o"); System.out.println("split(\"(r|s)o\") : "+Arrays.toString(teksty)); // Wyszukujemy wszystkie wystąpienia "o" System.out.println("indexOf(\"o\") : "+tekst3.indexOf("o")); int i=0; String miejsca = ""; while (i>=0) { // szukamy następnego łańcucha (tu literki "o") i=tekst3.indexOf("o",i); if (i>=0) { miejsca = miejsca+i+","; // przesuwamy indeks o 1, aby nie odszukać ponownie tej samej litery i++; } } String[] miejscaTab = miejsca.split("\\D"); System.out.println("miejsca.split(\"\\D\") :"+Arrays.toString(miejscaTab)); } } |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
package sprawdzaniesekwencji; import java.util.regex.Pattern; import java.util.regex.Matcher; public class SprawdzanieSekwencji { public static void main(String[] args) { // Prawidłowa sekwencja, zawiera znaki: A, T, C lub G String sekwencjaP = "AGCAGAATA"; // Nieprawidłowa sekwencja, zawiera nie tylko znaki: A, T, C lub G String sekwencjaN = "AGCAGAXTA"; // Tworzymy obiekt typu Pattern, przekazujemy mu wyrażenie regularne // oznaczające "jakikolkwiek znak poza A, T, C lub G" // a następnie kompilujemy je Pattern pat = Pattern.compile("[^ATCG]"); // Tworzymy obiekt typu Matcher, służący do porównywania, Matcher matcher = pat.matcher(sekwencjaP); // Jeżeli obiekt znajdzie element pasujący do wyrażenia regularnego // zwraca true, jesli nie, zwraca false, tu powinien zwrócić false // ponieważ w sekwencji prawidłowej nie ma innego znaku niż A, T, C lub G System.out.println("Prawidłowa: " + matcher.find()); // Teraz będziemy sprawdzać błędną sekwencję matcher = pat.matcher(sekwencjaN); // matcher zwraca true ponieważ z błędnej sekwencji znajduje się inny // znak niż A, T, C lub G System.out.println("Nieprawidłowa: " + matcher.find()); // "Prostszy sposób" - tu nie działa System.out.println("\nTo nie zadziała prawidłowo:"); System.out.println("Prawidłowa: " + sekwencjaP.contains("[^ATCG]")); System.out.println("Nieprawidłowa: " + sekwencjaN.contains("[^ATCG]")); } } |
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).
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ć?
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
Dziękuję za szybką odpowiedź.
Kurs póki co jest bardzo dobry, wyjaśnia mnóstwo kwestii w przystępny sposób :)
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.
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!
Racja:) Dziękuję!
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ć :)
Nie mam dosyć :-)
Ale spróbuję się temu przyjrzeć jutro.
Pozdrawiam!
Już doszedłem. Wystarczyło usunąć wers mat2.find(), który występował w tym zapisie 2 razy…
public static boolean testujNaRNA (String sekwencja2){
Pattern pat2 = Pattern.compile(„[U]”);
Matcher mat2 = pat2.matcher(sekwencja2);
mat2.find(); <— TO BYŁO ZBĘDNE
return mat2.find();
}