Có cách nào để lặp lại trên một loạt các số nguyên không?


174

Phạm vi của Go có thể lặp lại trên các bản đồ và lát, nhưng tôi đã tự hỏi liệu có cách nào để lặp lại trên một phạm vi số hay không, đại loại như thế này:

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

Hoặc có cách nào để biểu thị phạm vi số nguyên trong Go như cách Ruby thực hiện với Phạm vi lớp không?

Câu trả lời:


224

Bạn có thể, và nên, chỉ cần viết một vòng lặp for. Mã đơn giản, rõ ràng là cách đi.

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

266
Tôi không nghĩ rằng hầu hết mọi người sẽ gọi phiên bản ba biểu thức này đơn giản hơn những gì @Vishnu đã viết. Chỉ có lẽ sau nhiều năm và nhiều năm truyền bá C hoặc Java ;-)
Thomas Ahle

12
IMO vấn đề là bạn sẽ luôn có phiên bản ba biểu thức của vòng lặp for (nghĩa là bạn có thể làm nhiều hơn với nó, cú pháp từ OP chỉ tốt cho trường hợp hạn chế hơn của phạm vi số, vì vậy trong bất kỳ ngôn ngữ nào bạn sẽ muốn phiên bản mở rộng này) và nó hoàn thành đủ nhiệm vụ tương tự và dù sao cũng không khác biệt đáng kể, vậy tại sao phải học / nhớ một cú pháp khác. Nếu bạn đang mã hóa trên một dự án lớn và phức tạp, bạn có đủ lo lắng về việc đã có mà không phải đấu tranh với trình biên dịch về các cú pháp khác nhau cho một cái gì đó đơn giản như một vòng lặp.
Brad Peabody

3
@ThomasAhle đặc biệt xem xét C ++ chính thức thêm ký hiệu for_each (x, y) lấy cảm hứng từ thư viện mẫu boost
don sáng

5
@BradPeabody đây thực sự là một vấn đề ưu tiên. Python không có vòng lặp 3 biểu thức và hoạt động tốt. Nhiều người coi cú pháp for-every ít bị lỗi hơn và về bản chất không có gì là không hiệu quả.
VinGarcia

3
@necromancer ở đây là một bài đăng từ Rob Pike lập luận cho nhiều điều tương tự như câu trả lời của tôi. nhóm.google.com/d/msg/golang-nuts/7J8FY07dkW0/goWaNVOkQU0J . Có thể cộng đồng Go không đồng ý, nhưng khi nó đồng ý với một trong những tác giả của ngôn ngữ, nó thực sự không thể là câu trả lời tồi.
Paul Hankin

43

Đây là một chương trình để so sánh hai cách được đề xuất cho đến nay

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

Biên dịch như thế này để tạo ra sự tháo gỡ

go build -gcflags -S iter.go

Đây là đơn giản (Tôi đã xóa các hướng dẫn không có trong danh sách)

thiết lập

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

vòng

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     ,

Và đây là with_iter

thiết lập

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

vòng

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     ,

Vì vậy, bạn có thể thấy rằng giải pháp iter đắt hơn đáng kể mặc dù nó được hoàn toàn nội tuyến trong giai đoạn thiết lập. Trong giai đoạn vòng lặp có một hướng dẫn bổ sung trong vòng lặp, nhưng nó không quá tệ.

Tôi sẽ sử dụng vòng lặp đơn giản.


8
Tôi không thể "thấy rằng giải pháp lặp đắt hơn đáng kể." Phương pháp đếm hướng dẫn giả lắp ráp của bạn là thiếu sót. Chạy một điểm chuẩn.
peterSO

11
Một giải pháp gọi runtime.makeslicevà giải pháp khác thì không - Tôi không cần điểm chuẩn để biết rằng sẽ chậm hơn rất nhiều!
Nick Craig-Wood

6
runtime.makesliceđủ thông minh để không phân bổ bất kỳ bộ nhớ nếu bạn yêu cầu phân bổ kích thước bằng không. Tuy nhiên, ở trên vẫn gọi nó, và theo điểm chuẩn của bạn sẽ mất 10nS lâu hơn trên máy của tôi.
Nick Craig-Wood

4
điều này nhắc nhở mọi người đề xuất sử dụng C trên C ++ vì lý do hiệu suất
necromancer

