Hlavní navigace

Kontejnery v základní knihovně programovacího jazyka Go

Pavel Tišnovský

I dnešní část je věnována popisu možností standardní knihovny. Dnes se zaměříme na protokol HTTP, popis balíčků s implementací různých datových struktur a taktéž na popis balíčku s rozhraními a typy pro řazení a vyhledávání.

Doba čtení: 45 minut

Sdílet

11. Balíček sort: řadicí a vyhledávací algoritmy

12. Práce s poli či řezy s prvky typu int a float64

13. Složitější příklad: seřazení všech slov načtených z textového souboru

14. Typy IntSlice, Float64Slice a StringSlice

15. Řazení prvků na základě vlastní porovnávací funkce

16. Řazení pole/řezů struktur podle vlastních kritérií

17. Rozhraní Heap a jeho význam v praxi

18. Interní změna pozice prvků při práci s haldou

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Jednoduchý HTTP server využívající rozhraní ResponseWriter a strukturu Request

V předchozích dvou částech [1] [2] seriálu o programovacím jazyku Go jsme se seznámili se základními rozhraními a jejich implementacemi, které se používají pro vstupně-výstupní operace (standardní vstup, standardní výstup, soubory, síťová připojení, FIFO, paměťové buffery atd.). Dnes se tímto tématem budeme zabývat pouze okrajově, protože si ukážeme některé další možnosti poskytované implementací HTTP serveru, konkrétně datovými typy http.Server a http.ServeMUX. Touto problematikou jsme se již částečně zabývali v předchozích částech tohoto seriálu, takže si jen ve stručnosti ukažme, jakým způsobem je možné v programovacím jazyku Go vytvořit aplikaci, která bude pracovat jako HTTP server poskytující klientům statický obsah načítaný ze specifikované adresářové struktury:

package main
 
import (
        "io"
        "net/http"
)
 
func mainEndpoint(writer http.ResponseWriter, request *http.Request) {
        io.WriteString(writer, "Hello world!\n")
}
 
func main() {
        http.HandleFunc("/", mainEndpoint)
        http.ListenAndServe(":8000", nil)
}

Zajímavější je pochopitelně implementace HTTP serveru, která bude generovat dynamický obsah. Ten lze tvořit buď přímo „ručně“ v programu, nebo můžeme využít některé balíčky ze standardní knihovny programovacího jazyka Go pro generování dat ve formátu JSON, XML popř. knihovny s implementací šablon (templates). Dnes nás ovšem bude zajímat první způsob, tj. „ruční“ generování odpovědi, která je serverem posílána klientovi na základě jeho dotazu (request). Jedna z nejjednodušších implementací takového HTTP serveru může vypadat například následovně:

package main
 
import (
        "net/http"
)
 
func mainEndpoint(writer http.ResponseWriter, request *http.Request) {
        response := "Hello world!\n"
        writer.Write([]byte(response))
}
 
func main() {
        http.HandleFunc("/", mainEndpoint)
        http.ListenAndServe(":8000", nil)
}

Funkci takového serveru si můžeme snadno otestovat, například s využitím nástroje wget nebo ještě lépe curl. Vzhledem k tomu, že víme, na jakém portu server běží a jaký endpoint máme zavolat, sestavíme příkaz pro curl tímto způsobem:

$ curl -v localhost:8000

Výstup bude obsahovat i ladicí informace vyžádané přepínačem -v:

* Rebuilt URL to: localhost:8000/
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:8000
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Mon, 06 May 2019 18:14:06 GMT
< Content-Length: 13
< Content-Type: text/plain; charset=utf-8
<
Hello world!
* Connection #0 to host localhost left intact

Ve zdrojovém kódu si povšimněte především funkce, v níž je implementováno generování a posílání odpovědi. Této funkci jsou předány dvě hodnoty, přičemž první je typu rozhraní http.ResponseWriter a druhá je typu ukazatel na http.Request:

type ResponseWriter interface {
        Header() Header
        Write([]byte) (int, error)
        WriteHeader(statusCode int)
}

2. Chování HTTP serveru v případě odpovědi, která je zpracovávána delší dobu

Předchozí implementace HTTP serveru sice ukázala, jakým způsobem se generuje dynamický obsah, ovšem v některých případech je situace složitější, neboť je nutné zajistit, aby se po HTTP protokolu přenášelo větší množství dat, které klient bude zpracovávat postupně, jakoby se jednalo o stream (jedná se sice o určité zneužití původní oblasti použití HTTP, ovšem setkáme se s ním poměrně často). Vyzkoušejme si tedy, jak bude komunikace mezi klientem a serverem vypadat ve chvíli, kdy server bude dodávat data rozdělená do záznamů (record). V tomto případě využijeme toho, že v rozhraní http.ResponseWriter je předepsána metoda:

Write([]byte) (int, error)

Tuto metodu pochopitelně již dobře známe, protože jsme se s ní několikrát setkali v předchozích dvou článcích. Samotná funkce, která bude na HTTP dotaz postupně dodávat odpověď rozdělenou na mnoho záznamů, tedy může v tom nejjednodušším případě vypadat následovně:

func handler(writer http.ResponseWriter, request *http.Request) {
        println("handler started")
        for i := 0; i < 50; i++ {
                writer.Write([]byte("Hello world!\n"))
        }
        println("handler finished")
}

Úplnou implementaci takto modifikovaného HTTP serveru nalezneme na adrese https://github.com/tisnik/go-root/blob/master/article24/01_ser­ver.go:

package main
 
import (
        "net/http"
)
 
func handler(writer http.ResponseWriter, request *http.Request) {
        println("handler started")
        for i := 0; i < 50; i++ {
                writer.Write([]byte("Hello world!\n"))
        }
        println("handler finished")
}
 
func main() {
        http.HandleFunc("/data", handler)
        http.ListenAndServe(":8080", nil)
}

Chování HTTP serveru opět otestujeme nástrojem curl:

$ curl -v localhost:8080/data
 
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /data HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sat, 04 May 2019 17:43:57 GMT
< Content-Length: 650
< Content-Type: text/plain; charset=utf-8
<
Hello world!
...
...
...
Hello world!
* Connection #0 to host localhost left intact

Přidat můžeme i simulaci postupného vytváření a posílání jednotlivých záznamů:

func handler(writer http.ResponseWriter, request *http.Request) {
        println("handler started")
        for i := 0; i < 50; i++ {
                writer.Write([]byte("Hello world!\n"))
                time.Sleep(100 * time.Millisecond)
                print(".")
        }
        println("\nhandler finished")
}
Poznámka: časové odstupy mezi záznamy jsou zde simulovány uspáním gorutiny na určitý časový interval.

Následuje výpis úplného zdrojového kódu tohoto příkladu:

package main
 
import (
        "net/http"
        "time"
)
 
func handler(writer http.ResponseWriter, request *http.Request) {
        println("handler started")
        for i := 0; i < 50; i++ {
                writer.Write([]byte("Hello world!\n"))
                time.Sleep(100 * time.Millisecond)
                print(".")
        }
        println("\nhandler finished")
}
 
func main() {
        http.HandleFunc("/data", handler)
        http.ListenAndServe(":8080", nil)
}

Nyní si chování této nové implementace HTTP serveru můžeme otestovat, a to opět s využitím nástroje curl. Po poslání požadavku:

$ curl -v localhost:8080/data

Server ihned pošle první část odpovědi (konkrétně hlavičku HTTP odpovědi):

$ curl -v localhost:8080/data
 
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /data HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Fri, 03 May 2019 19:34:51 GMT
< Content-Length: 650
< Content-Type: text/plain; charset=utf-8

V této chvíli sice server postupně vytváří jednotlivé části těla odpovědi, ovšem klient tuto činnost nebude vidět, a to kvůli tomu, že se na straně klienta (a někdy i celého síťového stacku) používají buffery. Z pohledu klienta tedy v této chvíli dojde k jediné časové prodlevě o délce přibližně 100×50 ms = 5 sekund.

Až po této prodlevě klient přijme všechny části odpovědi, a to najednou:

Hello world!
Hello world!
Hello world!
...
...
...
Hello world!
Hello world!
Hello world!
* Connection #0 to host localhost left intact
Poznámka: ve skutečnosti ovšem bude chování serveru v praxi komplikovanější, protože klient bude přijímat bloky o velikosti odpovídající velikosti bufferu. V našem konkrétním značně zjednodušeném příkladu je však buffer větší, než suma velikosti všech poslaných částí odpovědi.

3. Využití metody Flush rozhraní Flusher

V případě, že je skutečně nutné, aby klient dostal jednotlivé části odpovědi ihned po jejich vygenerování serverem, musíme se podívat na to, jak vlastně metoda http.ResponseWriter.Write() interně pracuje. Její činnost je poměrně přesně popsána v dokumentaci:

$ go doc http ResponseWriter.Write
 
