Показаны сообщения с ярлыком slices (срезы) в Go. Показать все сообщения
Показаны сообщения с ярлыком slices (срезы) в Go. Показать все сообщения

четверг, 11 ноября 2021 г.

Go для Java разработчиков: срезы, создание значений

Срезы

Срез - это концептуально структура с тремя полями:

  • указатель на массив,
  • длина,
  • и емкость

Срезы поддерживают оператор [] для доступа к элементам базового массива.

  • Встроенная функция len возвращает длину среза.
  • Встроенная функция cap возвращает емкость.

Для данного массива или другого среза a новый срез создается через a[i:j].

  • Это создает новый срез, который ссылается на a, начинается с индекса i и заканчивается перед индексом j.
  • Имеет длину j - i.
  • Если i не указан, срез начинается с 0.
  • Если j опущен, срез заканчивается на len(a).

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

Емкость нового среза - это просто емкость минус i. Емкость массива - это длина массива.

var s []int
var a [10]int

s = a[:] // сокращение от s = a[0:len(a)]

Если вы создаете значение типа [100]byte (массив из 100 байтов, возможно, буфер) и хотите передать его функции, не копируя его, объявите параметр функции как тип []byte и передайте срез массива. Срезы также можно создавать с помощью функции make, как описано ниже.

Срезы в сочетании со встроенной функцией append предлагают во многом те же функции, что и ArrayList в Java.

s0 := []int{1, 2}
s1 := append(s0, 3)     // добавляем один элемент
s2 := append(s1, 4, 5)  // добавляем несколько элементов
s3 := append(s2, s0...) // добавляем срез

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

Создание значений

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

make(map[string]int)

возвращает вновь выделенное значение типа map[string]int.

В отличие от new, make возвращает фактический объект, а не адрес. Это согласуется с тем фактом, что карты и каналы являются ссылочными типами.

Для карт make принимает подсказку о емкости в качестве второго необязательного аргумента.

Для каналов есть необязательный второй аргумент, который устанавливает емкость буферизации канала; по умолчанию - 0 (без буферизации).

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

m := make([]int, 10, 20) // То же, что и new([20]int)[:10]


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


воскресенье, 8 августа 2021 г.

Трюки со срезами в Golang

Фильтрация без аллокации

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

b := a[:0]
for _, x := range a {
    if f(x) {
        b = append(b, x)
    }
}

Для элементов, которые должны быть удалены сборщиком мусора, впоследствии может быть включен следующий код:

for i := len(b); i < len(a); i++ {
    a[i] = nil // или нулевое значение T
}

Расположение содержимого среза в обратном порядке

Чтобы заменить содержимое среза теми же элементами, но в обратном порядке:

for i := len(a)/2-1; i >= 0; i-- {
    opp := len(a)-1-i
    a[i], a[opp] = a[opp], a[i]
}

То же самое, только с двумя индексами:

for left, right := 0, len(a)-1; left < right; left, right = left+1, right-1 {
    a[left], a[right] = a[right], a[left]
}

Перемешивание содержимого среза

Алгоритм Фишера – Йейтса:

Начиная с Go 1.10, это доступно по адресу math/rand.Shuffle

for i := len(a) - 1; i > 0; i-- {
    j := rand.Intn(i + 1)
    a[i], a[j] = a[j], a[i]
}

Пакетирование (создание батчей) с минимальным выделением ресурсов

Полезно, если вы хотите выполнять пакетную обработку больших срезов.

actions := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
batchSize := 3
batches := make([][]int, 0, (len(actions) + batchSize - 1) / batchSize)

for batchSize < len(actions) {
    actions, batches = actions[batchSize:], append(batches, actions[0:batchSize:batchSize])
}
batches = append(batches, actions)

Дает следующее:

[[0 1 2] [3 4 5] [6 7 8] [9]]

Дедупликация на месте (сопоставимая)

import "sort"

in := []int{3,2,1,4,3,2,1,4,1} // любой элемент можно отсортировать
sort.Ints(in)
j := 0
for i := 1; i < len(in); i++ {
	if in[j] == in[i] {
		continue
	}
	j++
	// сохраняем исходные данные
	// in[i], in[j] = in[j], in[i]
	// устанавливаем только то, что требуется
	in[j] = in[i]
}
result := in[:j+1]
fmt.Println(result) // [1 2 3 4]

