Matura z informatyki - przydatne funkcje - Java

Zbiór funkcji, które używałem rozwiązując zadania maturalne.


W tym poście zaprezentuję funkcje, z których korzystałem rozwiązując zadania maturalne z informatyki. Nie wiem jak w innych językach, ale w Javie jest całkiem sporo wbudowanych, które od razu robią za nas robotę. Jeśli znasz jakieś lepsze/szybsze sposoby na któreś rozwiązanie lub jeszcze inne przydatne funkcje, których nie wymieniłem, to daj znać!

Wczytanie pliku

Od tego zawsze zaczynamy. Ja korzystam z funkcji, która pakuje wszystkie dane z pliku tekstowego do tablicy o rożnych wymiarach, w zależności od zadania. Przykładowo dla tego typu danych:

AIHAHGHBEAFJAJDI HGIHFEHHJGBCBGD
FBJHCFFGADD EHADJAJBJBEGD
JHGHADJ AGFEHHEHIAEJFC
...

wygląda tak:

private static String[][] readFile(String file) throws IOException{
    String[][] array = new String[1000][2];
    Scanner data = new Scanner(new File("C:\\sciezka\\do\\pliku\\"+file));
    int i = 0;
    while (data.hasNextLine()){
        array[i][0] = data.next();
        array[i][1] = data.next();
        i++;
    }
    data.close();
    return array;
}

Należy pamiętać o escape'owaniu ukośników (\\\\) w ścieżce do pliku.

Konwersja liczb

Jak wiadomo to podstawa. Uzyskamy to szybko dzięki dwóm metodom, które przyjmują liczbę oraz system liczbowy jako argumenty:

Z różnych systemów na dziesiętny
public static int convertToInt(String number, int base){
    return Integer.parseInt(number, base);
}
I z dziesiętnego na inny:
public static String convertInt(int number, int base){
    return Integer.toString(number, base);
}

Zaokroglanie liczb

Równie często jest to nam potrzebne. Gdy mamy podać tylko wydrukowany wynik to jest to całkiem proste. Z pomocą przychodzi printf, np:

System.out.printf("%.2f%n", 265.335);
//265,34
Gdzie:
  • %.2 - określa precyzję, czyli ilość miejsc po przecinku, w tym przypadku 2
  • f - typ danych, w tym przypadku float
  • %n - nowa linia

Jeśli jednak musimy operować na zaokrąglonych liczbach, potrzebne jest nam coś więcej.

Na Stack OverFlow często znajdowałem takie rozwiązanie:

Math.round(x*100.0)/100.0

gdzie zera określają dokładność “przybliżenia”. Jednak nie zawsze zwraca to prawidłową odpowiedź. Przykładowo, jeśli za “x” podstawimy 265.335, to dostaniemy 265.33, co jest oczywiście niepoprawnym wynikiem, bo powinniśmy dostać 265.34.

Ja rozwiązałem to w ten sposób:

public static double round(double x, int n) {
    String rounded = String.format(Locale.ROOT,"%."+n+"f", x);
    return Double.parseDouble(rounded);
}

Wykorzystuje to podobną metodę co printf, tyle że później parsujemy to na double.

Zauważ, że tutaj, jako argument podałem jeszcze Locale.ROOT. Wynika to z tego, że w różnych regionach inaczej jest formatowany tekst i tak dla Polski w metodzie printf/format separatorem liczby typu double jest ",", co całkowicie nam psuje obliczenia, bo typ double korzysta z ".". Zmiana Locale na ROOT rozwiązuje ten problem.

Max/min wartość

W zadaniach zazwyczaj proszą o maksymalną lub minimalną wartość z jakiegoś zbioru. Gdy dane przetrzymujemy w tablicy mamy do tego gotowe funkcje:

public static int max(int[] array) {
    return Arrays.stream(array).max().getAsInt();
}
    
public static int min(int[] array) {
    return Arrays.stream(array).min().getAsInt();
}

Suma wartości

Również czasem sumowanie wszystkich wartości się zdarza i tu też mamy gotowca:

public static int sum(int[] array) {
    return Arrays.stream(array).sum();
}

Wszystko dzięki streamom, które są z nami od Java 8. Jeśli operujemy na listach, też mamy gotowe metody do wyznaczania min/max:

ArrayList<Integer> numbers = new ArrayList<>();
numbers.addAll(Arrays.asList(1,2,3,4));

