มีวิธีย้ำช่วงจำนวนเต็มหรือไม่


174

ช่วงของ Go สามารถทำซ้ำในแผนที่และส่วนต่าง ๆ แต่ฉันสงสัยว่าถ้ามีวิธีที่จะวนซ้ำในช่วงของตัวเลขบางอย่างเช่นนี้:

for i := range [1..10] {
    fmt.Println(i)
}

หรือมีวิธีที่จะแสดงช่วงของจำนวนเต็มใน Go เหมือนกับที่ Ruby ทำกับRange Classอย่างไร?

คำตอบ:


224

คุณสามารถและควรเพียงแค่เขียนสำหรับวง รหัสที่ง่ายและชัดเจนคือวิธีการใช้งาน

for i := 1; i <= 10; i++ {
    fmt.Println(i)
}

268
ฉันไม่คิดว่าคนส่วนใหญ่จะเรียกว่าเวอร์ชันสามนิพจน์นี้ง่ายกว่าสิ่งที่ @Vishnu เขียน อาจหลังจากผ่านไปหลายปีหรือหลายปีของการสอนภาษา C หรือ Java ;-)
Thomas Ahle

12
IMO ประเด็นคือคุณจะมีห่วง for for version สามนิพจน์เสมอ (เช่นคุณสามารถทำอะไรได้มากกว่านั้นอีกมากไวยากรณ์จาก OP นั้นดีสำหรับกรณีที่ จำกัด จำนวนมากเท่านั้นดังนั้น ในภาษาใด ๆ ที่คุณต้องการให้เป็นเวอร์ชั่นเพิ่มเติม) และมันก็ทำหน้าที่เดียวกันให้สำเร็จและไม่แตกต่างกันอย่างน่าทึ่งดังนั้นทำไมต้องเรียนรู้ / จำไวยากรณ์อื่น หากคุณกำลังเขียนโค้ดในโครงการขนาดใหญ่และซับซ้อนคุณมีความกังวลมากพออยู่แล้วโดยไม่ต้องต่อสู้กับคอมไพเลอร์เกี่ยวกับวากยสัมพันธ์ต่าง ๆ เพื่ออะไรที่ง่ายพอ ๆ กับลูป
แบรดพีบอดี

3
@ThomasAhle โดยเฉพาะการพิจารณา C ++ เป็นทางการเพิ่มสัญกรณ์ for_each (x, y) โดยได้แรงบันดาลใจจากไลบรารีเทมเพลตเพิ่ม
don bright

5
@BradPeabody นี่เป็นเรื่องจริงของการตั้งค่า Python ไม่มีลูป 3 นิพจน์และทำงานได้ดี หลายคนพิจารณาว่ารูปแบบสำหรับแต่ละข้อผิดพลาดมีแนวโน้มที่จะเกิดข้อผิดพลาดน้อยกว่าและไม่มีอะไรที่เกี่ยวกับเรื่องนี้
VinGarcia

3
@necromancer ที่นี่เป็นโพสต์จาก Rob Pike เถียงมากเช่นเดียวกับคำตอบของฉัน groups.google.com/d/msg/golang-nuts/7J8FY07dkW0/goWaNVOkQU0J อาจเป็นไปได้ว่าชุมชนโกไม่เห็นด้วย แต่เมื่อเห็นด้วยกับหนึ่งในผู้เขียนภาษาก็ไม่น่าจะเป็นคำตอบที่ไม่ดีจริงๆ
พอลฮั

43

นี่คือโปรแกรมสำหรับเปรียบเทียบทั้งสองวิธีที่แนะนำจนถึงปัจจุบัน

import (
    "fmt"

    "github.com/bradfitz/iter"
)

func p(i int) {
    fmt.Println(i)
}

func plain() {
    for i := 0; i < 10; i++ {
        p(i)
    }
}

func with_iter() {
    for i := range iter.N(10) {
        p(i)
    }
}

func main() {
    plain()
    with_iter()
}

รวบรวมอย่างนี้เพื่อสร้างการถอดแยกชิ้นส่วน

