Saturday, 22 April 2017

String trong Swift - phần 1



Biểu diễn các kí tự là vấn đề được đề cập đến đầu tiên khi các bạn tiếp cận một ngôn ngữ. Việc giao tiếp giữa máy chủ và client - cụ thể tại đây là thiết bị điện thoại đều được chuyển hóa qua dạng kí tự, ví dụ như response nhận lấy từ request thường thấy có dạng json.

Trong bài viết này, mình xin tổng hợp một vài cách thức cơ bản liên quan đến việc biểu diễn kí tự trên ngôn ngữ Swift và bài toán nhỏ để thực hành mà các bạn gặp phải trong lúc làm việc trong thực tế.

Và trong các bài viết sau, mình sẽ đề cập đến những kĩ thuật phức tạp như formating, comparing, sorting... 

Tất cả các ví dụ trong bài, mình sẽ sử dụng version mới nhất của Swift là 3.0.1

Để biểu diễn chuỗi kí tự trong Swift, ta sử dụng struct có tên là String để biểu diễn.

I - Initializing a String:

Có rất nhiều cách để khởi tạo một chuỗi

// Từ một chuỗi rỗng hoặc một chuỗi khác
var emptyString = ""
var stillEmpty = String()
let helloWorld = "Hello World!"

// Có thể tạo ra bằng cách lặp lại một chuỗi khác
let h = String(repeating:"01", count:3) //Output: 010101

// từ true/false - boolean
let a = String(true)

// Từ một kí tự
let b: Character = "A"          // Explicit type to create a Character
let c = String(b)               // from character "A"

// Từ giá trị số
let d = String(3.14)            // from Double "3.14"
let e = String(1000)            // from Int "1000"
let f = "Result = \(d)"         // Interpolation "Result = 3.14"
let g = "\u{2126}"              // Unicode Ohm sign

// Lấy từ một file
if let path = Bundle.main.path(forResource: "otherFile", ofType: "txt") {
  do {
    let content = try String(contentsOfFile: path, encoding: .utf8)
  } catch {
    print("Something went wrong")
  }
}


II - Strings are Value Types:

Nghĩa là trong phép gán, giá trị của nó được sao chép đến giá trị của biến String khác.

var aString = "Hello"
var bString = aString
print("\(aString)")     // "Hello"
bString += " World!"    // "Hello World!"

Testing String (Empty, Equality and Order):

// Để kiểm tra chuỗi đó là rỗng
emptyString.isEmpty     // true

// Sự giống nhau, một vài tài liệu tham khảo nói đến Unicode canonical equivalence,
// nghĩa là swift sẽ dùng định dạng Unicode của chuỗi để so sánh
let spain = "España"
let tilde = "\u{303}"
let country = "Espan" + "\(tilde)" + "a"
if country == spain {
  print("Matched!")       // "Matched!"
}

// So sánh dựa trên thứ tự:
if "aaa" < "bbb" {
  print("aaa is less than bbb")  // "aaa is less than bbb"
}

Suffix/prefix, upper/lower case

// Kiểm tra chuỗi bắt đầu hoặc kết thúc bằng một chuỗi khác
let line = "0001 Some test data here %%%%"
line.hasPrefix("0001")    // true
line.hasSuffix("%%%%")    // true

// Dạng viết hoa và viết thường của một chuỗi
let mixedCase = "AbcDef"
let upper = mixedCase.uppercased() // "ABCDEF"
let lower = mixedCase.lowercased() // "abcdef"

III - Views, Counting

Về bản chất, trong String, một chuỗi không phải là một tập hợp kí tự. Ví dụ bên dưới, bạn sẽ nhìn một sự khác biệt 


let cafe = "Cafe\u{301} du 🌍"
print(cafe)
// Prints "Café du 🌍

Chúng không biểu diễn 1-1 như các ngôn ngữ khác. Thay vào đó, String cung cấp nhiều hình thái cho những định dạng khác nhau bằng các thuộc tính bên trong.

Dạng biểu diễn - Character View: 

> A string’s characters property is a collection of extended grapheme clusters, which approximate human-readable characters.

print(cafe.characters.count)
// Prints "9"
print(Array(cafe.characters))
// Prints "["C", "a", "f", "é", " ", "d", "u", " ", "🌍"]"