Перемещение элемента на передний план или вставка, если отсуствует

// moveToFront перемещает needle в начало среза
func moveToFront(needle string, haystack []string) []string {
    if len(haystack) != 0 && haystack[0] == needle {
        return haystack
    }
    prev := needle
    for i, elem := range haystack {
        switch {
        case i == 0:
            haystack[0] = needle
            prev = elem
        case elem == needle:
            haystack[i] = prev
            return haystack
        default:
            haystack[i] = prev
            prev = elem
        }
    }
    return append(haystack, prev)
}

haystack := []string{"a", "b", "c", "d", "e"} // [a b c d e]
haystack = moveToFront("c", haystack)         // [c a b d e]
haystack = moveToFront("f", haystack)         // [f c a b d e]

Раздвижное окно

func slidingWindow(size int, input []int) [][]int {
    // возвращает входной срез как первый элемент
    if len(input) <= size {
        return [][]int{input}
    }

    // выделяем срез точного размера, который нам нужен
    r := make([][]int, 0, len(input)-size+1)

    for i, j := 0, size; j <= len(input); i, j = i+1, j+1 {
        r = append(r, input[i:j])
    }

    return r
}

a:=[]int{1,2,3,4,5}
aa := slidingWindow(3, a)
fmt.Println(aa) // [[1 2 3] [2 3 4] [3 4 5]]


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


четверг, 24 декабря 2020 г.

Go style guides: nil - это допустимый срез

nil - это допустимый срез длины 0. Это означает, что,

  • Вы не должны явно возвращать срез нулевой длины. Вместо этого верните nil.

    Менее удачный пример:

    if x == "" {
        return []int{}
    }
    

    Более удачный пример:

    if x == "" {
        return nil
    }
    

  • Чтобы проверить, пуст ли срез, всегда используйте len(s) == 0. Не проверяйте на nil.

    Менее удачный пример:

    func isEmpty(s []string) bool {
        return s == nil
    }
    

    Более удачный пример:

    func isEmpty(s []string) bool {
        return len(s) == 0
    }
    

  • Нулевое значение (срез, объявленный с помощью var) можно сразу использовать без make().

    Менее удачный пример:

    nums := []int{}
    // или, nums := make([]int)
    
    if add1 {
        nums = append(nums, 1)
    }
    
    if add2 {
        nums = append(nums, 2)
    }
    

    Более удачный пример:

    var nums []int
    
    if add1 {
        nums = append(nums, 1)
    }
    
    if add2 {
        nums = append(nums, 2)
    }
    

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


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


четверг, 10 декабря 2020 г.

Go style guides: производительность, указание емкости контейнера

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

Указание подсказок емкости карты

По возможности предоставляйте подсказки (hint) по емкости при инициализации карт с помощью make().

make(map[T1]T2, hint)

Предоставление подсказки о емкости для make() приводит к попытке подобрать правильный размер карты во время инициализации, что снижает потребность в увеличении карты и распределении по мере добавления элементов в карту.

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

Менее удачный вариант:

m := make(map[string]os.FileInfo)

files, _ := ioutil.ReadDir("./files")
for _, f := range files {
    m[f.Name()] = f
}

m создается без указания размера; во время назначения может быть больше выделений.

Более удачный вариант:

files, _ := ioutil.ReadDir("./files")

m := make(map[string]os.FileInfo, len(files))
for _, f := range files {
    m[f.Name()] = f
}

m создается с подсказкой размера; во время назначения может быть меньше выделений.

Указание емкости среза

По возможности предоставляйте подсказки о емкости при инициализации срезов с помощью make(), особенно при планировании дальнейших добавлений в срез.

make([]T, length, capacity)

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

Менее удачный вариант:

for n := 0; n < b.N; n++ {
    data := make([]int, 0)
    for k := 0; k < size; k++{
        data = append(data, k)
    }
}

BenchmarkBad    100000000    2.48s

Более удачный вариант:

for n := 0; n < b.N; n++ {
    data := make([]int, 0, size)
    for k := 0; k < size; k++{
        data = append(data, k)
    }
}

