Показаны сообщения с ярлыком структуры данных в Go. Показать все сообщения
Показаны сообщения с ярлыком структуры данных в Go. Показать все сообщения

среда, 5 августа 2020 г.

Пакет ring в Golang

Пакет ring реализует операции с круговыми списками.

Тип Ring

type Ring struct {
     Value interface{} // для использования клиентом; 
                       // нетронутый этой библиотекой
     // содержит отфильтрованные 
     // или неэкспортированные поля
}

Ring - это элемент кругового списка или кольца. У колец нет начала и конца; указатель на любой элемент кольца служит ссылкой на все кольцо. Пустые кольца представлены как nil указатели на Ring. Нулевое значение для Ring - это одноэлементное кольцо с nil Value.

Функция New

func New(n int) *Ring

New создает кольцо из n элементов.

Метод Do

func (r *Ring) Do(f func(interface{}))

Do вызывает функцию f для каждого элемента кольца в прямом порядке. Поведение Do не определено, если f изменяет *r.

Пример использования Do

package main

import (
    "container/ring"
    "fmt"
)

func main() {
    // Создаем новое кольцо размером 5
    r := ring.New(5)

    // Получаем длину кольца
    n := r.Len()

    // Инициализируем кольцо 
    // некоторыми целочисленными значениями
    for i := 0; i < n; i++ {
        r.Value = i
        r = r.Next()
    }

    // Обходим кольцо и распечатываем его содержимое
    r.Do(func(p interface{}) {
        fmt.Println(p.(int))
    })

}

Вывод:

0
1
2
3
4

Метод Len

func (r *Ring) Len() int

Len вычисляет количество элементов в кольце r. По времени выполняется пропорционально количеству элементов.

Пример использования Len

package main

import (
    "container/ring"
    "fmt"
)

func main() {
    // Создаем новое кольцо размером 4
    r := ring.New(4)

    // Печатаем его длину
    fmt.Println(r.Len())

}

Вывод:

4

Метод Link

func (r *Ring) Link(s *Ring) *Ring

Link соединяет кольцо r с кольцом s, так что r.Next() становится s и возвращает исходное значение для r.Next(). r не должно быть пустым.

Если r и s указывают на одно и то же кольцо, их связывание удаляет элементы между r и s из кольца. Удаленные элементы образуют вложенное кольцо, и результатом является ссылка на это вложенное кольцо (если элементы не были удалены, результатом все равно будет исходное значение для r.Next(), а не nil).

Если r и s указывают на разные кольца, их связывание создает одно кольцо с элементами s, вставленными после r. Результат указывает на элемент, следующий за последним элементом s после вставки.

Пример использования Link

package main

import (
    "container/ring"
    "fmt"
)

func main() {
    // Создаем два кольца, r и s, размера 2
    r := ring.New(2)
    s := ring.New(2)

    // Получаем длину кольца
    lr := r.Len()
    ls := s.Len()

    // Инициализируем r нулями
    for i := 0; i < lr; i++ {
        r.Value = 0
        r = r.Next()
    }

    // Инициализируем s единицами
    for j := 0; j < ls; j++ {
        s.Value = 1
        s = s.Next()
    }

    // Связать кольцо r и кольцо s
    rs := r.Link(s)

    // Обходим объединенное кольцо 
    // и распечатываем его содержимое
    rs.Do(func(p interface{}) {
        fmt.Println(p.(int))
    })

}

Вывод:

0
0
1
1

Метод Move

func (r *Ring) Move(n int) *Ring

Move перемещает n % r.Len() элементов назад (n < 0) или вперед (n >= 0) по кольцу и возвращает этот элемент кольца. r не должно быть пустым.

Пример использования Move

package main

import (
    "container/ring"
    "fmt"
)

func main() {
    // Создаем новое кольцо размером 5
    r := ring.New(5)

    // Получаем длину кольца
    n := r.Len()

    // Инициализируем кольцо 
    // некоторыми целочисленными значениями
    for i := 0; i < n; i++ {
        r.Value = i
        r = r.Next()
    }

    // Перемещаем указатель вперед на три шага
    r = r.Move(3)

    // Обходим кольцо и распечатываем его содержимое
    r.Do(func(p interface{}) {
        fmt.Println(p.(int))
    })

}

