В Go можно выполнить итерацию по пользовательскому типу?

У меня есть настраиваемый тип, внутри которого есть фрагмент данных.

Возможно ли, реализуя некоторые функции или интерфейс, которым нужен оператор диапазона, итерировать (используя диапазон) по моему пользовательскому типу?

Ответы

Ответ 1

Короткий ответ - нет.

Длинный ответ по-прежнему нет, но его можно взломать так, как будто это работает. Но, чтобы быть ясным, это, безусловно, хак.

Есть несколько способов сделать это, но общая тема между ними заключается в том, что вы хотите каким-то образом преобразовать свои данные в тип, который Go способен варьировать.

Подход 1: Ломтики

Поскольку вы упомянули, что у вас есть внутренний кусочек, это может быть проще всего для вашего случая использования. Идея проста: ваш тип должен иметь метод Iterate() (или аналогичный), возвращаемое значение которого является срезом соответствующего типа. При вызове создается новый срез, содержащий все элементы структуры данных в любом порядке, в котором вы хотите их повторить. Итак, например:

func (m *MyType) Iterate() []MyElementType { ... }

mm := NewMyType()
for i, v := range mm.Iterate() {
    ...
}

Здесь есть несколько проблем. Во-первых, выделение - если вы не хотите раскрывать ссылки на внутренние данные (что, в общем, вы, вероятно, нет), вам нужно сделать новый фрагмент и скопировать все элементы. С точки зрения большого О, это не так уж плохо (вы все равно выполняете линейное количество операций, итераций по всему), но для практических целей это может иметь значение.

Кроме того, это не обрабатывает итерацию по мутирующим данным. Это, вероятно, не является проблемой большую часть времени, но если вы действительно хотите поддерживать параллельные обновления и определенные типы семантики итераций, вам может быть интересно.

Подход 2: Каналы

Каналы также могут быть размещены в Go. Идея состоит в том, чтобы ваш метод Iterate() породил goroutine, который будет перебирать элементы в вашей структуре данных и записывать их в канал. Затем, когда итерация завершена, канал может быть закрыт, что приведет к завершению цикла. Например:

func (m *MyType) Iterate() <-chan MyElementType {
    c := make(chan MyElementType)
    go func() {
        for _, v := range m.elements {
            c <- v
        }
        close(c)
    }()
    return c
}

mm := NewMyType()
for v := range mm.Iterate() {
    ...
}

Существует два преимущества этого метода по методу среза: во-первых, вам не нужно выделять линейный объем памяти (хотя вы можете захотеть, чтобы ваш канал имел немного буфера по соображениям производительности) и во-вторых, вы можете заставить ваш итератор хорошо играть с параллельными обновлениями, если вы в этом разбираетесь.

Большой недостаток этого подхода состоит в том, что если вы не будете осторожны, вы можете пропустить goroutines. Единственный способ сделать это, чтобы ваш канал имел достаточно глубокий буфер, чтобы удерживать все элементы в вашей структуре данных, чтобы goroutine мог его заполнить, а затем вернуться, даже если никакие элементы не считываются с канала (и тогда канал может позже будет собран мусор). Проблема здесь в том, что: а) вы снова вернулись к линейному распределению и, б) вам нужно знать, сколько элементов вы собираетесь писать, что делает остановку для всей параллельной версии.

Мораль истории состоит в том, что каналы милые для итерации, но вы, вероятно, не хотите их использовать.

Подход 3: Внутренние итераторы

Подпишитесь hobbs за до этого до меня, но я расскажу об этом здесь для полноты (и потому, что я хочу сказать немного больше об этом).

Идея здесь заключается в создании объекта-итератора сорта (или просто для того, чтобы ваш объект поддерживал только один итератор за раз, и итерацию на нем напрямую), как и на языках, которые поддерживают это более непосредственно. Таким образом, вы вызываете метод Next(), который: а) продвигает итератор к следующему элементу и b) возвращает логическое значение, указывающее, осталось ли что-либо. Тогда вам понадобится отдельный метод Get(), чтобы фактически получить значение текущего элемента. Использование этого на самом деле не использует ключевое слово range, но оно выглядит довольно естественным:

mm := MyNewType()
for mm.Next() {
    v := mm.Get()
    ...
}