// Write writes the data to the connection as part of an HTTP reply.
//
// If WriteHeader has not yet been called, Write calls
// WriteHeader(http.StatusOK) before writing the data. If the Header
// does not contain a Content-Type line, Write adds a Content-Type set
// to the result of passing the initial 512 bytes of written data to
// DetectContentType. Additionally, if the total size of all written
// data is under a few KB and there are no Flush calls, the
// Content-Length header is added automatically.
//
// Depending on the HTTP protocol version and the client, calling
// Write or WriteHeader may prevent future reads on the
// Request.Body. For HTTP/1.x requests, handlers should read any
// needed request body data before writing the response. Once the
// headers have been flushed (due to either an explicit Flusher.Flush
// call or writing enough data to trigger a flush), the request body
// may be unavailable. For HTTP/2 requests, the Go HTTP server permits
// handlers to continue to read the request body while concurrently
// writing the response. However, such behavior may not be supported
// by all HTTP/2 clients. Handlers should read before writing if
// possible to maximize compatibility.
func Write([]byte) (int, error)

Řešením (i když zdaleka ne dokonalým) je tedy volání metody Flush po každém volání Write. Tato metoda je předepsána v rozhraní http.Flusher:

type Flusher interface {
    Flush()
}

Teoreticky by tedy handler mohl být upraven takto:

writer.Write([]byte("Hello world!\n"))
f.Flush()

Ve skutečnosti však nemusí všechny hodnoty implementující rozhraní ResponseWriter současně implementovat i rozhraní Flusher, takže musíme přidat test, zda je možné zavolat metodu Flush. Kód se nám nepatrně zkomplikuje:

writer.Write([]byte("Hello world!\n"))
 
if f, ok := writer.(http.Flusher); ok {
        f.Flush()
}

Úplný zdrojový kód upravené implementace HTTP serveru naleznete na adrese https://github.com/tisnik/go-root/blob/master/article24/03_flushin­g_server.go:

package main
 
import (
        "net/http"
        "time"
)
 
func handler(writer http.ResponseWriter, request *http.Request) {
        println("handler started")
        for i := 0; i < 50; i++ {
                writer.Write([]byte("Hello world!\n"))
                print(".")
                if f, ok := writer.(http.Flusher); ok {
                        f.Flush()
                }
                time.Sleep(100 * time.Millisecond)
        }
        println("\nhandler finished")
}
 
func main() {
        http.HandleFunc("/data", handler)
        http.ListenAndServe(":8080", nil)
}

Chování nové implementace HTTP serveru si můžeme otestovat, opět s využitím nástroje curl. Nyní by se již jednotlivé odpovědi měly objevovat postupně a přibližně v takových intervalech, v jakých je posílá server:

$ curl -v localhost:8080/data
 
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /data HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Fri, 03 May 2019 19:36:50 GMT
< Content-Length: 650
< Content-Type: text/plain; charset=utf-8
<

Postupné odpovědi serveru:

Hello world!
Hello world!
Hello world!
* Connection #0 to host localhost left intact
Poznámka: i když budeme metodu Flush volat po každém zápisu části odpovědi, nemusí to vždy znamenat, že klient bude tyto části postupně přijímat. Mezi klientem a serverem totiž může stát HTTP proxy fungující mj. i jako buffer. V případě, že jsou požadavky na postupné posílání jednotlivých záznamů skutečně striktní (chování aplikace na ní zcela závisí), je nutné využití jiné technologie, než HTTP protokolu založeného na principu požadavek → odpověď (request → response).

4. Test, zda se klient odpojil

V případě, že HTTP server popsaný v předchozí kapitole spustíme, s využitím nástroje curl vytvoříme požadavek a následně spojení ukončíme, například stiskem Ctrl+C, bude se server pokoušet dále volat metodu Write a posílat data do již uzavřeného připojení. Pro korektní činnost tedy musíme přidat testy na to, zda se zápis podařil:

n, err := writer.Write([]byte("Hello world!\n"))
if err != nil {
        println("\nI/O error:", err.Error())
        return
}

Tuto novou implementaci HTTP serveru, kterou naleznete na adrese https://github.com/tisnik/go-root/blob/master/article24/04_clo­se_detector.go, vypadá následovně:

package main
 
import (
        "net/http"
        "time"
)
 
func handler(writer http.ResponseWriter, request *http.Request) {
        println("handler started")
        for i := 0; i < 50; i++ {
                n, err := writer.Write([]byte("Hello world!\n"))
                if err != nil {
                        println("\nI/O error:", err.Error())
                        return
                }
                if n == 0 {
                        println("\nnothing written")
                        return
                }
                print(".")
                if f, ok := writer.(http.Flusher); ok {
                        f.Flush()
                }
                time.Sleep(100 * time.Millisecond)
        }
        println("\nhandler finished")
}
 
func main() {
        http.HandleFunc("/data", handler)
        http.ListenAndServe(":8080", nil)
}

Začátek testování:

$ curl -v localhost:8080/data
 
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /data HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Fri, 03 May 2019 19:38:41 GMT
< Content-Type: text/plain; charset=utf-8
< Transfer-Encoding: chunked
<
Hello world!
Hello world!
Hello world!
Hello world!
^C

Po stisku klávesové zkratky Ctrl+C by se na straně serveru mělo objevit toto hlášení:

handler started
.......
I/O error: write tcp 127.0.0.1:8080->127.0.0.1:47698: write: broken pipe

5. Základní kolekce podporované základní knihovnou programovacího jazyka Go

Ve druhé části dnešního článku si popíšeme ty balíčky, v nichž jsou implementovány takzvané kontejnery (containers). Ve skutečnosti se nejedná o nic složitého, pouze o rozšíření množiny složených datových struktur poskytovaných samotným programovacím jazykem Go. Připomeňme si, že mezi složené struktury patří zejména záznamy (struct), pole (array), od nich odvozený koncept řezů (slice) a samozřejmě taktéž mapy (map). Další kontejnery nalezneme v podbalíčcích balíčku container:

Podbalíček Stručný popis
container/heap rozhraní předepisující metody a chování datového typu halda (heap)
container/list obousměrně vázaný lineární seznam
container/ring kruhová fronta či seznam s pevnou kapacitou

Do těchto kontejnerů se ukládají prvky typu interface{}, což v programovacím jazyku Go de facto znamená „hodnoty neznámého typu“. Pokud z kontejneru nějakou hodnotu přečteme, musí se většinou explicitně specifikovat, jaký typ prvků očekáváme. K tomuto účelu se v Go používají takzvané typové aserce, které se zapisují následujícím způsobem:

i := x.(T)
 
i, ok := x.(T)

kde se za T doplní konkrétní datový typ, například int nebo string. Ve druhém případě se do nové proměnné ok zapíše pravdivostní hodnota značící, zda se operace provedla či nikoli.

Poznámka: před popisem možností, které programátorům nabízí rozhraní pro datový typ halda si nejprve ukážeme některé možnosti nabízené balíčkem sort, protože právě možnost (resp. přesněji řečeno nutnost) porovnávání a řazení prvků je pro použití haldy nezbytná.

6. Obousměrně vázaný seznam

Touto datovou strukturou jsme se již zabývali v deváté části seriálu o programovacím jazyku Go, takže si jen ve stručnosti ukažme, jak se vytvoří nový (prázdný) seznam, vloží se do něj tři prvky a následně se celý seznam vypíše s využitím iterace přes všechny jeho prvky:

package main
 
import (
        "container/list"
        "fmt"
)
 
func printList(l *list.List) {
        for e := l.Front(); e != nil; e = e.Next() {
                fmt.Println(e.Value)
        }
}
 
func main() {
        l := list.New()
        l.PushBack("foo")
        l.PushBack("bar")
        l.PushBack("baz")
        printList(l)
}

Výsledkem běhu tohoto příkladu budou následující tři řádky:

foo
bar
baz

7. Použití seznamů ve funkci zásobníku pro implementaci RPN

I s využitím obousměrně vázaného seznamu ve funkci zásobníku (stack) jsme se seznámili v deváté části tohoto seriálu. Stačí se pouze rozhodnout, který konec seznamu bude sloužit jako vrchol zásobníku (TOS – Top of Stack). Pokud zvolíme, že TOS bude ležet na konci seznamu, můžeme si vytvořit dvě pomocné funkce pro dvě základní operace se zásobníkem – push a pop. V případě, že zásobník bude obsahovat pouze celá čísla, může implementace obou zmíněných funkcí vypadat následovně:

Implementace operace push

func push(l *list.List, number int) {
        l.PushBack(number)
}

Implementace operace pop

func pop(l *list.List) int {
        tos := l.Back()
        l.Remove(tos)
        return tos.Value.(int)
}

Zásobník využijeme například při implementaci kalkulačky, která rozpozná priority operátorů:

package main
 
import (
        "container/list"
        "fmt"
        "strconv"
        "strings"
)
 
