« poprzedni punkt  następny punkt »

2. Rozbiór tekstów

Bardzo często w programowaniu występują sytuacje, gdy trzeba rozbić, rozłożyć jakiś tekst (napis) na poszczególne występujące w nim symbole (np. słowa).

Wyobraźmy sobie np., że łańcuch znakowy zawiera napisy reprezentujące liczbę całkowitą, dowolną liczbę spacji, znak operacji arytmetycznej, dowolną liczbę spacji i napis reprezentujący drugą liczbę cłakowitą. Naszym zadaniem jest "wyłuskanie" z całego napisu napisów-liczb, przekształcenie ich do postaci binarnej i wykonanie na nich podanej operacji.

r Napis taki może wyglądać następująco: "21 + 21"

Wyłuskiwane symbole są rozdzielone separatorami.



W przykładzie z liczbami separatorami będą spacje.
Dowolny ciąg znaków, który nie zawiera spacji - będzie symbolem.
Dowolna liczba spacji będzie oddzielać symbole od siebie.


Do wyłuskiwania symboli z łańcuchów znakowych służy klasa StringTokenizer z pakietu java.util.

Po to by dokonać rozbioru tekstu - tworzymy obiekt klasy StringTokenizer, podając jako argument konstruktora - tekst do rozbioru np.

String expr = "21 + 21";
StringTokenizer st = new StringTokenizer(expr);

Ta postać konstruktora zakłada domyślnie, że separatorami są znaki z następującego zestawu " \t\n\r\f" (czyli znak spacji, tabulacji, przejścia do nowego wiersza, powrotu karetki, nowej strony). W tym przypadku symbolami będą ciągi znaków, które nie zawierają żadnego z wymienionych separatorów.

Obiektu klasy StringTokenizer możemy teraz zapytać o to ile symboli zawiera przekazany konstruktorowi napis:


int n = st.countTokens(); // n = 3

Wyłuskiwanie symboli odbywa się sekwencyjnie poczynając od początku łańcucha. Służy temu metoda nextToken(), która zwraca kolejny symbol jako String. Pierwsze wywołanie tej metody zwróci pierwszy symbol, następne - będą zwracać kolejne symbole łańcucha.

String s1 = st.nextToken(); // napis "21"
String s2 = st.nextToken(); // napis "+"
String s3 = st.nextToken(); // napis "21"

Gdy nie ma już symboli "do zwrotu" - wywołanie nextToken() spowoduje powstanie wyjątku NoSuchElementException.

Zatem zawsze przeglądamy łańcuch od początku i "wyłuskując" symbole przesuwamy się do jego końca (uwaga: kolejne symbole są zwracane, ale oczywiście - nie są usuwane z łańcucha).

Do stwierdzenia, czy w łańcuchu znakowym są jeszcze symbole do zwrotu służy metoda hasMoreTokens().
Metoda hasMoreTokens() zwraca true, jeśli w łańcuchu znakowym są jeszcze nie "wyluskane" symbole i false w przeciwnym razie. Inaczej mówiąc: hasMoreTokens() zwraca true, jeśli następne odwołanie do nextToken() zwróci kolejny symbol i zwraca false, gdy ew. następne odwołanie do nextToken() spowoduje wyjątek NoSuchElementException.

Łatwo zapisać pętle, w której będziemy pobierać kolejne symbole:

while (st.hasMoreTokens()) {
  Sring s = st.nextToken();
  // ... tu coś robimy z s
}

Wiedząc to wszystko można teraz napisać program, który w oknie dialogowym prosi użytkownika o wprowadzeia liczby cąłkowitej, spacji, operatora, spacji i drugiej liczby i wykonuje żądaną operację arytmetyczną na tych dwóch liczbach.


Przed lekturą dalszego tekstu proszę to zadanie rozwiązać samodzielnie


Rozwiązanie:

import java.util.*;
import javax.swing.*;

public class Oper {

  public static void main(String[] args) {

    String normalQuest = "Liczba1 op Liczba2",
           errorQuest = "Wadliwe dane. Jeszcze raz.\n" + normalQuest,
           quest = normalQuest;

    String expr;

    while ((expr = JOptionPane.showInputDialog(quest)) != null) {

      StringTokenizer st = new StringTokenizer(expr);

      if (st.countTokens() != 3) {
          quest = errorQuest;
          continue;
      }

      String snum1 = st.nextToken(),
             sop  = st.nextToken(),
             snum2 = st.nextToken();

      int num1 = Integer.parseInt(snum1),
          num2 = Integer.parseInt(snum2),
          res = 0;

      char op = sop.charAt(0);

      switch (op) {
        case '+' : res = num1 + num2; break;
        case '-' : res = num1 - num2; break;
        case '*' : res = num1 * num2; break;
        case '/' : res = num1 / num2; break;
        default: {
          quest = errorQuest;
          continue;
        }
      }
      JOptionPane.showMessageDialog(null, "Wynik = " + res);
      quest = normalQuest;
    }
    System.exit(0);

  }

}