Вывод:

3
4
0
1
2

Метод Next

func (r *Ring) Next() *Ring

Next возвращает следующий элемент кольца. r не должно быть пустым.

Пример использования Next

package main

import (
    "container/ring"
    "fmt"
)

func main() {
    // Создаем новое кольцо размером 5
    r := ring.New(5)

    // Получаем длину кольца
    n := r.Len()

    // Инициализируем кольцо 
    // некоторыми целочисленными значениями
    for i := 0; i < n; i++ {
        r.Value = i
        r = r.Next()
    }

    // Обходим кольцо и распечатываем его содержимое
    for j := 0; j < n; j++ {
        fmt.Println(r.Value)
        r = r.Next()
    }

}

Вывод:

0
1
2
3
4

Метод Prev

func (r *Ring) Prev() *Ring

Prev возвращает предыдущий элемент кольца. r не должно быть пустым.

Пример использования Prev

package main

import (
    "container/ring"
    "fmt"
)

func main() {
    // Создаем новое кольцо размером 5
    r := ring.New(5)

    // Получаем длину кольца
    n := r.Len()

    // Инициализируем кольцо 
    // некоторыми целочисленными значениями
    for i := 0; i < n; i++ {
        r.Value = i
        r = r.Next()
    }

    // Обходим кольцо в обратном направлении 
    // и распечатываем его содержимое
    for j := 0; j < n; j++ {
        fmt.Println(r.Value)
        r = r.Prev()
    }

}

Вывод:

4
3
2
1
0

Метод Unlink

func (r *Ring) Unlink(n int) *Ring

Unlink удаляет n % r.Len() элементов из кольца r, начиная с r.Next(). Если n % r.Len() == 0, r остается неизменным. Результат - удаленное подкольцо. r не должно быть пустым.

Пример использования Unlink

package main

import (
    "container/ring"
    "fmt"
)

func main() {
    // Создаем новое кольцо размером 6
    r := ring.New(6)

    // Получаем длину кольца
    n := r.Len()

    // Инициализируем кольцо 
    // некоторыми целочисленными значениями
    for i := 0; i < n; i++ {
        r.Value = i
        r = r.Next()
    }

    // Отключаем три элемента от r, начиная с r.Next()
    r.Unlink(3)

    // Обходим оставшееся кольцо
    // и распечатываем его содержимое
    r.Do(func(p interface{}) {
        fmt.Println(p.(int))
    })

}

Вывод:

0
4
5


Читайте также:


четверг, 20 февраля 2020 г.

Две базовых реализации очереди FIFO в Golang

Простой способ реализовать структуру данных временной очереди в Go - это использовать срез:

  • чтобы добавить в очередь, вы используете встроенную функцию append
  • чтобы удалить из очереди - срезать первый элемент

var queue []string

queue = append(queue, "Hello ") // Добавление в очередь
queue = append(queue, "world!")

for len(queue) > 0 {
    fmt.Print(queue[0]) // Первый элемент
    queue = queue[1:]   // Удаление из очереди
}

Вывод:

Hello world!

Остерегайтесь утечек памяти

Вы можете удалить первый элемент перед удалением из очереди.

// Удаление из очереди
queue[0] = "" // Удаляем элемент 
              // (записываем нулевое значение)
queue = queue[1:]

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

Связанный список

Пакет container/list реализует двусвязный список, который можно использовать в качестве очереди.

queue := list.New()

queue.PushBack("Hello ") // Добавление в очередь
queue.PushBack("world!")

for queue.Len() > 0 {
    e := queue.Front() // Первый элемент
    fmt.Print(e.Value)

    queue.Remove(e) // Удаление из очереди
}

Вывод:

Hello world!


Читайте также:


купить игрушку gopher

пятница, 15 февраля 2019 г.

Go FAQ: Когда следует использовать указатель на интерфейс?

Почти никогда. Указатели на интерфейсные значения возникают только в редких, сложных ситуациях, связанных с маскировкой типа значения интерфейса для отложенной оценки.

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

Рассмотрим объявление переменной,

var w io.Writer