BenchmarkGood   100000000    0.21s


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


пятница, 6 ноября 2020 г.

Go style guides: копирование срезов и карт на границах

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

Получение срезов и карт

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

Неудачный вариант:

func (d *Driver) SetTrips(trips []Trip) {
    d.trips = trips
}

trips := ...
d1.SetTrips(trips)

// Вы хотели изменить d1.trips?
trips[0] = ...

Хороший вариант:

func (d *Driver) SetTrips(trips []Trip) {
    d.trips = make([]Trip, len(trips))
    copy(d.trips, trips)
}

trips := ...
d1.SetTrips(trips)

// Теперь мы можем изменить trips[0], 
// не затрагивая d1.trips.
trips[0] = ...

Возврат срезов и карт

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

Неудачный вариант:

type Stats struct {
    mu sync.Mutex
    counters map[string]int
}

// Snapshot возвращает текущую статистику.
func (s *Stats) Snapshot() map[string]int {
    s.mu.Lock()
    defer s.mu.Unlock()

    return s.counters
}

// snapshot больше не защищен мьютексом, поэтому любой
// доступ к snapshot повод для гонки данных.
snapshot := stats.Snapshot()

Хороший вариант:

type Stats struct {
    mu sync.Mutex
    counters map[string]int
}

func (s *Stats) Snapshot() map[string]int {
    s.mu.Lock()
    defer s.mu.Unlock()

    result := make(map[string]int, len(s.counters))
    for k, v := range s.counters {
        result[k] = v
    }
    return result
}

// Snapshot теперь является копией.
snapshot := stats.Snapshot()


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


воскресенье, 4 октября 2020 г.

Срезы как аргументы в Golang

Недавно я наткнулся на несколько примеров использования срезов в качестве аргументов функции, которые приводят к неожиданным результатам. Этот пост посвящен этим примерам.

Первый пример. Срез передается функции в качестве аргумента, где он будет изменен.

https://play.golang.org/p/r5mKX5ErwLC

package main

import "fmt"

func change(abc []int) {
    for i := range abc {
        abc[i] = 4
    }
    fmt.Println(abc)
}

func main() {
    abc := []int{1, 2, 3}
    change(abc)
    fmt.Println(abc)
}

Вывод:

[4 4 4]
[4 4 4]

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

Второй пример.

https://play.golang.org/p/5ruLrp6ZJJc

package main

import "fmt"

func change(abc []int) {
    abc = append(abc, 4)
    for i := range abc {
        abc[i] = 4
    }
    fmt.Println(abc)
}

func main() {
    abc := []int{1, 2, 3}
    change(abc)
    fmt.Println(abc)
}

Вывод:

[4 4 4 4]
[1 2 3]

Что изменилось? Чем этот пример отличается от первого? Мы просто добавляем один элемент в срез внутри функции. Но это существенно меняет срез. Создает новый срез. Как? Когда мы создаем срез вне функции, он создает базовый массив размером для 3 элементов. Затем мы передаем ссылку в функцию. Но когда мы хотим добавить еще один элемент, у нас нет места для него в базовом массиве. Затем встроенная функция добавления создает новый массив и новый срез. Когда мы меняем в нем значения, мы не меняем первый срез.

Третий пример.

https://play.golang.org/p/0tKWomkDCk3

package main

import "fmt"

func change(abc []int) {
    abc = append(abc, 5)
    for i := range abc {
        abc[i] = 4
    }
    fmt.Println(abc)
}

func main() {
    abc := []int{1, 2, 3}
    change(abc)
    abc = append(abc, 4)
    fmt.Println(abc)
}

Вывод:

[4 4 4 4]
[1 2 3 4]

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

Пример четвертый.

https://play.golang.org/p/uCDG59fJLFm

package main

import "fmt"

func change(abc []int) {
    abc = append(abc, 4)
    for i := range abc {
        abc[i] = 4
    }
    fmt.Println(abc)
}

func main() {
    abc := []int{1, 2, 3}
    abc = append(abc, 4)
    change(abc)
    fmt.Println(abc)
}

Вывод:

[4 4 4 4 4]
[4 4 4 4]