5
Tranh luận về hiệu suất thời gian chạy của các hoạt động CPU nano giây, trong khi phổ biến ở Goland, có vẻ ngớ ngẩn đối với tôi. Tôi sẽ xem xét rằng một sự xem xét cuối cùng rất xa, sau khi đọc được. Ngay cả khi hiệu suất của CPU có liên quan, nội dung của vòng lặp for hầu như sẽ luôn tràn ngập bất kỳ sự khác biệt nào do chính vòng lặp phát sinh.
Jonathan Hartley

34

Mark Mishyn đã đề xuất sử dụng lát cắt nhưng không có lý do gì để tạo mảng với makevà sử dụng trong forlát cắt trả về của nó khi mảng được tạo qua nghĩa đen có thể được sử dụng và nó ngắn hơn

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

8
Nếu bạn không sử dụng biến, bạn cũng có thể bỏ qua phía bên trái và sử dụngfor range [5]int{} {
blockloop

6
Hạn chế là 5ở đây là một nghĩa đen và không thể được xác định tại thời điểm chạy.
Steve Powell

Là nó nhanh hơn hoặc so sánh với ba biểu thức bình thường cho vòng lặp?
Amit Tripathi

@AmitTripathi có, nó có thể so sánh được, thời gian thực hiện gần như giống nhau cho hàng tỷ lần lặp.
Daniil Grankin

18

iter là một gói rất nhỏ chỉ cung cấp một cách tổng hợp khác nhau để lặp qua các số nguyên.

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

Rob Pike (một tác giả của Go) đã chỉ trích nó :

Dường như hầu như mỗi khi ai đó nghĩ ra một cách để tránh làm điều gì đó như vòng lặp theo cách thành ngữ, bởi vì nó quá dài hoặc cồng kềnh, kết quả gần như luôn luôn là nhiều lần nhấn phím hơn so với điều được cho là ngắn hơn. [...] Điều đó đã bỏ qua tất cả những chi phí điên rồ mà những "cải tiến" này mang lại.


16
Phê bình của Pike rất đơn giản ở chỗ nó chỉ giải quyết các tổ hợp phím chứ không phải là chi phí tinh thần của các phạm vi liên tục chuyển hướng. Ngoài ra, với hầu hết các trình soạn thảo hiện đại, iterphiên bản thực sự sử dụng ít tổ hợp phím hơn rangeitersẽ tự động hoàn tất.
Chris Redford

1
@ lang2, forcác vòng lặp không phải là công dân hạng nhất của Unix giống như họ đang đi. Bên cạnh đó, không giống như for, seqluồng đến đầu ra tiêu chuẩn một chuỗi số. Có hay không lặp đi lặp lại trên chúng là tùy thuộc vào người tiêu dùng. Mặc dù for i in $(seq 1 10); do ... done là phổ biến trong Shell, nhưng đó chỉ là một cách để thực hiện một vòng lặp for, mà bản thân nó chỉ là một cách để tiêu thụ đầu ra seq, mặc dù là một cách rất phổ biến.
Daniel Farrell

2
Ngoài ra, Pike đơn giản không xem xét thực tế rằng một trình biên dịch (được cung cấp thông số ngôn ngữ bao gồm cú pháp phạm vi cho trường hợp sử dụng này) có thể được xây dựng theo cách để xử lý i in range(10)chính xác như thế nào i := 0; i < 10; i++.
Rouven B.

8

Đây là một điểm chuẩn để so sánh một Go for câu lệnh với câu lệnh ForClause và Go rangebằng cách sử dụng itergói.

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
}

Đầu ra:

$ 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
Nếu bạn đặt vòng lặp thành 10 thì thử lại điểm chuẩn bạn sẽ thấy sự khác biệt rõ rệt. Trên máy của tôi, ForClause mất 5,6 ns trong khi Iter mất 15,4 ns, do đó, việc gọi bộ cấp phát (mặc dù nó đủ thông minh để không phân bổ bất cứ thứ gì) vẫn tốn 10ns và cả đống mã phá vỡ I-cache.
Nick Craig-Wood

Tôi sẽ quan tâm để xem điểm chuẩn và phê bình của bạn cho gói tôi đã tạo và tham chiếu trong câu trả lời của tôi .
Chris Redford

5