Функция печати fmt.Fprintf принимает в качестве первого аргумента значение, которое удовлетворяет io.Writer - то, что реализует канонический метод Write. Таким образом, мы можем написать

fmt.Fprintf(w, "hello, world\n")

Однако если мы передадим адрес w, программа не скомпилируется.

// Compile-time error (Ошибка компиляции).
fmt.Fprintf(&w, "hello, world\n") 

Единственным исключением является то, что любое значение, даже указатель на интерфейс, может быть назначено переменной пустого типа интерфейса (interface{}). Тем не менее, это почти наверняка ошибка, если значение является указателем на интерфейс; результат может сбивать с толку.


Читайте также:


среда, 13 февраля 2019 г.

Go FAQ: карты (maps) в Go

Почему карты в Go являются встроенным типом?

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

Почему карты не допускают использование срезов в качестве ключей?

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

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

Почему карты, срезы и каналы ссылаются, в то время как массивы являются значениями?

По этой теме есть длинная история. Сначала, карты и каналы были синтаксически указателями, и было невозможно объявить или использовать экземпляр без указателя. Также создатели Go боролись с тем, как должны работать массивы. В конце концов было решено, что строгое разделение указателей и значений сделало язык труднее в использовании. Изменение этих типов, чтобы они действовали как ссылки на связанные, совместно используемые структуры данных разрешило эти проблемы. Это изменение добавило прискорбную сложность языку, но оказало большое влияние на удобство использования: Go стал более продуктивным, удобным языком, когда он был представлен.


Читайте также:


Go FAQ: Как работают константы в Go?

Хотя Go строг по преобразованию между переменными разных числовых типов, константы в языке гораздо более гибкие. Литеральные константы, такие как 23, 3.14159 и math.Pi занимают своего рода идеальное числовое пространство с произвольной точностью и нет переполнения или ненаполнения. Например, значение math.Pi указано в 63 местах в исходном коде, а константные выражения, включающие значение, сохраняют точность выше того, что может хранить float64. Только когда константа или константное выражение присваиваются переменной - области памяти в программе - она становится «компьютерным» числом с обычными свойствами, с плавающей точкой и точностью.

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

sqrt2 := math.Sqrt(2)

без претензий от компилятора, потому что идеальное число 2 может быть преобразовано безопасно и точно в float64 для вызова math.Sqrt.


Читайте также:


понедельник, 11 февраля 2019 г.

Go FAQ: Почему в Go нет вариантных типов (variant types)?

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

Создатели Go рассмотрели добавление вариантных типов в Go, но после обсуждения решили оставить их, потому что они пересекаются с интерфейсами. Что будет, если элементы вариантного типа будут сами интерфейсами?

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


Читайте также:


Go FAQ: преобразования срезов

Могу ли я преобразовать []T в []interface{}?

Не напрямую. Это запрещено в спецификации языка, потому что два типа не имеют одинакового представления в памяти. Необходимо скопировать элементы индивидуально в целевой срез. В следующем примере преобразуется срез int в срез interface{}:

t := []int{1, 2, 3, 4}
s := make([]interface{}, len(t))
for i, v := range t {
    s[i] = v
}

Могу ли я преобразовать []T1 в []T2, если T1 и T2 имеют одинаковый базовый тип?

Последняя строка этого примера кода не компилируется.

type T1 int
type T2 int
var t1 T1
var x = T2(t1) // OK
var st1 []T1
var sx = ([]T2)(st1) // не OK, не компилируется

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


Читайте также:


суббота, 9 февраля 2019 г.

Go FAQ: Почему операции с map (картой) не определены как атомарные?

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

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

Доступ к карте небезопасен только когда происходят обновления. Пока все программы только читают - ищут элементы на карте, в том числе итерируя с помощью цикла for range, и не изменяя карты посредством присваивания к элементам карты или выполнения удаления - такой процесс (чтение) безопасен и при одновременном доступе к карте без синхронизации.

В качестве помощи, чтобы исправить использование карты, некоторые реализации языка содержат специальную проверку, которая автоматически сообщает в runtime (окружение среды исполнения) о небезопасном изменении карты при параллельном исполнении.


Читайте также:


суббота, 26 января 2019 г.

