Чи є спосіб перебрати цілий спектр цілих чисел?


174

Діапазон Go може повторювати карти та фрагменти, але мені було цікаво, чи є спосіб перебрати ряд діапазонів, як-от так:

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

Або є спосіб представити діапазон цілих чисел у Go, як Ruby робить з класом Range ?

Відповіді:


224

Можна, і слід, просто написати цикл. Простий, очевидний код - це шлях.

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

268
Я не думаю, що більшість людей називали б цю тривиразну версію більш простою, ніж те, що написав @Vishnu. Лише, можливо, через роки та роки індоктринізації С або Java ;-)
Томас Ейл

12
Справа в IMO полягає в тому, що ви завжди будете мати цю тривиразну версію циклу for (тобто ви можете зробити набагато більше з нею, синтаксис з OP хороший лише для більш обмеженого випадку діапазону чисел, так будь-якою мовою ви хочете отримати цю розширену версію), і вона в достатній мірі виконує одне і те ж завдання, і нічим не відрізняється, тому навіщо вивчати / запам'ятовувати інший синтаксис. Якщо ви кодуєте великий і складний проект, вам достатньо турбуватися вже про те, щоб не боротися з компілятором про різні синтаксиси за щось таке просто, як цикл.
Бред Пібоді

3
@ThomasAhle, особливо зважаючи на C ++, офіційно додає позначення for_each (x, y), натхнене бібліотекою шаблонів підвищення
нехай яскравий

5
@BradPeabody це насправді питання переваги. Python не має 3-х виразного циклу і працює чудово. Багато хто вважає синтаксис для кожного набагато менш схильним до помилок, і в цьому немає нічого власне неефективного.
VinGarcia

3
@necromancer ось пост від Роб Пайк, який сперечається за те саме, що і моя відповідь. groups.google.com/d/msg/golang-nuts/7J8FY07dkW0/goWaNVOkQU0J . Можливо, спільнота Go не погоджується, але коли вона погоджується з одним з авторів мови, це насправді не може бути таким поганим у відповіді.
Пол Ханкін

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     ,

І ось з_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     ,

Таким чином, ви можете бачити, що рішення ітера значно дорожче, хоча воно повністю вбудоване у фазі налаштування. У циклі циклу є додаткова інструкція щодо циклу, але це не дуже погано.

Я б використав простий для циклу.


8
Я не можу "бачити, що рішення ітера значно дорожче". Ваш метод підрахунку інструкцій Go псевдоскладача є помилковим. Виконати орієнтир.
peterSO

11
Одне рішення дзвонить, runtime.makesliceа інше ні - мені не потрібен орієнтир, щоб знати, що буде набагато повільніше!
Нік Крейг-Вуд

6
Так runtime.makeslice, досить розумно, щоб не виділяти жодної пам'яті, якщо ви попросите розподілити нульовий розмір. Однак вищезгадане все ще називає це, і згідно з вашим еталоном, на моїй машині потрібно більше 10nS.
Нік Крейг-Вуд

4
це нагадує людям, які пропонують використовувати C над C ++ з міркувань продуктивності
некромант

5
Дебати під час виконання операцій наносекундних процесорів, хоча вони поширені в Goland, здаються мені дурними. Я вважаю, що це дуже далекий останній розгляд після читабельності. Навіть якщо продуктивність процесора була релевантною, вміст циклу for for майже завжди буде заповнено незалежно від відмінностей, що виникають у самій циклі.
Джонатан Хартлі

34

Марк Мішин запропонував використовувати фрагмент, але немає причин створювати масив makeі використовувати у forповерненому фрагменті його, коли масив, створений за допомогою літералу, може бути використаний, і він коротший

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

