Apakah ada cara untuk mengulangi berbagai bilangan bulat?

175

Rentang Go dapat beralih di atas peta dan irisan, tetapi saya bertanya-tanya apakah ada cara untuk beralih ke rentang angka, seperti ini:

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

Atau adakah cara untuk merepresentasikan rentang bilangan bulat di Go seperti yang dilakukan Ruby dengan Range kelas ?

Wisnu
sumber

Jawaban:

225

Anda dapat, dan harus, hanya menulis untuk loop. Kode sederhana dan jelas adalah cara Go.

for i := 1; i <= 10; i++ {
    fmt.Println(i)
}
Paul Hankin
sumber
269
Saya tidak berpikir kebanyakan orang akan menyebut versi tiga ekspresi ini lebih sederhana dari apa yang ditulis oleh Dewa Wisnu. Hanya mungkin setelah bertahun-tahun C atau Java indoktrinasi ;-)
Thomas Ahle
12
IMO intinya adalah bahwa Anda akan selalu memiliki versi tiga ekspresi dari for loop (yaitu Anda dapat melakukan lebih banyak dengan itu, sintaks dari OP hanya baik untuk kasus yang lebih terbatas dari rentang angka, jadi dalam bahasa apa pun Anda ingin versi yang diperluas ini) dan cukup menyelesaikan tugas yang sama, dan bagaimanapun juga tidak terlalu berbeda, jadi mengapa harus belajar / mengingat sintaks lain. Jika Anda melakukan pengkodean pada proyek besar dan kompleks, Anda sudah cukup khawatir tanpa harus melawan kompiler tentang berbagai sintaks untuk sesuatu yang sederhana seperti loop.
Brad Peabody
3
@ThomasAhle terutama mengingat C ++ secara resmi menambahkan notasi for_each (x, y) yang terinspirasi oleh pustaka template boost
don bright
5
@BradPeabody ini sebenarnya masalah preferensi. Python tidak memiliki loop 3-ekspresi dan berfungsi dengan baik. Banyak yang menganggap sintaks untuk masing-masing jauh lebih sedikit rawan kesalahan dan secara intrinsik tidak ada yang efisien.
VinGarcia
3
@necromancer di sini adalah posting dari Rob Pike berdebat untuk hal yang sama seperti jawaban saya. groups.google.com/d/msg/golang-nuts/7J8FY07dkW0/goWaNVOkQU0J . Mungkin saja komunitas Go tidak setuju, tetapi ketika setuju dengan salah satu penulis bahasa, tidak mungkin jawaban seburuk itu.
Paul Hankin
43

Berikut adalah program untuk membandingkan dua cara yang disarankan sejauh ini

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()
}

Kompilasi seperti ini untuk menghasilkan pembongkaran

go build -gcflags -S iter.go

Ini jelas (saya telah menghapus instruksi dari daftar)

mempersiapkan

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

lingkaran

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     ,

Dan di sini adalah with_iter

mempersiapkan

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

lingkaran

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     ,

Jadi, Anda dapat melihat bahwa solusi iter jauh lebih mahal meskipun sepenuhnya inline pada fase setup. Pada fase loop ada instruksi tambahan di loop, tetapi itu tidak terlalu buruk.

Saya akan menggunakan sederhana untuk loop.

Nick Craig-Wood
sumber
8
Saya tidak bisa "melihat bahwa solusi iter jauh lebih mahal." Metode menghitung instruksi Go pseudo-assembler Anda salah. Jalankan benchmark.
peterSO
11
Satu panggilan solusi runtime.makeslicedan yang lainnya tidak - Saya tidak perlu tolok ukur untuk mengetahui bahwa ini akan menjadi jauh lebih lambat!
Nick Craig-Wood
6
Ya runtime.makeslicecukup pintar untuk tidak mengalokasikan memori apa pun jika Anda meminta alokasi ukuran nol. Namun hal di atas masih menyebutnya, dan menurut tolok ukur Anda membutuhkan waktu 10nS lebih lama pada mesin saya.
Nick Craig-Wood
4
ini mengingatkan orang yang menyarankan untuk menggunakan C lebih dari C ++ untuk alasan kinerja
necromancer
5
Memperdebatkan kinerja runtime dari operasi CPU nanosecond, sementara umum di Goland, tampak konyol bagi saya. Saya akan mempertimbangkan pertimbangan terakhir yang sangat jauh, setelah keterbacaan. Bahkan jika kinerja CPU relevan, isi dari for loop akan hampir selalu membanjiri perbedaan apa pun yang ditimbulkan oleh loop itu sendiri.
Jonathan Hartley
34

