Chương 8: Chuỗi kí tự

8.1  Kí tự

Trong Java cũng như các ngôn ngữ hướng đối tượng khác thì đối tượng là tập hợp những dữ liệu có liên quan, cùng với một tập các phương thức. Những phương thức nafyhoajt động trên đối tượng kể trên, thực hiện tính toán và đôi lúc thay đổi dữ liệu trong đối tượng đó.

String (chuỗi kí tự) là các đối tượng, bởi vậy bạn có thể hỏi “Có dữ liệu nào được chứa trong một đối tượng String?” và “Có những phương thức nào mà ta có thể kích hoạt được từ đối tượng String?” Những thành phần trong một đối tượng String là các chữ cái, hay tổng quát hơn, là những kí tự. Không phải mọi kí tự đều là chữ cái; còn những kí tự là chữ số, kí hiệu, và các thứ khác. Để đơn giản tôi sẽ gọi chúng đều là các chữ cái. Có nhiều phương thức khác nhau, nhưng trong sách này chỉ dùng một số ít. Các phương thức còn lại được chỉ dẫn ở http://download.oracle.com/javase/6/docs/api/java/lang/String.html.

Phương thức đầu tiên mà ta xét đến là charAt; phương thức này cho phép bạn kết xuất những chữ cái từ một Stringchar là kiểu biến dùng được để lưu trữ từng kí tự riêng lẻ (trái ngược lại với một chuỗi các kí tự).

char cũng hoạt động như các kiểu dữ liệu khác ta đã gặp:

    char ltr = 'c'; 
    if (ltr == 'c') { 
      System.out.println(ltr); 
    }

Những giá trị của kí tự đều xuất hiện trong cặp dấu nháy đơn, như ’c’. Khác với giá trị của chuỗi (xuất hiện giữa cặp dấu nháy kép), các giá trị kí tự chỉ có thể chứa một chữ cái hoặc một kí hiệu.

Sau đây là cách dùng phương thức charAt:

    String fruit = "banana"; 
    char letter = fruit.charAt(1); 
    System.out.println(letter);

fruit.charAt() có nghĩa rằng tôi đang kích hoạt phương thức charAt lên đối tượng có tên fruit. Tôi đang truyền đối số 1 vào phương thức này, tức là tôi đang muốn biết chữ cái đầu tiên của chuỗi là gì. Kết quả là một kí tự, và được lưu vào trong một char có tên letter. Khi tôi in ra giá trị của letter, tôi bị bất ngờ:

a

a không phải là chữ cái đầu tiên của "banana". Trừ khi bạn nghiên cứu khoa học máy tính. Vì những lí do kĩ thuật mà giới khoa học máy tính đều đếm từ số không. Chữ cái thứ 0 của "banana" là chữ b. Chữ cái thứ 1 là a và thứ 2 là n.

Nếu bạn muốn biết chữ cái thứ 0 của một chuỗi, bạn phải truyền tham số là 0:

    char letter = fruit.charAt(0);

8.2  Length

Phương thức tiếp theo đối với String mà ta xét đến là length, vốn trả lại số kí tự có trong chuỗi. Chẳng hạn:

    int length = fruit.length();

length không nhận đối số truyền vào, và trả lại một số nguyên, trong trường hợp này bằng 6. Lưu ý rằng việc có một biến trùng tên với phương thức là hoàn toàn hợp lệ (mặc dù điều này có thể gây nhầm lẫn đối với người đọc mã lệnh).

Để tìm chữ cái cuối cùng trong chuỗi, bạn có thể bị xui khiến để thử theo cách làm sau:

    int length = fruit.length(); 
    char last = fruit.charAt(length); // SAI!!

Cách này không có tác dụng. Lý do là không có chữ cái thứ 6 nào trong "banana". Vì ta đã bắt đầu đếm từ 0, nên sáu chữ cái trong chuỗi được đếm từ 0 tới 5. Để lấy chữ cái cuối cùng, ta phải trừ length đi một.

    int length = fruit.length(); 
    char last = fruit.charAt(length-1);