Collections.max(numbers);
//4

Collections.min(numbers);
//1

Chociaż do sumowania już jest nieco mniej przyjaźnie i musimy użyć streamów:

numbers.stream().mapToInt(i->i.intValue()).sum()
//10

Odwracanie stringa

Często w zadaniach trzeba odwracać stringi (np. aby sprawdzić czy są tzw. “palindromami”). Można to zrobić błyskawicznie z pomocą StringBuildera.

public static String reverse(String string) {
    return new StringBuilder(string).reverse().toString();
}

Operowanie na literach w słowie

Tu znacznie ułatwia sprawę funkcja, która każdy znak pakuje nam do tablicy, dzięki czemu później łatwo możemy na nich operować:

char[] letters = "Anagram".toCharArray();
//[A, n, a, g, r, a, m]

Znajdowanie podciągów w stringach

Sprawdzanie czy dany ciąg znaków zawiera inny podciąg jest bajecznie proste:

"fooandbar".contains("and");
//true

Zdarzyło się też porównywać prefiksy lub sufiksy między stringami, ale również do tego mamy gotowe metody, które sprawdzają czy ciąg zaczyna się lub kończy podanym stringiem:

"fooandbar".startsWith("foo");
//true

"fooandbar".endsWith("bar");
//true

Czasem jednak musimy dobrać się do środka ciągów. W jednym zadaniu, z genotypu trzeba było wyciągać geny, które rozpoczynały się od “AA” a kończyły na “BB” i na nich przeprowadzać operację. Do tego typu zadań warto znać klasy Pattern i Matcher oraz umieć posługiwać się regexami. Do tego zadania użyłem metodę podobną do tej, jednak tę przerobiłem do ogólnego użytku, nie tylko pod zadanie:

public static String[] getMatchesBetween(String string, String a, String b) {
    Pattern between = Pattern.compile(a+"(.*?)"+b);
    Matcher next = between.matcher(string);
    
    ArrayList<String> matches = new ArrayList<>();
    
    while(next.find()){
        matches.add(next.group());
    }
    
    return matches.toArray(new String[matches.size()]);
}

W skrócie, znajduję nam wszystkie podciągi miedzy argumentami a i b, oraz pakuje je do listy, którą na koniec konwertujemy na tablicę.

Gdybyśmy mieli tylko sprawdzać czy dany ciąg pasuje do innego podciągu, moglibyśmy się posłużyć tą metodą:

"fooandbar".matches("foo(.*?)bar");
//true

Kluczowy w tych metodach jest regex: (.\*?) gdzie . oznacza dowolny znak, a \*? to "leniwy" kawntyfikator? (ang. Reluctant quantifier), który określa, że . (dowolny znak) może się wystąpić zero lub więcej razy. Jeśli, np. nie uznawalibyśmy pustych ciągów między a i b to użylibyśmy kwantyfikatora +?, który określa, że znak musi wystąpić co najmniej raz. Z kolei inny rodzaj kwantyfikatora - zachłanny (ang. greedy), w np. w postaci * pobrałby najwięcej ile się da, czyli w naszym przypadku od znalezienia pierwszego 'AA' po ostatnie 'BB'. Różnica miedzy kwantyfikatorami leniwymi a zachłannymi polega na tym, że ten pierwszy dopasuje najmniej jak się da, a drugi jak najwięcej. Po więcej info odsyłam do dokumentacji Pattern

Zaprezentuje to na przykładzie. Dajmy na to, że mamy taką o to tablicę z danymi:

String[] genotypes = new String[3];
        genotypes[0] = "EBEAADDEDCBCAEBABBDBEADBBDAABBDDACDDECECECBBDDBCBEDAEBDAADCE";
        genotypes[1] = "BCBAAEEDDBEDCBBCCABDAACEBBEBCCCCCABDDDBBAACCEDDCBBCEAAEEADBBDE";
        genotypes[2] = "AADDEEDBBCCEEBBDEEDDAAAACDCDDCDBBEAADDEDDBBDCDDCDCAA";

i wywołując tą metodę na każdym elemencie tej tablicy, dostaniemy nową tablicę z dopasowaniami, które tu w tym przykładzie od razu drukuję:

for (String genotype : genotypes) {
    System.out.println(Arrays.toString(getMatchesBetween(genotype, "AA", "BB")));
}