type Stack list.List
 
func printStack(l *list.List) {
        for e := l.Front(); e != nil; e = e.Next() {
                fmt.Println(e.Value)
        }
}
 
func push(l *list.List, number int) {
        l.PushBack(number)
}
 
func pop(l *list.List) int {
        tos := l.Back()
        l.Remove(tos)
        return tos.Value.(int)
}
 
func main() {
        expression := "1 2 + 2 3 * 8 + *"
        terms := strings.Split(expression, " ")
        stack := list.New()
 
        for _, term := range terms {
                switch term {
                case "+":
                        operand1 := pop(stack)
                        operand2 := pop(stack)
                        push(stack, operand1+operand2)
                case "-":
                        operand1 := pop(stack)
                        operand2 := pop(stack)
                        push(stack, operand2-operand1)
                case "*":
                        operand1 := pop(stack)
                        operand2 := pop(stack)
                        push(stack, operand1*operand2)
                case "/":
                        operand1 := pop(stack)
                        operand2 := pop(stack)
                        push(stack, operand2/operand1)
                default:
                        number, err := strconv.Atoi(term)
                        if err == nil {
                                push(stack, number)
                        }
                }
        }
        printStack(stack)
}

Výsledek výpočtu je jednoznačný:

42

Nepatrnou úpravou tohoto programu získáme nástroj, který nejenže daný RPN výraz vypočítá, ale bude v průběhu výpočtu vypisovat i aktuální obsah zásobníku. Úprava spočívá v přidání řádků vypisujících prováděné operace a jejich operandy:

package main
 
import (
        "container/list"
        "fmt"
        "strconv"
        "strings"
)
 
type Stack list.List
 
func printStack(l *list.List) {
        for e := l.Front(); e != nil; e = e.Next() {
                fmt.Printf("%2d ", e.Value)
        }
        println()
}
 
func push(l *list.List, number int) {
        l.PushBack(number)
}
 
func pop(l *list.List) int {
        tos := l.Back()
        l.Remove(tos)
        return tos.Value.(int)
}
 
func main() {
        expression := "1 2 + 2 3 * 8 + *"
        terms := strings.Split(expression, " ")
        stack := list.New()
 
        for _, term := range terms {
                switch term {
                case "+":
                        operand1 := pop(stack)
                        operand2 := pop(stack)
                        push(stack, operand1+operand2)
                        print("+ :     ")
                        printStack(stack)
                case "-":
                        operand1 := pop(stack)
                        operand2 := pop(stack)
                        push(stack, operand2-operand1)
                        print("- :     ")
                        printStack(stack)
                case "*":
                        operand1 := pop(stack)
                        operand2 := pop(stack)
                        push(stack, operand1*operand2)
                        print("* :     ")
                        printStack(stack)
                case "/":
                        operand1 := pop(stack)
                        operand2 := pop(stack)
                        push(stack, operand2/operand1)
                        print("/ :     ")
                        printStack(stack)
                default:
                        number, err := strconv.Atoi(term)
                        if err == nil {
                                push(stack, number)
                        }
                        fmt.Printf("%-2d:     ", number)
                        printStack(stack)
                }
        }
        print("Result: ")
        printStack(stack)
}

Postupné provádění výpočtu s jeho vizualizací:

1 :      1
2 :      1  2
+ :      3
2 :      3  2
3 :      3  2  3
* :      3  6
8 :      3  6  8
+ :      3 14
* :     42
Result: 42

8. Cyklická fronta (cyklický seznam)

Druhým typem kontejneru, který si v dnešním článku popíšeme, je kruhová fronta popř. kruhový seznam s pevnou kapacitou, který se označuje termínem ring. Vlastní kontejner, který obsahuje prvky typu Ring, se vytváří konstruktorem New (pochopitelně se zadáním názvu balíčku v prefixu), kterému je nutné předat požadovanou kapacitu kontejneru:

func New(n int) *Ring

Příkladem může být cyklická fronta s kapacitou deseti prvků:

r := ring.New(10)
Poznámka: ve skutečnosti by se termín cyklická fronta měl použít jen tehdy, pokud by se ukazatele čteného a zapisovaného prvku měnily nezávisle na sobě. To lze programově snadno dosáhnout.

Ve chvíli, kdy je kontejner o požadované kapacitě vytvořen, je vrácena hodnota představovaná ukazatelem na strukturu nazvanou taktéž Ring. Tato struktura představující první prvek v kruhovém seznamu interně obsahuje jediný prvek nazvaný Value, který je typu interface{}:

type Ring struct {
    Value interface{}
}

Vzhledem k tomu, že prázdné rozhraní interface{} je implementováno všemi datovými typy programovacího jazyka Go (jak těmi základními, tak i uživatelskými), je možné do kontejneru ukládat prvky libovolného typu, pochopitelně včetně celých čísel:

r := ring.New(10)
r.Value = 42

Na předchozí resp. na následující prvek v kontejneru se přechází metodami :

func (r *Ring) Next() *Ring

a:

func (r *Ring) Prev() *Ring
Poznámka: povšimněte si, že tyto metody vrací ukazatel na jiný prvek patřící do kontejneru. To vlastně znamená, že programátoři nemají k dispozici žádný „obal“ nad jednotlivými prvky a ring je skutečně tvořen pouze vázaným seznamem jednotlivých prvků typu Ring. A právě tato volnost nám umožňuje snadno vytvořit i cyklickou frontu.

Celková kapacita kontejneru se získá metodou Len(), která však musí všemi prvky projít, takže její časová složitost je O(n).

Mezi další užitečné metody patří:

Metoda Význam
Move přesun ukazatele na aktivní prvek dopředu či dozadu o n prvků
Link spojení dvou cyklických seznamů
Unlink odstranění n elementů z kontejneru
Do umožňuje iterovat nad jednotlivými prvky cyklického seznamu

9. Naplnění cyklické fronty hodnotami, hodnoty neinicializovaných prvků

Základní vlastnosti cyklické fronty či cyklického seznamu si ukážeme na několika následujících demonstračních příkladech.

V prvním demonstračním příkladu, jehož zdrojový kód naleznete na adrese https://github.com/tisnik/go-root/blob/master/article24/08_em­pty_ring.go, je vytvořen kontejner o kapacitě deseti prvků, které ovšem nejsou žádným způsobem inicializovány. Tyto prvky (Ring.Value) ve výchozím stavu obsahují hodnotu nil:

package main
 
import (
        "container/ring"
        "fmt"
)
 
func printRing(r *ring.Ring) {
        length := r.Len()
        for i := 0; i < length; i++ {
                fmt.Println(i, r.Value)
                r = r.Next()
        }
}
 
func main() {
        r := ring.New(10)
        printRing(r)
}

O tom, že se skutečně jedná o prvky s hodnotou nil se snadno přesvědčíme:

0 <nil>
1 <nil>
2 <nil>
3 <nil>
4 <nil>
5 <nil>
6 <nil>
7 <nil>
8 <nil>
9 <nil>

Druhý demonstrační příklad ukazuje, jakým způsobem je možné naplnit prvky kontejneru a jak se hodnoty uložené do prvků opět vytisknou (či zpracují jiným způsobem). Musíme mít přitom na paměti, že se jedná o kruhový seznam/frontu, takže když se budeme pohybovat pouze jedním směrem (Ring.Next() či naopak pouze Ring.Prev()), budou se prvky cyklicky opakovat. To je patrné i z funkce provádějící výpis, protože zde procházíme každým prvkem dvakrát:

length := r.Len()
for i := 0; i < length*2; i++ {
        fmt.Println(i, r.Value)
        r = r.Next()
}

Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:

package main
 
import (
        "container/ring"
        "fmt"
)
 
func printRing(r *ring.Ring) {
        length := r.Len()
        for i := 0; i < length*2; i++ {
                fmt.Println(i, r.Value)
                r = r.Next()
        }
}
 
func main() {
        r := ring.New(3)
        r.Value = "foo"
        r = r.Next()
        r.Value = "bar"
        r = r.Next()
        r.Value = "baz"
        printRing(r)
}

Jeho výsledky:

0 baz
1 foo
2 bar
3 baz
4 foo
5 bar

V dalším příkladu jsou některé prvky kontejneru naplněny a jiné naopak přeskočeny:

r := ring.New(10)
r.Value = "foo"
r = r.Next()
r.Value = "bar"
r = r.Next()
r.Value = "baz"

To se pochopitelně projeví i ve výpisu, ovšem je nutné mít na paměti, že s hodnotami nil je možné v programovacím jazyce Go pracovat zcela bez obavy.

Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:

package <strong>main</strong>
 
import (
        "container/ring"
        "fmt"
)
 
func <strong>printRing</strong>(r *ring.Ring) {
        length := r.Len()
        for i := 0; i < length*2; i++ {
                fmt.Println(i, r.Value)
                r = r.Next()
        }
}
 
