Existe uma maneira de iterar em um intervalo de números inteiros?


175

O intervalo do Go pode iterar em mapas e fatias, mas eu queria saber se existe uma maneira de iterar em um intervalo de números, algo como isto:

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

Ou existe uma maneira de representar o intervalo de números inteiros no Go, como o Ruby faz com a classe Range ?

Respostas:


225

Você pode, e deve, apenas escrever um loop for. Código simples e óbvio é o caminho a seguir.

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

269
Eu não acho que a maioria das pessoas chamaria essa versão de três expressões mais simples do que o que o @Vishnu escreveu. Só talvez depois de anos e anos de C ou Java doutrinação ;-)
Thomas Ahle

12
Na IMO, o ponto é que você sempre terá essa versão de três expressões do loop for (ou seja, você pode fazer muito mais com ela, a sintaxe do OP é boa apenas para o caso mais restrito de um intervalo numérico, portanto em qualquer idioma, você desejará esta versão estendida) e ela realiza suficientemente a mesma tarefa, e não é notavelmente diferente de qualquer maneira, então por que ter que aprender / lembrar de outra sintaxe. Se você está codificando em um projeto grande e complexo, já tem o suficiente para se preocupar sem precisar combater o compilador sobre várias sintaxes para algo tão simples quanto um loop.
Brad Peabody

3
@ThomasAhle especialmente considerando C ++ está adicionando oficialmente for_each notação (x, y) inspirado pela biblioteca de impulso template
don brilhante

5
@ BradPeabody isso é realmente uma questão de preferência. O Python não possui o loop de 3 expressões e funciona bem. Muitos consideram a sintaxe de cada uma delas muito menos propensa a erros e não há nada intrinsecamente ineficiente nela.
VinGarcia

3
@ necromancer aqui está um post de Rob Pike discutindo quase a mesma coisa que a minha resposta. groups.google.com/d/msg/golang-nuts/7J8FY07dkW0/goWaNVOkQU0J . Pode ser que a comunidade Go discorde, mas quando concorda com um dos autores do idioma, não pode ser uma resposta tão ruim assim.
Paul Hankin

43

Aqui está um programa para comparar as duas maneiras sugeridas até agora

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

Compile assim para gerar desmontagem

go build -gcflags -S iter.go

Aqui está simples (removi as instruções não da lista)

configuração

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

ciclo

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     ,

E aqui está with_iter

configuração

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

ciclo

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     ,

Assim, você pode ver que a solução iter é consideravelmente mais cara, mesmo estando totalmente alinhada na fase de configuração. Na fase do loop, há uma instrução extra no loop, mas não é tão ruim.

Eu usaria o simples para loop.


8
Não consigo "ver que a solução iter é consideravelmente mais cara". Seu método de contar as instruções do pseudo-montador Go é defeituoso. Execute uma referência.
PeterSO

11
Uma solução chama runtime.makeslicee a outra não - eu não preciso de um benchmark para saber que será muito mais lento!
Nick Craig-Wood

6
Sim runtime.makesliceé inteligente o suficiente para não alocar memória se você solicitar uma alocação de tamanho zero. No entanto, o que foi dito acima ainda o chama e, de acordo com o seu benchmark, leva 10nS mais na minha máquina.
Nick Craig-Wood

4
Isto lembra de pessoas que sugerem usar C sobre C ++ por motivos de desempenho
necromante

5
Debater o desempenho em tempo de execução das operações da CPU em nanossegundos, embora seja comum em Goland, parece bobagem para mim. Eu consideraria essa última consideração muito distante, depois da legibilidade. Mesmo se o desempenho da CPU for relevante, o conteúdo do loop for quase sempre inundará quaisquer diferenças incorridas pelo próprio loop.
Jonathan Hartley

34

Foi sugerido por Mark Mishyn usar a fatia, mas não há razão para criar uma matriz com makee usar na forfatia retornada quando a matriz criada via literal pode ser usada e é mais curta

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

8
Se você não está indo para usar a variável que você também pode omitir o lado esquerdo e usofor range [5]int{} {
blockloop

6
A desvantagem é que 5aqui é um literal e não pode ser determinado em tempo de execução.
22418 Steve

É mais rápido ou comparável às três expressões normais do loop?
Amit Tripathi

@AmitTripathi sim, é comparável, o tempo de execução é quase o mesmo para bilhões de iterações.
Daniil Grankin 27/03/19

18

O iter é um pacote muito pequeno que apenas fornece uma maneira diferente de fazer iterações sobre números inteiros.

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

Rob Pike (um autor de Go) criticou :

Parece que quase sempre que alguém cria uma maneira de evitar fazer algo como um loop for da maneira idiomática, porque parece muito longa ou complicada, o resultado é quase sempre mais pressionamentos de tecla do que supostamente mais curto. [...] Isso deixa de lado toda a sobrecarga louca que essas "melhorias" trazem.


16
A crítica de Pike é simplista, pois aborda apenas as teclas digitadas e não a sobrecarga mental de intervalos constantemente redeclarantes. Além disso, na maioria dos editores modernos, a iterversão realmente usa menos digitação, porque rangee itervão autocomplete.
22420 Chris Redford

1
@ lang2, os forloops não são um cidadão de primeira classe do Unix como eles estão. Além disso, ao contrário for, os seqfluxos para saída padrão de uma sequência de números. Se iterar ou não sobre eles depende do consumidor. Embora for i in $(seq 1 10); do ... done seja comum no Shell, é apenas uma maneira de fazer um loop for, que é apenas uma maneira de consumir a saída seq, embora muito comum.
Daniel Farrell

2
Além disso, Pike simplesmente não considera o fato de que uma compilação (dadas as especificações de idioma incluíam uma sintaxe de intervalo para este caso de uso) poderia ser criada de maneira a tratar i in range(10)exatamente da mesma maneira i := 0; i < 10; i++.
Rouven B.

8

Aqui está uma referência para comparar uma forinstrução Go com uma instrução ForClause e Go rangeusando o iterpacote.

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
}

