ย้อนกลับช่วงใน Swift


105

มีวิธีทำงานกับช่วงย้อนกลับใน Swift หรือไม่?

ตัวอย่างเช่น:

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

เป็นวงวนที่ไม่มีที่สิ้นสุด

ใน Swift เวอร์ชันที่ใหม่กว่านั้นโค้ดจะคอมไพล์ แต่ที่รันไทม์ให้ข้อผิดพลาด:

ข้อผิดพลาดร้ายแรง: ไม่สามารถสร้างช่วงด้วย upperBound <lowerBound

ฉันรู้ว่าฉันสามารถใช้1..5แทนคำนวณj = 6 - iและใช้jเป็นดัชนีของฉันได้ ฉันแค่สงสัยว่ามีอะไรที่ชัดเจนกว่านี้อีกไหม?


ดูคำตอบของฉันสำหรับคำถามที่คล้ายกันซึ่งให้ถึง 8 วิธีในการแก้ปัญหาของคุณด้วย Swift 3
Imanou Petit

คำตอบ:


184

อัปเดตสำหรับ Swift 3 ล่าสุด (ยังใช้งานได้ใน Swift 4)

คุณสามารถใช้reversed()วิธีการในช่วง

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

หรือstride(from:through:by:)วิธีการ

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

stide(from:to:by:) คล้ายกัน แต่ไม่รวมค่าสุดท้าย

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

อัปเดตสำหรับ Swift 2 ล่าสุด

ก่อนอื่นส่วนขยายโปรโตคอลจะเปลี่ยนวิธีการreverseใช้งาน:

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

Stride ได้รับการปรับปรุงใหม่ใน Xcode 7 Beta 6 การใช้งานใหม่คือ:

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

นอกจากนี้ยังใช้ได้กับDoubles:

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

ระวังจุดลอยตัวเปรียบเทียบที่นี่สำหรับขอบเขต

