Phạm vi đảo ngược trong Swift


105

Có cách nào để làm việc với phạm vi đảo ngược trong Swift không?

Ví dụ:

for i in 5...1 {
  // do something
}

là một vòng lặp vô hạn.

Trong các phiên bản Swift mới hơn, mã này được biên dịch, nhưng trong thời gian chạy gây ra lỗi:

Lỗi nghiêm trọng: Không thể tạo Phạm vi với upperBound <underBound

Tôi biết tôi có thể sử dụng 1..5thay thế, tính toán j = 6 - ivà sử dụng jlàm chỉ mục của mình. Tôi chỉ tự hỏi liệu có điều gì dễ đọc hơn không?


Xem câu trả lời của tôi cho một câu hỏi tương tự cho phép lên đến 8 cách để giải quyết vấn đề của bạn với Swift 3.
Imanou Petit

Câu trả lời:


184

Cập nhật cho Swift 3 mới nhất (vẫn hoạt động trong Swift 4)

Bạn có thể sử dụng reversed()phương pháp trên một phạm vi

for i in (1...5).reversed() { print(i) } // 5 4 3 2 1

Hoặc stride(from:through:by:)phương pháp

for i in stride(from:5,through:1,by:-1) { print(i) } // 5 4 3 2 1

stide(from:to:by:) tương tự nhưng loại trừ giá trị cuối cùng

for i in stride(from:5,to:0,by:-1) { print(i) } // 5 4 3 2 1

Cập nhật cho Swift 2 mới nhất

Trước hết, phần mở rộng giao thức thay đổi cách reversesử dụng:

for i in (1...5).reverse() { print(i) } // 5 4 3 2 1

Stride đã được làm lại trong Xcode 7 Beta 6. Cách sử dụng mới là:

for i in 0.stride(to: -8, by: -2) { print(i) } // 0 -2 -4 -6
for i in 0.stride(through: -8, by: -2) { print(i) } // 0 -2 -4 -6 -8

Nó cũng hoạt động cho Doubles:

for i in 0.5.stride(to:-0.1, by: -0.1) { print(i) }

Hãy cảnh giác với các so sánh dấu phẩy động ở đây cho các giới hạn.

