Dátové kolekcie v Go - polia, výrezy, mapy

Wednesday, Sep 30, 2020 by Nelo
Go (golang) programovanie - polia (arrays), výrezy (slices), mapy (maps)

Dátové kolekcie patria medzi základné typy v každom modernom jazyku. Umožňujú ukladať prvky rovnakého typu a následne k nim pristupovať pomocou indexu alebo kľúča.

Go pozná tri základné typy dátových kolekcií:

  1. Pole: array[N]T,
  2. Výrez: slice[]T,
  3. Mapa: map[K]T.

Kde T je akýkoľvek typ, K je typ, ktorý implementuje rovnosť (napríklad string) a N je kladné celé číslo.

Pole - Array

Pole, rovnako ako v iných programovacích jazykoch je súbor rovnakých dátových typov s fixnou veľkosťou. Deklarácia je veľmi jednoduchá, hoci iná ako vo väčšine ostatných jazykoch, podobná Slovenskému pravidlu “píš ako počuješ” a môže byť rovnako ako premenné inicializované niekoľkými spôsobmi:

[N]T
[N]T{v1,v2,...,vN}
[...]T{v1,v2,...,vN}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Deklarácia 1
var arr = [5]int{1, 2, 3, 4, 5}

// Deklarácia 2
arr := [5]int{1,2,3,4,5} // arr je pole s 10 hodnotami typu int

// Deklarácia 3
var arr [5]int // pole inicializované hodnotami [0 0 0 0 0]
arr = [5]int{1, 2, 3, 4, 5}

// Deklarácia 4
arr := [...]int{1, 2, 3, 4, 5} // Go dopočíta počet členov

arr[0] = 9 // prvý člen poľa má index '0', [9 2 3 4 5]
fmt.Print(arr[5]) // vypíše chybu: invalid array index 5 (out of bounds for 5-element array)

arr := [4]bool{1: true, true} // [false true true false]

var matrix [2][3]int // matica inicializovaná hodnotami [[0 0 0] [0 0 0]]
matrix = [...][3]int{ {1,2,3}, {4,5,6} }
fmt.Print(matrix) // [[1 2 3] [4 5 6]]
matrix[0][1] = 9
fmt.Print(matrix[0]) // [1 9 3]

Pole má fixnú veľkosť v pamäti alokovanú pri inicializácii, čo zlepšuje rýchlosť oproti dynamicky alokovaným dátovým štruktúram, ale veľkosť nie je možné zmeniť. Taktiež v Go je pole samostatný typ ([2]int je iný typ ako [3]int). Pole je hodnotový typ, odovzdáva sa medzi funkciami hodnotou a nie ako adresa objektu. To znamená že v parametri pri volaní funkcie sa celé kopíruje čo znižuje výkon. Kvôli týmto obmedzeniam sa v Go používa sa veľmi zriedka.

Výrez - Slice

Výrez je najdôležitejší kompozitný generický typ a je rozšírená verzia poľa:

  1. deklaruje sa ako pole ale bez veľkosti, alebo make([]Type, length, capacity)
  2. môže dynamicky rásť a zmenšovať sa
  3. má veľkosť, kapacitu a ukazovateľ na element v pamäti

Základné operácie:

  1. pridanie prvkov: append(slice,v1,v2,...,vN)
  2. odkrojenie, pričom pôvodný a nový slice zdieľa prvky: [begin:end]
  3. kopírovanie prvkov: copy(dest, src)

Kde begin je index od začiatku a end je počet preskočených prvkov od konca.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// Deklarácia 1
var slice = []int{1, 2, 3, 4, 5}

// Deklarácia 2
slice := []int{1,2,3,4,5}

// Deklarácia z poľa
var arr [100]int // alokuje miesto v pamäti
sliceA := arr[:50]
sliceB := arr[50:]

// Deklarácia 3
var slice []int // prázdny výrez []
fmt.Println(slice == nil) // true
var s1 = slice
slice = []int{1, 2, 3} // [1 2 3]
slice = append(slice, 4, 5) // [1 2 3 4 5]
var s1 = slice // 
slice = slice[1:] // vytvorí nový slice od indexu 1, [2 3 4 5]
slice = slice[:2] // vytvorí nový slice po index menší ako 2, [2 3]
fmt.Printf("velkost = %d, kapacita = %d\n", len(slice), cap(slice)) // velkost = 2, kapacita = 5
s1[1] = 7 // [1 7 3 4 5]
fmt.Print(slice) // [7 3] - pretože slice aj s1 smerujú na rovnaký prvý objekt v pamäti