การแก้ไขก่อนหน้านี้สำหรับ Swift 1.2 : ตั้งแต่ Xcode 6 Beta 4 โดยและReverseRangeไม่มีอยู่อีกต่อไป: [

หากคุณต้องการย้อนกลับช่วงฟังก์ชันย้อนกลับคือสิ่งที่คุณต้องการ:

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

ตามที่โพสต์โดย0x7fffffffมีโครงสร้างก้าวใหม่ที่สามารถใช้เพื่อทำซ้ำและเพิ่มขึ้นโดยพลการจำนวนเต็ม Apple ยังระบุด้วยว่าการสนับสนุนจุดลอยตัวกำลังจะมาถึง

มาจากคำตอบของเขา:

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 มีการเปลี่ยนแปลงตั้งแต่เบต้า 6
DogCoffee

1
ควรเป็น(1...5).reverse()(เป็นการเรียกใช้ฟังก์ชัน) โปรดอัปเดตคำตอบของคุณ (เนื่องจากเป็นเพียงสองตัวอักษรฉันจึงไม่สามารถแก้ไขโพสต์ของคุณได้)
Behdad

ในสวิฟท์ 3.0 PREVIEW-4 (และอาจจะก่อนหน้านี้) (1...5).reversed()มันควรจะเป็น นอกจากนี้ตัวอย่างสำหรับ.stride()ใช้งานไม่ได้และต้องการฟังก์ชันฟรีstride(from:to:by:)แทน
davidA

2
ดูเหมือนว่าstrideรหัสของ swift 3 จะไม่ถูกต้องเล็กน้อย หากต้องการรวมหมายเลข 1 คุณจะต้องใช้throughตรงข้ามกับtoสิ่งนี้:for i in stride(from:5,through:1,by:-1) { print(i) }
262Hz

4
เป็นสิ่งที่ควรค่าแก่การชี้ให้เห็นว่าreversedมีความซับซ้อนของ O (1) เพราะมันเป็นเพียงการรวมคอลเลกชันดั้งเดิมและไม่จำเป็นต้องมีการทำซ้ำเพื่อสร้างมัน
devios1

21

มีบางอย่างที่น่าหนักใจเกี่ยวกับความไม่สมมาตรของสิ่งนี้:

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

... ตรงข้ามกับสิ่งนี้:

for i in 1..<5 {

หมายความว่าทุกครั้งที่ฉันต้องการทำช่วงย้อนกลับฉันต้องจำไว้ว่าต้องใส่วงเล็บบวกกับฉันต้องเขียนที่.reverse()ท้ายยื่นออกมาเหมือนนิ้วหัวแม่มือเจ็บ นี่เป็นสิ่งที่น่าเกลียดมากเมื่อเทียบกับรูปแบบ C สำหรับลูปซึ่งเป็นการนับขึ้นและนับลงแบบสมมาตร ดังนั้นฉันจึงมักจะใช้สไตล์ C สำหรับลูปแทน แต่ใน Swift 2.2 รูปแบบ C สำหรับลูปกำลังจะหายไป! ดังนั้นฉันจึงต้องรีบเปลี่ยนรูปแบบ C ที่ลดลงทั้งหมดของฉันสำหรับลูปด้วย.reverse()โครงสร้างที่น่าเกลียดนี้- สงสัยตลอดเวลาว่าทำไมบนโลกนี้ถึงไม่มีตัวดำเนินการย้อนกลับ?

แต่เดี๋ยวก่อน! นี่คือ Swift - เราได้รับอนุญาตให้กำหนดตัวดำเนินการของเราเอง !! ไปเลย:

infix operator >>> {
    associativity none
    precedence 135
}

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

ตอนนี้ฉันได้รับอนุญาตให้พูดว่า:

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

สิ่งนี้ครอบคลุมเฉพาะกรณีที่พบบ่อยที่สุดที่เกิดขึ้นในรหัสของฉัน แต่เป็นกรณีที่พบบ่อยที่สุดดังนั้นจึงเป็นสิ่งที่ฉันต้องการในปัจจุบัน

ฉันมีวิกฤตภายในเกิดขึ้นกับผู้ปฏิบัติงาน ฉันอยากจะใช้>..เหมือนเป็นสิ่งที่ตรงกันข้าม..<แต่มันไม่ถูกกฎหมาย: คุณไม่สามารถใช้จุดหลังจากที่ไม่ใช่จุดมันจะปรากฏขึ้น ผมถือว่าแต่ตัดสินใจว่ามันเป็นเรื่องยากที่จะแยกแยะความแตกต่างจาก..> ..<สิ่งที่ดี>>>คือมันกรีดร้องใส่คุณ: " ลงไป!" (แน่นอนว่าคุณมีอิสระที่จะหาโอเปอเรเตอร์อื่น แต่คำแนะนำของฉันคือ: สำหรับความสมมาตรขั้นสูงให้กำหนดว่า<<<จะทำอะไร..<และตอนนี้คุณมี<<<แล้ว>>>ซึ่งสมมาตรและง่ายต่อการพิมพ์)


เวอร์ชัน Swift 3 (Xcode 8 seed 6):

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

เวอร์ชัน 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()
}

เวอร์ชัน 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()
}

เวอร์ชัน 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
ว้าว>>>ผู้ดำเนินรายการเจ๋งมาก! ฉันหวังว่านักออกแบบของ Swift จะให้ความสนใจกับสิ่งต่างๆเช่นนั้นเพราะการยึดreverse()ท้ายนิพจน์ในวงเล็บทำให้รู้สึกแปลก ๆ ไม่มีเหตุผลใดที่พวกเขาไม่สามารถจัดการกับ5>..0ช่วงโดยอัตโนมัติได้เนื่องจากForwardIndexTypeโปรโตคอลเป็นdistanceTo()ตัวดำเนินการที่เหมาะสมอย่างสมบูรณ์ซึ่งสามารถใช้เพื่อดูว่าการก้าวเดินนั้นควรเป็นบวกหรือลบ
dasblinkenlight