Disarankan oleh Mark Mishyn untuk menggunakan slice tetapi tidak ada alasan untuk membuat array dengan makedan digunakan dalam forslice yang dikembalikan ketika array yang dibuat melalui literal dapat digunakan dan itu lebih pendek

for i := range [5]int{} {
        fmt.Println(i)
}
Daniil Grankin
sumber
8
Jika Anda tidak akan menggunakan variabel, Anda juga dapat menghilangkan sisi kiri dan menggunakanfor range [5]int{} {
blockloop
6
Kekurangannya adalah bahwa 5ini adalah literal dan tidak dapat ditentukan pada saat run-time.
Steve Powell
Apakah lebih cepat atau sebanding dengan tiga ekspresi normal untuk loop?
Amit Tripathi
@AmitTripathi ya, ini sebanding, waktu pelaksanaannya hampir sama untuk milyaran iterasi.
Daniil Grankin
18

iter adalah paket yang sangat kecil yang hanya menyediakan cara yang berbeda secara sintetik untuk beralih lebih dari bilangan bulat.

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

Rob Pike (penulis Go) mengkritiknya :

Tampaknya hampir setiap kali seseorang menemukan cara untuk menghindari melakukan sesuatu seperti for loop dengan cara idiomatis, karena rasanya terlalu lama atau rumit, hasilnya hampir selalu lebih banyak penekanan tombol daripada hal yang seharusnya lebih pendek. [...] Yang mengesampingkan semua overhead gila ini "perbaikan" membawa.

elithrar
sumber
16
Kritik Pike sederhana karena hanya membahas penekanan tombol daripada overhead mental dari rentang redeclaring yang terus-menerus. Juga, dengan sebagian besar editor modern, iterversi ini sebenarnya menggunakan penekanan tombol yang lebih sedikit karena rangedan iterakan melengkapi otomatis.
Chris Redford
1
@ lang2, forloop bukan warga negara kelas satu Unix seperti sedang dalam perjalanan. Selain itu, tidak seperti for, seqstreaming ke output standar urutan angka. Apakah atau tidak untuk mengulangi mereka terserah konsumen. Meskipun for i in $(seq 1 10); do ... done umum di Shell, itu hanya satu cara untuk melakukan loop, yang dengan sendirinya hanya satu cara untuk mengkonsumsi output seq, meskipun sangat umum.
Daniel Farrell
2
Juga, Pike sama sekali tidak mempertimbangkan fakta bahwa kompilasi (mengingat spesifikasi bahasa menyertakan sintaks kisaran untuk kasus penggunaan ini) dapat dibuat dengan cara hanya memperlakukan i in range(10)persis seperti i := 0; i < 10; i++.
Rouven B.
8

Berikut ini adalah tolok ukur untuk membandingkan forpernyataan Go dengan ForClause dan rangepernyataan Go menggunakan iterpaket.

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
}

Keluaran:

$ 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
$
peterSO
sumber
5
Jika Anda menetapkan loop ke 10 maka coba kembali tolok ukur Anda akan melihat perbedaan yang nyata. Pada mesin saya ForClause membutuhkan 5,6 ns sedangkan Iter mengambil 15,4 ns, jadi memanggil pengalokasi (meskipun cukup pintar untuk tidak mengalokasikan apa pun) masih membutuhkan biaya 10ns dan seluruh tumpukan kode penghilang cache I-cache tambahan.
Nick Craig-Wood
Saya akan tertarik untuk melihat tolok ukur dan kritik Anda untuk paket yang saya buat dan rujuk dalam jawaban saya .
Chris Redford
5

Sementara saya bersimpati dengan kekhawatiran Anda tentang kekurangan fitur bahasa ini, Anda mungkin hanya ingin menggunakan forloop normal . Dan Anda mungkin akan lebih oke dengan itu daripada yang Anda pikirkan saat Anda menulis lebih banyak kode Go.

