Regular Expression – Phần 8: Ngắt chuỗi với lớp java.util.Scanner

Trong 2 phần trước, chúng ta đã tìm hiểu việc ứng dụng Regular Expression trong việc thay thế chuỗi con hoặc kí tự trong một chuỗi cha.

Trong các phần tiếp theo này, chúng ta sẽ tìm hiểu việc ứng dụng Regular Expression để ngắt các chuỗi con, hay còn gọi là token từ chuỗi cha.

Có 2 thuật ngữ chúng ta lưu ý trước khi thực hiện công việc ngắt chuỗi, hoặc thao tác lên chuỗi nói chung:

Token: là các từ có nghĩa

Delimiter: các kí tự phân cách các token

Ví dụ chúng ta có chuỗi sau:

I love you so much

Trong chuỗi trên:

Token là các từ: I, love, you, so, much

Delimiter: là các kí tự khoảng trắng dùng phân cách các từ (token)

Tuy nhiên, chúng ta có thể linh hoạt chỉ định kí tự dùng làm delimiter là gì, không nhất thiết phải là kí tự khoảng trắng. Mặc dù, mặc định hầu hết các hàm trong Java đều dùng kí tự khoảng trắng làm delimiter.

Có lẽ chúng ta đã quen thuộc với lới java.util.Scanner dùng để lấy dữ liệu người dùng nhập vào từ cửa sổ dòng lệnh (console interface).

Tuy nhiên lớp java.util.Scanner còn có thể nhận đầu vào là một biến String, hoặc một tập tin dữ liệu (file).

Ví dụ, tôi có chuỗi sau:

I love you so much. I want to marry you

 Và tôi muốn ngắt từ từ trong chuỗi trên ra.

Chúng ta xem đoạn chương trình sau:


import java.util.Scanner;
public class Demo {
    public static void main(String[] args) {
        Scanner sc;
        String s = "I love you so much. I want to marry you";
        sc = new Scanner(s);
        while (sc.hasNext()) {
            String token = sc.next();
            System.out.println(token);
        }
    }
}

Trong đoạn chương trình trên, trước tiên tôi khai báo đối tượng lớp Scanner. Và thay vì truyền tham số cho constructor là System.in để chúng ta lấy dữ liệu từ cửa sổ dòng lệnh, thì tôi truyền vào là biến:


String s = "I love you so much. I want to marry you";

Như vậy, lớp Scanner sẽ đọc dữ liệu từ biến s này:


sc = new Scanner(s);

Tiếp theo, tôi dùng vòng lặp while để duyệt qua tất cả các token trong chuỗi.

Trong điều kiện lặp của while(), tôi sử dụng phương thức hasNext() để kiểm tra có còn token nào trong chuỗi hay không:


while (sc.hasNext())

Phương thức hasNext() này sẽ trả về giá trị true nếu trong chuỗi vẫn còn token, ngược lại trả về false nếu đã hết token, tức là kết thúc chuỗi.

Mặc định phương thức hasNext() sẽ dùng kí tự khoảng trắng làm kí tự phân cách (delimiter) để phân biệt giữa các token.

Phương thức hasNext() sẽ đọc một token, và dừng lại nếu gặp kí tự delimter (trong trường hợp này là kí tự khoảng trắng). Nếu tiếp tục được gọi, phương thức hasNext() sẽ đọc token kế tiếp, và dừng lại nếu gặp kí tự delimiter. Và tiếp tục như vậy cho đến khi nào kết token, tức là kết thúc chuỗi thì thôi.

Thực ra phương thức hasNext() không thật sự đọc token, mà chỉ kiểm tra xem còn token hay không.

Phương thức thật sự đọc và trả về token là phương thức next():


String token = sc.next();

Và cuối cùng, chúng ta chỉ việc xử lý các token này. Trong trường hợp ví dụ ở đây, tôi chỉ in ra màn hình:


System.out.println(token);

Chạy chương trình, chúng ta được kết quả như sau:


I
love
you
so
much.
I
want
to
marry
you

Chúng ta được kết quả như trên là do mặc định, phương thức hasNext() sử dụng khoảng trắng làm kí tự phân cách.

Tuy nhiên chúng ta có thể chỉ định bất kì kí tự nào làm kí tự phân cách cũng được.

Ví dụ, tôi muốn sử dụng 2 kí tự là khoảng trắng và dấu chấm (.) làm kí tự phân cách thì tôi làm như sau:


import java.util.Scanner;
public class Demo {
    public static void main(String[] args) {
        Scanner sc;
        String s = "I love you so much. I want to marry you";
        sc = new Scanner(s);
        sc.useDelimiter("[ .]");
        while (sc.hasNext()) {
            String token = sc.next();
            System.out.println(token);
        }
    }
}