Почему этот пример отличается от третьего? Когда мы добавляем срез перед передачей его функции, он создает базовый массив с емкостью не только для одного добавленного элемента, но и с начальным числом элементов, кратным 1,5 - для 6 элементов. Мы можем это увидеть:

package main

import "fmt"

func change(abc []int) {
    abc = append(abc, 4)
    for i := range abc {
        abc[i] = 4
    }
    fmt.Println(abc)
}

func main() {
    abc := []int{1, 2, 3}
    abc = append(abc, 4)
    fmt.Println(cap(abc))
    change(abc)
    fmt.Println(abc)
}

Вывод:

6
[4 4 4 4 4]
[4 4 4 4]

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

Эти примеры могут привести к неожиданным для кого-то результатам. Как этого избежать?

Просто верните срез из функции. Он вернет ссылку на новый срез, если он был создан.

package main

import "fmt"

func change(abc []int) []int {
    abc = append(abc, 4)
    for i := range abc {
        abc[i] = 4
    }
    fmt.Println(abc)
    return abc
}

func main() {
    abc := []int{1, 2, 3}
    abc = change(abc)
    fmt.Println(abc)
}

Вывод:

[4 4 4 4]
[4 4 4 4]

И напишите модульные тесты. Скорее всего, они обнаружат неожиданное поведение. Если применимо, сначала напишите тесты - используйте Test Driven Development.


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


среда, 20 мая 2020 г.

Почему copy не копирует?

Почему копия исчезает?

var src, dst []int
src = []int{1, 2, 3}
copy(dst, src) // Копируем элементы в dst из src.
fmt.Println("dst:", dst)

dst: []

Ответ

Количество элементов, копируемых функцией copy, является минимумом len(dst) и len(src). Чтобы сделать полную копию, вы должны выделить достаточно большой целевой срез.

var src, dst []int
src = []int{1, 2, 3}
dst = make([]int, len(src))
n := copy(dst, src)
fmt.Println("dst:", dst, "(copied", n, "numbers)")

dst: [1 2 3] (copied 3 numbers)

Возвращаемое значение функции копирования - количество скопированных элементов.

Используя append

Вы также можете использовать функцию append, чтобы сделать копию, добавив к нулевому срезу.

var src, dst []int
src = []int{1, 2, 3}
dst = append(dst, src...)
fmt.Println("dst:", dst)

dst: [1 2 3]

Обратите внимание, что емкость среза, выделенного append, может быть немного больше, чем len(src).


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


вторник, 19 мая 2020 г.

Неизменяемые строки в Golang

Почему этот код не компилируется?

s := "hello"
s[0] = 'H'
fmt.Println(s)

../main.go:3:7: cannot assign to s[0]

Ответ

Строки Go являются неизменяемыми и ведут себя как байтовые срезы только для чтения (с несколькими дополнительными свойствами).

Чтобы обновить данные, используйте взамен срез рун.

buf := []rune("hello")
buf[0] = 'H'
s := string(buf)
fmt.Println(s)  // "Hello"

Если строка содержит только символы ASCII, вы также можете использовать байтовый срез, поскольку каждый ASCII символ занимает только 1 байт.


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


понедельник, 18 мая 2020 г.

Неожиданный перевод строки

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

func main() {
    fruit := []string{
        "apple",
        "banana",
        "cherry"
    }
    fmt.Println(fruit)
}

../main.go:5:11: syntax error: unexpected newline, expecting comma or }

Ответ

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

func main() {
    fruit := []string{
        "apple",
        "banana",
        "cherry", // добавлена запятая
    }
    fmt.Println(fruit) // "[apple banana cherry]"
}

Такое поведение является следствием правил вставки точек с запятой в Go.

В результате вы можете добавлять и удалять строки без изменения окружающего кода.


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


воскресенье, 17 мая 2020 г.

Массив не изменится

Почему значение массива сохраняется прежним?

func Foo(a [2]int) {
    a[0] = 8
}

func main() {
    a := [2]int{1, 2}
    Foo(a)         // Попытка изменить a[0].
    fmt.Println(a) // Вывод: [1 2]
}

Ответ

  • Массивы в Go являются значениями.
  • Когда вы передаете массив функции, массив копируется.