func <strong>main</strong>() {
        r := ring.New(10)
        r.Value = "foo"
        r = r.Next()
        r.Value = "bar"
        r = r.Next()
        r.Value = "baz"
        printRing(r)
}

Z výstupu je patrné, že se skutečně naplnily pouze některé prvky:

0 baz
1 <nil>
2 <nil>
3 <nil>
4 <nil>
5 <nil>
6 <nil>
7 <nil>
8 foo
9 bar
10 baz
11 <nil>
12 <nil>
13 <nil>
14 <nil>
15 <nil>
16 <nil>
17 <nil>
18 foo
19 bar

10. Procházení všemi prvky cyklické fronty: metoda Do

Pro procházení všemi prvky cyklické fronty/seznamu je určena metoda nazvaná Do, která umožňuje, aby se při průchodu každým prvkem (tedy v každé iteraci) zavolala určitá funkce, která se předá hodnota uložená do prvku. V následujícím – nutno říci, že velmi jednoduchém – příkladu je ukázáno, jak je možné zavolat funkci nazvanou printItem:

func printItem(item interface{}) {
        fmt.Println(item)
}

Samotný průchod se pro cyklickou frontu r zajistí jediným programovým řádkem:

r.Do(printItem)

Následuje výpis zdrojového kódu tohoto příkladu:

package main
 
import (
        "container/ring"
        "fmt"
)
 
func printItem(item interface{}) {
        fmt.Println(item)
}
 
func main() {
        r := ring.New(3)
        r.Value = "foo"
        r = r.Next()
        r.Value = "bar"
        r = r.Next()
        r.Value = "baz"
        r.Do(printItem)
}

S výsledky:

baz
foo
bar

V případě, že budeme chtít funkci volané pro každý prvek předávat i další parametr, například spočítaný index prvku:

func printItem(index int, item interface{}) {
        fmt.Println(index, item)
}

Je nutné použít uzávěr (closure), který je v jazyku Go taktéž podporován:

r.Do(func(item interface{}) {
        printItem(i, item)
        i++
})

Opět si ukažme upravený zdrojový kód příkladu:

package main
 
import (
        "container/ring"
        "fmt"
)
 
func printItem(index int, item interface{}) {
        fmt.Println(index, item)
}
 
func main() {
        r := ring.New(3)
        r.Value = "foo"
        r = r.Next()
        r.Value = "bar"
        r = r.Next()
        r.Value = "baz"
 
        i := 1
        r.Do(func(item interface{}) {
                printItem(i, item)
                i++
        })
}

Nyní se ve výsledcích objeví i indexy jednotlivých prvků (pokud ovšem mají v kruhové struktuře nějaký zvláštní význam):

1 baz
2 foo
3 bar

11. Balíček sort: řadicí a vyhledávací algoritmy

Nyní popis kontejnerů, které nalezneme ve standardní knihovně programovacího jazyka Go na chvíli opustíme, protože si popíšeme některé možnosti poskytované balíčkem nazvaným sort. Jak již název tohoto balíčku naznačuje, jsou v něm implementovány algoritmy pro řazení prvků; kromě toho zde však nalezneme i algoritmy pro vyhledávání prvků. Datové typy a metody v balíčku sort jsou vybrány poněkud zvláštním způsobem, protože zde nalezneme jak zcela obecné metody (řazení prvků libovolných typů), tak i metody určené pro některé konkrétní vybrané typy: pole celých čísel typu int, pole čísel s plovoucí řádovou čárkou typu float64 a konečně pole řetězců (string).

Pro samotné řazení prvků se používají následující funkce:

# Funkce Význam
1 func Ints(a []int) seřazení řezu celých čísel
2 func Float64s(a []float64) seřazení řezu čísel typu float64
3 func Strings(a []string) seřazení řezu řetězců
4 func Sort(data Interface) seřazení libovolné kolekce implementující rozhraní sort.Interface
5 func Stable(data Interface) stabilní varianta algoritmu řazení libovolné kolekce implementující rozhraní sort.Interface
6 func Reverse(data Interface) otočení pořadí prvků v kolekci

Kromě toho je možné zavolat i další funkce, které zjistí, zda je určitý řez nebo kolekce již seřazená:

# Funkce Význam
1 func IntsAreSorted(a []int) bool test, zda je řez celých čísel setříděn
2 func Float64sAreSorted(a []float64) bool test, zda je řez čísel typu float64 setříděn
3 func StringsAreSorted(a []string) bool test, zda je řez obsahující řetězce setříděn
4 func IsSorted(data Interface) bool test, zda jsou prvky v obecné kolekci setříděny

Tyto funkce si popíšeme v dalších demonstračních příkladech.

Důležité je taktéž rozhraní nazvané přímočaře Interface, v němž jsou předepsány metody volané při řazení sekvencí. Toto rozhraní se používá u všech datových typů, tj. i u uživatelských typů:

type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

12. Práce s poli či řezy s prvky typu int a float64

V prvním demonstračním příkladu, jehož zdrojový kód naleznete na adrese https://github.com/tisnik/go-root/blob/master/article24/13_in­teger_sort.go, je ukázán způsob seřazení sekvence celých čísel a současně i zjištění, jestli je již řez, v němž je sekvence uložena, setříděn či nikoli. Na začátku je řez inicializován sekvencí náhodných hodnot; tato sekvence je vypsána, setříděna a poté znovu vypsána:

package main
 
import (
        "fmt"
        "math/rand"
        "sort"
)
 
func printArray(prefix string, numbers []int) {
        var state string
        if sort.IntsAreSorted(numbers) {
                state = "sorted"
        } else {
                state = "unsorted"
        }
        fmt.Printf("%s variant of %s array: %v\n", prefix, state, numbers)
}
 
func main() {
        numbers := make([]int, 20)
 
        for i := 0; i < len(numbers); i++ {
                numbers[i] = rand.Int() % 10
        }
        printArray("1st", numbers)
 
        sort.Ints(numbers)
        printArray("2nd", numbers)
}

Po spuštění příkladu se nejdříve zobrazí původní náhodná sekvence a následně sekvence seřazená:

1st variant of unsorted array: [0 1 1 1 7 0 8 8 6 9 4 7 4 6 5 3 8 1 0 1]
2nd variant of sorted array: [0 0 0 1 1 1 1 1 3 4 4 5 6 6 7 7 8 8 8 9]

Druhý demonstrační příklad se do značné míry podobá příkladu předchozímu, ovšem nyní budeme pracovat s polem čísel s plovoucí řádovou čárkou, konkrétně s datovým typem float64. Až na rozdílný způsob inicializace hodnot se tento příklad podobá příkladu předchozímu. Jeho zdrojový kód naleznete na adrese https://github.com/tisnik/go-root/blob/master/article24/14_flo­at_sort.go:

package main
 
import (
        "fmt"
        "math"
        "sort"
)
 
func printArray(prefix string, numbers []float64) {
        var state string
        if sort.Float64sAreSorted(numbers) {
                state = "sorted"
        } else {
                state = "unsorted"
        }
        fmt.Printf("%s variant of %s array: %v\n", prefix, state, numbers)
}
 
func main() {
        numbers := make([]float64, 20)
 
        for i := 0; i < len(numbers); i++ {
                numbers[i] = math.Sin(float64(i) * math.Pi / float64(len(numbers)))
        }
        printArray("1st", numbers)
 
        sort.Float64s(numbers)
        printArray("2nd", numbers)
}

Výsledek činnosti tohoto příkladu:

1st variant of unsorted array: [0 0.15643446504023087 0.3090169943749474 0.45399049973954675 0.5877852522924731 0.7071067811865475 0.8090169943749475 0.8910065241883678 0.9510565162951535 0.9876883405951377 1 0.9876883405951378 0.9510565162951536 0.8910065241883679 0.8090169943749475 0.7071067811865477 0.5877852522924732 0.45399049973954686 0.3090169943749475 0.15643446504023098]
2nd variant of sorted array: [0 0.15643446504023087 0.15643446504023098 0.3090169943749474 0.3090169943749475 0.45399049973954675 0.45399049973954686 0.5877852522924731 0.5877852522924732 0.7071067811865475 0.7071067811865477 0.8090169943749475 0.8090169943749475 0.8910065241883678 0.8910065241883679 0.9510565162951535 0.9510565162951536 0.9876883405951377 0.9876883405951378 1]

13. Složitější příklad: seřazení všech slov načtených z textového souboru

Třetí příklad je již nepatrně složitější, protože se v něm pokusíme o načtení textového souboru, rozdělení obsahu tohoto souboru na jednotlivá slova a následně o lexikografické seřazení těchto slov. V první části je nutné načíst soubor a rozdělit ho na slova. To se provede takto:

reader, err := os.Open(filename)
if err != nil {
        log.Fatal(err)
}
defer reader.Close()
 
bufferedReader := bufio.NewReader(reader)
 