8.3  Duyệt chuỗi

Một công việc thường làm với một chuỗi là bắt đầu từ điểm đầu của chuỗi, lần lượt chọn từng kí tự, thực hiện một số thao tác đối với chữ cái đó, và công việc được tiếp diễn cho các chữ cái còn lại đến hết chuỗi. Kiểu xử lý như thế này được gọi là duyệt. Một cách tự nhiên để thực hiện việc duyệt là dùng vòng lặp while:

    int index = 0; 
    while (index < fruit.length()) { 
      char letter = fruit.charAt(index); 
      System.out.println(letter); 
      index = index + 1; 
    }

Vòng lặp này để duyệt chuỗi và hiển thị từng chữ cái trên một dòng riêng. Lưu ý điều kiện lặp là index < fruit.length(), nghĩa là khi index bằng với chiều dài của chuỗi, thì điều kiện bị vi phạm, và phần thân của vòng lặp không được thực hiện. Kí tự cuối cùng được truy cập đến sẽ tương ứng với chỉ số fruit.length()-1.

Tên của biến vòng lặp là index (có nghĩa là “chỉ số”). Một chỉ số là một biến hay giá trị được dùng để chỉ định một thành viên của một tập hợp được xếp thứ tự, trong trường hợp này là chuỗi các kí tự. Chỉ số có nhiệm vụ chỉ định thành viên nào bạn cần biết (vì vậy mà nó có tên “chỉ số”).

8.4  Lỗi thực thi

Trở về Mục 1.3.2 tôi đã nói tới các lỗi thực thi, những lỗi không xuất hiện đến tận khi chương trình bắt đầu chạn. Trong Java, những lỗi thực thi được gọi là các biệt lệ.

Có thể bạn không thấy nhiều lỗi thực thi, song vì ta chưa thực hiện nhiều thao tác có khả năng gây nên những lỗi loại này. Và bây giờ ta sẽ gây lỗi. Nếu bạn dùng phương thức charAt rồi cung cấp một chỉ số là số âm hoặc lớn hơn length-1, Java sẽ phát ra một biệt lệ. Bạn có thể hình dung việc “phát” biệt lệ cũng như phát ra một cơn giận dữ.

Khi điều này xảy đến, Java in ra một thông báo lỗi có ghi kiểu biệt lệ và một lần vết ngăn xếp, trong đó có biểu thị những phương thức đang hoạt động khi có biệt lệ xảy ra. Sau đây là một ví dụ:

public class BadString { 
  public static void main(String[] args) { 
    processWord("banana"); 
  } 
  public static void processWord(String s) { 
    char c = getLastLetter(s); 
    System.out.println(c); 
  } 
  public static char getLastLetter(String s) { 
    int index = s.length();    // SAI! 
    char c = s.charAt(index); 
    return c; 
  } 
}

Lưu ý rằng lỗi nằm trong getLastLetter: chỉ số của kí tự cuối cùng đáng ra phải là s.length()-1. Sau đây là kết quả bạn thu được:

Exception in thread "main" java.lang.StringIndexOutOfBoundsException:
String index out of range: 6
        at java.lang.String.charAt(String.java:694)
        at BadString.getLastLetter(BadString.java:24)
        at BadString.processWord(BadString.java:18)
        at BadString.main(BadString.java:14)

Sau đó chương trình kết thúc. Lần vết ngăn xếp này có thể khó đọc, song nó chứa đựng rất nhiều thông tin.

8.5  Đọc tài liệu

Nếu bạn truy cập đến http://download.oracle.com/javase/6/docs/api/java/lang/String.html và kích chuột vào charAt, bạn sẽ xem được tài liệu sau đây (hoặc với nội dung tương tự):

public char charAt(int index)