Эффективный Go: карты (maps)

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

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

var timeZone = map[string]int{
    "UTC":  0*60*60,
    "EST": -5*60*60,
    "CST": -6*60*60,
    "MST": -7*60*60,
    "PST": -8*60*60,
}

Назначение и выборка значений карты выглядит синтаксически так же, как выполнение того же самого для массивов и срезов, за исключением того, что индекс не должен быть целым числом.

offset := timeZone["EST"]

Попытка получить значение карты с ключом, который отсутствует в карте вернет нулевое значение для типа из записей в карте. Например, если карта содержит целые числа, поиск несуществующего ключа вернет 0. Сет (набор уникальных значений) может быть реализован как карта с типом значения bool. Задайте для элемента карты значение true, чтобы поместить значение в сет, а затем проверяйте его с помощью простой индексации.

attended := map[string]bool{
    "Ann": true,
    "Joe": true,
    ...
}

// условие будет false если персона не в карте
if attended[person] { 
    fmt.Println(person, " был на встрече")
}

Иногда вам нужно отличить отсутствующую запись от нулевого значения. Есть ли запись для "UTC" или это 0, потому что его нет в карте вообще? Вы можете различать с помощью формы множественного назначения.

var seconds int
var ok bool
seconds, ok = timeZone[tz]

По понятным причинам это называется “comma ok” идиома. В этом примере, если присутствует tz, seconds будет установлен соответствующим образом, и ok будет true; если нет, seconds будет установлен равным нулю, а ok быть false. Вот функция, которая объединяет это с хорошим сообщением об ошибке:

func offset(tz string) int {
    if seconds, ok := timeZone[tz]; ok {
        return seconds
    }
    log.Println("unknown time zone:", tz)
    return 0
}

Чтобы проверить наличие в карте, не беспокоясь о фактическом значении, вы можете использовать пустой идентификатор(_) вместо обычной переменной для значения.

_, present := timeZone[tz]

Чтобы удалить запись в карте, используйте встроенную функцию delete, аргументами которой являются карта и ключ для удаления. Это безопасно делать, даже если ключ уже отсутствует в карте.

delete(timeZone, "PDT")


Читайте также:


пятница, 25 января 2019 г.

Эффективный Go: двумерные срезы

Массивы и срезы в Go являются одномерными. Чтобы создать эквивалент двумерного массива или среза, необходимо определить массив массивов или срез срезов, например:

// 3x3 массив, на самом деле массив массивов.
type Transform [3][3]float64  

// Срез byte срезов.
type LinesOfText [][]byte     

Поскольку срезы имеют переменную длину, возможно иметь каждый внутренний срез с разной длиной. Это может быть обычной ситуацией, как в нашем LinesOfText примере: каждая строка имеет независимую длину.

text := LinesOfText{
    []byte("Now is the time"),
    []byte("for all good gophers"),
    []byte("to bring some fun to the party."),
}

Иногда необходимо выделить двумерный срез - ситуация, которая может возникнуть, когда, например, происходит обработка сканированных строк пикселей. Есть два способа добиться этого. Одним из них является выделение каждого среза независимо; другой состоит в том, чтобы выделить один массив и указать отдельные срезы в нем. Какой из них использовать, зависит от вашего приложения. Если срезы могут расти или сокращаться, они должны быть выделены независимо чтобы избежать перезаписи следующей строки; если нет, то может быть более эффективным построение объекта с однократной аллокацией. Вот эскизы двух методов. Первый способ, строка за раз:

// Аллоцируем срез самого верхнего уровня.
// Один ряд на каждый элемент y.
picture := make([][]uint8, YSize) 

// Цикл по рядам, аллоцируем срез для каждого ряда.
for i := range picture {
    picture[i] = make([]uint8, XSize)
}

И второй способ - как одно аллоцирование, разбитое на строки:

// Аллоцируем срез самого верхнего уровня, 
// такой же как и прежде.
// Один ряд на каждый элемент y.
picture := make([][]uint8, YSize) 

// Аллоцируем один большой срез для хранения всех пикселей.
// Имеем тип []uint8 даже хотя изображение это [][]uint8.
pixels := make([]uint8, XSize*YSize) 

