[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 ...
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ư ' ' để xuống dòng, dùng raw
scala> println(raw"abc bca") abc bca scala> println("abc bca") 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"