Returns the char value at the specified index. An index ranges
from 0 to length() - 1. The first char value of the sequence is
at index 0, the next at index 1, and so on, as for array indexing.

Parameters: index - the index of the char value.

Returns: the char value at the specified index of this string.
  The first char value is at index 0.

Throws: IndexOutOfBoundsException - if the index argument is
  negative or not less than the length of this string.

Dòng đầu tiên là nguyên mẫu của phương thức, có nhiệm vụ quy định tên của phương thức, kiểu dữ liệu của các tham số cũng như kiểu trả lại.

Dòng tiếp theo miêu tả những công việc mà phương thức thực hiện. Các dòng sau đó giải thích các tham số và giá trị trả lại. Trong trường hợp này, việc giải thích là quá thừa, nhưng tài liệu luôn được thiết kế để phù hợp một dạng mẫu tiêu chuẩn. Còn dòng cuối cùng mô tả các biệt lệ mà phương thức này có thể phát ra.

Có lẽ bạn sẽ mất chút thời gian để làm quen với kiểu tài liệu thế này, nhưng thời gian công sức bỏ ra cũng đáng.

8.6  Phương thức indexOf

indexOf là phép nghịch đảo của charAtcharAt nhận vào một chỉ số rồi trả lại kí tự ở vị trí chỉ số đó; indexOf nhận một kí tự rồi tìm chỉ số mà kí tự đó xuất hiện.

charAt thất bại nếu chỉ số nằm ngoài phạm vi chuỗi, khi đó phương thức này sẽ phát biệt lệ. indexOf thất bại nếu kí tự không có mặt trong chuỗi, và trả lại giá trị -1.

    String fruit = "banana"; 
    int index = fruit.indexOf('a');

Đoạn mã lệnh này tìm chỉ số của chữ cái ’a’ trong chuỗi. Với trường hợp này, chữ cái nêu trên xuất hiện ba lần, nên ta chưa thấy ngay rằng indexOf nên làm gì. Nhưng theo tài liệu, thì phương thức này sẽ trả lại chỉ số của lần xuất hiện đầu tiên.

Để tìm các lần xuất hiện tiếp theo, còn có một dạng khác của indexOf. Nó nhận vào một đối số thứ hai quy định xem cần bắt đầu tìm kiếm từ vị trí nào trong chuỗi. Đây là một dạng quá tải toán tử, để biết thêm chi tiết, bạn hãy xem Mục 6.4.

Nếu ta kích hoạt:

   int index = fruit.indexOf('a', 2);

nó sẽ bắt đầu ở chữ cái số hai (chữ n đầu tiên) rồi tìm chữ a thứ hai, vốn có chỉ số là 3. Nếu tình cờ chữ cái đó xuất hiện ngay ở chỉ số khởi đầu, thì câu trả lời chính là chỉ số đầu này. Bởi vậy

    int index = fruit.indexOf('a', 5);

sẽ trả lại 5.

8.7  Lặp quay vòng và đếm

Chương trình dưới đây đếm số lần xuất hiện của chữ ’a’ trong một chuỗi:

    String fruit = "banana"; 
    int length = fruit.length(); 
    int count = 0; 
    int index = 0; 
    while (index < length) { 
      if (fruit.charAt(index) == 'a') { 
        count = count + 1; 
      } 
      index = index + 1; 
    } 
    System.out.println(count);

Chương trình này cho thấy một cách viết quen tay thông dụng, đó là một biến đếm. Biến count được khởi tạo bằng không và sau đó tăng thêm một ứng với mỗi lần ta tìm thấy một chữ ’a’. Việc tăng ở đây là chỉ tăng thêm một đơn vị; nó ngược lại với thao tác giảm. Khi ta thoát khỏi vòng lặp, count sẽ chứa kết quả, đó là tổng số các chữ a.

8.8  Các toán tử tăng và giảm