words := []string{}
 
for {
        str, err := bufferedReader.ReadString('\n')
        if err != nil {
                break
        } else {
                str = strings.Trim(str, "\n")
                ws := strings.Split(str, " ")
                words = append(words, ws...)
        }
}
Poznámka #1: povšimněte si triku při volání funkce append sloužící pro přidání nového prvku do řezu (slice). Tato funkce totiž akceptuje libovolný počet argumentů:
func append(slice []Type, elems ...Type) []Type

Zápisem:

words = append(words, ws...)

je možné zajistit, že se řez řetězců rozdělí a jeho jednotlivé prvky se do funkce append předají jako samostatné parametry.

Poznámka #2: ve skutečnosti není výše uvedený kód zcela korektní, protože nedokáže odfiltrovat znaky umístěné přímo za slovy. V našem konkrétním případě se jedná o čárky a tečky. Úpravu příkladu takovým způsobem, aby byl zcela korektní, již ponechám na váženém čtenáři.

Úplný zdrojový kód tohoto příkladu vypadá následovně:

package main
 
import (
        "bufio"
        "fmt"
        "log"
        "os"
        "sort"
        "strings"
)
 
func printArray(prefix string, values []string) {
        var state string
        if sort.StringsAreSorted(values) {
                state = "sorted"
        } else {
                state = "unsorted"
        }
        fmt.Printf("%s variant of %s array:\n", prefix, state)
        for _, s := range values {
                fmt.Printf("    %s\n", s)
        }
}
 
const filename = "test_input.txt"
 
func main() {
        reader, err := os.Open(filename)
        if err != nil {
                log.Fatal(err)
        }
        defer reader.Close()
 
        bufferedReader := bufio.NewReader(reader)
 
        words := []string{}
 
        for {
                str, err := bufferedReader.ReadString('\n')
                if err != nil {
                        break
                } else {
                        str = strings.Trim(str, "\n")
                        ws := strings.Split(str, " ")
                        words = append(words, ws...)
                }
        }
 
        printArray("1st", words)
 
        sort.Strings(words)
        printArray("2nd", words)
}

Příklad si otestujeme na textovém souboru s následujícím obsahem:

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum.

Výsledek činnosti příkladu – nejdříve se vypíše seznam slov a následně se tento seznam seřadí:

1st variant of unsorted array:
    Lorem
    ipsum
    dolor
    sit
    amet,
    consectetur
    adipiscing
    elit,
    sed
    do
    eiusmod
    tempor
    incididunt
    ut
    labore
    et
    dolore
    magna
    aliqua.
    Ut
    enim
    ad
    minim
    veniam,
    quis
    nostrud
    exercitation
    ullamco
    laboris
    nisi
    ut
    aliquip
    ex
    ea
    commodo
    consequat.
    Duis
    aute
    irure
    dolor
    in
    reprehenderit
    in
    voluptate
    velit
    esse
    cillum
    dolore
    eu
    fugiat
    nulla
    pariatur.
    Excepteur
    sint
    occaecat
    cupidatat
    non
    proident,
    sunt
    in
    culpa
    qui
    officia
    deserunt
    mollit
    anim
    id
    est
    laborum.
2nd variant of sorted array:
    Duis
    Excepteur
    Lorem
    Ut
    ad
    adipiscing
    aliqua.
    aliquip
    amet,
    anim
    aute
    cillum
    commodo
    consectetur
    consequat.
    culpa
    cupidatat
    deserunt
    do
    dolor
    dolor
    dolore
    dolore
    ea
    eiusmod
    elit,
    enim
    esse
    est
    et
    eu
    ex
    exercitation
    fugiat
    id
    in
    in
    in
    incididunt
    ipsum
    irure
    labore
    laboris
    laborum.
    magna
    minim
    mollit
    nisi
    non
    nostrud
    nulla
    occaecat
    officia
    pariatur.
    proident,
    qui
    quis
    reprehenderit
    sed
    sint
    sit
    sunt
    tempor
    ullamco
    ut
    ut
    velit
    veniam,
    voluptate

14. Typy IntSlice, Float64Slice a StringSlice

Kromě výše zmíněných funkcí nalezneme v balíčku sort i několik užitečných datových typů a jedno rozhraní:

# Typ/rozhraní Stručný popis
1 Interface rozhraní, kterou musí splňovat jakákoli kolekce, která má být seřaditelná
2 IntSlice implementace (nejenom) rozhraní Interface pro řez celých čísel
3 Float64Slice implementace (nejenom) rozhraní Interface pro řez čísel typu float64
4 StringSlice implementace (nejenom) rozhraní Interface pro řez řetězců

Datové typy xxxSlice umožňují snadnější práci s řezy celých čísel, čísel s plovoucí řádovou čárkou a taktéž s řezy řetězců; například je možné zavolat operaci pro otočení celé sekvence Reverse:

package main
 
import (
        "fmt"
        "math/rand"
        "sort"
)
 
func printArray(prefix string, numbers []int) {
        var state string
        if sort.IntsAreSorted(numbers) {
                state = "sorted"
        } else {
                state = "unsorted"
        }
        fmt.Printf("%s variant of %s array: %v\n", prefix, state, numbers)
}
 
func main() {
        numbers := make([]int, 20)
 
        for i := 0; i < len(numbers); i++ {
                numbers[i] = rand.Int() % 10
        }
        printArray("1st", numbers)
 
        sort.Sort(sort.Reverse(sort.IntSlice(numbers)))
        printArray("2nd", numbers)
}

Univerzální funkce Reverse skutečně otočila i běžný řez celých čísel:

1st variant of unsorted array: [0 1 1 1 7 0 8 8 6 9 4 7 4 6 5 3 8 1 0 1]
2nd variant of unsorted array: [9 8 8 8 7 7 6 6 5 4 4 3 1 1 1 1 1 0 0 0]

15. Řazení prvků na základě vlastní porovnávací funkce

Další způsob využití typů xxxSlice spočívá v tom, že je možné daný řez seřadit na základě vlastní callback funkce volané při porovnávání dvou prvků v sekvenci. Používá se přitom metoda nazvaná Slice, které se předá jak sekvence, která se má seřadit, tak i definice příslušné callback funkce:

sort.Slice(numbers, func(i, j int) bool {
        return numbers[i] > numbers[j]
})

Výše uvedená callback funkce způsobí, že se sekvence seřadí sestupně:

IntSlice: [0 1 1 1 7 0 8 8 6 9 4 7 4 6 5 3 8 1 0 1]
IntSlice: [9 8 8 8 7 7 6 6 5 4 4 3 1 1 1 1 1 0 0 0]
Poznámka: pro seřazení vzestupně pouze otočte operátor >.

Samozřejmě si opět ukážeme celý zdrojový kód demonstračního příkladu:

package main
 
import (
        "fmt"
        "math/rand"
        "sort"
)
 
func printIntSlice(numbers sort.IntSlice) {
        fmt.Printf("IntSlice: %v\n", numbers)
}
 
func main() {
        numbers := make([]int, 20)
 
        for i := 0; i < len(numbers); i++ {
                numbers[i] = rand.Int() % 10
        }
        printIntSlice(numbers)
 
        sort.Slice(numbers, func(i, j int) bool {
                return numbers[i] > numbers[j]
        })
 
        printIntSlice(numbers)
}

16. Řazení pole/řezů struktur podle vlastních kritérií

Poslední příklad, který se týká přímo balíčku sort, ukazuje, jakým způsobem lze zajistit seřazení pole/řezu obsahujícího uživatelsky definované struktury, zde konkrétně strukturu:

type Role struct {
        name    string
        surname string
}

Seřazení podle jména zajistí nám již známá funkce sort.Slice s vhodně upravenou callback funkcí:

sort.Slice(roles, func(i, j int) bool { return roles[i].name < roles[j].name })

Naopak seřazení podle příjmení bude implementováno takto:

sort.Slice(roles, func(i, j int) bool { return roles[i].surname < roles[j].surname })

Následuje výpis příkladu, který seřadí postavy ze hry Švestka napřed podle jména a potom podle příjmení:

package main
 
import (
        "fmt"
        "sort"
)
 
type Role struct {
        name    string
        surname string
}
 
func printRoles(roles []Role) {
        for i, role := range roles {
                fmt.Printf("#%d: %s %s\n", i, role.name, role.surname)
        }
}
 
func main() {
        roles := []Role{
                Role{"Eliška", "Najbrtová"},
                Role{"Jenny", "Suk"},
                Role{"Anička", "Šafářová"},
                Role{"Sváťa", "Pulec"},
                Role{"Blažej", "Motyčka"},
                Role{"Eda", "Wasserfall"},
                Role{"Přemysl", "Hájek"},
        }
 
        fmt.Println("Unsorted:")
        printRoles(roles)
        fmt.Println("--------------------")
 
        sort.Slice(roles, func(i, j int) bool { return roles[i].name < roles[j].name })
 
        fmt.Println("Sorted by name:")
        printRoles(roles)
        fmt.Println("--------------------")
 
        sort.Slice(roles, func(i, j int) bool { return roles[i].surname < roles[j].surname })
 
        fmt.Println("Sorted by surname:")
        printRoles(roles)
        fmt.Println("--------------------")
}