// Цикл по рядам, создаем срез каждого ряда 
// от начала среза оставшихся пикселей.
for i := range picture {
    picture[i], pixels = pixels[:XSize], pixels[XSize:]
}


Читайте также:


воскресенье, 13 января 2019 г.

Основы Go: изменения карт

Добавление или изменение элемента карты m:

m[key] = elem

Получить элемент:

elem = m[key]

Удалить элемент:

delete(m, key)

Проверить, что ключ присутствует с помощью присвоения двух значений:

elem, ok = m[key]

Если key присутствует в m, ok будет true. Если нет, ok - false.

Если key не присутствует в карте, то elem будет нулевым значением типа элементов карты.

Примечание: если elem или ok не были объявлены, то вы можете использовать краткую форму:

elem, ok := m[key]

Пример изменения карты:

package main

import "fmt"

func main() {
  m := make(map[string]int)

  m["Answer"] = 42
  fmt.Println("Значение:", m["Answer"])

  m["Answer"] = 48
  fmt.Println("Значение:", m["Answer"])

  delete(m, "Answer")
  fmt.Println("Значение:", m["Answer"])

  v, ok := m["Answer"]
  fmt.Println("Значение:", v, "Присутствует?", ok)
}

Вывод:

Значение: 42
Значение: 48
Значение: 0
Значение: 0 Присутствует? false


Читайте также:


суббота, 12 января 2019 г.

Основы Go: карты (словари)

Карта (map) ассоциирует ключ и значение.

Нулевое значение карты это nil. Нулевая карта не содержит ключи, и ключи не могут быть добавлены.

Функция make возвращает карту заданного типа, инициализированную и готовую к использованию.

package main

import "fmt"

type Vertex struct {
  Lat, Long float64
}

var m map[string]Vertex

func main() {
  m = make(map[string]Vertex)
  m["Bell Labs"] = Vertex{
    40.68433, -74.39967,
  }
  fmt.Println(m["Bell Labs"])
}

Вывод:

{40.68433 -74.39967}

Литералы карт

Литералы карт похожи на литералы структур, но ключи обязательны.

package main

import "fmt"

type Vertex struct {
  Lat, Long float64
}

var m = map[string]Vertex{
  "Bell Labs": Vertex{
    40.68433, -74.39967,
  },
  "Google": Vertex{
    37.42202, -122.08408,
  },
}

func main() {
  fmt.Println(m)
}

Вывод:

map[Bell Labs:{40.68433 -74.39967} 
    Google:{37.42202 -122.08408}]

Если тип верхнего уровня всего лишь имя типа, то вы можете не указывать его для элементов литерала.

package main

import "fmt"

type Vertex struct {
  Lat, Long float64
}

var m = map[string]Vertex{
  "Bell Labs": {40.68433, -74.39967},
  "Google":    {37.42202, -122.08408},
}

func main() {
  fmt.Println(m)
}

Вывод:

map[Bell Labs:{40.68433 -74.39967} 
    Google:{37.42202 -122.08408}]


Читайте также:


Основы Go: range, итерация по срезам и картам

Итерация по срезу или карте (map) производится с помощью формы цикла for с ключевым словом range.

На каждой итерации по срезу возвращаются два значения. Первое - это индекс, а второе - копия элемента по этому индексу.

package main

import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
  for i, v := range pow {
    fmt.Printf("2**%d = %d\n", i, v)
  }
}

Вывод:

2**0 = 1
2**1 = 2
2**2 = 4
2**3 = 8
2**4 = 16
2**5 = 32
2**6 = 64
2**7 = 128

Вы можете опустить индекс или значение путем его присвоения к _.

Если вам нужен только индекс, опустите ", value" полностью.

package main

import "fmt"

func main() {
  pow := make([]int, 10)
  for i := range pow {
    pow[i] = 1 << uint(i) // == 2**i
  }
  for _, value := range pow {
    fmt.Printf("%d\n", value)
  }
}

Вывод:

1
2
4
8
16


Читайте также:


Основы Go: добавление к срезу

Необходимость добавления новых элементов к срезу возникает часто, и Go предоставляет для этого встроенную функцию append. Документация по встроенному пакету описывает append следующим образом.