Bản chỉnh sửa trước đó cho Swift 1.2 : Kể từ Xcode 6 Beta 4, củaReverseRange không còn tồn tại nữa: [

Nếu bạn chỉ muốn đảo ngược một phạm vi, thì chức năng đảo ngược là tất cả những gì bạn cần:

for i in reverse(1...5) { println(i) } // prints 5,4,3,2,1

Như được đăng bởi 0x7fffffff, có một cấu trúc bước tiến mới có thể được sử dụng để lặp lại và tăng dần theo số nguyên tùy ý. Apple cũng tuyên bố rằng hỗ trợ dấu chấm động sắp ra mắt.

Nguồn từ câu trả lời của anh ấy:

for x in stride(from: 0, through: -8, by: -2) {
    println(x) // 0, -2, -4, -6, -8
}

for x in stride(from: 6, to: -2, by: -4) {
    println(x) // 6, 2
}

FYI Stride đã thay đổi kể từ phiên bản beta 6
DogCoffee

1
nó phải là (1...5).reverse()(như một cuộc gọi hàm), vui lòng cập nhật câu trả lời của bạn. (Vì chỉ có hai ký tự nên tôi không thể chỉnh sửa bài đăng của bạn.)
Behdad 26/09/15

Trong Swift 3.0-PREVIEW-4 (và có thể sớm hơn), nó phải như vậy (1...5).reversed(). Ngoài ra, ví dụ cho .stride()không hoạt động và stride(from:to:by:)thay vào đó cần chức năng miễn phí .
davidA

2
có vẻ như stridemã cho swift 3 hơi không chính xác. để bao gồm số 1, bạn sẽ cần phải sử dụng throughtrái ngược với tonhư thế này:for i in stride(from:5,through:1,by:-1) { print(i) }
262Hz

4
Cần chỉ ra rằng reversedcó độ phức tạp là O (1) bởi vì nó chỉ gói gọn tập hợp ban đầu và không yêu cầu lặp lại để xây dựng nó.
defos1

21

Có điều gì đó đáng lo ngại về sự không đối xứng của điều này:

for i in (1..<5).reverse()

... trái ngược với điều này:

for i in 1..<5 {

Có nghĩa là mỗi khi muốn làm một khoảng lùi, tôi phải nhớ đặt dấu ngoặc đơn, thêm nữa là phải viết .reverse()ở cuối, lòi ra như ngón tay cái bị đau. Điều này thực sự xấu so với C-style for vòng lặp, chúng đếm lên và đếm ngược đối xứng. Vì vậy, tôi có xu hướng sử dụng C-style cho các vòng lặp thay thế. Nhưng trong Swift 2.2, C-style for loop sẽ biến mất! Vì vậy, tôi đã phải loay hoay thay thế tất cả các vòng lặp kiểu C đang giảm dần của tôi bằng .reverse()cấu trúc xấu xí này - luôn tự hỏi, tại sao trên trái đất không có toán tử phạm vi ngược?

Nhưng đợi đã! Đây là Swift - chúng tôi được phép xác định các toán tử của riêng mình !! Chúng ta bắt đầu:

infix operator >>> {
    associativity none
    precedence 135
}

func >>> <Pos : ForwardIndexType where Pos : Comparable>(end:Pos, start:Pos)
    -> ReverseRandomAccessCollection<(Range<Pos>)> {
        return (start..<end).reverse()
}

Vì vậy, bây giờ tôi được phép nói:

for i in 5>>>1 {print(i)} // 4, 3, 2, 1

Điều này chỉ bao gồm trường hợp phổ biến nhất xảy ra trong mã của tôi, nhưng nó trường hợp phổ biến nhất và xa vời nhất, vì vậy đó là tất cả những gì tôi cần hiện tại.

Tôi đã có một loại khủng hoảng nội bộ đến với nhà điều hành. Tôi muốn sử dụng >.., như là ngược lại ..<, nhưng điều đó không hợp pháp: bạn không thể sử dụng dấu chấm sau dấu chấm không, nó xuất hiện. Tôi đã cân nhắc ..>nhưng quyết định rằng nó quá khó để phân biệt ..<. Điều thú vị >>>là nó hét vào mặt bạn: " xuống !" (Tất nhiên bạn có thể tự do tìm ra một toán tử khác. Nhưng lời khuyên của tôi là: đối với siêu đối xứng, hãy xác định <<<để làm những gì ..<, và bây giờ bạn đã có <<<>>>cái nào là đối xứng và dễ nhập.)


Phiên bản Swift 3 (Xcode 8 hạt giống 6):

infix operator >>> : RangeFormationPrecedence
func >>><Bound>(maximum: Bound, minimum: Bound) ->
    ReversedRandomAccessCollection<CountableRange<Bound>> 
    where Bound : Comparable, Bound.Stride : Integer {
        return (minimum..<maximum).reversed()
}

Phiên bản Swift 4 (Xcode 9 beta 3):

infix operator >>> : RangeFormationPrecedence
func >>><Bound>(maximum: Bound, minimum: Bound)
    -> ReversedRandomAccessCollection<CountableRange<Bound>>
    where Bound : Comparable & Strideable { 
        return (minimum..<maximum).reversed()
}

Phiên bản Swift 4.2 (Xcode 10 beta 1):

infix operator >>> : RangeFormationPrecedence
func >>><Bound>(maximum: Bound, minimum: Bound)
    -> ReversedRandomAccessCollection<Range<Bound>>
    where Bound : Strideable { 
        return (minimum..<maximum).reversed()
}

Phiên bản Swift 5 (Xcode 10.2.1):

infix operator >>> : RangeFormationPrecedence
func >>><Bound>(maximum: Bound, minimum: Bound)
    -> ReversedCollection<Range<Bound>>
    where Bound : Strideable {
        return (minimum..<maximum).reversed()
}

1
Wow, >>>nhà điều hành thực sự tuyệt vời! Tôi hy vọng các nhà thiết kế Swift chú ý đến những thứ như vậy, bởi vì việc dán reverse()vào cuối các biểu thức trong ngoặc đơn sẽ cảm thấy kỳ lạ. Không có lý do gì họ không thể xử lý các 5>..0phạm vi một cách tự động, bởi vì ForwardIndexTypegiao thức cung cấp một distanceTo()toán tử hoàn toàn hợp lý có thể được sử dụng để tìm ra xem bước tiến nên tích cực hay tiêu cực.
dasblinkenlight

1
Cảm ơn @dasblinkenlight - Điều này xuất hiện cho tôi vì trong một trong các ứng dụng của tôi, tôi có rất nhiều vòng lặp C for (trong Objective-C) đã chuyển sang Swift C-style cho vòng lặp và bây giờ phải được chuyển đổi vì C-style cho vòng lặp đang biến mất. Điều này chỉ đơn giản là đáng sợ, bởi vì những vòng lặp này đại diện cho cốt lõi logic của ứng dụng của tôi và việc viết lại mọi thứ có nguy cơ bị hỏng. Vì vậy, tôi muốn một ký hiệu sẽ làm cho sự tương ứng với ký hiệu kiểu C (được chú thích) rõ ràng nhất có thể.
matt

khéo léo! Tôi thích nỗi ám ảnh của bạn với mã trông sạch sẽ!
Yohst

10

Có vẻ như câu trả lời cho câu hỏi này đã thay đổi một chút khi chúng tôi tiến hành thử nghiệm. Kể từ phiên bản beta 4, cả by()hàm và ReversedRangekiểu đều đã bị xóa khỏi ngôn ngữ. Nếu bạn đang muốn tạo một phạm vi đảo ngược, các tùy chọn của bạn bây giờ như sau:

1: Tạo một phạm vi chuyển tiếp, và sau đó sử dụng reverse()chức năng để đảo ngược phạm vi đó.

for x in reverse(0 ... 4) {
    println(x) // 4, 3, 2, 1, 0
}

for x in reverse(0 ..< 4) {
    println(x) // 3, 2, 1, 0
}

2: Sử dụng các stride()hàm mới đã được thêm vào trong bản beta 4, bao gồm các hàm để chỉ định chỉ mục bắt đầu và kết thúc, cũng như số lượng để lặp lại.

for x in stride(from: 0, through: -8, by: -2) {
    println(x) // 0, -2, -4, -6, -8
}

for x in stride(from: 6, to: -2, by: -4) {
    println(x) // 6, 2
}

Lưu ý rằng tôi cũng đã bao gồm toán tử phạm vi độc quyền mới trong bài đăng này. ..đã được thay thế bằng ..<.

Chỉnh sửa: Từ ghi chú phát hành Xcode 6 beta 5 , Apple đã thêm đề xuất sau để xử lý vấn đề này:

ReverseRange đã bị xóa; sử dụng lazy (x ..

Đây là một ví dụ.

for i in lazy(0...5).reverse() {
    // 0, 1, 2, 3, 4, 5
}

+1 Tôi đang tìm tài liệu tham khảo lazy(0....5).reverse()và tôi không thấy ghi chú phát hành beta 5. Bạn có biết về các cuộc thảo luận khác về chủ đề này? Ngoài ra, FYI, các số bên trong forvòng lặp đó ngược lại trong ví dụ của bạn.
Rob

1
@Rob Hmm, lẽ ra tôi phải đoán rằng các ghi chú phát hành cho bản beta cuối cùng sẽ bị gỡ xuống. Khi tôi viết điều này, đó là nơi duy nhất tôi thấy tham chiếu đến lazy (), nhưng tôi sẽ sẵn lòng xem xét thêm một số và cập nhật / sửa lỗi này khi tôi về nhà sau. Cảm ơn vì đã chỉ ra điều này.
Mick MacCallum

1
@ 0x7fffffff Trong ví dụ cuối cùng của bạn, nhận xét có nội dung // 0, 1, 2, 3, 4, 5nhưng thực sự nên đảo ngược lại. Nhận xét là sai lệch.
Pang

5

Xcode 7, beta 2:

for i in (1...5).reverse() {
  // do something
}

3

Swift 3, 4+ : bạn có thể làm như thế này:

for i in sequence(first: 10, next: {$0 - 1}) {

    guard i >= 0 else {
        break
    }
    print(i)
}

kết quả : 10, 9, 8 ... 0

Bạn có thể tùy chỉnh nó theo bất kỳ cách nào bạn muốn. Để biết thêm thông tin đọc func sequence<T>tham khảo


1

Đây có thể là một cách khác để làm điều này.

(1...5).reversed().forEach { print($0) }

-2

Hàm Reverse () được sử dụng cho số đảo ngược.

Var n: Int // Nhập số

Đối với tôi trong 1 ... n.reverse () {Print (i)}

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.