Если вы хотите, чтобы Foo обновлял элементы a, используйте вместо этого срез.

func Foo(a []int) {
    if len(a) > 0 {
        a[0] = 8
    }
}

func main() {
    a := []int{1, 2}
    Foo(a)         // Изменяем a[0].
    fmt.Println(a) // Вывод: [8 2]
}

Срез не хранит никаких данных, он просто описывает часть подлежащего массива.

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


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


воскресенье, 10 мая 2020 г.

Как добавить что-либо (элемент, срез или строку) к срезу в Golang

С помощью встроенной функции append вы можете использовать срез в качестве динамического массива. Функция добавляет любое количество элементов в конец среза:

  • если емкости достаточно, базовый массив используется повторно;
  • если нет, выделяется новый базовый массив и данные копируются.

append возвращает обновленный срез, поэтому вам нужно сохранить результат добавления, часто в переменной, содержащей сам срез:

a := []int{1, 2}
a = append(a, 3, 4) // a == [1 2 3 4]

В частности, совершенно нормально добавлять к пустому срезу:

a := []int{}
a = append(a, 3, 4) // a == [3 4]

Предупреждение. Вот пример того, что может произойти, если вы забудете, что append может повторно использовать базовый массив:

a := []byte("ba")

a1 := append(a, 'd')
a2 := append(a, 'g')

fmt.Println(string(a1)) // bag
fmt.Println(string(a2)) // bag

Если есть место для большего количества элементов, append повторно использует базовый массив. Давайте взглянем:

a := []byte("ba")
fmt.Println(len(a), cap(a)) // 2 32
// длина среза - 2, но емкость - 32

Это означает, что срезы a, a1 и a2 будут ссылаться на один и тот же базовый массив в нашем примере.

Чтобы избежать этого, нам нужно использовать два отдельных байтовых массива.

const prefix = "ba"

a1 := append([]byte(prefix), 'd')
a2 := append([]byte(prefix), 'g')

fmt.Println(string(a1)) // bad
fmt.Println(string(a2)) // bag

Но бывает так что данная проблема не всегда сразу видна. В некоторых реализациях Go []byte("ba") выделяет только два байта, а затем код работает: первая строка "bad", а вторая "bag".

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

Добавить один срез в другой

Вы можете объединить два среза, используя нотацию трех точек:

a := []int{1, 2}
b := []int{11, 22}
a = append(a, b...) // a == [1 2 11 22]

... распаковывает b. Без точек код будет пытаться добавить срез целиком, что недопустимо.

Результат не зависит от того, перекрываются ли аргументы:

a := []int{1, 2}
a = append(a, a...) // a == [1 2 1 2]

Добавить строку к байтовому срезу

В особом случае допустимо добавлять строку к срезу байтов:

slice := append([]byte("Hello "), "world!"...)

Производительность

Добавление одного элемента занимает постоянное амортизированное время.


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


пятница, 17 апреля 2020 г.

Последний элемент в срезе/массиве в Golang

Читать последней элемент

Используйте индекс len(a)-1 для доступа к последнему элементу среза или массива a.

a := []string{"A", "B", "C"}
s := a[len(a)-1] // C

В Go нет отрицательной индексации, как в Python. Это продуманное дизайнерское решение, поскольку простой язык может помочь вам избежать мелких ошибок.

Удалить последний элемент

a := []string{"A", "B", "C"}
a = a[:len(a)-1] // [A B]

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

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

a[len(a)-1] = "" // Удалить элемент (записать нулевое значение)
a = a[:len(a)-1] // [A B]


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


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

четверг, 16 апреля 2020 г.

Найти элемент в срезе/массиве линейным или бинарным поиском в Golang

Линейный поиск

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

// Find возвращает наименьший индекс i, 
// при котором x == a[i],
// или len(a), если такого индекса нет.
func Find(a []string, x string) int {
    for i, n := range a {
        if x == n {
            return i
        }
    }
    return len(a)
}

// Contains указывает, содержится ли x в a.
func Contains(a []string, x string) bool {
    for _, n := range a {
        if x == n {
            return true
        }
    }
    return false
}

Бинарный поиск

"Бинарный поиск быстрее линейного, но работает, только если ваши данные в порядке. Это сортировка." - Дэн Бентли