Inna postać konstruktora klasy StringTokenizer pozwala na określenie zbioru separatorów, które będą służyć do wyróżniania symboli.
Jeżeli napiszemy np.

StringTokenizer st = new StringTokenizer(s, "., " - to separatorami będą kropka, przecinek i spacja.

Zadanie:
Stworzyć klasę Words, której obiekty będą zawierać tablicę słów napisu przekazanego jako argument konstruktora. Za słowa uznajemy ciągi znaków rozdzielonych spacjami i znakami punktuacji (oraz może innymi - zastanowić się jakimi).
W klasie dostarczyć metod:
getWordsCount() - zwraca liczbę słów
getWord(int i) - zwraca i-te słowo napisu (i =1,2, ... n; gdzie n liczba słów w napisie)
getWords() - zwraca tablicę słów
getMaxLenWord() - zwraca najdłuższe słowo
getMinLenWord() - zwraca najkrótsze słowo


Przed lekturą dalszego tekstu proszę to zadanie rozwiązać samodzielnie


Możliwe rozwiązanie:

import java.util.*;

public class Words {

  private String[] words;      // tablica slów
  private String maxLenWord;   // słowo o max długości
  private String minLenWord;   // słowo o minimalnej długości

  // Konstruktro
  public Words(String txt) {

    // Uwzględniamy bogaty zestaw separatorów słów

    StringTokenizer st = new StringTokenizer(txt, " \t\n\r\f.,:;()[]\"'?!-{}");

    words = new String[st.countTokens()];  // utworzenie tablicy słów
    int maxL = 0;                          // max dlugość
    int minL = 10000;                      // min długość
    int i = 0;                             // indeks w tablicy

    while (st.hasMoreTokens()) {   // dopóki są słowa
      String s = st.nextToken();
      int len = s.length();
      if (len > maxL) {            // maksymalna długość ?
         maxL = len;
         maxLenWord = s;
      }
      if (len < minL) {            // minimalna długość ?
        minL = len;
        minLenWord = s;
      }
      words[i++] = s;              // słowo -> do tablicy; zwiększenie indeksu
    }
  }

  // Zwraca liczbę słów
  public int getWordsCount() {
    return words.length;
  }

  // Zwraca i-te słowo (liczymy od 1)
  // jeśli podano wadliwy indeks - zwraca null
  public String getWord(int i) {
    return (i < 1 || i > words.length) ? null : words[i-1];
  }

  // Zwraca tablicę slów
  public String[] getWords() {
    return words;
  }

  // Zwraca słowo o max długości
  public String getMaxLenWord() {
    return maxLenWord;
  }
  // Zwraca słowo o min długości
  public String getMinLenWord() {
    return minLenWord;
  }


}

I klasa testująca:

import javax.swing.*;

class TestWords {

  public static void main(String[] args) {
    String txt;
    while ((txt = JOptionPane.showInputDialog("Wpisz tekst")) != null) {
      Words w = new Words(txt);
      int n = w.getWordsCount();
      say("Liczba słów: " + n);
      say("Kolejne słowa: ");
      for (int i=1; i <= n; i++) say(w.getWord(i));
      say("Słowo o numerze n+1: " + w.getWord(n+1));
      say("Kolejne słowa: ");
      String[] wrds = w.getWords();
      for (int i=0; i < wrds.length; i++) say(wrds[i]);
      say("Najdluższe słowo: " + w.getMaxLenWord());
      say("Najkrótsze słowo: " + w.getMinLenWord());
    }
    System.exit(0);
  }

  static void say(String s) {
    System.out.println(s);
  }

}

Klasa StringTokenizer dostarcza programiście jeszcze pewnych dodatkowych możliwości (np. zmianę separatorów pomiędzy kolejnymi operacjami wyłuskiwania symboli;  zwracanie nie tylko symboli, ale i separatorów w wyniku odwołania nextToken()).

Proszę zapoznać się z dokumentacją klasy StringTokenizer.


« poprzedni punkt  następny punkt »