Tăng và giảm là những thao tác thông dụng đến nỗi Java có những toán tử riêng cho chúng. Toán tử ++ cộng thêm một vào giá trị hiện thời của một int hay char-- thì trừ đi một. Hai toán tử trên đều không có tác dụng đối với doubleboolean hay String.

Về khía cạnh kĩ thuật, sẽ hoàn toàn hợp lệ nếu ta tăng một biến rồi đồng thời sử dụng nó trong một biểu thức. Chẳng hạn, bạn có thể thấy lệnh kiểu như sau:

    System.out.println(i++);

Nhìn vào câu lệnh này, thật không rõ là liệu việc tăng sẽ tiến hành trước hay sau khi giá trị được in ra. Bởi vì những biểu thức thế này có xu hướng gây nhầm lẫn, tôi khuyên bạn nên hạn chế sử dụng chúng. Thậm chí, để hạn chế hơn nữa, tôi sẽ không nói cho bạn biết kết quả bằng bao nhiêu. Nếu thực sự muốn biết, bạn có thể thử xem.

Bằng cách dùng toán tử tăng, ta có thể viết lại mã lệnh đếm chữ:\

    int index = 0; 
    while (index < length) { 
      if (fruit.charAt(index) == 'a') { 
        count++; 
      } 
      index++; 
    }

Một lỗi sai thường gặp là viết lệnh kiểu như sau:

    index = index++; // SAI!!

Tuy nhiên, cách này lại hợp lệ về mặt cú pháp, nên trình biên dịch sẽ không cảnh báo bạn. Hiệu ứng của lệnh này là giữ nguyên giá trị của index. Đây thường là một lỗi khó tìm ra.

Hãy nhớ, bạn có thể viết index = index+1, hay index++, nhưng đừng trộn lẫn hai cách viết này.

8.9  String có tính không đổi

Như đã đọc tài liệu về các phương thức của String, có thể bạn phát hiện ra hai phương thức toUpperCase và toLowerCase. Hai phương thức này thường gây nhầm lẫn, vì chúng có tên gọi nghe như thể chúng có tác dụng thay đổi chuỗi hiện có. Song thực ra, chẳng có phương thức nào nói chung và hai phương thức này nói riêng, có thể thay đổi được chuỗi, vì chuỗi có tính không đổi.

Khi bạn kích hoạt toUpperCase đối với một String, bạn sẽ thu được một String mới làm kết quả trả lại. Chẳng hạn:

    String name = "Alan Turing"; 
    String upperName = name.toUpperCase();

Sau khi dòng lệnh thứ hai được thực thi, upperName sẽ chứa giá trị "ALAN TURING", còn name vẫn chứa "Alan Turing".

8.10  String có tính không so sánh được

Ta thường cần so sánh hai chuỗi để xem chúng có giống nhau không, hay chuỗi nào sẽ xếp trước theo thứ tự bảng chữ cái. Thật tuyệt nếu ta sử dụng được các toán tử so sánh như == và >, song ta không thể làm vậy.

Để so sánh các String, ta phải dùng các phương thức equals và compareTo. Chẳng hạn:

    String name1 = "Alan Turing"; 
    String name2 = "Ada Lovelace"; 
    if (name1.equals (name2)) { 
      System.out.println("hai tên này là một."); 
    } 
    int flag = name1.compareTo (name2); 
    if (flag == 0) { 
      System.out.println("Hai tên gọi này là một."); 
    } else if (flag < 0) { 
      System.out.println("tên 1 xếp trước tên 2."); 
    } else if (flag > 0) { 
      System.out.println("tên 2 xếp trước tên 1."); 
    }

Cú pháp ở đây hơi kì quặc. Để so sánh hai String, bạn phải kích hoạt một phương thức lên một chuỗi rồi truyền chuỗi còn lại làm tham số.

Giá trị trả về từ equals thật dễ hiểu; true nếu hai chuỗi có chứa cùng các kí tự, và false trong trường hợp còn lại.