Výsledky:

Unsorted:
#0: Eliška Najbrtová
#1: Jenny Suk
#2: Anička Šafářová
#3: Sváťa Pulec
#4: Blažej Motyčka
#5: Eda Wasserfall
#6: Přemysl Hájek
--------------------
Sorted by name:
#0: Anička Šafářová
#1: Blažej Motyčka
#2: Eda Wasserfall
#3: Eliška Najbrtová
#4: Jenny Suk
#5: Přemysl Hájek
#6: Sváťa Pulec
--------------------
Sorted by surname:
#0: Přemysl Hájek
#1: Blažej Motyčka
#2: Eliška Najbrtová
#3: Sváťa Pulec
#4: Jenny Suk
#5: Eda Wasserfall
#6: Anička Šafářová
--------------------

17. Rozhraní Heap a jeho význam v praxi

V balíčku container nalezneme i definici rozhraní Heap, které předepisuje chování datové struktury typu halda (heap), viz též podrobnější popis na Wikipedii – https://en.wikipedia.org/wi­ki/Heap_%28data_structure%29.

Aby byla halda implementována korektně, musíme pro datový typ, který ji implementuje (zde například StringHeap) implementovat i metody Len, Less, Swap, Push a Pop, například tak, jak je to ukázáno v dalším příkladu:

package main
 
import (
        "container/heap"
        "fmt"
)
 
type StringHeap []string
 
func (h StringHeap) Len() int {
        return len(h)
}
 
func (h StringHeap) Less(i, j int) bool {
        return h[i] < h[j]
}
 
func (h StringHeap) Swap(i, j int) {
        h[i], h[j] = h[j], h[i]
}
 
func (h *StringHeap) Push(x interface{}) {
        *h = append(*h, x.(string))
}
 
func (h *StringHeap) Pop() interface{} {
        old := *h
        n := len(old)
        x := old[n-1]
        *h = old[0 : n-1]
        return x
}
 
func main() {
        h := &StringHeap{"foo", "bar", "baz", "zzz", "aaa"}
        heap.Init(h)
        heap.Push(h, "ZZZ")
        heap.Push(h, "AAA")
        fmt.Printf("First item: %s\n", (*h)[0])
        i := 0
        for h.Len() > 0 {
                i++
                fmt.Printf("item #%d = %s\n", i, heap.Pop(h))
        }
}

Při čtení prvků z haldy zjistíme, že prvky jsou automaticky seřazeny:

First item: AAA
item #1 = AAA
item #2 = ZZZ
item #3 = aaa
item #4 = bar
item #5 = baz
item #6 = foo
item #7 = zzz

18. Interní změna pozice prvků při práci s haldou

Pokud budeme chtít prozkoumat, jak a na základě jakých kritérií se mění pozice prvků v haldě, můžeme si předchozí příklad nepatrně upravit – přidat do něj trasovací příkazy:

package main
 
import (
        "container/heap"
        "fmt"
)
 
type StringHeap []string
 
func (h StringHeap) Len() int {
        return len(h)
}
 
func (h StringHeap) Less(i, j int) bool {
        fmt.Printf("compare %s < %s\n", h[i], h[j])
        return h[i] < h[j]
}
 
func (h StringHeap) Swap(i, j int) {
        fmt.Printf("swap    %s <-> %s\n", h[i], h[j])
        h[i], h[j] = h[j], h[i]
}
 
func (h *StringHeap) Push(x interface{}) {
        *h = append(*h, x.(string))
}
 
func (h *StringHeap) Pop() interface{} {
        old := *h
        n := len(old)
        x := old[n-1]
        *h = old[0 : n-1]
        return x
}
 
func main() {
        h := &StringHeap{}
 
        heap.Init(h)
        heap.Push(h, "a")
        heap.Push(h, "z")
        heap.Push(h, "c")
        heap.Push(h, "b")
        heap.Push(h, "d")
        heap.Push(h, "x")
 
        fmt.Println("\n-----------------------------")
        fmt.Printf("First item: %s\n", (*h)[0])
        i := 0
        for h.Len() > 0 {
                i++
                fmt.Printf("item #%d = %s\n", i, heap.Pop(h))
        }
}

Při zápisu a čtení prvků bude docházet k jejich reorganizaci na základě jejich vzájemného porovnání:

compare z < a
compare c < a
compare b < z
swap    z <-> b
compare b < a
compare d < b
compare x < c
 
-----------------------------
First item: a
swap    a <-> x
compare c < b
compare b < x
swap    x <-> b
compare d < z
compare d < x
swap    x <-> d
item #1 = a
swap    b <-> x
compare c < d
compare c < x
swap    x <-> c
item #2 = b
swap    c <-> z
compare x < d
compare d < z
swap    z <-> d
item #3 = c
swap    d <-> x
compare z < x
item #4 = d
swap    x <-> z
item #5 = x
swap    z <-> z
item #6 = z
Poznámka: k této datové struktuře se ještě vrátíme při popisu prioritních front.

19. Repositář s demonstračními příklady

Zdrojové kódy všech dnes popsaných demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně dva megabajty), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Soubor Popis Cesta
1 01_server.go jednoduchý HTTP server posílající dynamicky generovaný obsah https://github.com/tisnik/go-root/blob/master/article24/01_ser­ver.go
2 02_slow_server.go zpomalení generování jednotlivých bloků generovaného obsahu https://github.com/tisnik/go-root/blob/master/article24/02_slow_ser­ver.go
3 03_flushing_server.go využití metody Flush z rozhraní Flusher https://github.com/tisnik/go-root/blob/master/article24/03_flushin­g_server.go
4 04_close_detector.go test, zda klient neukončil spojení https://github.com/tisnik/go-root/blob/master/article24/04_clo­se_detector.go
5 05_list.go operace nad seznamem https://github.com/tisnik/go-root/blob/master/article24/05_lis­t.go
6 06_list_as_stack.go použití seznamu ve funkci zásobníku https://github.com/tisnik/go-root/blob/master/article24/06_lis­t_as_stack.go
7 07_print_stack_content.go vylepšení předchozího příkladu https://github.com/tisnik/go-root/blob/master/article24/07_prin­t_stack_content.go
8 08_empty_ring.go prázdná cyklická fronta https://github.com/tisnik/go-root/blob/master/article24/08_em­pty_ring.go
9 09_filled_ring.go cyklická fronta s naplněnými prvky https://github.com/tisnik/go-root/blob/master/article24/09_fi­lled_ring.go
10 10_ring_empty_items.go cyklická fronta s některými prázdnými prvky https://github.com/tisnik/go-root/blob/master/article24/10_rin­g_empty_items.go
11 11_ring_do_iterator.go iterace nad prvky cyklické fronty https://github.com/tisnik/go-root/blob/master/article24/11_rin­g_do_iterator.go
12 12_ring_do_closure.go vylepšená iterace nad prvky cyklické fronty https://github.com/tisnik/go-root/blob/master/article24/12_rin­g_do_closure.go
13 13_integer_sort.go seřazení sekvence celých čísel https://github.com/tisnik/go-root/blob/master/article24/13_in­teger_sort.go
14 14_float_sort.go seřazení sekvence čísel s plovoucí řádovou čárkou https://github.com/tisnik/go-root/blob/master/article24/14_flo­at_sort.go
15 15_string_sort.go načtení textového souboru s jeho rozdělením na slova a seřazením https://github.com/tisnik/go-root/blob/master/article24/15_strin­g_sort.go
16 16_reverse_sort.go opačně seřazení sekvence/řezu https://github.com/tisnik/go-root/blob/master/article24/16_re­verse_sort.go
17 17_int_slice.go použití datového typu IntSlice https://github.com/tisnik/go-root/blob/master/article24/17_in­t_slice.go
18 18_sort_by.go seřazení sekvence uživatelských datových struktur https://github.com/tisnik/go-root/blob/master/article24/18_sor­t_by.go
19 19_heap.go základní způsob využití datového typu halda https://github.com/tisnik/go-root/blob/master/article24/19_he­ap.go
20 20_heap_trace.go úprava předchozího příkladu, aby zobrazoval prováděné operace https://github.com/tisnik/go-root/blob/master/article24/20_he­ap_trace.go