Есть несколько преимуществ этой техники за предыдущие два. Во-первых, это не связано с распределением памяти вверх. Во-вторых, он поддерживает ошибки очень естественно. Хотя это не итератор, это именно то, что делает bufio.Scanner. В основном идея состоит в том, чтобы метод Error(), который вы вызываете после завершения итерации, завершен, чтобы увидеть, завершена ли итерация, потому что это было сделано, или потому, что ошибка была встречена на полпути. Для чисто структур данных в памяти это может быть неважно, но для тех, которые включают IO (например, перемещение дерева файловой системы, повторение результатов запроса к базе данных и т.д.), Это действительно приятно. Итак, чтобы завершить фрагмент кода выше:

mm := MyNewType()
for mm.Next() {
    v := mm.Get()
    ...
}
if err := mm.Error(); err != nil {
    ...
}

Заключение

Go не поддерживает ранжирование по произвольным структурам данных - или пользовательские итераторы - но вы можете его взломать. Если вам нужно сделать это в производственном коде, третий подход - это 100% путь, поскольку он является как самым чистым, так и наименее взломанным (в конце концов, стандартная библиотека включает этот шаблон).

Ответ 2

Нет, не используя range. range принимает массивы, фрагменты, строки, карты и каналы и что он.

Обычный тип идиомы для повторяющихся вещей (например, a bufio.Scanner) кажется

iter := NewIterator(...)
for iter.More() {
    item := iter.Item()
    // do something with item
}

но универсального интерфейса (в любом случае, не будет очень полезно, учитывая тип системы), и различные типы, реализующие шаблон, обычно имеют разные имена для своих методов More и Item (например, Scan и Text при a bufio.Scanner)

Ответ 3

Джошлф дал отличный ответ, но я хотел бы добавить пару вещей:

Использование каналов

Типичная проблема с итераторами каналов заключается в том, что вам нужно охватить всю структуру данных, иначе подпрограмма, подающая канал, останется навсегда зависшей. Но это может быть довольно легко обойти, вот один из способов:

func (s intSlice) chanIter() chan int {
    c := make(chan int)
    go func() {
        for _, i := range s {
            select {
            case c <- i:
            case <-c:
                close(c)
                return
            }
        }
        close(c)
    }()
    return c
}

В этом случае обратная запись в канал итератора прерывает итерацию раньше:

s := intSlice{1, 2, 3, 4, 5, 11, 22, 33, 44, 55}
c := s.chanIter()
for i := range c {
    fmt.Println(i)
    if i > 30 {
        // Send to c to interrupt
        c <- 0
    }
}

Здесь очень важно, чтобы вы не просто break из для цикла. Вы можете сломаться, но вы должны сначала написать на канал, чтобы гарантировать, что программа будет завершена.

Использование замыканий

Метод итерации, который я часто склоняюсь, заключается в использовании замыкания итератора. В этом случае итератор является значением функции, которое при повторном вызове возвращает следующий элемент и указывает, может ли итерация продолжаться:

func (s intSlice) cloIter() func() (int, bool) {
    i := -1
    return func() (int, bool) {
        i++
        if i == len(s) {
            return 0, false
        }
        return s[i], true
    }
}

Используйте это так:

iter := s.cloIter()
for i, ok := iter(); ok; i, ok = iter() {
    fmt.Println(i)
}

В данном случае это совершенно нормально, чтобы вырваться из циклы рано, iter будет в конечном итоге будет мусор.

Детская площадка

Здесь ссылка на реализации выше: http://play.golang.org/p/JC2EpBDQKA

Ответ 4

Есть еще один вариант, который не был упомянут.

Вы можете определить функцию Iter (fn func (int)), которая принимает некоторую функцию, которая будет вызываться для каждого элемента в вашем пользовательском типе.

type MyType struct {
    data []int
}

func (m *MyType) Iter(fn func(int)) {
    for _, item := range m.data {
        fn(item)
    }
}

И это можно использовать так:

d := MyType{
    data: []int{1,2,3,4,5},
}

f := func(i int) {
    fmt.Println(i)
}
d.Iter(f)

Детская площадка
Ссылка на рабочую реализацию: https://play.golang.org/p/S3CTQmGXj79