Giá trị trả về từ compareTo lại kì quặc. Đó là khoảng cách giữa hai chữ cái đầu tiên có sự khác biệt ở hai chuỗi. Nếu hai chuỗi bằng nhau thì khoảng cách này bằng 0. Nếu chuỗi thứ nhất (chuỗi mà ta kích hoạt phương thức lên) đứng trước theo thứ tự bảng chữ cái, thì khoảng cách này có giá trị âm. Ngược lại, khoảng cách có giá trị dương. Trong trường hợp này, giá trị trả lại bằng 8, vì chữ cái thứ hai của “Ada” đi trước chữ cái thứ hai của “Alan” là 8 vị trí.

Để trọn vẹn, tôi cũng nói thật rằng việc dùng toán tử == đối với các Strings là hợp lệ nhưng ít khi đúng đắn. Tôi sẽ giải thích lí do trong Mục 13.4; song bây giờ thì chưa.

8.11  Thuật ngữ

đối tượng:
Một tập hợp các dữ liệu có liên quan cùng với một tập các phương thức hoạt động với nó. Các đối tượng mà ta dùng cho đến giờ gồm có String, Bug, Rock, và những đối tượng khác trong GridWorld.
chỉ số:
Một biến hay giá trị được dùng để chọn một trong các thành viên (phần tử) của một tập hợp được xếp thứ tự, như chọn kí tự từ một chuỗi.
biệt lệ:
Một lỗi khi thực thi chương trình.
phát:
Gây nên một biệt lệ.
lần vết ngăn xếp:
Một bản báo cáo cho thấy trạng thái chương trình khi có biệt lệ xảy ra.occurs.
nguyên mẫu:
Dòng đầu tiên của một phương thức, trong đó quy định tên, các tham số và kiểu trả lại.
duyệt:
Việc lặp qua tất cả mọi phần tử của một tập hợp nhằm thực hiện một công việc tương tự đối với từng phần tử.
biến đếm:
Một biến dùng để đếm thứ gì đó; biến này thường được khởi tạo bằng không sau đó tăng thêm.
tăng:
Việc tăng giá trị của biến thêm một đơn vị. Toán tử tăng trong Java là ++.
giảm:
Việc giảm giá trị của biến thêm đi đơn vị. Toán tử giảm trong Java là --.

8.12  Bài tập

Bài tập 1   Hãy viết một phương thức nhận vào một String làm đối số rồi in tất cả các chữ cái theo chiều ngược lại trên cùng một dòng.
Bài tập 2  Hãy đọc nội dung lần vết ngăn xếp ở Mục 8.4 rồi trả lời những câu hỏi sau:

  • Những loại biệt lệ nào đã xảy ra, và những biệt lệ này được định nghĩa trong các gói (package) nào?
  • Giá trị nào của chỉ số gây nên biệt lệ?
  • Phương thức nào phát ra biệt lệ, và phương thức đó được định nghĩa ở đâu?
  • Phương thức nào kích hoạt charAt?
  • Trong BadString.java, charAt được kích hoạt tại dòng số mấy?
Bài tập 3  Hãy bao bọc đoạn mã ở Mục 8.7 vào một phương thức có tên countLetters, sau đó khái quát hoá sao cho nó chấp nhận các đối số là chuỗi và chữ cái cần đếm. Tiếp theo, viết lại phương thức sao cho nó sử dụng indexOf để định vị các chữ a, thay vì kiểm tra từng chữ cái một.
Bài tập 4   Mục đích của bài tập này là ôn lại phép bao bọc và khái quát hoá.

  1. Hãy bao bọc đoạn mã lệnh sau, chuyển đổi nó thành một phương thức nhận vào đối số là một String rồi trả lại giá trị cuối cùng của count.
  2. Mô tả ngắn gọn công dụng của phương thức vừa lập nên (mà không đi vào chi tiết các bước thực hiện như thế nào).
  3. Bây giờ khi bạn đã khái quát hoá để mã lệnh hoạt động được với chuỗi bất kì rồi, bạn còn có thể khái quát hoá theo cách nào nữa?
    String s = "((3 + 7) * 2)"; 
    int len = s.length(); 
    int i = 0; 
    int count = 0; 
    while (i < len) { 
      char c = s.charAt(i); 
      if (c == '(') { 
        count = count + 1; 
      } else if (c == ')') { 
        count = count - 1; 
      } 
      i = i + 1; 
    } 
    System.out.println(count);