Trong khi tôi đồng ý với mối quan tâm của bạn về việc thiếu tính năng ngôn ngữ này, có lẽ bạn sẽ chỉ muốn sử dụng một ngôn ngữ bình thường for vòng lặp . Và có lẽ bạn sẽ ổn với điều đó hơn bạn nghĩ khi bạn viết nhiều mã Go hơn.

Tôi đã viết gói iter này - được hỗ trợ bởi một forvòng lặp đơn giản, thành ngữ trả về các giá trị trên a chan int- trong nỗ lực cải thiện thiết kế được tìm thấy trong https://github.com/bradfitz/iter , được chỉ ra là có các vấn đề về bộ nhớ đệm và hiệu năng, cũng như một cách thực hiện thông minh, nhưng lạ và không trực quan. Phiên bản của riêng tôi hoạt động theo cùng một cách:

package main

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

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

Tuy nhiên, điểm chuẩn cho thấy việc sử dụng kênh là một lựa chọn rất tốn kém. Việc so sánh 3 phương thức có thể được chạy iter_test.gotrong gói của tôi bằng cách sử dụng

go test -bench=. -run=.

định lượng hiệu suất của nó kém như thế nào

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

Trong quá trình, điểm chuẩn này cũng cho thấy bradfitzgiải pháp hoạt động kém hơn so với formệnh đề tích hợp cho kích thước vòng lặp là10 .

Nói tóm lại, cho đến nay dường như không có cách nào được phát hiện để nhân đôi hiệu suất của formệnh đề tích hợp trong khi cung cấp một cú pháp đơn giản cho[0,n) giống như tìm thấy trong Python và Ruby.

Thật là xấu hổ vì có lẽ sẽ dễ dàng cho nhóm Go thêm một quy tắc đơn giản vào trình biên dịch để thay đổi một dòng như

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

để mã máy giống như for i := 0; i < 10; i++.

Tuy nhiên, để công bằng, sau khi tự viết iter.N(nhưng trước khi điểm chuẩn nó), tôi đã quay lại một chương trình được viết gần đây để xem tất cả những nơi tôi có thể sử dụng nó. Thật sự không có nhiều. Chỉ có một vị trí, trong một phần không quan trọng trong mã của tôi, nơi tôi có thể nhận được mà không cần mặc định đầy đủ hơnfor mệnh đề .

Vì vậy, mặc dù có vẻ như đây là một sự thất vọng lớn đối với ngôn ngữ về nguyên tắc, bạn có thể thấy - giống như tôi đã làm - rằng bạn thực sự không cần nó trong thực tế. Giống như Rob Pike được biết là nói về thuốc generic, bạn có thể không thực sự bỏ lỡ tính năng này nhiều như bạn nghĩ.


1
Sử dụng một kênh để lặp lại rất tốn kém; khỉ đột và kênh rẻ, chúng không miễn phí. Nếu phạm vi lặp trên kênh kết thúc sớm, goroutine không bao giờ kết thúc (rò rỉ goroutine). Phương thức Iter đã bị xóa khỏi gói vector . " Container / vector: xóa Iter () khỏi giao diện (Iter () gần như không bao giờ là cơ chế phù hợp để gọi). " Giải pháp lặp của bạn luôn đắt nhất.
peterSO

4

Nếu bạn muốn chỉ lặp đi lặp lại trong một phạm vi sử dụng và chỉ mục hoặc bất cứ điều gì khác, mẫu mã này hoạt động tốt với tôi. Không cần khai báo thêm, không _. Mặc dù không kiểm tra hiệu suất.

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

PS Ngày đầu tiên ở GoLang. Xin vui lòng, phê bình nếu đó là một cách tiếp cận sai.


Cho đến nay (phiên bản 1.13.6), nó không hoạt động. Ném non-constant array boundvào tôi.
WHS

1

Bạn cũng có thể kiểm tra github.com/wushilin/stream

Nó là một luồng lười biếng như khái niệm về 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()

Hi vọng điêu nay co ich


0
package main

import "fmt"

func main() {

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

1
Thêm một số bối cảnh vào mã của bạn để giúp người đọc trong tương lai hiểu rõ hơn về ý nghĩa của nó.
Grant Miller

3
Cái này là cái gì? tổng không được xác định.
naftalimich

0

Tôi đã viết một gói trong Golang bắt chước hàm phạm vi của Python:

Gói 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
}

Lưu ý: Tôi đã viết cho vui! Btw, đôi khi nó có thể hữu ích

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.