Slicing Swift collections.
Bài đăng này đã không được cập nhật trong 5 năm
-
Một trong những mục tiêu của Swift là trở thành một ngôn ngữ lập trình có tính ứng dụng cao từ các
task high-level, xây dựng UI,scripting, đến lập trìnhlow-level system. Đó là một mục tiêu đầy tham vọng và chưa hoàn thành, nhưng có một số đặc điểm của Swift khiến nó dễ dàng mở rộng. -
Một yêu cầu là làm thế nào
standard librarylàm việc với cácbuilt-in collectionscủa nó hiệu quả nhất bằng cách giảm số lượng trường hợp cácelementcủa chúng được sao chép, di chuyển.
1/ A slice of a binary:
-
Trong Swift, một
slicelà một loạicollectionđặc biệt không thực sự lưu trữ bất kỳelementnào của riêng nó mà hoạt động như một proxy (hoặcview) để cho phép chúng ta truy cập và làm việc với một tập hợp con của một bộ sưu tập khác riêng biệt. -
VD, chúng ta có một mảng chứa 10 số và chúng ta muốn lấy ra 5 số đầu để làm việc. Điều đó được thực hiện bằng cách sử dụng
Range-basedsubscripting, như sau:
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let firstFive = numbers[..<5]
-
Mới nhìn thì
firstFivesẽ có cùng loại vớinumberslàArray<Int>. Trong thực tế, những gì chúng ta đã làm trên là tạo ra mộtsliceloạiArraySlice<Int> -
Thay vì sao chép 5 phần tử đầu vào 1
instanceArraymới, thay vào đó,standard librarychỉ cung cấp cho chúng ta 1 cái nhìn vàorangecácelementgiúp tăng hiệu suất đáng kể, đặc biệt là khi làm việc với cáccollectionlớn hơn. -
Bằng cách không thực hiện bất kỳ sao chép hoặc cấp phát bộ nhớ bổ sung nào cho
collection, mộtslicecó thể được khởi tạo trong thời gian không đổi (O(1). Điều đó giúp tạo ra mộtslicenhanh hơn + tạo ra cácslicenhư thể chúng ta thực hiện nó trêncollectionban đầu.
2/ Prefixes and suffixes:
- Hãy bắt đầu bằng cách xem cách chúng ta có thể sử dụng
slicingđể lấy ra cácprefixesvàsuffixestừcollection. Ví dụ, chúng ta làm việc trênTodoApp:
struct TodoList {
var name: String
var items = [Item]()
...
}
- Bây giờ, chúng ta đang xây dựng một
featurecho phépusercủa chúng ta xem nhanh ba mục đầu trong danh sách cụ thể - ví dụ như trong tiện ích mở rộng củaTodayApptrêniOShoặcmacOS. Chúng tacó thể sử dụng API đăng ký tương tự như chúng ta đã sử dụng khislicing arraysố trên của chúng ta:
extension TodoList {
var topItems: ArraySlice<Item> {
items[..<3]
}
}
-
Mặc dù ở trên có cú pháp rất gọn gàng nhưng nó lại là một
implementationkhá nguy hiểm. Vì chúng ta có thể biết bao nhiêuitemTodoListsẽ thực sự được sử dụng, app của chúng tacó thể bịcrashkhi truy cập vàopropertytrên vì cũng giống như khi lấy một phần tử từ một mảng, việc đăng ký theorangecũng gây ra sự cố khi được sử dụng với các phần từ ngoàirange. -
Trong khi chúng ta có thể kiểm tra giới hạn của riêng mình vào việc
implementation:
extension TodoList {
var topItems: ArraySlice<Item> {
items.prefix(3)
}
}
-
Bây giờ
APImới của chúng ta sẽ hoạt động như mong đợi, ngay cả khiTodoListchứa ít hơn 3itemvà việcimplementationcủa chúng ta vẫn có độ phức tạp O(1) nghĩa là chúng ta có thể thoải mái để nócomputed propertymà không có nguy cơ gây ra việc giảm hiệu năng. -
Tuy nhiên có một điều mà chúng ta phải ghi nhớ khi làm việc với các
slicelà chúng thực sự là một loại riêng biệt so với cáccollectionban đầu- nghĩa là chúng ta có thể chuyển qua một cá thểArraySlicecho bất kỳAPInào chấp nhậnArrayvà ngược lại. Nó cho chúng ta toàn quyền kiểm soát khi mộtsliceđược tách ra (và cácelementcủa nó được sao chép) khỏicollectionban đầu của nó. -
Ví dụ , chúng ta sử dụng một cách khác của
standard-ibraryAPIprefix(cùng vớisuffixcủa nó) để phân chia một package thành hai package riêng biệt dựa trên cácindex. Vì chúng ta không muốn modelShipmentcủa mình chứaArraySlicenên chúng ta phải chuyển đổi haislicecủa chúng ta thànhArray<Package>:
struct Shipment {
var destination: Address
var packages = [Package]()
...
}
extension Shipment {
func split() -> (first: Shipment, second: Shipment) {
guard packages.count > 1 else {
return (self, Shipment(destination: destination))
}
let splitIndex = packages.count / 2
return (
Shipment(
destination: destination,
packages: Array(packages.prefix(upTo: splitIndex))
),
Shipment(
destination: destination,
packages: Array(packages.suffix(from: splitIndex))
)
)
}
}
- Chúng ta đã tính toán các
prefixvàsuffixcủa chúng tadựa trên số lượng phần tử và index, nhưng chúng ta cũng có thể sử dụng logic tùy biến. Ví dụ: chúng ta gọiprefixbằng cách:
let qualifiedPlayers = topPlayers.prefix { $0.score > 100_000 }
3/ Dropping elements:
- Ví dụ, chúng ta muốn
removebất kỳ số nào xuất hiện ở đầustring, để chuẩn bị mộtstringđược sử dụng dưới dạng được định danh theo tiêu chuẩn. Chúng ta có thể yêu cầustringbỏ tất cả cácelementtrong khielementhiện tại là mộtnumber:
extension StringProtocol {
func trimmingLeadingNumbers() -> SubSequence {
drop(while: { $0.isNumber })
}
}
- Một trong những lợi ích chính của API
collectiondựa trênslicingtrong Swift là chúng có thể được khởi tạo mà không cần sao chép. Đó là lợi ích mà chúng ta có trong các tình huống như bên dưới:
func normalizeUsername(_ username: String) -> String {
username.trimmingLeadingNumbers().filter {
$0.isLetter || $0.isNumber
}
}
- Chúng ta hãy xem cách kết hợp khác
dopFirstvớiprefixđể dễ dàng thêm hỗ trợ phân trang cho bất kỳ BidirectionalCollection nào (bao gồm các loại như Array, Range, v.v.). Bằng cách gọi dropFirst đầu tiên để xóa tất cả các thành phần trước khi trang hiện tại bắt đầu và sau đó sử dụng tiền tố để trích xuất một lát có cùng kích thước với kích thước trang của chúng tôi, chúng tôi có thể triển khai tiện ích mở rộng phân trang như thế này:
extension BidirectionalCollection {
func page(withIndex pageIndex: Int, size: Int) -> SubSequence {
dropFirst(pageIndex * size).prefix(size)
}
}
- Quay trở lại loại TodoList của chúng tôi từ trước đó, sau đó chúng tôi có thể bọc API ở trên một mức độ trừu tượng cao hơn một chút, cho chúng tôi một phương pháp phân trang thực sự đẹp có thể được sử dụng để hiển thị bất kỳ danh sách các mục việc cần làm nào trong một trang cách thức:
extension TodoList {
func page(at index: Int) -> ArraySlice<Item> {
items.page(withIndex: index, size: 25)
}
}
- Lúc đầu, có vẻ hơi lạ khi trả lại
ArraySlicetừAPItrên, thay vì chuyển đổi kết quả thành mộtArraythích hợp. Tuy nhiên chúng ta đã tuân theo các quy tắc giống nhưstandard librarycho phép trang web quyết định cách thức và thời điểm chuyển đổi từngslicecho phép chúng ta thực hiện thêmstringmà không bị ảnh hưởng hiệu suất.
All rights reserved