slice := make([]int, 5, 20) // velkost = 5, kapacita = 20

// slice môže obsahovať pointre na adresy
var heads = []*[4]byte{
	&[4]byte{'P', 'N', 'G', ' '},
	&[4]byte{'G', 'I', 'F', ' '},
	{'J', 'P', 'E', 'G'},
}
fmt.Print(heads) // [0xc0000b601c 0xc0000b6020 0xc0000b6024]

// alebo typy
type fruits struct {
	name string
	weight int
}

var f = [...]fruits{
	fruits{"apple", 25},
	fruits{"banana", 11},
	{"pear", 9},
}
fmt.Print(f) // [{apple 25} {banana 11} {pear 9}]

// Kopírovanie
src := []int{1, 2, 3, 4}
dst:= []int{5, 6, 7, 8, 9}
n := copy(dst[1:], src[2:4])
fmt.Println(n, dst) // 2 [5 3 4 8 9]

Na pochopenie ako fungujú slice pomôže rozumieť ako sú implementované - ako nadstavbová štruktúra nad <code>array</code> ktorá obsahuje pointer na prvý element, veľkosť a kapacitu, je kopírovaná pri odovzdávaní ako parameter do fukncií a poskytuje užitočné funkcie copy a append. Preto ak nemeníme veľkosť výrezu, iba hodnotu prvkov, nemusíme odovzdávať funkcii ukazovateľom.

Mapa - Map

Asociačná mapa je nezoradný súbor dát uložených ako pár kľúč-hodnota, podobná “dictionary” alebo “hashmap” z iných programovacích jazykov. Používa sa na rýchle vytiahnutie hodnoty podľa kľúča.

Interne je mapa implementovaná ako ukazovateľ na <code>runtime.hmap</code> štruktúru, ktorá obsahuje veľkosť, funkciu equal(), funkciu hash(), pointer na pole bucketov (bucket je pole K/V hodnôt) a ďalšie atribúty. Kľúče aj hodnoty môžu mať hodnotu nil. Mapu teda v parametri funkcie nemusíme odovzdávať ukazovateľom, ak chceme obsah meniť.

Základné operácie:

  1. vytvorenie: make(M, n), pričom M je typ mapy a n kladné celé číslo úvodná kapacita
  2. iná forma vytvorenia: *new(map[K]N)
  3. zmazanie prvku delete(map, key)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Deklarácia 1
var m map[int]string // môžme použiť ako pamäťovo efektívne riedke pole

// Deklarácia 2
m := map[string]bool{}

// Deklarácia 3
m := make(map[string][]*Person) // Person je napríklad: type Person struct {Name string}
m := make(map[string]map[string]int) // mapa map

// Príklad použitia
type artist struct{age int}
m := map[string]artist{}
m["Warhol"] = artist{age: 36}
m["Picasso"] = artist{age: 58}

val, ok := m["Warhol"] // vyhľadanie hodnoty a potvrdenia či existuje
fmt.Printf("%d %t\n", val, ok ) // {36} true

for key, value := range m {
    fmt.Printf("%q is the key for the value %d\n", key, value)
}
// "Warhol" is the key for the value {36}
// "Picasso" is the key for the value {58}

Mapy, rovnako ako výrezy nie sú bezpečné pre konkurentné použitie - simultánny zápis a čítanie prvkov je nedefinovaná operácia. Konkurentný prístup musí byť koordinovaný cez nejaký synchronizačný mechanizmus (sync.Mutex). Pri iterovaní prvkami mapy poradie nie je špecifikované ani garantované, že bude rovnaké medzi dvomi iteráciami. Pre garantované poradie musí byť udržiavaná osobitná štruktúra kľúčov v požadovanom poradí.

Referencie

  1. Go Data Structures
  2. Go Slices: usage and internals
  3. Arrays, slices (and strings): The mechanics of 'append'
  4. Package list - linked list - obojstranne linkovaný zoznam, PushBack, Front, Remove
  5. Go maps in action
  6. Dave Cheney - Practical Go

Vaše otázky, návrhy a komentáre

Verím, že vás tento návod inšpiroval a budem vďačný ak dáte spätnú väzbu a pomôže mi zamerať sa na to čo by vás zaujímalo.

TAK ČO HOVORÍŠ ?

Kontaktuj nás ak potrebuješ pomoc, veľmi radi pomôžeme.

Kontaktuj nás