"break" และ "ต่อ" ใน "forEach" ใน Kotlin


122

Kotlin มีฟังก์ชั่นการทำซ้ำที่ดีมากเช่นforEachหรือrepeatแต่ฉันไม่สามารถทำให้ตัวดำเนินการbreakและcontinueตัวดำเนินการทำงานร่วมกับพวกเขาได้ (ทั้งในและนอกพื้นที่):

repeat(5) {
    break
}

(1..5).forEach {
    continue@forEach
}

เป้าหมายคือการเลียนแบบลูปตามปกติโดยมีไวยากรณ์การทำงานใกล้เคียงที่สุด เป็นไปได้แน่นอนใน Kotlin เวอร์ชันเก่า ๆ บางรุ่น แต่ฉันพยายามสร้างไวยากรณ์ขึ้นมาใหม่

ปัญหาอาจเป็นข้อบกพร่องเกี่ยวกับป้ายกำกับ (M12) แต่ฉันคิดว่าตัวอย่างแรกควรใช้งานได้อยู่ดี

สำหรับฉันแล้วดูเหมือนว่าฉันเคยอ่านเคล็ดลับ / คำอธิบายประกอบพิเศษที่ไหนสักแห่ง แต่ฉันไม่พบข้อมูลอ้างอิงใด ๆ ในเรื่องนี้ อาจมีลักษณะดังต่อไปนี้:

public inline fun repeat(times: Int, @loop body: (Int) -> Unit) {
    for (index in 0..times - 1) {
        body(index)
    }
}

1
ใน Kotlin ปัจจุบันคุณสามารถเลียนแบบสิ่งนี้ได้ (ในขณะที่รอcontinue@labelและbreak@labelคุณสมบัติ) โปรดดูคำถามที่เกี่ยวข้อง: stackoverflow.com/questions/34642868/…
Jayson Minard

1
คำถามนี้สามารถใช้การชี้แจงเกี่ยวกับว่าคุณจะถามเพียงการดำรงอยู่ของbreakและcontinueสำหรับลูปการทำงานหรือถ้าคุณกำลังมองหาคำตอบทางเลือกที่จะทำสิ่งเดียวกัน อดีตดูเหมือนจะเป็นเช่นนั้นเพราะคุณปฏิเสธอย่างหลัง
Jayson Minard

ดูเหมือนว่าพวกเขาจะเพิ่มเข้ามาใน kotlin 1.3
Tigran Babajanyan

@TigranBabajanyan ว้าว! คุณมีลิงค์หรือไม่?
voddan

@voddan ไม่ฉันเพิ่งลองใช้งาน
Tigran Babajanyan

คำตอบ:


69

แก้ไข :
ตามเอกสารของ Kotlin เป็นไปได้โดยใช้คำอธิบายประกอบ

fun foo() {
    listOf(1, 2, 3, 4, 5).forEach lit@{
        if (it == 3) return@lit // local return to the caller of the lambda, i.e. the forEach loop
        print(it)
    }
    print(" done with explicit label")
}

คำตอบเดิม :
เนื่องจากคุณจัดหา a (Int) -> Unitคุณจะไม่สามารถแยกออกจากมันได้เนื่องจากคอมไพเลอร์ไม่ทราบว่ามีการใช้งานแบบวนซ้ำ

คุณมีทางเลือกน้อย:

ใช้ปกติสำหรับการวนซ้ำ:

for (index in 0 until times) {
    // your code here
}

หากลูปเป็นรหัสสุดท้ายในเมธอด
คุณสามารถใช้returnเพื่อออกจากเมธอด (หรือreturn valueถ้าไม่ใช่unitเมธอด)

ใช้วิธีการ
สร้างวิธีการทำซ้ำแบบกำหนดเองที่ส่งคืนBooleanสำหรับการดำเนินการต่อ

public inline fun repeatUntil(times: Int, body: (Int) -> Boolean) {
    for (index in 0 until times) {
        if (!body(index)) break
    }
}

อันที่จริงคำถามของฉันเกี่ยวกับการทำให้ไวยากรณ์เฉพาะใช้งานได้ไม่ใช่เกี่ยวกับการทำซ้ำ คุณจำไม่ได้หรือว่ามันเป็นไปได้ในเหตุการณ์สำคัญบางอย่างของ Kotlin?
voddan

1
ฉันจำไม่ได้ แต่อาจจะเป็นเพราะไม่ได้ใช้ break & ต่อมาก เจอฉบับนี้ขึ้นว่า "การประมาณ - ไม่มีการประมาณ"
Yoav Sternberg

1
breakและcontinueทำงานในลูปเท่านั้น forEach, repeatและทุกวิธีการอื่น ๆ เป็นเพียงที่: วิธีการและไม่ได้ลูป Yoav นำเสนอทางเลือกบางอย่าง แต่breakและcontinueเป็นเพียงไม่ ment ในการทำงานสำหรับวิธีการ
Kirill Rakhman