Saya menulis paket iter ini - yang didukung oleh forloop idiomatik sederhana yang mengembalikan nilai lebih dari chan int- dalam upaya untuk memperbaiki desain yang ditemukan di https://github.com/bradfitz/iter , yang telah ditunjukkan memiliki caching dan masalah kinerja, serta implementasi yang cerdas, aneh, dan tidak intuitif. Versi saya sendiri beroperasi dengan cara yang sama:

package main

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

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

Namun, pembandingan menunjukkan bahwa penggunaan saluran adalah pilihan yang sangat mahal. Perbandingan 3 metode, yang dapat dijalankan dari iter_test.godalam paket saya menggunakan

go test -bench=. -run=.

mengukur seberapa buruk kinerjanya

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

Dalam prosesnya, tolok ukur ini juga menunjukkan bagaimana bradfitzsolusi berkinerja buruk dibandingkan dengan forklausa bawaan untuk ukuran loop 10.

Singkatnya, sejauh ini tidak ada cara yang ditemukan untuk menduplikasi kinerja forklausa built-in sambil memberikan sintaksis sederhana untuk [0,n)seperti yang ditemukan di Python dan Ruby.

Yang memalukan karena mungkin akan mudah bagi tim Go untuk menambahkan aturan sederhana ke kompiler untuk mengubah garis seperti

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

ke kode mesin yang sama dengan for i := 0; i < 10; i++.

Namun, agar adil, setelah menulis sendiri iter.N(tetapi sebelum membuat tolok ukur), saya kembali melalui program yang baru ditulis untuk melihat semua tempat yang bisa saya gunakan. Sebenarnya tidak banyak. Hanya ada satu tempat, di bagian non-vital dari kode saya, di mana saya bisa bertahan tanpa forklausa default yang lebih lengkap .

Jadi walaupun mungkin terlihat seperti ini adalah kekecewaan besar untuk bahasa pada prinsipnya, Anda mungkin menemukan - seperti saya - bahwa Anda sebenarnya tidak benar-benar membutuhkannya dalam praktik. Seperti yang diketahui oleh Rob Pike sebagai generik, Anda mungkin tidak benar-benar melewatkan fitur ini sebanyak yang Anda kira akan terjadi.

Chris Redford
sumber
1
Menggunakan saluran untuk iterasi sangat mahal; goroutine dan saluran murah, mereka tidak gratis. Jika rentang berulang saluran berakhir lebih awal, goroutine tidak pernah berakhir (kebocoran goroutine). Metode Iter telah dihapus dari paket vektor . " container / vektor: hapus Iter () dari antarmuka (Iter () hampir tidak pernah merupakan mekanisme yang tepat untuk memanggil). " Solusi iter Anda selalu yang paling mahal.
peterSO
4

Jika Anda ingin hanya mengulangi rentang tanpa menggunakan dan indeks atau apa pun, contoh kode ini berfungsi dengan baik untuk saya. Tidak perlu deklarasi tambahan, tidak _. Belum memeriksa kinerjanya.

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

PS Hari pertama di GoLang. Tolong, lakukan kritik jika itu pendekatan yang salah.

WHS
sumber
Sejauh ini (versi 1.13.6), itu tidak berfungsi. Melempar non-constant array boundke arahku.
WHS
1

Anda juga dapat melihat github.com/wushilin/stream

Ini adalah aliran malas seperti konsep 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()

Semoga ini membantu

Wu Shilin
sumber
0
package main

import "fmt"

func main() {

    nums := []int{2, 3, 4}
    for _, num := range nums {
       fmt.Println(num, sum)    
    }
}
Dvv Avinash
sumber
1
Tambahkan beberapa konteks ke kode Anda untuk membantu pembaca selanjutnya memahami maknanya.
Grant Miller
3
apa ini? jumlah tidak ditentukan.
naftalimich
0

Saya telah menulis sebuah paket di Golang yang meniru fungsi jangkauan Python:

Paket 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
}

Catatan: Saya telah menulis untuk bersenang-senang! Btw, kadang-kadang mungkin bermanfaat

Saddam Hossain
sumber