20. Odkazy na Internetu

  1. Go Proverbs: Simple, Poetic, Pithy
    https://go-proverbs.github.io/
  2. Handling Sparse Files on Linux
    https://www.systutorials.com/136652/han­dling-sparse-files-on-linux/
  3. Gzip (Wikipedia)
    https://en.wikipedia.org/wiki/Gzip
  4. Deflate
    https://en.wikipedia.org/wiki/DEFLATE
  5. 10 tools written in Go that every developer needs to know
    https://gustavohenrique.net/en/2019/01/10-tools-written-in-go-that-every-dev-needs-to-know/
  6. Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
    https://www.root.cz/clanky/he­xadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/
  7. Hex dump
    https://en.wikipedia.org/wi­ki/Hex_dump
  8. Rozhraní io.ByteReader
    https://golang.org/pkg/io/#ByteReader
  9. Rozhraní io.RuneReader
    https://golang.org/pkg/io/#RuneReader
  10. Rozhraní io.ByteScanner
    https://golang.org/pkg/io/#By­teScanner
  11. Rozhraní io.RuneScanner
    https://golang.org/pkg/io/#Ru­neScanner
  12. Rozhraní io.Closer
    https://golang.org/pkg/io/#Closer
  13. Rozhraní io.Reader
    https://golang.org/pkg/io/#Reader
  14. Rozhraní io.Writer
    https://golang.org/pkg/io/#Writer
  15. Typ Strings.Reader
    https://golang.org/pkg/strin­gs/#Reader
  16. VACUUM (SQL)
    https://www.sqlite.org/lan­g_vacuum.html
  17. VACUUM (Postgres)
    https://www.postgresql.or­g/docs/8.4/sql-vacuum.html
  18. go-cron
    https://github.com/rk/go-cron
  19. gocron
    https://github.com/jasonlvhit/gocron
  20. clockwork
    https://github.com/whiteShtef/cloc­kwork
  21. clockwerk
    https://github.com/onatm/clockwerk
  22. JobRunner
    https://github.com/bamzi/jobrunner
  23. Rethinking Cron
    https://adam.herokuapp.com/pas­t/2010/4/13/rethinking_cron/
  24. In the Beginning was the Command Line
    https://web.archive.org/web/20180218045352/htt­p://www.cryptonomicon.com/be­ginning.html
  25. repl.it (REPL pro různé jazyky)
    https://repl.it/languages
  26. GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
    https://github.com/jroimartin/gocui
  27. Read–eval–print loop
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  28. go-prompt
    https://github.com/c-bata/go-prompt
  29. readline
    https://github.com/chzyer/readline
  30. A pure golang implementation for GNU-Readline kind library
    https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/
  31. go-readline
    https://github.com/fiorix/go-readline
  32. 4 Python libraries for building great command-line user interfaces
    https://opensource.com/article/17/5/4-practical-python-libraries
  33. prompt_toolkit 2.0.3 na PyPi
    https://pypi.org/project/prom­pt_toolkit/
  34. python-prompt-toolkit na GitHubu
    https://github.com/jonathan­slenders/python-prompt-toolkit
  35. The GNU Readline Library
    https://tiswww.case.edu/php/chet/re­adline/rltop.html
  36. GNU Readline (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Readline
  37. readline — GNU readline interface (Python 3.x)
    https://docs.python.org/3/li­brary/readline.html
  38. readline — GNU readline interface (Python 2.x)
    https://docs.python.org/2/li­brary/readline.html
  39. GNU Readline Library – command line editing
    https://tiswww.cwru.edu/php/chet/re­adline/readline.html
  40. gnureadline 6.3.8 na PyPi
    https://pypi.org/project/gnureadline/
  41. Editline Library (libedit)
    http://thrysoee.dk/editline/
  42. Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
    https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/
  43. libedit or editline
    http://www.cs.utah.edu/~bi­gler/code/libedit.html
  44. WinEditLine
    http://mingweditline.sourceforge.net/
  45. rlcompleter — Completion function for GNU readline
    https://docs.python.org/3/li­brary/rlcompleter.html
  46. rlwrap na GitHubu
    https://github.com/hanslub42/rlwrap
  47. rlwrap(1) – Linux man page
    https://linux.die.net/man/1/rlwrap
  48. readline(3) – Linux man page
    https://linux.die.net/man/3/readline
  49. history(3) – Linux man page
    https://linux.die.net/man/3/history
  50. Dokumentace k balíčku oglematchers
    https://godoc.org/github.com/ja­cobsa/oglematchers
  51. Balíček oglematchers
    https://github.com/jacobsa/o­glematchers
  52. Dokumentace k balíčku ogletest
    https://godoc.org/github.com/ja­cobsa/ogletest
  53. Balíček ogletest
    https://github.com/jacobsa/ogletest
  54. Dokumentace k balíčku assert
    https://godoc.org/github.com/stret­chr/testify/assert
  55. Testify – Thou Shalt Write Tests
    https://github.com/stretchr/testify/
  56. package testing
    https://golang.org/pkg/testing/
  57. Golang basics – writing unit tests
    https://blog.alexellis.io/golang-writing-unit-tests/
  58. An Introduction to Programming in Go / Testing
    https://www.golang-book.com/books/intro/12
  59. An Introduction to Testing in Go
    https://tutorialedge.net/golang/intro-testing-in-go/
  60. Advanced Go Testing Tutorial
    https://tutorialedge.net/go­lang/advanced-go-testing-tutorial/
  61. GoConvey
    http://goconvey.co/
  62. Testing Techniques
    https://talks.golang.org/2014/tes­ting.slide
  63. 5 simple tips and tricks for writing unit tests in #golang
    https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742
  64. Afinní transformace
    https://cs.wikibooks.org/wi­ki/Geometrie/Afinn%C3%AD_tran­sformace_sou%C5%99adnic
  65. package gg
    https://godoc.org/github.com/fo­gleman/gg
  66. Generate an animated GIF with Golang
    http://tech.nitoyon.com/en/blog/2016/01/07/­go-animated-gif-gen/
  67. Generate an image programmatically with Golang
    http://tech.nitoyon.com/en/blog/2015/12/31/­go-image-gen/
  68. The Go image package
    https://blog.golang.org/go-image-package
  69. Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
    https://github.com/llgcode/draw2d
  70. Draw a rectangle in Golang?
    https://stackoverflow.com/qu­estions/28992396/draw-a-rectangle-in-golang
  71. YAML
    https://yaml.org/
  72. edn
    https://github.com/edn-format/edn
  73. Smile
    https://github.com/FasterXML/smile-format-specification
  74. Protocol-Buffers
    https://developers.google.com/protocol-buffers/
  75. Marshalling (computer science)
    https://en.wikipedia.org/wi­ki/Marshalling_(computer_sci­ence)
  76. Unmarshalling
    https://en.wikipedia.org/wi­ki/Unmarshalling
  77. Introducing JSON
    http://json.org/
  78. Package json
    https://golang.org/pkg/encoding/json/
  79. The Go Blog: JSON and Go
    https://blog.golang.org/json-and-go
  80. Go by Example: JSON
    https://gobyexample.com/json
  81. Writing Web Applications
    https://golang.org/doc/articles/wiki/
  82. Golang Web Apps
    https://www.reinbach.com/blog/golang-webapps-1/
  83. Build web application with Golang
    https://legacy.gitbook.com/bo­ok/astaxie/build-web-application-with-golang/details
  84. Golang Templates – Golang Web Pages
    https://www.youtube.com/wat­ch?v=TkNIETmF-RU
  85. Simple Golang HTTPS/TLS Examples
    https://github.com/denji/golang-tls
  86. Playing with images in HTTP response in golang
    https://www.sanarias.com/blog/1214Pla­yingwithimagesinHTTPrespon­seingolang
  87. MIME Types List
    https://www.freeformatter.com/mime-types-list.html
  88. Go Mutex Tutorial
    https://tutorialedge.net/golang/go-mutex-tutorial/
  89. Creating A Simple Web Server With Golang
    https://tutorialedge.net/go­lang/creating-simple-web-server-with-golang/
  90. Building a Web Server in Go
    https://thenewstack.io/building-a-web-server-in-go/
  91. How big is the pipe buffer?
    https://unix.stackexchange­.com/questions/11946/how-big-is-the-pipe-buffer
  92. How to turn off buffering of stdout in C
    https://stackoverflow.com/qu­estions/7876660/how-to-turn-off-buffering-of-stdout-in-c
  93. setbuf(3) – Linux man page
    https://linux.die.net/man/3/setbuf
  94. setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
    https://linux.die.net/man/3/setvbuf
  95. Select waits on a group of channels
    https://yourbasic.org/golang/select-explained/
  96. Rob Pike: Simplicity is Complicated (video)
    http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893
  97. Algorithms to Go
    https://yourbasic.org/
  98. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/
  99. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/
  100. Go Defer Simplified with Practical Visuals
    https://blog.learngoprogram­ming.com/golang-defer-simplified-77d3b2b817ff
  101. 5 More Gotchas of Defer in Go — Part II
    https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa
  102. The Go Blog: Defer, Panic, and Recover
    https://blog.golang.org/defer-panic-and-recover
  103. The defer keyword in Swift 2: try/finally done right
    https://www.hackingwithswift.com/new-syntax-swift-2-defer
  104. Swift Defer Statement
    https://andybargh.com/swift-defer-statement/
  105. Modulo operation (Wikipedia)
    https://en.wikipedia.org/wi­ki/Modulo_operation
  106. Node.js vs Golang: Battle of the Next-Gen Languages
    https://www.hostingadvice­.com/blog/nodejs-vs-golang/
  107. The Go Programming Language (home page)
    https://golang.org/
  108. GoDoc
    https://godoc.org/
  109. Go (programming language), Wikipedia
    https://en.wikipedia.org/wi­ki/Go_(programming_langua­ge)
  110. Go Books (kniha o jazyku Go)
    https://github.com/dariubs/GoBooks
  111. The Go Programming Language Specification
    https://golang.org/ref/spec
  112. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  113. Package builtin
    https://golang.org/pkg/builtin/
  114. Package fmt
    https://golang.org/pkg/fmt/
  115. The Little Go Book (další kniha)
    https://github.com/dariubs/GoBooks
  116. The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
    https://www.safaribookson­line.com/library/view/the-go-programming/9780134190570/e­book_split010.html
  117. Learning Go
    https://www.miek.nl/go/
  118. Go Bootcamp
    http://www.golangbootcamp.com/
  119. Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
    http://www.informit.com/sto­re/programming-in-go-creating-applications-for-the-21st-9780321774637
  120. Introducing Go (Build Reliable, Scalable Programs)
    http://shop.oreilly.com/pro­duct/0636920046516.do
  121. Learning Go Programming
    https://www.packtpub.com/application-development/learning-go-programming
  122. The Go Blog
    https://blog.golang.org/
  123. Getting to Go: The Journey of Go's Garbage Collector
    https://blog.golang.org/ismmkeynote
  124. Go (programovací jazyk, Wikipedia)
    https://cs.wikipedia.org/wi­ki/Go_(programovac%C3%AD_ja­zyk)
  125. Rychle, rychleji až úplně nejrychleji s jazykem Go
    https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/
  126. Installing Go on the Raspberry Pi
    https://dave.cheney.net/2012/09/25/in­stalling-go-on-the-raspberry-pi
  127. How the Go runtime implements maps efficiently (without generics)
    https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics
  128. Niečo málo o Go – Golang (slovensky)
    http://golangsk.logdown.com/
  129. How Many Go Developers Are There?
    https://research.swtch.com/gop­hercount
  130. Most Popular Technologies (Stack Overflow Survery 2018)
    https://insights.stackover­flow.com/survey/2018/#most-popular-technologies
  131. Most Popular Technologies (Stack Overflow Survery 2017)
    https://insights.stackover­flow.com/survey/2017#techno­logy
  132. JavaScript vs. Golang for IoT: Is Gopher Winning?
    https://www.iotforall.com/javascript-vs-golang-iot/
  133. The Go Programming Language: Release History
    https://golang.org/doc/de­vel/release.html
  134. Go 1.11 Release Notes
    https://golang.org/doc/go1.11
  135. Go 1.10 Release Notes
    https://golang.org/doc/go1.10
  136. Go 1.9 Release Notes (tato verze je stále používána)
    https://golang.org/doc/go1.9
  137. Go 1.8 Release Notes (i tato verze je stále používána)
    https://golang.org/doc/go1.8
  138. Go on Fedora
    https://developer.fedorapro­ject.org/tech/languages/go/go-installation.html
  139. Writing Go programs
    https://developer.fedorapro­ject.org/tech/languages/go/go-programs.html
  140. The GOPATH environment variable
    https://tip.golang.org/doc/co­de.html#GOPATH
  141. Command gofmt
    https://tip.golang.org/cmd/gofmt/
  142. The Go Blog: go fmt your code
    https://blog.golang.org/go-fmt-your-code
  143. C? Go? Cgo!
    https://blog.golang.org/c-go-cgo
  144. Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
    https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/
  145. 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
    https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd
  146. Gofmt No Longer Allows Spaces. Tabs Only
    https://news.ycombinator.com/i­tem?id=7914523
  147. Why does Go „go fmt“ uses tabs instead of whitespaces?
    https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces
  148. Interactive: The Top Programming Languages 2018
    https://spectrum.ieee.org/sta­tic/interactive-the-top-programming-languages-2018
  149. Go vs. Python
    https://www.peterbe.com/plog/govspy
  150. PackageManagementTools
    https://github.com/golang/go/wi­ki/PackageManagementTools
  151. A Tour of Go: Type inference
    https://tour.golang.org/basics/14
  152. Go Slices: usage and internals
    https://blog.golang.org/go-slices-usage-and-internals
  153. Go by Example: Slices
    https://gobyexample.com/slices
  154. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  155. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  156. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  157. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  158. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  159. nils In Go
    https://go101.org/article/nil.html
  160. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  161. Go-is-no-good (nelze brát doslova)
    https://github.com/ksimka/go-is-not-good
  162. Rust vs. Go
    https://news.ycombinator.com/i­tem?id=13430108
  163. Seriál Programovací jazyk Rust
    https://www.root.cz/seria­ly/programovaci-jazyk-rust/
  164. Modern garbage collection: A look at the Go GC strategy
    https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e
  165. Go GC: Prioritizing low latency and simplicity
    https://blog.golang.org/go15gc
  166. Is Golang a good language for embedded systems?
    https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems
  167. Running GoLang on an STM32 MCU. A quick tutorial.
    https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial
  168. Go, Robot, Go! Golang Powered Robotics
    https://gobot.io/
  169. Emgo: Bare metal Go (language for programming embedded systems)
    https://github.com/ziutek/emgo
  170. UTF-8 history
    https://www.cl.cam.ac.uk/~mgk25/uc­s/utf-8-history.txt
  171. Less is exponentially more
    https://commandcenter.blog­spot.com/2012/06/less-is-exponentially-more.html
  172. Should I Rust, or Should I Go
    https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9
  173. Setting up and using gccgo
    https://golang.org/doc/install/gccgo
  174. Elastic Tabstops
    http://nickgravgaard.com/elastic-tabstops/
  175. Strings, bytes, runes and characters in Go
    https://blog.golang.org/strings
  176. Datový typ
    https://cs.wikipedia.org/wi­ki/Datov%C3%BD_typ
  177. Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09
  178. Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
    https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06
  179. Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
    https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05
  180. Printf Format Strings
    https://www.cprogramming.com/tu­torial/printf-format-strings.html
  181. Java: String.format
    https://docs.oracle.com/ja­vase/8/docs/api/java/lang/Strin­g.html#format-java.lang.String-java.lang.Object…-
  182. Java: format string syntax
    https://docs.oracle.com/ja­vase/8/docs/api/java/util/For­matter.html#syntax
  183. Selectors
    https://golang.org/ref/spec#Selectors
  184. Calling Go code from Python code
    http://savorywatt.com/2015/09/18/ca­lling-go-code-from-python-code/
  185. Go Data Structures: Interfaces
    https://research.swtch.com/interfaces
  186. How to use interfaces in Go
    http://jordanorelli.com/pos­t/32665860244/how-to-use-interfaces-in-go
  187. Interfaces in Go (part I)
    https://medium.com/golangspec/in­terfaces-in-go-part-i-4ae53a97479c
  188. Part 21: Goroutines
    https://golangbot.com/goroutines/
  189. Part 22: Channels
    https://golangbot.com/channels/
  190. [Go] Lightweight eventbus with async compatibility for Go
    https://github.com/asaske­vich/EventBus
  191. What about Trait support in Golang?
    https://www.reddit.com/r/go­lang/comments/8mfykl/what_a­bout_trait_support_in_golan­g/
  192. Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
    https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/
  193. Control Flow
    https://en.wikipedia.org/wi­ki/Control_flow
  194. Structured programming
    https://en.wikipedia.org/wi­ki/Structured_programming
  195. Control Structures
    https://www.golang-book.com/books/intro/5
  196. Control structures – Go if else statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-if-else-statement.html
  197. Control structures – Go switch case statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-switch-case.html
  198. Control structures – Go for loop, break, continue, range
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-for-loop-break.html
  199. Single Function Exit Point
    http://wiki.c2.com/?Single­FunctionExitPoint
  200. Entry point
    https://en.wikipedia.org/wi­ki/Entry_point
  201. Why does Go have a GOTO statement?!
    https://www.reddit.com/r/go­lang/comments/kag5q/why_do­es_go_have_a_goto_statemen­t/
  202. Effective Go
    https://golang.org/doc/ef­fective_go.html
  203. GoClipse: an Eclipse IDE for the Go programming language
    http://goclipse.github.io/
  204. GoClipse Installation
    https://github.com/GoClip­se/goclipse/blob/latest/do­cumentation/Installation.md#in­stallation