go build -gcflags -S iter.go

นี่คือธรรมดา (ฉันได้ลบคำแนะนำที่ไม่ใช่จากรายชื่อ)

ติดตั้ง

0035 (/home/ncw/Go/iter.go:14) MOVQ    $0,AX
0036 (/home/ncw/Go/iter.go:14) JMP     ,38

ห่วง

0037 (/home/ncw/Go/iter.go:14) INCQ    ,AX
0038 (/home/ncw/Go/iter.go:14) CMPQ    AX,$10
0039 (/home/ncw/Go/iter.go:14) JGE     $0,45
0040 (/home/ncw/Go/iter.go:15) MOVQ    AX,i+-8(SP)
0041 (/home/ncw/Go/iter.go:15) MOVQ    AX,(SP)
0042 (/home/ncw/Go/iter.go:15) CALL    ,p+0(SB)
0043 (/home/ncw/Go/iter.go:15) MOVQ    i+-8(SP),AX
0044 (/home/ncw/Go/iter.go:14) JMP     ,37
0045 (/home/ncw/Go/iter.go:17) RET     ,

และนี่คือ with_iter

ติดตั้ง

0052 (/home/ncw/Go/iter.go:20) MOVQ    $10,AX
0053 (/home/ncw/Go/iter.go:20) MOVQ    $0,~r0+-24(SP)
0054 (/home/ncw/Go/iter.go:20) MOVQ    $0,~r0+-16(SP)
0055 (/home/ncw/Go/iter.go:20) MOVQ    $0,~r0+-8(SP)
0056 (/home/ncw/Go/iter.go:20) MOVQ    $type.[]struct {}+0(SB),(SP)
0057 (/home/ncw/Go/iter.go:20) MOVQ    AX,8(SP)
0058 (/home/ncw/Go/iter.go:20) MOVQ    AX,16(SP)
0059 (/home/ncw/Go/iter.go:20) PCDATA  $0,$48
0060 (/home/ncw/Go/iter.go:20) CALL    ,runtime.makeslice+0(SB)
0061 (/home/ncw/Go/iter.go:20) PCDATA  $0,$-1
0062 (/home/ncw/Go/iter.go:20) MOVQ    24(SP),DX
0063 (/home/ncw/Go/iter.go:20) MOVQ    32(SP),CX
0064 (/home/ncw/Go/iter.go:20) MOVQ    40(SP),AX
0065 (/home/ncw/Go/iter.go:20) MOVQ    DX,~r0+-24(SP)
0066 (/home/ncw/Go/iter.go:20) MOVQ    CX,~r0+-16(SP)
0067 (/home/ncw/Go/iter.go:20) MOVQ    AX,~r0+-8(SP)
0068 (/home/ncw/Go/iter.go:20) MOVQ    $0,AX
0069 (/home/ncw/Go/iter.go:20) LEAQ    ~r0+-24(SP),BX
0070 (/home/ncw/Go/iter.go:20) MOVQ    8(BX),BP
0071 (/home/ncw/Go/iter.go:20) MOVQ    BP,autotmp_0006+-32(SP)
0072 (/home/ncw/Go/iter.go:20) JMP     ,74

ห่วง

0073 (/home/ncw/Go/iter.go:20) INCQ    ,AX
0074 (/home/ncw/Go/iter.go:20) MOVQ    autotmp_0006+-32(SP),BP
0075 (/home/ncw/Go/iter.go:20) CMPQ    AX,BP
0076 (/home/ncw/Go/iter.go:20) JGE     $0,82
0077 (/home/ncw/Go/iter.go:20) MOVQ    AX,autotmp_0005+-40(SP)
0078 (/home/ncw/Go/iter.go:21) MOVQ    AX,(SP)
0079 (/home/ncw/Go/iter.go:21) CALL    ,p+0(SB)
0080 (/home/ncw/Go/iter.go:21) MOVQ    autotmp_0005+-40(SP),AX
0081 (/home/ncw/Go/iter.go:20) JMP     ,73
0082 (/home/ncw/Go/iter.go:23) RET     ,

ดังนั้นคุณจะเห็นได้ว่าโซลูชัน iter มีราคาแพงกว่ามากแม้ว่าจะอยู่ในช่วงการติดตั้งอย่างสมบูรณ์ก็ตาม ในเฟสลูปมีคำสั่งพิเศษในลูป แต่ก็ไม่ได้แย่เกินไป

ฉันจะใช้ง่ายสำหรับวน


8
ฉันไม่สามารถ "เห็นว่าโซลูชัน iter มีราคาแพงกว่ามาก" วิธีการนับคำแนะนำของคุณในชุดประกอบ pseudo-assembler นั้นมีข้อบกพร่อง เรียกใช้เกณฑ์มาตรฐาน
peterSO

11
ทางออกหนึ่งเรียกใช้runtime.makesliceและวิธีอื่นไม่ได้ - ฉันไม่ต้องการมาตรฐานเพื่อทราบว่าจะช้ากว่านี้มาก!
Nick Craig-Wood

6
ใช่runtime.makesliceฉลาดพอที่จะไม่จัดสรรหน่วยความจำใด ๆ หากคุณขอการจัดสรรขนาดเป็นศูนย์ อย่างไรก็ตามข้างต้นยังคงเรียกมันและตามมาตรฐานของคุณจะใช้เวลา 10nS บนเครื่องของฉันอีกต่อไป
Nick Craig-Wood

4
สิ่งนี้เป็นการเตือนผู้คนที่แนะนำให้ใช้ C มากกว่า C ++ สำหรับเหตุผลด้านประสิทธิภาพ
หมอผี

5
การโต้วาทีประสิทธิภาพรันไทม์ของการทำงานของ CPU ระดับนาโนวินาทีในขณะที่ทั่วไปใน Goland ดูเหมือนจะโง่สำหรับฉัน ฉันจะพิจารณาว่าการพิจารณาครั้งสุดท้ายที่ห่างไกลมากหลังจากอ่านง่าย แม้ว่าประสิทธิภาพของ CPU จะมีความเกี่ยวข้องกันเนื้อหาของ for loop นั้นเกือบจะล้นทุกสิ่งที่เกิดขึ้นจาก loop
Jonathan Hartley

34

แนะนำโดย Mark Mishyn ให้ใช้การแบ่งเป็นชิ้น แต่ไม่มีเหตุผลที่จะสร้าง array ด้วยmakeและใช้ในforslice ที่ส่งคืนของมันเมื่อ array ที่สร้างผ่านตัวอักษรสามารถใช้งานได้และสั้นกว่า

for i := range [5]int{} {
        fmt.Println(i)
}

8
หากคุณไม่ต้องการใช้ตัวแปรคุณสามารถละเว้นด้านซ้ายและใช้for range [5]int{} {
blockloop

6
ข้อเสียเปรียบคือที่5นี่เป็นตัวอักษรและไม่สามารถกำหนดได้ในเวลาทำงาน
Steve Powell

มันเร็วกว่าหรือเทียบเท่ากับสามนิพจน์ปกติของลูป?
Amit Tripathi

@AmitTripathi ใช่มันเทียบเคียงเวลาดำเนินการเกือบจะเหมือนกันสำหรับการทำซ้ำหลายพันล้านครั้ง
Daniil Grankin

18

iterเป็นแพคเกจขนาดเล็กมากที่เพิ่งให้วิธีการที่แตกต่างกันในการทำซ้ำมากกว่าจำนวนเต็ม

for i := range iter.N(4) {
    fmt.Println(i)
}

Rob Pike (ผู้เขียน Go) ได้วิจารณ์ว่า :

ดูเหมือนว่าเกือบทุกครั้งที่มีคนหาวิธีหลีกเลี่ยงการทำสิ่งใดสิ่งหนึ่งเพื่อทำวนสำนวนเพราะรู้สึกยาวเกินไปหรือยุ่งยากผลลัพธ์ก็คือการกดแป้นมากกว่าปกติที่สั้นกว่าที่ควรจะเป็น [... ] นั่นคือการละทิ้งความบ้าคลั่งทั้งหมดที่ "การปรับปรุง" นำมาให้


16
การวิพากษ์วิจารณ์ของหอกเป็นเรื่องง่ายในแง่ที่ว่ามันเป็นเพียงการกดแป้นมากกว่าที่จะอยู่เหนือจิตใจของช่วงการประกาศใหม่อย่างต่อเนื่อง นอกจากนี้สำหรับเครื่องมือแก้ไขที่ทันสมัยส่วนใหญ่iterเวอร์ชันจะใช้การกดแป้นพิมพ์น้อยลงเพราะrangeและiterจะเติมข้อความอัตโนมัติ
Chris Redford

1
@ lang2, forลูปไม่ได้เป็นพลเมืองอันดับหนึ่งของ Unix เหมือนพวกเขาอยู่ในระหว่างการเดินทาง นอกจากนี้ไม่เหมือนfor, seqลำธารออกมาตรฐานลำดับของตัวเลข ขึ้นอยู่กับผู้บริโภคหรือไม่ แม้ว่าจะfor i in $(seq 1 10); do ... done เป็นเรื่องปกติในเชลล์ แต่ก็มีวิธีเดียวที่จะทำเพื่อลูปซึ่งเป็นวิธีเดียวที่จะใช้เอาต์พุตของseqแม้ว่าจะเป็นวิธีที่ใช้กันทั่วไป
Daniel Farrell

2
นอกจากนี้หอกก็ไม่ได้พิจารณาข้อเท็จจริงที่ว่ารวบรวม (ได้รับรายละเอียดภาษารวมถึงไวยากรณ์ช่วงสำหรับกรณีการใช้งานนี้) อาจจะสร้างในทางที่จะเป็นเพียงแค่การรักษาเหมือนกันi in range(10) i := 0; i < 10; i++
ระลึก B.

8

นี่คือมาตรฐานในการเปรียบเทียบforคำสั่งGo กับคำสั่ง ForClause และ Go rangeโดยใช้iterแพคเกจ

iter_test.go

package main

import (
    "testing"

    "github.com/bradfitz/iter"
)

const loops = 1e6

func BenchmarkForClause(b *testing.B) {
    b.ReportAllocs()
    j := 0
    for i := 0; i < b.N; i++ {
        for j = 0; j < loops; j++ {
            j = j
        }
    }
    _ = j
}

func BenchmarkRangeIter(b *testing.B) {
    b.ReportAllocs()
    j := 0
    for i := 0; i < b.N; i++ {
        for j = range iter.N(loops) {
            j = j
        }
    }
    _ = j
}

// It does not cause any allocations.
func N(n int) []struct{} {
    return make([]struct{}, n)
}

func BenchmarkIterAllocs(b *testing.B) {
    b.ReportAllocs()
    var n []struct{}
    for i := 0; i < b.N; i++ {
        n = iter.N(loops)
    }
    _ = n
}

เอาท์พุท:

$ go test -bench=. -run=.
testing: warning: no tests to run
PASS
BenchmarkForClause      2000       1260356 ns/op           0 B/op          0 allocs/op
BenchmarkRangeIter      2000       1257312 ns/op           0 B/op          0 allocs/op
BenchmarkIterAllocs 20000000            82.2 ns/op         0 B/op          0 allocs/op
ok      so/test 7.026s
$

5
หากคุณตั้งค่าลูปเป็น 10 ให้ลองเกณฑ์มาตรฐานอีกครั้งคุณจะเห็นความแตกต่างที่ทำเครื่องหมายไว้ บนเครื่องของฉัน ForClause ใช้เวลา 5.6 ns ในขณะที่ Iter ใช้เวลา 15.4 ns ดังนั้นให้เรียกตัวจัดสรร (แม้ว่ามันจะฉลาดพอที่จะไม่จัดสรรอะไร) ยังคงมีค่าใช้จ่าย 10ns และรหัสการจับภาพแคช I-cache จำนวนมาก
Nick Craig-Wood

ผมจะมีความสนใจที่จะดูมาตรฐานและการวิพากษ์วิจารณ์ของคุณสำหรับแพคเกจที่ผมสร้างขึ้นและการอ้างอิงในคำตอบของฉัน
Chris Redford

5

ในขณะที่ฉันรับปากกับความกังวลของคุณเกี่ยวกับการขาดคุณสมบัติภาษานี้คุณอาจจะแค่ต้องการใช้forวงวนปกติ และคุณอาจจะโอเคกับสิ่งนั้นมากกว่าที่คุณคิดในขณะที่คุณเขียนโค้ด Go เพิ่มเติม

ฉันเขียนแพ็คเกจ iter นี้ - ซึ่งได้รับการสนับสนุนโดยforลูปที่เรียบง่ายและเป็นสำนวนที่คืนค่ามากกว่าchan int- ในความพยายามที่จะปรับปรุงการออกแบบที่พบในhttps://github.com/bradfitz/iterซึ่งได้รับการชี้ให้เห็นว่ามี ปัญหาการแคชและประสิทธิภาพรวมถึงการใช้งานที่ฉลาด แต่แปลกและไม่เข้าใจง่าย รุ่นของฉันทำงานด้วยวิธีเดียวกัน:

package main

import (
    "fmt"
    "github.com/drgrib/iter"
)

func main() {
    for i := range iter.N(10) {
        fmt.Println(i)
    }
}

อย่างไรก็ตามการเปรียบเทียบพบว่าการใช้ช่องเป็นตัวเลือกที่แพงมาก การเปรียบเทียบ 3 วิธีซึ่งสามารถเรียกใช้จากiter_test.goในแพ็คเกจของฉันโดยใช้

go test -bench=. -run=.

วัดประสิทธิภาพการทำงานของเครื่อง

BenchmarkForMany-4                   5000       329956 ns/op           0 B/op          0 allocs/op
BenchmarkDrgribIterMany-4               5    229904527 ns/op         195 B/op          1 allocs/op
BenchmarkBradfitzIterMany-4          5000       337952 ns/op           0 B/op          0 allocs/op

BenchmarkFor10-4                500000000         3.27 ns/op           0 B/op          0 allocs/op
BenchmarkDrgribIter10-4            500000      2907 ns/op             96 B/op          1 allocs/op
BenchmarkBradfitzIter10-4       100000000        12.1 ns/op            0 B/op          0 allocs/op

ในกระบวนการมาตรฐานนี้ยังแสดงให้เห็นถึงวิธีการที่bradfitzต่ำกว่าการแก้ปัญหาในการเปรียบเทียบกับในตัวข้อสำหรับขนาดของวงfor10

ในระยะสั้นดูเหมือนว่าจะไม่มีทางค้นพบเพื่อทำซ้ำประสิทธิภาพของforประโยคในตัวในขณะที่มีไวยากรณ์ง่าย ๆ[0,n)เช่นเดียวกับที่พบใน Python และ Ruby

ซึ่งเป็นความอัปยศเพราะอาจเป็นเรื่องง่ายสำหรับทีม Go ที่จะเพิ่มกฎง่ายๆให้กับคอมไพเลอร์เพื่อเปลี่ยนบรรทัดเช่น

for i := range 10 {
    fmt.Println(i)
}

for i := 0; i < 10; i++ไปที่รหัสเครื่องเดียวกับ

อย่างไรก็ตามเพื่อความเป็นธรรมหลังจากเขียนของฉันเองiter.N(แต่ก่อนที่จะทำการเปรียบเทียบ) ฉันกลับไปที่โปรแกรมที่เขียนเมื่อเร็ว ๆ นี้เพื่อดูสถานที่ทั้งหมดที่ฉันสามารถใช้ได้ จริงๆแล้วมีไม่มาก มีเพียงจุดเดียวในส่วนที่ไม่สำคัญของรหัสของฉันซึ่งฉันสามารถทำได้โดยไม่ต้องใช้forประโยคเริ่มต้นที่สมบูรณ์มากขึ้น

ดังนั้นในขณะที่อาจดูเหมือนว่านี่เป็นความผิดหวังอย่างมากสำหรับภาษาในหลักการคุณอาจพบว่า - เหมือนที่ฉันทำ - ที่จริงแล้วคุณไม่ต้องการใช้มัน เช่นเดียวกับที่ Rob Pike รู้จักกันในชื่อ generics คุณอาจไม่พลาดฟีเจอร์นี้มากเท่าที่คุณคิด


1
การใช้ช่องทางสำหรับการทำซ้ำมีราคาแพงมาก goroutines และช่องมีราคาถูกพวกเขาไม่ได้ฟรี หากช่วงการวนซ้ำของช่องสัญญาณสิ้นสุดลงเร็วกว่า goroutine จะไม่สิ้นสุด (การรั่วไหลของ goroutine) วิธี Iter ถูกลบออกจากแพคเกจเวกเตอร์ " container / vector: remove Iter () จาก interface (Iter () แทบจะไม่เคยมีกลไกที่เหมาะสมในการโทร) " โซลูชันiterของคุณแพงที่สุดเสมอ
peterSO

4

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

for range [N]int{} {
    // Body...
}

ป.ล. วันแรกที่ GoLang กรุณาทำคำวิจารณ์ถ้ามันเป็นวิธีที่ผิด


จนถึงตอนนี้ (เวอร์ชั่น 1.13.6) มันใช้งานไม่ได้ ขว้างมาnon-constant array boundที่ฉัน
WHS

1

นอกจากนี้คุณยังสามารถดู github.com/wushilin/stream

มันเป็นสตรีมขี้เกียจเช่นแนวคิดของ java.util.stream

// It doesn't really allocate the 10 elements.
stream1 := stream.Range(0, 10)

// Print each element.
stream1.Each(print)

// Add 3 to each element, but it is a lazy add.
// You only add when consume the stream
stream2 := stream1.Map(func(i int) int {
    return i + 3
})

// Well, this consumes the stream => return sum of stream2.
stream2.Reduce(func(i, j int) int {
    return i + j
})

// Create stream with 5 elements
stream3 := stream.Of(1, 2, 3, 4, 5)

// Create stream from array
stream4 := stream.FromArray(arrayInput)

// Filter stream3, keep only elements that is bigger than 2,
// and return the Sum, which is 12
stream3.Filter(func(i int) bool {
    return i > 2
}).Sum()

หวังว่านี่จะช่วยได้


0
package main

import "fmt"

func main() {

    nums := []int{2, 3, 4}
    for _, num := range nums {
       fmt.Println(num, sum)    
    }
}

1
เพิ่มบริบทลงในโค้ดของคุณเพื่อช่วยให้ผู้อ่านในอนาคตเข้าใจความหมายของมันได้ดียิ่งขึ้น
Grant Miller

3
นี่คืออะไร? ไม่ได้กำหนดจำนวนเงิน
naftalimich

0

ฉันได้เขียนแพ็คเกจใน Golang ซึ่งเลียนแบบฟังก์ชั่นพิท ธ :

แพ็คเกจhttps://github.com/thedevsaddam/iter

package main

import (
    "fmt"

    "github.com/thedevsaddam/iter"
)

func main() {
    // sequence: 0-9
    for v := range iter.N(10) {
        fmt.Printf("%d ", v)
    }
    fmt.Println()
    // output: 0 1 2 3 4 5 6 7 8 9

    // sequence: 5-9
    for v := range iter.N(5, 10) {
        fmt.Printf("%d ", v)
    }
    fmt.Println()
    // output: 5 6 7 8 9

    // sequence: 1-9, increment by 2
    for v := range iter.N(5, 10, 2) {
        fmt.Printf("%d ", v)
    }
    fmt.Println()
    // output: 5 7 9

    // sequence: a-e
    for v := range iter.L('a', 'e') {
        fmt.Printf("%s ", string(v))
    }
    fmt.Println()
    // output: a b c d e
}

หมายเหตุ: ฉันได้เขียนเพื่อความสนุก! Btw บางครั้งมันอาจจะมีประโยชน์

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