func append(s []T, vs ...T) []T

Первый параметр s для append это срез элементов типа T, а остальные - значения типа T для добавления к срезу.

Результатом append является срез, который содержит все элементы оригинального среза, а также новые значения.

Если нижележащий массив среза s слишком мал, чтобы вместить все значения, то будет создан новый массив большего размера. Результирующий срез будет ссылаться на этот новый массив.

Пример использования append:

package main

import "fmt"

func main() {
  var s []int
  printSlice(s)

  // append работает на нулевых массивах.
  s = append(s, 0)
  printSlice(s)

  // Срез растет по необходимости.
  s = append(s, 1)
  printSlice(s)

  // Мы можем добавить больше одного элемента за раз.
  s = append(s, 2, 3, 4)
  printSlice(s)
}

func printSlice(s []int) {
  fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

Вывод:

len=0 cap=0 []
len=1 cap=2 [0]
len=2 cap=2 [0 1]
len=5 cap=8 [0 1 2 3 4]


Читайте также:


пятница, 11 января 2019 г.

Основы Go: создание среза с помощью make

Срезы могут быть созданы с помощью встроенной функции make; так можно создавать массивы с динамическим размером.

Функция make создает обнуленный массив и возвращает срез, который ссылается на этот массив.

a := make([]int, 5)  // len(a)=5

Чтобы указать вместимость, укажите третий аргумент к make:

b := make([]int, 0, 5) // len(b)=0, cap(b)=5

b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:]      // len(b)=4, cap(b)=4

Пример использования make:

package main

import "fmt"

func main() {
  a := make([]int, 5)
  printSlice("a", a)

  b := make([]int, 0, 5)
  printSlice("b", b)

  c := b[:2]
  printSlice("c", c)

  d := c[2:5]
  printSlice("d", d)
}

func printSlice(s string, x []int) {
  fmt.Printf("%s len=%d cap=%d %v\n", 
      s, len(x), cap(x), x)
}

Вывод:

a len=5 cap=5 [0 0 0 0 0]
b len=0 cap=5 []
c len=2 cap=5 [0 0]
d len=3 cap=3 [0 0 0]

Срезы срезов

Срезы могут содержать данные любого типа, в том числе и другие срезы.

package main

import (
  "fmt"
  "strings"
)

func main() {
  // Создаем доску для крестиков-ноликов.
  board := [][]string{
    []string{"_", "_", "_"},
    []string{"_", "_", "_"},
    []string{"_", "_", "_"},
  }

  // Игроки делают ходы.
  board[0][0] = "X"
  board[2][2] = "O"
  board[1][2] = "X"
  board[1][0] = "O"
  board[0][2] = "X"

  for i := 0; i < len(board); i++ {
    fmt.Printf("%s\n", strings.Join(board[i], " "))
  }
}

Вывод:

X _ X
O _ X
_ _ O


Читайте также:


четверг, 10 января 2019 г.

Основы Go: размер и вместимость среза

Срез имеет размер (длину) и вместимость.

Размер среза - это количество элементов, которые он содержит.

Вместимость среза - это количество элементов в его нижележащем массиве, начиная с первого элемента в срезе.

Размер и вместимость среза s могут быть получены с помощью len(s) и cap(s).

Размер среза может быть увеличен путем повторной операции "срезания", при условии, что он обладает достаточной вместимостью.

package main

import "fmt"

func main() {
  s := []int{2, 3, 5, 7, 11, 13}
  printSlice(s)

  // Срезаем срез, чтобы задать ему нулевую длину.
  s = s[:0]
  printSlice(s)

  // Расширяем его длину.
  s = s[:4]
  printSlice(s)

  // Удаляем два его первых значения.
  s = s[2:]
  printSlice(s)
}

func printSlice(s []int) {
  fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

Вывод:

len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]

Нулевые срезы

Нулевым значением среза является nil.

Длина и вместимость нулевого среза равны 0, и у него нет нижележащего массива.

package main

import "fmt"

func main() {
  var s []int
  fmt.Println(s, len(s), cap(s))
  if s == nil {
    fmt.Println("nil!")
  }
}

Вывод:

[] 0 0
nil!


Читайте также: