[Scala] - Strings

Giới thiệu

Thoạt nhìn thoáng qua, Scala string cũng giống Java string. Ví dụ, khi bạn thao tác trên môi trường Scala Read-Evaluate-Print-Loop (REPL) và in ra tên (get class name) của một chuỗi chữ thì REPL sẽ in ra cho bạn kiểu java.lang.String

tienduongvan@Tien-PC:~$ scala
Welcome to Scala version 2.11.7 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_111).
Type in expressions to have them evaluated.
Type :help for more information.

scala> val s = "Hello everyone"
s: String = Hello everyone

scala> s.getClass.getName
res1: String = java.lang.String   // cách viết tương đương  :   "Hello everyone".getClass.getName

Scala Read Evaluate Print Loop là môi trường tương tác giúp bạn có thể viết code, kiểm tra các câu lệnh scala Về bản chất gốc, Scala string là Java string, bạn có thể sử dụng các phương thức của Java string bình thường. Bạn có thể khai báo một biến kiểu string theo cách của scala :

scala> val s =  " this is a string"
// và lấy độ dài chuỗi :
scala> s.length
res0: Int = 16
// hoặc cộng chuỗi :
scala> val str = "Hi all," + "I'm scala"
str: String = Hi all,I'm scala

Hầu hết các toán tử xử lý chuỗi đều khá quen thuộc và giống với java. Nhưng vì scala cung cấp cách thức chuyển đổi ngầm (implicit conversions), string trong scala có thể truy cập tất cả các phương thức của class StringOps, như vậy bạn sẽ có thêm nhiều sự lựa chọn cho phương thức xử lý chuỗi hơn. Như kết quả dưới đây, với phương thức foreach bạn có thể lặp qua từng kí tự của chuỗi.

scala> "Chao 2017".foreach(println)
C
h
a
o

2
0
1
7
// hoặc sử dụng cách lặp thuần với vòng for quen thuộc
scala> for (c <- "Chao 2017") println(c)
C
h
a
o

2
0
1
7
// hoặc lấy bytes để xử lý khi cần thiết
scala> "hello".getBytes.foreach(println)
104
101
108
108
111

Một phương thức khác khá hay cũng thường được sử dụng đó là filter

scala> val stringResult = "smile herherherher".filter(_!='r')
stringResult: String = smile hehehehe

Bởi vì còn rất nhiều phương thức, hàm có sẵn khác trong thư viện của class StringOps mà mình không thể giới thiệu hết, các bạn có thể tìm hiểu thêm tùy vào mục đích sử dụng. Ví dụ đơn giản về cách ghép nối phương thức với nhau:

scala>  "Thomasedison".drop(6).take(3).capitalize
res4: String = Edi
// drop(6) : xóa bỏ 6 ký tự đầu
// take(3) : lấy 3 ký tự trong chuỗi còn lại (edison) -> edi
// capitalize : viết hoa chữ cái đầu trong chuỗi vừa lấy ra

1.1. So sánh "==" với string

Problem Bạn muốn so sánh 2 chuỗi để xem chúng có giống nhau, chúng có cùng chứa một chuỗi ký tự hay không. Solution Bạn có thể dùng toán tử == (hay cũng có thể gọi là phương thức ==)

scala> val s1  = "con Tuat"
s1: String = con Tuat
scala> val s2 = "con Tuat"
s2: String = con Tuat
scala> val s3 = "con" + " Tuat"
s3: String = con Tuat

scala> s1 == s2
res1: Boolean = true

scala> s1 == s3
res2: Boolean = true

Trong scala phương thức == không bắn ra ngoại lệ NullPointerException nếu như so sánh với một string là null, hoặc 2 string null với nhau

scala> val s4: String = null
s4: String = null

scala> s1 == s4
res3: Boolean = false

nhưng hãy cẩn thận khi sử dụng trong trường hợp nếu một string là null mà bạn cố gắng chuyển đổi, điều này sẽ bắn ra NullPointerException

scala> val s5: String = null
s5: String = null

scala> s5.toUpperCase
java.lang.NullPointerException
  ... 33 elided

Toán tử == so sánh có phân biệt chữ hoa chữ thường

scala> val x1 = "Abc"
x1: String = Abc

scala> val x2 = "abc"
x2: String = abc

scala> x1 == x2
res4: Boolean = false

Khi so sánh không cần phân biệt chữ hoa chữ thường có thể sử dụng phương thức equalsIgnoreCase của Java String class

scala> "HelloScala".equalsIgnoreCase("HelloScala")
res8: Boolean = true

Discussion Trong scala, để so sánh sự bằng nhau của đối tượng dùng phương thức == . Điều này là khác với Java, trong Java sử dụng phương thức equals để so sánh hai object. Phương thức == được định nghĩa trong class AnyRef. Nó hoạt động gồm 2 bước, đầu tiên là kiểm tra null, sau đó gọi phương thức equals trên đối tượng đầu tiên để kiểm tra đối tượng thứ hai. Vì vậy bạn không cần kiểm tra null khi so sánh 2 string với nhau.

1.2. Tạo chuỗi theo format trên nhiều dòng

Problem Bạn muốn tạo chuỗi string trên nhiều dòng trong source code. Solution Bạn có thể làm được điều đó bằng cách bao quanh string của bạn bởi 3 dấu ngoặc kép ("""string""")

// trên REPL
scala> val str = """Đây nè,
     | bạn có thấy
     | nhiều dòng không? """
str: String =
"Đây nè,
bạn có thấy
nhiều dòng không? "

// trong source code
val str =  """String này
được viết
trên nhiều dòng code"""

Discussion Mặc dù cách khai báo như trên trong source code làm việc, nhưng ở dòng thứ 2 và dòng thứ 3 sẽ bị chèn thêm khoảng trắng. Điều đó dẫn đến kết quả in ra có dạng:

String này
      được viết
      trên nhiều dòng code

Để giải xử lý vấn đề này ta dùng phương thức stripMargin và truyền vào một ký tự bất kỳ để xử lý:

val str =  """String này
# được viết
# trên nhiều dòng code""".stripMargin('#')

// kết quả in ra sẽ là
String này
được viết
trên nhiều dòng code

1.3. Split strings (tách chuỗi)

Problem Bạn muốn tách tư, tách chuỗi theo mẫu truyền vào, mẫu đó có thể là khoảng trắng để tách từ, là dấu chấm để tách câu, hoặc dấu phảy,... Solution Sử dụng phương thức split, phương thức này có sẵn trong String object

scala> val mySchool  = "Post and Telecommunications Institute of Technology".split(" ")
mySchool: Array[String] = Array(Post, and, Telecommunications, Institute, of, Technology)

scala> val myFullname = "Duong Van Tien".split(" ").foreach(println(_))
Duong
Van
Tien

Discussion Trong một số trường hợp khi tách đoạn, tách từ mặc dù ta đưa đúng mẫu (tham số) đầu vào để xử lý nhưng kết quả đầu ra có thể vẫn chưa đúng như ý. Khi đó bạn cần dùng thêm các phương thức xử lý khác. Dưới đây chỉ là một ví dụ nhỏ, tùy vào tình huống gặp phải mà sẽ đưa ra những cách xử lý khác nhau:

scala> val employees = "Huy, Dung, An, Tien, Long"
employees: String = Huy, Dung, An, Tien, Long

scala> employees.split(",")
res9: Array[String] = Array(Huy, " Dung", " An", " Tien", " Long")   //  các tên Dung, An, Tien, Long bị chứa khoảng trắng ở phía trước mỗi tên

// xử lý, dùng thêm phương thức trim() để xóa khoảng trắng ở hai đầu
scala> employees.split(",").map(_.trim())
res13: Array[String] = Array(Huy, Dung, An, Tien, Long)

1.4. Nhúng biến vào chuỗi

Problem Bạn muốn nhúng biến số, các hằng số vào trong chuỗi Solution Đặt chuỗi của bạn sau chữ s , bên trong chuỗi của bạn đối với mỗi biến cần được đặt trong ký tự *$$ và cặp dấu {} . Ví dụ


scala> val x =  123
x: Int = 123

scala> println(s"Gia tri cua bien x la : ${x}")
Gia tri cua bien x la : 123

một thể hiện bất kỳ cũng có thể nhúng trong $$}

scala> println(s"kiem tra xem 1 co bang 2 hay khong : ${1==2}")
kiem tra xem 1 co bang 2 hay khong : false

nếu biến kiểu float thì cần thêm định dạng f để hiển thị kiểu số float, có thể kèm thêm %.xf - với x là số chữ số muốn làm tròn phần thập phân

scala> val y = 123.2356
y: Double = 123.2356

scala> println(f"$y%.2f")
123.24

trong một số trường hợp bạn muốn bỏ qua kí tự đặc biệt, ví dụ như '\n' để xuống dòng, dùng raw

scala> println(raw"abc\nbca")
abc\nbca

scala> println("abc\nbca")
abc
bca