i output dla poszczególnych kwantyfikatorów wygląda następująco:

//dla (.*?)

[AADDEDCBCAEBABB, AABB]
[AAEEDDBEDCBB, AACEBB, AACCEDDCBB, AAEEADBB]
[AADDEEDBB, AAAACDCDDCDBB, AADDEDDBB]

//dla (.+?)

[AADDEDCBCAEBABB, AABBDDACDDECECECBB]
[AAEEDDBEDCBB, AACEBB, AACCEDDCBB, AAEEADBB]
[AADDEEDBB, AAAACDCDDCDBB, AADDEDDBB]

//dla (.*)

[AADDEDCBCAEBABBDBEADBBDAABBDDACDDECECECBB]
[AAEEDDBEDCBBCCABDAACEBBEBCCCCCABDDDBBAACCEDDCBBCEAAEEADBB]
[AADDEEDBBCCEEBBDEEDDAAAACDCDDCDBBEAADDEDDBB]

Ilości liter - statystyka

Trafiały się też zadania gdzie trzeba było znaleźć ilości liter w słowie i wykonać na tym różne operacje (np. sprawdzanie czy słowa są anagramami). Napisałem do tego taką funkcję, która zwraca tablicę z liczbą poszczególnych liter:

public static int[] countLetters(String word) {
    int[] letters = new int[26];//26 liter w alfabecie

    for ( int i = 0; i < word.length(); i++ ) {
        letters[word.charAt(i)-'A']++;
    }
    return letters;
}

Kluczowym czynnikiem jest tu operacja word.charAt(i)-'A', dzięki której obliczane jest miejsce w tablicy, które powinno zostać policzone w aktualnej iteracji.

Jeśli nie wiesz co tu się dzieje, to znak jest przechowywany jako liczba (np. alfabet wielkich liter przyjmuje wartości od 60 - dla A, do 90 - dla Z), a my metodą charAt wyciągamy aktualny znak, dajmy na to 'B'(61) i odejmujemy od tego 'A'(60), zatem dostajemy pozycję 1 (61-60).

Nasza tablica dla pewnego słowa po wydrukowaniu wyglądałaby tak:

// 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

  [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0]

Warto wiedzieć też, że można inkrementować znaki!

Przykładowo:

char a = 'A';
a++;
System.out.println(a);
//B

Mając już taką wiedzę, możemy dostarczyć pełne statystyki jakiegoś tekstu. W jednym z zadań to własnie trzeba było zrobić. Najpierw wpakowałem wszystkie słowa do tablicy i następnie stworzyłem funkcję, która na podstawie podananej tablicy ze słowami, drukuje nam statystyki danego tekstu:

private static void letterStats(String[] text) {
    int[] lettersCount = new int[26];
    
    for (String word : text) {
        
        for(int i = 0; i<word.length(); i++){
            lettersCount[word.charAt(i)-'A']++;
            
        }
    }
    
    double numberOfallLetters = Arrays.stream(lettersCount).sum();
    char a = 'A';
    
    for (int count : lettersCount) {
        double percentage = count*100.0/numberOfallLetters;
        System.out.println(a++ + ": " + count + " (" + String.format(Locale.ROOT, "%.2f", percentage) + "%)");
    }
}

A wyglądało to tak:

A: 632 (7.55%)
B: 196 (2.34%)
C: 162 (1.94%)
D: 422 (5.04%)
E: 1093 (13.06%)
F: 213 (2.55%)
G: 151 (1.80%)
H: 566 (6.76%)
I: 522 (6.24%)
J: 2 (0.02%)
K: 64 (0.76%)
L: 402 (4.80%)
M: 193 (2.31%)
N: 557 (6.66%)
O: 641 (7.66%)
P: 93 (1.11%)
Q: 6 (0.07%)
R: 524 (6.26%)
S: 485 (5.80%)
T: 792 (9.46%)
U: 185 (2.21%)
V: 84 (1.00%)
W: 196 (2.34%)
X: 3 (0.04%)
Y: 185 (2.21%)
Z: 0 (0.00%)

I do tej pory to by było na tyle. Jeśli uzbieram większy zbiór przydatnych funkcji lub algorytmów, to przeleję to na kolejny wpis.


Jeśli uważasz, że to co robię jest przydatne, polub stronę bloga na Facebooku. Wrzucam tam m.in. informacje o nowych wpisach, o promocjach dla programistów i inne.