@YoavSternberg สุดยอด! ความสงบของเอกสารเก่านี้คือสิ่งที่ฉันกำลังมองหา! ดังนั้นคุณลักษณะนี้จึงยังไม่ได้ใช้งานเหลือไว้สำหรับเวอร์ชันในอนาคต หากคุณต้องการสร้างคำตอบแยกต่างหากฉันจะทำเครื่องหมาย
voddan

ใน Kotlin ปัจจุบันคุณสามารถเลียนแบบสิ่งนี้ได้ (ในขณะที่รอcontinue@labelและbreak@labelคุณสมบัติ) โปรดดูคำถามที่เกี่ยวข้อง: stackoverflow.com/questions/34642868/…
Jayson Minard

106

สิ่งนี้จะพิมพ์ 1 ถึง 5 การreturn@forEachทำหน้าที่เหมือนคีย์เวิร์ดcontinueใน Java ซึ่งหมายความว่าในกรณีนี้จะยังคงดำเนินการทุกลูป แต่จะข้ามไปยังการทำซ้ำครั้งถัดไปหากค่ามากกว่า 5

fun main(args: Array<String>) {
    val nums = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    nums.forEach {
       if (it > 5) return@forEach
       println(it)
    }
}

สิ่งนี้จะพิมพ์ 1 ถึง 10 แต่ข้าม 5

fun main(args: Array<String>) {
    val nums = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    nums.forEach {
       if (it == 5) return@forEach
       println(it)
    }
}

ลองพวกเขาที่Kotlin สนามเด็กเล่น


เยี่ยมมาก แต่สิ่งนี้ยังไม่สามารถแก้ปัญหาการไม่สามารถยุติ forEach ก่อนเวลาอันควรเมื่อตรงตามเงื่อนไขบางประการ มันยังคงดำเนินการวนซ้ำ
The Fox

1
@TheFox ใช่มันดำเนินการทุกลูปและทุกอย่างหลังจากการส่งคืนจะถูกข้ามเมื่อตรงตามเงื่อนไข แต่ละการดำเนินการใน forEach เป็นฟังก์ชันแลมบ์ดาปัจจุบันไม่มีการดำเนินการแบ่งที่แน่นอนสำหรับการดำเนินการ forEach การแบ่งมีอยู่ในลูปโปรดดู: kotlinlang.org/docs/reference/returns.html
s-hunter

นี่คือตัวอย่างของ Kotlin Playground ที่รันได้พร้อมทั้ง a continueและbreakตัวอย่าง: pl.kotl.in/_LAvET-wX
ashughes

36

สามารถหยุดพักได้โดยใช้:

//Will produce"12 done with nested loop"
//Using "run" and a tag will prevent the loop from running again. Using return@forEach if I>=3 may look simpler, but it will keep running the loop and checking if i>=3 for values >=3 which is a waste of time.
fun foo() {
    run loop@{
        listOf(1, 2, 3, 4, 5).forEach {
            if (it == 3) return@loop // non-local return from the lambda passed to run
            print(it)
        }
    }
    print(" done with nested loop")
}

และสามารถดำเนินการต่อได้ด้วย:

//Will produce: "1245 done with implicit label"
fun foo() {
    listOf(1, 2, 3, 4, 5).forEach {
        if (it == 3) return@forEach // local return to the caller of the lambda, i.e. the forEach loop
        print(it)
    }
    print(" done with implicit label")
}

ตามที่ใคร ๆ แนะนำ ... อ่านเอกสาร: P https://kotlinlang.org/docs/reference/returns.html#return-at-labels


ทางออกที่ดี ทำงานได้ดีมาก แม้ว่าจะดูเหมือนว่าโดยไม่ใช้ก็@loopให้ผลลัพธ์ที่ต้องการเช่นเดียวกัน
Paras Sidhu

ในความเป็นจริงคุณสามารถละเว้นแท็ก "@loop" ที่ชัดเจนและใช้แท็ก "@run" โดยนัยได้ ประเด็นสำคัญที่นี่คือการกลับไปยังผู้เรียกแลมด้าในท้องถิ่น โปรดทราบว่าคุณต้องห่อวงในขอบเขตบางส่วนเพื่อให้คุณสามารถกลับมาภายในได้ในภายหลัง
Raymond Arteaga

นี่ตอบคำถามจริงๆ แต่ฉันคิดว่านี่อาจไม่ใช่เส้นทางที่ถูกต้องสำหรับการเขียนโปรแกรมเชิงฟังก์ชัน สิ่งที่เราต้องการคือจาก Lodash transformซึ่งคุณสามารถทำลายภายใต้เงื่อนไขบางอย่างเช่น reduceReturnIf(acc, value, returnIf: func)
windmaomao

28