Resultado:

$ 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
Se você definir loops para 10 e tentar novamente o benchmark, verá uma diferença acentuada. Na minha máquina, a ForClause usa 5,6 ns, enquanto o Iter usa 15,4 ns. Portanto, chamar o alocador (mesmo que seja inteligente o suficiente para não alocar nada) ainda custa 10ns e um monte de código extra de armazenamento em cache de I.
Nick Craig-Wood

Gostaria de ver seus benchmarks e críticas para o pacote que criei e referenciei na minha resposta .
Chris Redford

5

Embora eu tenha pena da sua preocupação por não ter esse recurso de idioma, você provavelmente só desejará usar um forloop normal . E você provavelmente ficará mais bem com isso do que pensa ao escrever mais código Go.

Eu escrevi este pacote iter - que é apoiado por um forloop idiomático simples que retorna valores acima de um chan int- em uma tentativa de melhorar o design encontrado em https://github.com/bradfitz/iter , que foi apontado como tendo problemas de cache e desempenho, bem como uma implementação inteligente, mas estranha e não intuitiva. Minha própria versão funciona da mesma maneira:

package main

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

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

No entanto, o benchmarking revelou que o uso de um canal era uma opção muito cara. A comparação dos 3 métodos, que podem ser executados iter_test.gono meu pacote usando

go test -bench=. -run=.

quantifica o quão ruim é seu desempenho

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

No processo, essa referência também mostra como a bradfitzsolução tem um desempenho inferior ao da forcláusula interna para um tamanho de loop de 10.

Em suma, até agora não parece haver nenhuma maneira de duplicar o desempenho do for cláusula interna, fornecendo uma sintaxe simples, [0,n)como a encontrada em Python e Ruby.

O que é uma pena, porque provavelmente seria fácil para a equipe Go adicionar uma regra simples ao compilador para alterar uma linha como

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

para o mesmo código de máquina que for i := 0; i < 10; i++ .

No entanto, para ser justo, depois de escrever o meu próprio iter.N (mas antes de compará-lo), voltei a um programa recentemente escrito para ver todos os lugares onde eu poderia usá-lo. Na verdade, não havia muitos. Havia apenas um ponto, em uma seção não vital do meu código, onde eu poderia sobreviver sem a forcláusula padrão mais completa .

Portanto, embora possa parecer uma decepção enorme para o idioma em princípio, você pode descobrir - como eu fiz - que realmente não precisa dele na prática. Como Rob Pike é conhecido por dizer sobre genéricos, você pode realmente não perder esse recurso tanto quanto pensa.


1
Usar um canal para iteração é muito caro; goroutines e canais são baratos, não são gratuitos. Se o intervalo iterativo sobre o canal terminar cedo, a goroutine nunca termina (um vazamento de goroutine). O método Iter foi excluído do pacote vetorial . " container / vector: remova Iter () da interface (Iter () quase nunca é o mecanismo certo para chamar). " Sua solução iter é sempre a mais cara.
PeterSO 4/04

4

Se você deseja apenas iterar em um intervalo sem o uso de índices ou qualquer outra coisa, esse exemplo de código funcionou bem para mim. Nenhuma declaração extra necessária, não _. Não verifiquei o desempenho, no entanto.

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

PS O primeiro dia em GoLang. Por favor, critique se for uma abordagem errada.


Até agora (versão 1.13.6), ele não funciona. Jogando non-constant array boundpara mim.
WHS

1

Você também pode conferir github.com/wushilin/stream

É um fluxo lento como o conceito de 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()

Espero que isto ajude


0
package main

import "fmt"

func main() {

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

1
Adicione algum contexto ao seu código para ajudar os futuros leitores a entenderem melhor o significado.
26618 Grant Miller

3
o que é isso? soma não está definida.
Naftalimich 6/11

0

Eu escrevi um pacote no Golang que imita a função range do Python:

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

Nota: eu escrevi por diversão! Btw, às vezes pode ser útil

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.