Ta có thể nhìn thấy biểu hiện của chúng bằng mắt thường. Một kí tự biểu diễn trên có thể được tạo nên bởi nhiều kí tự Unicode.


Biểu diễn Unicode - Unicode Scalar View

> A string’s unicodeScalars property is a collection of Unicode scalar values, the 21-bit codes that are the basic unit of Unicode. Each scalar value is represented by a UnicodeScalar instance and is equivalent to a UTF-32 code unit.
print(cafe.unicodeScalars.count)
// Prints "10"
print(Array(cafe.unicodeScalars))
// Prints "["C", "a", "f", "e", "\u{0301}", " ", "d", "u", " ", "\u{0001F30D}"]"
print(cafe.unicodeScalars.map { $0.value })
// Prints "[67, 97, 102, 101, 769, 32, 100, 117, 32, 127757]"

Nó là một tập hợp kí tự được biểu diễn với giá trị 32bit

Biểu diễn 16bit - UTF-16 View

> A string’s utf16 property is a collection of UTF-16 code units, the 16-bit encoding form of the string’s Unicode scalar values. Each code unit is stored as a UInt16 instance.
print(cafe.utf16.count)
// Prints "11"
print(Array(cafe.utf16))
// Prints "[67, 97, 102, 101, 769, 32, 100, 117, 32, 55356, 57101]"
Chú ý: NSString API cũng sử dụng định dạng khi truy cập các kí tự
let nscafe = cafe as NSString
print(nscafe.length)
// Prints "11"
print(nscafe.character(at: 3))
// Prints "101"

Biểu diễn 8bit - UTF-8 View

> A string’s utf8 property is a collection of UTF-8 code units, the 8-bit encoding form of the string’s Unicode scalar values. Each code unit is stored as a UInt8 instance.
print(cafe.utf8.count)
// Prints "14"
print(Array(cafe.utf8))
// Prints "[67, 97, 102, 101, 204, 129, 32, 100, 117, 32, 240, 159, 140, 141]"
Chú ý: C API sử dụng định dạng này để truy cập các phần tử bên trong chuỗi.
let cLength = strlen(cafe)
print(cLength)
// Prints "14"

IV - Bài toán thực tế:

> Kiểm tra password mà người dùng nhập vào có hợp lệ hay ko? Với password hợp lệ có ít nhất 6 kí tự và thuộc những kí tự hợp lệ cho trước. 

Với những ngôn ngữ khác như PHP, Java Script, chúng nó sẽ sử dụng Regular Expression.
Nhưng tại đây, mình sẽ sử dụng những kiến thức cơ bản về String bên trên giải quyết bài toán trên.

Ý tưởng: 

Mình sẽ sử dụng định dạng Unicode của String vì chúng có miền giá trị 32bit, sẽ xử lý bài toán một cách tổng quát cho bất kì chuỗi kí tự nào người dùng nhập từ bàn phím.

Kèm theo [bảng mã Unicode](http://www.codetable.net/unicodecharacters?page=1) để xác định miền giá trị cho những kí tự hợp lệ.

enum ValidatedStatus {
    case notEnoughForLength
    case invalidFormat
    case valid
}
func validate(password:String) -> ValidatedStatus {
    // I choose the range of Unicode value from 32(!) to 126(~) to be validated range
    let minimalValue:UInt32 = 32
    let maximalValue:UInt32 = 126
  
    let minimalLength = 6
  
    let unicodeView = password.unicodeScalars
  
    if unicodeView.count < minimalLength {
        return .notEnoughForLength
    }
  
    for char in unicodeView {
        if char.value < minimalValue || char.value > maximalValue {
            return .invalidFormat
        }
    }
  
    return .valid
}

V - Kết luận: 

  • Độ dài của chuỗi và định dạng của chuỗi sẽ liên quan chặt chễ đến nhau.
  • Với từng bài toán cụ thể, ta sẽ sử dụng định dạng của chuỗi cho phù hợp.


VI - Tham khảo:

  1. https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html
  2. https://developer.apple.com/reference/swift/string
  3. https://useyourloaf.com/blog/swift-string-cheat-sheet/



Mình sẽ luôn luôn cập nhập thêm kiến thức về String tại đây