Discusison Từ phiên bản scala 2.10 trở về trước chưa hỗ trợ chuỗi nội suy (string interpolation), vì vậy các cách dùng ở trên không sử dụng được. Để giải quyết vấn đề này cho các phiên bản trước 2.10 bạn cần dùng phương thức format trên string.

scala> val age = 33
age: Int = 33

scala> val name = "Huy"
name: String = Huy

scala> println(s"${name} - ${age}")
Huy - 33

scala> println("%s - %d".format(name,age))
Huy - 33

Bảng các ký hiệu áp dụng với phương thức format trên string

Format specifier Column 2
%c Character
%d Decimal number (integer, base 10)
%e Exponential floating-point number
%f Floating-point number
%i Integer (base 10)
%o Octal number (base 8)
%s A string of characters
%u Unsigned decimal (integer) number
%x Hexadecimal number (base 16)
%% Print a “percent” character
% Print a “percent” character

1.5. Truy xuất, xử lý kí tự trong chuỗi

Problem Bạn muốn lặp qua mỗi kí tự trong chuỗi, và muốn xử lý một cái gì đó trên từng kí tự vừa lặp qua. Solution Phụ thuộc vào bạn cần bạn muốn gì mà bạn có thể sử dụng phương thức map hoặc foreach , cũng có thể là vòng lặp for hoặc một cách tiếp cận khác. Dưới đây là một ví dụ đơn giản, bạn có một đầu vào là một chuỗi, bạn muốn lặp qua từng kí tự trong chuỗi và đưa nó thành chữ in hoa :

scala> val s1 = "chao nam moi dinh dau - 2017"
s1: String = chao nam moi dinh dau - 2017

scala> val str1 = "chao nam moi Dinh Dau - 2017".map(_.toUpper)
str1: String = CHAO NAM MOI DINH DAU - 2017

scala> val str2 = for(c <- s1) yield c.toUpper
str2: String = CHAO NAM MOI DINH DAU - 2017

Discussion Nếu như cũng với yêu cầu như trên, nhưng theo cách tiếp cận của Java, bạn sẽ cần làm

    String str = "chao nam moi Dinh Dau - 2017";
    StringBuilder sb = new StringBuilder();
    for(int i = 0; i<str.length(); ++i) {
        char c = s.charAt(i);
        // xử lý  ...
        // sb.append ...
    }
    String result = sb.toString();

Rõ ràng là scala có nền tảng dựa trên hướng đối tượng và hướng lập trình hàm đã có cách tiếp cận ngắn gọn hơn nhưng vẫn dễ đọc hiểu.

1.6. Tìm kiếm mẫu trong chuỗi (Finding paterns in strings)

Problem Bạn muốn xác định xem trong một String có chứa một quy tắc theo mẫu nào không. Solution Tạo ra một đối tượng Regex với phương thức .r được hỗ trợ trong class String. Và sau đó sử dụng findFirstIn để tìm kiếm kết quả đầu tiên trả về hoặc findAllIn để xem tất cả kết quả tìm thấy. Ví dụ sau sẽ tìm kiếm các số trong chuỗi:

scala> val str = "nha minh co 79 con ga trong, 123 con ga mai, va 256 con ga con"
str: String = nha minh co 79 con ga trong, 123 con ga mai, va 256 con ga con

scala> val matches = "[0-9]+".r
matches: scala.util.matching.Regex = [0-9]+

scala> val result = matches.findAllIn(str)
result: scala.util.matching.Regex.MatchIterator = non-empty iterator

scala> val result2 = matches.findAllIn(str).foreach(println(_))
79
123
256

Discussion Sử dụng .r là cách đơn giản để tạo ra đối tượng Regex. Hoặc có cách khác đó là import class Regex, sau đó tạo đối tượng regex và sử dụng chúng như một thể hiện của class đó.

scala> val numberRegex = new Regex("[0-9]+")
numberRegex: scala.util.matching.Regex = [0-9]+

scala>  val str = "nha minh co 79 con ga trong, 123 con ga mai, va 265 con ga con"
str: String = nha minh co 79 con ga trong, 123 con ga mai, va 265 con ga con

scala> val result = numberRegex.findAllIn(str).foreach(println(_))
79
123
265

1.7. Tìm thay thế mẫu trong chuỗi (Replacing paterns in strings)

Tương tự như phần trên, bạn cùng xem ví dụ dưới đây để hiểu nó làm gì :

scala> val str = "i'm tien"
str: String = i'm tien

scala> val regex = "t".r
regex: scala.util.matching.Regex = t

scala> val result = regex.replaceFirstIn(str,"T")
result: String = i'm Tien         // đổi chữ "t" thành "T"