Если массив отсортирован, вы можете использовать бинарный поиск. Это будет намного эффективнее, поскольку бинарный поиск выполняется в наихудшем логарифмическом времени, делая O(log n) сравнений, где n - размер среза.

Существует три пользовательские функции бинарного поиска: sort.SearchInts, sort.SearchStrings или sort.SearchFloat64s.

Все они имеют сигнатуру

func SearchType(a []Type, x Type) int

и возвращают

  • наименьший индекс i, при котором x <= a[i]
  • или len(a), если такого индекса нет.

Срез должен быть отсортирован в порядке возрастания.

a := []string{"A", "C", "C"}

fmt.Println(sort.SearchStrings(a, "A")) // 0
fmt.Println(sort.SearchStrings(a, "B")) // 1
fmt.Println(sort.SearchStrings(a, "C")) // 1
fmt.Println(sort.SearchStrings(a, "D")) // 3

Общий бинарный поиск

Существует также универсальная функция бинарного поиска sort.Search.

func Search(n int, f func(int) bool) int

Она возвращает:

  • наименьший индекс i, при котором f(i) истинно (равно true)
  • или n, если такого индекса нет.

Требуется, чтобы f было false для некоторого (возможно, пустого) префикса входного диапазона, а затем true для остальной части.

Этот пример отражает предыдущий, но использует общую sort.Search вместо sort.SearchInts.

a := []string{"A", "C", "C"}
x := "C"

i := sort.Search(len(a), func(i int) bool { return x <= a[i] })
if i < len(a) && a[i] == x {
    fmt.Printf("Найдено %s по индексу %d в %v.\n", x, i, a)
} else {
    fmt.Printf("Не найдено %s в %v.\n", x, a)
}
// Вывод: Найдено C по индексу 1 в [A C C].

Вариант карты

Если вы делаете повторный поиск и обновление, вы можете использовать карту (map) вместо среза. Карта обеспечивает операции поиска, вставки и удаления за O(1) ожидаемое амортизированное время.


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


среда, 15 апреля 2020 г.

Два способа удалить элемент из среза в Golang

Быстрая версия (меняет порядок)

a := []string{"A", "B", "C", "D", "E"}
i := 2

// Удалить элемент по индексу i из a.

// 1. Копировать последний элемент в индекс i.
a[i] = a[len(a)-1] 

// 2. Удалить последний элемент (записать нулевое значение).
a[len(a)-1] = ""  

// 3. Усечь срез.
a = a[:len(a)-1]  

fmt.Println(a) // [A B E D]

Код копирует один элемент и выполняется за постоянное время.

Медленная версия (сохраняет порядок)

a := []string{"A", "B", "C", "D", "E"}
i := 2

// Удалить элемент по индексу i из a.

// 1. Выполнить сдвиг a[i+1:] влево на один индекс.
copy(a[i:], a[i+1:])

// 2. Удалить последний элемент (записать нулевое значение).
a[len(a)-1] = ""

// 3. Усечь срез.
a = a[:len(a)-1]

fmt.Println(a) // [A B D E]

Код копирует (len(a) - i - 1) элементов и выполняется за линейное время.


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


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

Как лучше всего очистить срез в Golang: пустой против нулевого

Удалить все элементы

Чтобы удалить все элементы, просто установите срез равным nil.

a := []string{"A", "B", "C", "D", "E"}
a = nil
fmt.Println(a, len(a), cap(a)) // [] 0 0

Это освободит подлежащий массив для сборщика мусора (при условии, что нет других ссылок).

Сохранить выделенную память

Чтобы сохранить подлежащий массив, нарежьте срез до нулевой длины.

a := []string{"A", "B", "C", "D", "E"}
a = a[:0]
fmt.Println(a, len(a), cap(a)) // [] 0 5

Если срез снова расширяется, исходные данные появляются снова.

fmt.Println(a[:2]) // [A B]

Пустой срез против нулевого среза

На практике нулевые срезы и пустые срезы часто можно обрабатывать одинаково:

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

var a []int = nil
fmt.Println(len(a)) // 0
fmt.Println(cap(a)) // 0
fmt.Println(a)      // []