1
ขอบคุณ @dasblinkenlight - สิ่งนี้เกิดขึ้นสำหรับฉันเพราะในหนึ่งในแอปของฉันฉันมีC จำนวนมากสำหรับลูป (ใน Objective-C) ที่ย้ายไปใช้สไตล์ Swift C สำหรับลูปและตอนนี้ต้องถูกแปลงเพราะสไตล์ C สำหรับลูป กำลังจะจากไป นี่เป็นเรื่องที่น่ากลัวมากเพราะลูปเหล่านี้แสดงถึงหลักของตรรกะของแอปของฉันและเขียนใหม่ทุกอย่างที่เสี่ยงต่อการแตกหัก ดังนั้นฉันจึงต้องการสัญกรณ์ที่จะทำให้ความสอดคล้องกับสัญกรณ์รูปแบบ C (แสดงความคิดเห็น) ชัดเจนที่สุด
แมต

เรียบร้อย! ฉันชอบความหลงไหลของคุณด้วยรหัสที่ดูสะอาด!
Yohst

10

ดูเหมือนว่าคำตอบสำหรับคำถามนี้มีการเปลี่ยนแปลงเล็กน้อยเมื่อเราดำเนินการผ่านเบต้า สำหรับเบต้า 4 ทั้งby()ฟังก์ชันและReversedRangeประเภทได้ถูกลบออกจากภาษาแล้ว หากคุณต้องการสร้างช่วงที่กลับด้านตอนนี้ตัวเลือกของคุณมีดังนี้:

1: สร้างช่วงไปข้างหน้าจากนั้นใช้reverse()ฟังก์ชันเพื่อย้อนกลับ

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: ใช้stride()ฟังก์ชันใหม่ที่เพิ่มเข้ามาในเบต้า 4 ซึ่งรวมถึงฟังก์ชันในการระบุดัชนีเริ่มต้นและสิ้นสุดรวมทั้งจำนวนที่ต้องวนซ้ำ

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
}

โปรดทราบว่าฉันได้รวมตัวดำเนินการช่วงพิเศษใหม่ไว้ในโพสต์นี้ด้วย ถูกแทนที่ด้วย....<

แก้ไข:จากบันทึกประจำรุ่น Xcode 6 beta 5 Apple ได้เพิ่มคำแนะนำต่อไปนี้สำหรับการจัดการสิ่งนี้:

ReverseRange ถูกลบออก ขี้เกียจใช้ (x ..

นี่คือตัวอย่าง

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

+1 ฉันกำลังมองหาข้อมูลอ้างอิงlazy(0....5).reverse()และไม่เห็นบันทึกประจำรุ่นเบต้า 5 คุณรู้จักการสนทนาอื่น ๆ ในหัวข้อนี้หรือไม่? นอกจากนี้ FYI ตัวเลขภายในforลูปนั้นจะย้อนกลับในตัวอย่างของคุณ
Rob

1
@Rob อืมฉันควรจะคิดว่าบันทึกประจำรุ่นสำหรับเบต้าจะถูกลบในที่สุด เมื่อฉันเขียนสิ่งนี้นั่นเป็นที่เดียวที่ฉันเห็นการอ้างอิงถึง lazy () แต่ฉันยินดีที่จะหาข้อมูลเพิ่มเติมและอัปเดต / แก้ไขเมื่อกลับถึงบ้านในภายหลัง ขอบคุณที่ชี้ให้เห็น
Mick MacCallum

1
@ 0x7fffffff ในตัวอย่างสุดท้ายของคุณความคิดเห็นบอกว่า// 0, 1, 2, 3, 4, 5แต่จริงๆแล้วควรกลับด้าน ความคิดเห็นที่ทำให้เข้าใจผิด
ปัง


3

Swift 3, 4+ : คุณสามารถทำได้ดังนี้:

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

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

ผลลัพธ์ : 10, 9, 8 ... 0

คุณสามารถปรับแต่งได้ตามต้องการ สำหรับข้อมูลเพิ่มเติมอ่านfunc sequence<T>อ้างอิง



โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.