8
Якщо ви не збираєтесь використовувати змінну, ви можете також опустити ліву частину та використатиfor range [5]int{} {
blockloop

6
Недолік полягає в тому, що 5це буквально і не може бути визначений під час виконання.
Стів Пауелл

Це швидше або порівняно з звичайними трьома виразами для циклу?
Аміт Трипаті

@AmitTripathi так, це порівнянно, час виконання майже однаковий для мільярдів ітерацій.
Даніїл

18

iter - це дуже маленький пакет, який просто забезпечує синтаксично інший спосіб перебору чисел.

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

Роб Пайк (автор Go) критикував це :

Здається, що майже кожен раз, коли хтось придумує спосіб уникнути щось подібне до циклу ідіоматичним способом, тому що він відчуває себе занадто довгим або громіздким, в результаті майже завжди є більше натискань на клавіші, ніж річ, яка нібито коротша. [...] Це залишає осторонь усі божевільні накладні витрати, які ці "покращення" приносять.


16
Критика Пайка спрощена тим, що вона стосується лише натискань клавіш, а не розумових накладних частот, що постійно переосмислюються. Крім того , більшість сучасних редакторів, то iterверсія на насправді використовує менше натискань клавіш , тому що rangeі iterбуде автозаповнення.
Кріс Редфорд

1
@ lang2, forпетлі не є першокласним громадянином Unix, як вони в дорозі. Крім того, в відміну for, seqпотоки в стандартний висновок послідовність чисел. Незалежно від того, чи слід повторювати їх, залежить від споживача. Хоча for i in $(seq 1 10); do ... done це звичайно в Shell, це лише один спосіб зробити цикл for, який сам по собі є лише одним із способів споживання результатів seq, хоча і дуже поширеним.
Даніель Фаррелл

2
Крім того , щука просто не враховувати той факт , що при компіляції ( з урахуванням мови специфікації включені синтаксис діапазону для цього випадку використання) може бути побудована таким чином , щоб просто лікувати i in range(10)точно як i := 0; i < 10; i++.
Рувен Б.

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 нс, тоді як Iter займає 15,4 нс, тому виклик алокатора (навіть якщо він досить розумний, щоб нічого не виділяти) все ще коштує 10нс і цілу купу додаткового коду перезавантаження кешу.
Нік Крейг-Вуд

Мені було б цікаво побачити ваші орієнтири та критику щодо пакету, який я створив і на який посилався у своїй відповіді .
Кріс Редфорд

5

Хоча я схвалюю ваше занепокоєння з приводу відсутності цієї мовної функції, ви, мабуть, просто захочете використовувати звичайний forцикл. І вам, мабуть, буде з цим все гаразд, ніж ви думаєте, коли пишете більше Go-коду.

Я написав цей пакет iter - який підтримується простим, ідіоматичним forциклом, який повертає значення за a 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)
    }
}

Однак бенчмаркінг виявив, що використання каналу було дуже дорогим варіантом. Порівняння трьох методів, за допомогою яких можна запустити 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рішення має низькі результати порівняно з вбудованим forпунктом для розміру циклу 10.

Коротше кажучи, видається, що поки що не знайдено способу дублювання продуктивності вбудованого forпункту, надаючи простий синтаксис на [0,n)зразок того, що знайдений у Python та Ruby.

Що шкода, тому що, мабуть, команді Go було б легко додати до компілятора просте правило, щоб змінити рядок типу

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

до того ж машинного коду, що і for i := 0; i < 10; i++.

Однак, щоб бути справедливим, після написання свого власного iter.N(але перед порівняльним оцінкою) я повернувся через нещодавно написану програму, щоб побачити всі місця, де я міг би її використати. Насправді їх було не так багато. У нежиттєвому розділі мого коду було лише одне місце, куди я міг обійтись без більш повного за замовчуванням for.

Тож, хоча це може здатися, це величезне розчарування для мови в принципі, ви можете виявити - як і я, - що ви насправді цього не потребуєте на практиці. Як відомо, що Роб Пайк говорить про дженерики, ви, можливо, насправді не пропустите цю функцію настільки, наскільки думаєте, що хочете.


1
Використовувати канал для ітерації дуже дорого; городини та канали дешеві, вони не безкоштовні. Якщо діапазон ітерацій над каналом закінчується рано, програма ніколи не закінчується (витік goroutut). Метод Iter був видалений з векторного пакету . " контейнер / вектор: видалити Iter () з інтерфейсу (Iter () майже ніколи не є правильним механізмом для виклику). " Ваше рішення ітера завжди найдорожче.
peterSO

4

Якщо ви хочете просто повторити діапазон без використання та індексів чи чогось іншого, цей зразок коду для мене спрацював чудово. Ніякої додаткової декларації не потрібно, ні _. Хоча не перевірили продуктивність.

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

PS Найперший день у 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
Додайте у свій код якийсь контекст, щоб допомогти майбутнім читачам краще зрозуміти його значення.
Грант Міллер

3
що це? сума не визначена.
нафталіміч

0

Я написав пакет у Golang, який імітує функцію діапазону Python:

Пакет 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, іноді це може бути корисно

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.