Bài tập 5   Mục đích của bài tập này là khám phá những kiểu dữ liệu trong Java và điền vào một số thông tin chi tiết chưa được đề cập đến trong chương này.

  1. Hãy tạo nên một chương trình mới có tên Test.java rồi viết một phương thức main có chứa những biểu thức có kết hợp nhiều kiểu dữ liệu bằng toán tử +. Chẳng hạn, điều gì sẽ xảy ra nếu bạn “cộng” một String và một char? Liệu nó có thực hiện tính tổng hay kết nối? Kiểu của kết quả sẽ là gì? (Bạn xác định được kiểu của kết quả như thế nào?)
  2. Hãy sao chép lại và mở rộng bảng dưới đây rồi điền vào nó. Trong từng ô giao cắt giữa hai kiểu dữ liệu, bạn cần phải xác định xem liệu có hợp lệ nếu dùng toán tử + với những kiểu này không, phép toán nào được thực hiện (cộng hay kết nối), và kiểu kết quả sẽ là gì.
    boolean char int String
    boolean
    char
    int
    String
  3. Hãy tưởng tượng xem các nhà thiết kế nên ngôn ngữ Java đã lựa chọn thế nào khi họ điền vào bảng trên. Trong số các ô điền, có bao nhiêu ô dường như là lựa chọn chắc chắn? Có bao nhiêu ô dường như là lựa chọn tuỳ ý mà có vài phương án tốt như nhau? Có bao nhiêu ô có vẻ còn chứa đựng vấn đề?
  4. Sau đây là một câu đố: thông thường, câu lệnh x++ đúng bằng x = x + 1. Nhưng nếu x là một char, thì nó sẽ không còn đúng! Trong trường hợp này, x++ là hợp lệ, nhưng x = x + 1 sẽ gây ra lỗi. Hãy thử lại và xem thông báo lỗi là gì, và sau đó xem liệu bạn có thể hình dung được điều gì đang diễn ra không.
Bài tập 6  Kết quả của chương trình dưới đây là gì? Bằng một câu, hãy mô tả xem mystery làm gì (chứ không phải các bước thực hiện ra sao).

public class Mystery { 
  public static String mystery(String s) { 
    int i = s.length() - 1; 
    String total = ""; 
    while (i >= 0 ) { 
      char ch = s.charAt(i); 
      System.out.println(i + " " + ch); 
      total = total + ch; 
      i--; 
    } 
    return total; 
  } 
  public static void main(String[] args) { 
    System.out.println(mystery("Allen")); 
  } 
}
Bài tập 7   Một người bạn cho bạn xem phương thức sau đây và diễn giải rằng nếu number là số có hai chữ số bất kì, thì chương trình sẽ in các chữ số theo chiều ngược lại. Người ấy khẳng định rằng nếu number là 17, thì phương thức sẽ cho ra kết quả bằng 71. Liệu người đó có đúng không? Nếu không, hãy giải thích chương trình thực sự làm gì và sửa chữa để nó cho kết quả đúng.

    int number = 17; 
    int lastDigit = number%10; 
    int firstDigit = number/10; 
    System.out.println(lastDigit + firstDigit);
Bài tập 8   Kết quả của chương trình sau là gì?