b := []int{}
fmt.Println(len(b)) // 0
fmt.Println(cap(b)) // 0
fmt.Println(b)      // []

Однако при необходимости вы можете заметить разницу.

var a []int = nil
var a0 []int = make([]int, 0)

fmt.Println(a == nil)  // true
fmt.Println(a0 == nil) // false

fmt.Printf("%#v\n", a)  // []int(nil)
fmt.Printf("%#v\n", a0) // []int{}

Официальная Go wiki рекомендует использовать нулевые срезы вместо пустых срезов.

Нулевой срез является предпочтительным стилем.

Обратите внимание, что существуют ограниченные обстоятельства, когда предпочтителен не nil срез, а срез нулевой длины, например, при кодировании объектов JSON (nil срез кодируется в null, а []string{} кодируется в массив JSON []).

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


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


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

пятница, 3 апреля 2020 г.

Преобразование между массивом/срезом байтов и строкой в Golang

Когда вы конвертируете между строкой и срезом (массивом) байтов, вы получаете совершенно новый срез, который содержит те же байты, что и строка, и наоборот.

  • Преобразование не меняет данные;
  • единственное отличие состоит в том, что строки являются неизменяемыми, а срезы байтов могут быть изменены.

Если вам нужно манипулировать символами (рунами) строки, вы можете вместо этого преобразовать строку в срез рун.

Конвертировать строку в байты

Когда вы преобразуете строку в срез байтов, вы получаете новый срез, который содержит те же байты, что и строка.

b := []byte("ABC€")
fmt.Println(b) // [65 66 67 226 130 172]

Обратите внимание, что символ € кодируется в UTF-8 с использованием 3 байтов.

Конвертировать байты в строку

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

s := string([]byte{65, 66, 67, 226, 130, 172})
fmt.Println(s) // ABC€

Производительность

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

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


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


Три способа разделить строку на срез в Golang

Разделить по запятой или другой подстроке

Используйте функцию strings.Split, чтобы разбить строку на значения, разделенные запятыми.

s := strings.Split("a,b,c", ",")
fmt.Println(s)
// Вывод: [a b c]

Чтобы включить разделители, используйте strings.SplitAfter. Чтобы разделить только первые n значений, используйте strings.SplitN и strings.SplitAfterN.

Вы можете использовать strings.TrimSpace для удаления начальных и конечных пробелов из результирующих строк.

Разделить по пробелам и символам новой строки

Используйте функцию strings.Fields, чтобы разбить строку на подстроки, удаляя любые пробелы, включая символы новой строки.

s := strings.Fields(" a \t b \n")
fmt.Println(s)
// Вывод: [a b]

Разделить по регулярному выражению

В более сложных ситуациях метод regexp Split может помочь.

Он разбивает строку на подстроки, разделенные регулярным выражением. Метод принимает целочисленный аргумент n; если n >= 0, он возвращает не более n подстрок.

a := regexp.MustCompile(`a`)              // единичный `a`
fmt.Printf("%q\n", a.Split("banana", -1)) // ["b" "n" "n" ""]
fmt.Printf("%q\n", a.Split("banana", 0))  // [] (nil slice)
fmt.Printf("%q\n", a.Split("banana", 1))  // ["banana"]
fmt.Printf("%q\n", a.Split("banana", 2))  // ["b" "nana"]

zp := regexp.MustCompile(` *, *`)             // пробелы и одна запятая
fmt.Printf("%q\n", zp.Split("a,b ,  c ", -1)) // ["a" "b" "c "]


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


пятница, 28 февраля 2020 г.

Три способа сравнивания срезов (массивов) в Golang

В большинстве случаев вы захотите написать собственный код для сравнения элементов двух срезов.

// Equal проверяет, что a и b содержат одинаковые элементы.
// nil аргумент эквивалентен пустому срезу.
func Equal(a, b []int) bool {
    if len(a) != len(b) {
        return false
    }
    for i, v := range a {
        if v != b[i] {
            return false
        }
    }
    return true
}

Однако для массивов вы можете использовать операторы сравнения == и !=.

a := [2]int{1, 2}
b := [2]int{1, 3}
fmt.Println(a == b) // false