คุณสามารถใช้return from lambda expressionซึ่งเลียนแบบ a continueหรือbreakขึ้นอยู่กับการใช้งานของคุณ

คำถามนี้ครอบคลุมอยู่ในคำถามที่เกี่ยวข้อง: ฉันจะ "หยุดพัก" หรือ "ดำเนินการต่อ" ได้อย่างไรเมื่ออยู่ในลูปการทำงานภายใน Kotlin


16

ตามที่เอกสารของ Kotlin กล่าวว่าการใช้returnคือหนทางที่จะไป ข้อดีของ kotlin คือถ้าคุณมีฟังก์ชันซ้อนกันคุณสามารถใช้ป้ายกำกับเพื่อเขียนว่าผลตอบแทนของคุณมาจากไหน:

การกลับมาของขอบเขตฟังก์ชัน

fun foo() {
  listOf(1, 2, 3, 4, 5).forEach {
    if (it == 3) return // non-local return directly to the caller of foo()
    print(it)
  }
  println("this point is unreachable")
}

และ Local Return (ไม่หยุดดำเนินการ forEach = ต่อเนื่อง)

fun foo() {
  listOf(1, 2, 3, 4, 5).forEach lit@{
    if (it == 3) return@lit // local return to the caller of the lambda, i.e. the forEach loop
    print(it)
  }
  print(" done with explicit label")
}

ตรวจสอบเอกสารมันดีจริงๆ :)


3
คำเตือน: return @ lit ไม่หยุดforEach
Jemshit Iskenderov

ถูกต้อง. มันตั้งใจ วิธีแก้ปัญหาแรกทำได้ แต่ถ้าคุณมีคำแนะนำภายในลูปคุณสามารถเลือกได้ว่าต้องการกลับไปที่ / ข้ามไปที่ใด ในกรณีที่สองถ้าเราใช้ return มันจะหยุด ;-)
cesards

Calling Return @ lit likes Continue
pqtuan86

10

continue พิมพ์พฤติกรรมใน forEach

list.forEach { item -> // here forEach give you data item and you can use it 
    if () {
        // your code
        return@forEach // Same as continue
    }

    // your code
}

สำหรับbreakพฤติกรรมประเภทที่คุณต้องใช้for in untilหรือfor inตามรายการคือNullableหรือNon-Nullable

  1. สำหรับรายการNullable :

    for (index in 0 until list.size) {
        val item = list[index] // you can use data item now
        if () {
            // your code
            break
        }
    
        // your code
    }
  2. สำหรับรายการที่ไม่เป็นโมฆะ :

    for (item in list) { // data item will available right away
        if () {
            // your code
            break
        }
    
        // your code
    }

2

คำสั่งแบ่งสำหรับลูปที่ซ้อนกัน forEach ():

listOf("a", "b", "c").forEach find@{ i ->
    listOf("b", "d").forEach { j ->
        if (i == j) return@find
        println("i = $i, j = $j")
    }
}

ผลลัพธ์:

i = a, j = b
i = a, j = d
i = c, j = b
i = c, j = d

ดำเนินการต่อด้วยฟังก์ชันที่ไม่ระบุชื่อ:

listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
    if (value == 3) return
    print("$value ")
})

ผลลัพธ์:

1 2 4 5 

0

อาจมีการเปลี่ยนแปลง forEach to

for(it in myList){
   if(condition){
     doSomething()
   }else{
     break //or continue
    }
} 

มันใช้ได้กับแฮชแมป

 for(it in myMap){
     val k = it.key
     val v = it.value

       if(condition){
         doSomething()
       }else{
         break //or continue
        }
    }

0

ฉันรู้ว่าสิ่งนี้อาจไม่เกี่ยวข้องกับการหยุดต่อเนื่องforEachแต่เนื่องจาก1.3.70เรามีscanเพื่อให้อย่างน้อยเราได้ผลลัพธ์ระดับกลาง

    fun charValue(c: Char) : Int {
      return if (c == '(') 1 else -1
    }

    val all = s.scan(0) { acc, c -> acc + charValue(c) }
    return all.indexOf(-1)

ตอนนี้ถ้าคุณสามารถรวบรวมผลลัพธ์ทั้งหมดได้ (คล้ายกับ map ) ซึ่งคุณอาจไม่ต้องการอย่างไรก็ตามหากคุณปล่อยให้มันเสร็จสิ้นคุณสามารถข้ามช่วงพักและตรวจสอบอาร์เรย์สุดท้ายได้

IMHO การเขียนโปรแกรมฟังก์ชั่นพื้นฐานไม่ได้คำนึงถึงtransformการดำเนินการที่จำเป็นต้องกระโดดออกมาตรงกลางโดยไม่ทำให้เสร็จ ในการแก้ไขปัญหานี้อย่างแท้จริงเราจำเป็นต้องสร้างฟังก์ชันที่คำนึงถึงเงื่อนไขการคืนสินค้าเช่น

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