public class Enigma { 
  public static void enigma(int x) { 
    if (x == 0) { 
      return; 
    } else { 
      enigma(x/2); 
    } 
    System.out.print(x%2); 
  } 
  public static void main(String[] args) { 
    enigma(5); 
    System.out.println(""); 
  } 
}

Hãy giải thích ngắn gọn bằng 4-5 từ xem phương thức enigma thực sự làm điều gì.

Bài tập 9  

  1. Hãy lập một chương trình mới có tên Palindrome.java.
  2. Viết một phương thức có tên first nhận vào một String rồi trả lại chữ cái đầu tiên, và một phương thức last để trả lại chữ cái cuối cùng.
  3. Viết một phương thức có tên  middle nhận vào một String rồi trả lại một chuỗi con có chứa mọi thứ trừ hai chữ cái đầu và cuối. Gợi ý: hãy đọc tài liệu về phương thức substring trong lớp String. Hãy chạy một vài phép thử để chắc rằng bạn hiểu rõ cách hoạt động của substring trước khi thử viết middle. Điều gì sẽ xảy ra nếu bạn kích hoạt middle lên một chuỗi chỉ có hai chứ cái? Một chữ cái? Không có chữ cái nào?
  4. Cách định nghĩa thông thường của một palindrome là một từ mà đọc xuôi ngược đều giống nhau, chẳng hạn “otto” và “palindromeemordnilap.” Một cách khác để định nghĩa một thuộc tính như thế baft là quy định một cách kiểm tra thuộc tính đó. Chẳng hạn, ta có thể nói “một chữ cái là một palindrome, và một từ hai chữ là một palindrome nếu hai chữ cái của nó giống nhau, và một từ bất kì khác là một palindrome nếu chữ cái đầu giống chữ cái cuối và khúc giữa cũng là một palindrome.” Hãy viết một phương thức đệ quy có tên isPalindrome nhận vào một String và trả lại một boolean cho biết từ đó có phải là palindrome hay không.
  5. Một khi bạn đã có đoạn mã để kiểm tra palindrome, hãy tìm cách đơn giản hoá nó bằng cách giảm số điều kiện trong phép kiểm tra. Gợi ý: việc lấy định nghĩa chuỗi rỗng cũng là palindrome có thể giúp ích.
  6. Hãy viết ra trên giấy một chiến lược có tính lặp để kiểm tra palindrome. Có một số phương án khả dĩ, bởi vậy bạn hãy đảm bảo chắc chắn một kế hoạch rõ ràng trước khi bắt đầu viết mã lệnh.
  7. Hãy tạo lập chiến lược bạn chọn thành một phương thức có tên isPalindromeIter.
  8. Câu hỏi phụ: Phụ lục B có mã lệnh để đọc một danh sách các từ vựng từ một file. Hãy đọc một danh sách các từ rồi in ra những palindrome.
Bài tập 10  Một từ được gọi là “abecedarian” nếu các chữ cái trong từ đó xuất hiện theo thứ tự bảng chữ cái. Chẳng hạn, sau đây là tất cả những từ abecedarian gồm 6 chữ cái trong tiếng Anh.

abdest, acknow, acorsy, adempt, adipsy, agnosy, befist, behint, beknow, bijoux, biopsy, cestuy, chintz, deflux, dehors, dehort, deinos, diluvy, dimpsy

  1. Hãy miêu tả một quy trình kiểm tra xem một từ (String) cho trước là abecedarian hay không, nếu coi rằng từ đó chỉ gồm các chữ cái thường. Quy trình này có thể mang tính lặp hay đệ quy.
  2. Tạo dựng quy trình trên thành một phương thức mang tên isAbecedarian.