Trong đoạn chương trình trên, tôi thêm vào dòng code sau:


sc.useDelimiter("[ .]");

Tôi sử dụng phương thức useDelimiter() để chỉ định các kí tự dùng làm kí tự phân cách. Tôi sử dụng kí tự khoảng trắng và dấu chấm (.).

Chú ý khi chúng ta đã sử dụng phương thức useDelimiter() để tùy chọn các kí tự delimiter, thì kí tự khoảng trắng không còn được sử dụng mặc định nữa. Do vậy, nếu vẫn muốn dùng kí tự khoảng trắng làm delimiter thì chúng ta phải chỉ định tường minh ở đây.

Chạy chương trình lên, chúng ta được kết quả như sau:


I
love
you
so
much
 
I
want
to
marry
you

Trong kết quả trên, chúng ta thấy có một dòng trống. Sở dĩ có dòng trống này là do chúng ta sử dụng 2 kí tự delimiter là khoảng trắng và dấu chấm (.); và có thời điểm 2 kí tự này nằm liền kề nhau (giữa từ much và từ I).

Nếu chúng ta muốn xử lý 2 (hoặc nhiều hơn) kí tự delimiter nằm liền kề nhau như một kí tự delimiter thì chúng ta làm như sau:


sc.useDelimiter("[ .]+");

Chúng ta chỉ cần thêm dấu cộng (+) ngay phía sau dấu ngoặc vuông ([]).

Như vậy, trong trường hợp nếu có 2 hay nhiều kí tự delimiter nằm liền kề nhau, thì phương thức hasNext() sẽ xử lý như một kí tự delimiter mà thôi.

Chạy chương trình, chúng ta được kết quả như sau:


I
love
you
so
much
I
want
to
marry
you

Như vậy, chúng ta không còn dòng trống nữa.

Ngoài việc chỉ định các kí tự cụ thể làm delimiter, thì chúng ta còn có thể chỉ định một mẫu của Regular Expression làm các kí tự delimiter.

Ví dụ tôi có chuỗi văn bản sau:

I love you 4 so much. 34 I 23 want to marry you

Có các số trong chuỗi văn bản trên, và tôi muốn ngắt các chuỗi con dựa trên các số này.

Chúng ta xem đoạn chương trình sau:


import java.util.Scanner;
public class Demo {
    public static void main(String[] args) {
        Scanner sc;
        String s = "I love you 4 so much. 34 I 23 want to marry you";
        sc = new Scanner(s);
        sc.useDelimiter("\\d+");
        while (sc.hasNext()) {
            String token = sc.next();
            System.out.println(token);
        }
    }
}

Trong đoạn chương trình trên, tôi chỉ thay đổi mẫu trong phương thức useDelimiter() như sau:


sc.useDelimiter("\\d+");

Tôi chỉ định tất cả các kí tự số từ  0 đến 9 làm kí tự delimiter. Chú ý chúng ta cần sử dụng dấu cộng (+) theo sau \\d, để phương thức hasNext() xử lý các kí tự số nằm gần nhau như một kí tự delimiter mà thôi.

Chạy chương trình lên, chúng ta có kết quả như sau:


I love you
 so much.
 I
 want to marry you

Như vậy, chuỗi ban đầu đã được ngắt ra thành các chuỗi con dựa vào các kí tự số.

Hoặc cũng trong trường hợp trên, tôi muốn lấy ra các chữ số 4, 34, và 23. Tức là tôi sử dụng tất cả các kí tự khác, ngoại trừ kí tự số từ 0 đến 9, làm kí tự delimiter để ngắt các số ra.

Chúng ta xem đoạn chương trình sau:


import java.util.Scanner;
public class Demo {
    public static void main(String[] args) {
        Scanner sc;
        String s = "I love you 4 so much. 34 I 23 want to marry you";
        sc = new Scanner(s);
        sc.useDelimiter("[^\\d]+");
        while (sc.hasNext()) {
            String token = sc.next();
            System.out.println(token);
        }
    }
}

Trong đoạn chương trình trên, tôi thay đổi mẫu như sau:


sc.useDelimiter("[^\\d]+");

Tôi sử dụng dấu mũ (^) phía trước kí tự \\d để chỉ định tất cả các kí tự khác, ngoại trừ các kí tự số từ 0 đến 9, sẽ được sử dụng như các kí tự delimiter.

Chú ý, do chúng ta sử dụng dấu mũ (^) nên chúng ta phải đặt toàn bộ mẫu trong cặp dấu ngoặc vuông ([]).

Chạy chương trình, chúng ta có kết quả như sau:


4
34
23

Như vậy là chúng ta đã tách ra được các chữ số từ chuỗi ban đầu.