Значения массива сравнимы, если значения типа элемента массива сравнимы. Два значения массива равны, если их соответствующие элементы равны.

Оптимизированный код для байтовых срезов

Чтобы сравнить байтовые срезы, используйте оптимизированные bytes.Equal. Эта функция также обрабатывает nil аргументы как эквивалент пустых срезов.

Универсальный код для рекурсивного сравнения

В целях тестирования вы можете использовать reflect.DeepEqual. Он сравнивает два элемента любого типа рекурсивно.

var a []int = nil
var b []int = make([]int, 0)
fmt.Println(reflect.DeepEqual(a, b)) // false

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


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


Купить gopher

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

Структура данных базового стека (LIFO) в Golang

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

  • для push (добавления элемента в стек) вы используете встроенную функцию append
  • для pop (извлечения верхнего элемента стека) вы выполняете срезание верхнего элемента

var stack []string

stack = append(stack, "world!") // Push
stack = append(stack, "Hello ")

for len(stack) > 0 {
    n := len(stack) - 1 // Верхний элемент
    fmt.Print(stack[n])

    stack = stack[:n] // Pop
}

Вывод:

Hello world!

Производительность

Добавление отдельного элемента к срезу требует постоянного амортизированного времени.

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

// Pop
stack[n] = "" // Удаляем элемент (записываем нулевое значение)
stack = stack[:n]

О том что такое стек рекомендуем эту статью.


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


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

вторник, 18 февраля 2020 г.

Срезы и массивы в Golang: создание, индексирование, нарезка, итерация

Срез не хранит никаких данных, он просто описывает часть базового массива.

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

Срез может расти и уменьшаться в пределах основного массива.

Срезы индексируются обычным способом: s[i] обращается к i-му элементу, начиная с нуля.

Создание

var s []int                   // nil срез
s1 := []string{"foo", "bar"}
s2 := make([]int, 2)          // то же что и []int{0, 0}
s3 := make([]int, 2, 4)       // то же что и new([4]int)[:2]
fmt.Println(len(s3), cap(s3)) // 2 4

Нулевым значением по умолчанию для среза является nil. Функции len, cap и append все рассматривают nil как пустой срез с нулевой емкостью.

Вы создаете срез с помощью литерала среза или вызова функции make, которая принимает длину и необязательную емкость в качестве аргументов.

Встроенные функции len и cap определяют длину и емкость.

Нарезка

a := [...]int{0, 1, 2, 3} // массив
s := a[1:3]               // s == []int{1, 2}        cap(s) == 3
s = a[:2]                 // s == []int{0, 1}        cap(s) == 4
s = a[2:]                 // s == []int{2, 3}        cap(s) == 2
s = a[:]                  // s == []int{0, 1, 2, 3}  cap(s) == 4

Вы также можете создать срез, разрезая существующий массив или срез.

Срез формируется путем указания нижней границы и верхней границы: a[low:high]. Эта конструкция выбирает полуоткрытый диапазон, который включает первый элемент, но исключает последний.

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

s := []int{0, 1, 2, 3, 4} // срез
s = s[1:4]                // s == []int{1, 2, 3}
s = s[1:2]                // s == []int{2} (индексирование относительно среза)
s = s[:3]                 // s == []int{2, 3, 4} (расширение длины)

Когда вы нарезаете срез, индексы относятся к самому срезу, а не к подлежащему массиву.

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

Попытка выйти за пределы возможностей вызывает панику.

Итерация

s := []string{"Foo", "Bar"}
for i, v := range s {
    fmt.Println(i, v)
}

Вывод:

0 Foo
1 Bar

Выражение диапазона s вычисляется один раз перед началом цикла.

Значения итерации присваиваются соответствующим переменным итерации, i и v, как в операторе присваивания.

Вторая итерационная переменная является необязательной.

Если срез равен nil, количество итераций равно 0.

Append и copy

Функция append добавляет элементы к срезу. Он будет автоматически выделять больший резервный массив при превышении емкости.

Функция copy копирует элементы в целевой срез dst из исходного среза src. Количество копируемых элементов - это минимум len(dst) и len(src).

Стеки и очереди

Идиоматический способ реализации стека или очереди в Go - это непосредственное использование среза.


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