Bài tập 11   Một dupledrome là một từ chỉ chứa các chữ cái ghép đôi, chẳng hạn như “llaammaa” hay “ssaabb”. Tôi đề ra giả thiết ràng trong tiếng Anh thông dụng không hề có dupledrome nào. Để kiểm chứng giả thiết đó, tôi muốn có chương trình đọc lần lượt các từ vựng từ một cuốn từ điển rồi kiểm tra xem từ đó có phải là dupledrome hay không. Hãy viết một phương thức mang tên isDupledrome nhận vào một String rồi trả lại một boolean để cho biết từ đó có phải là dupledrome không.
Bài tập 12

  1. Vòng giải mã Captain Crunch hoạt động bằng cách lấy mỗi chữ cái trong một chuỗi rồi cộng 13 vào nó. Chẳng hạn, ’a’ trở thành ’n’ và ’b’ trở thành ’o’. Đến cuối, các chữ cái “quay vòng lại”, bởi vậy ’z’ trở thành ’m’. Hãy viết một phương thức nhận vào một String rồi trả lại một String mới có chứa chuỗi sau mã hoá. Bạn cần coi ràng String ban đầu chỉ chứa các chữ in, chữ thường, dấu cách, mà không có dấu chấm phẩy gì khác. Các chữ thường thì được mã hoá thành chữ thường, chữ in thành chữ in. Bạn không được mã hoá  các dấu cách.
  2. Hãy khái quát hoá phương thức Captain Crunch sao cho thay vì cộng 13 vào từng chữ cái, nó có thể cộng thêm bất kì số nào. Bây giờ bạn có thể mã hoá bằn cách cộng 13 rồi giải mã bằng cách cộng -13. Hãy thử làm điều này.
Bài tập 13    Nếu bạn đã giải các bài tập GridWorld trong Chương 5, có thể bạn sẽ thích bài tập này. Mục đích là dùng toán lượng giác để khiến các con bọ (Bug) đuổi bắt lẫn nhau. Hãy sao chép file BugRunner.java thành ChaseRunner.java rồi nhập nó vào môi trường phát triển của bạn. Trước khi thay đổi bất cứ điều gì, hãy kiểm tra đảm bảo rằng bạn biên dịch và chạy được chương trình.

  • Tạo nên hai Bug, một con màu đỏ và một màu xanh lam.
  • Viết một phương thức mang tên distance nhận vào hai Bug rồi tính khoảng cách giữa chúng. Hãy nhớ rằng bạn có thể lấy được toạ độ x của một Bug như sau:
    int x = bug.getLocation().getCol();
  • Viết một phương thức mang tên turnToward nhận vào hai Bug rồi quay mặt một con hướng đến con kia. GỢI Ý: dùng Math.atan2, nhưng hãy nhớ rằng kết quả theo đơn vị radian, bởi vậy bạn phải chuyển sang độ. Ngoài ra, đối với Bug, 0 độ là hướng Bắc chứ không phải hướng Đông.
  • Viết một phương thức mang tên moveToward nhận vào hai Bug, quay mặt con thứ nhất về phía con thứ hai, rồi di chuyển con thứ nhất, nếu có thể.
  • Viết một phương thức mang tên moveBugs nhận hai Bug và một số nguyên n, rồi di chuyển một con Bug về phía con kia n lần. Bạn có thể viết phương thức này theo cách đệ quy, hoặc dùng một vòng lặp while.
  • Kiểm tra từng phương thức vừa viết ở trên ngay khi bạn phát triển chúng. Khi chúng đều hoạt động được, hãy tìm mọi cơ hội cải thiện. Chẳng hạn, nếu bạn có mã lệnh dư thừa trong distance và turnToward, thì bạn có thể bao bọc đoạn mã lệnh lặp lại vào trong một phương thức.

3 phản hồi

Filed under Think Java

3 responses to “Chương 8: Chuỗi kí tự

  1. Pingback: Think Java: Cách suy nghĩ như nhà khoa học máy tính | Blog của Chiến

  2. không hiểu ý 4 của bài tập 9 ns gì đọc như kiểu đọc tiếng ả rập

Gửi phản hồi

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s