20160314:TP:Go:Introduction

De wiki-prog
Aller à : navigation, rechercher


Introduction

L'objectif de cette séance de TP est de faire vos premiers pas en Go. La séance commencera par un peu de set-up, puis vous réaliserez quelques petits programmes simple.

Mise-en-place

Répertoires

Pour travailler en Go, normalement vous devriez avoir un répertoire dédié qui contiendra votre code source, vos binaires et vos packages compilés. En théorie, les répertoires versionnés peuvent (partiellement) gérés par Go, mais ce n'est pas pratique pour ce que nous voulons faire ici.

La stratégie est la suivante: vous allez mettre votre GOPATH dans votre dépot. Il faudra juste faire attention à ne pas versionner les sous-répertoires bin et pkg.

Pour l'exemple suivant, vous vous placerez dans votre dépot de TP.

On va d'abord créer l'architecture, il vous faut les répertoires suivants:

  • gowork/
    • bin/
    • pkg/
    • src/
      • tutorial20160314/

Vous allez ensuite fixer votre variable d'environnement GOPATH avec le chemin complet vers gowork. Pensez à rajouter cette définition dans votre configuration shell.

On va maintenant ajouter un fichier gowork/.gitignore pour éviter d'avoir bin et pkg dans le dépot. Le contenu du fichier doit ressembler à ça:

bin/
pkg/

En Go, lorsque vous créer un package main (un programme quoi) le nom de l'exécutable est déterminé non pas par le nom du fichier mais par le nom du répertoire de travail. Par exemple, si j'ai un fichier ${GOPATH}/src/my_app/foo.go et que je le compile, j'obtiendrai l'exécutable my_app et non pas foo. On notera également que depuis n'importe où (tant que vous pouvez écrire dans le répertoire courant) vous pouvez construire un programme Go se trouvant dans votre GOPATH. Par exemple, si j'ai un programme dans ${GOPATH}/src/my_project_path/my_app/some_source_code.go, je peux le compiler avec go build my_project_path/my_app ce qui produira dans le répertoire courant le binaire my_app.

Pour la suite, chaque exercice sera donc dans son sous-répertoire (indiqué au début de l'exercice.)

Hello World

Pour tester notre set-up, nous allons faire un petit Hello, World!. Go aime bien que tout soit ranger, vous allez donc créer un sous-répertoire hello dans le répertoire tutorial20160314 dans lequel vous pourrez écrire votre fichier hello.go:

package main
 
import "fmt"
 
func main() {
	fmt.Println("Hello, World !")
}

Placez vous dans le répertoire créer pour tester votre set-up:

# Ces tests devraient marcher même si votre GOPATH n'est pas bon
shell> go run hello.go
Hello, World !
shell> go build hello.go
shell> ./hello
Hello, World !
shell> go clean
# Ces tests nécessitent un GOPATH correct
shell> go build
shell> ./hello
Hello, World !
shell> go clean
shell> go install
shell> ${GOPATH}/bin/hello
Hello, World !
# remove produced AND installed files
shell> go clean -i

Vous pouvez maintenant ajouter votre fichier à votre dépot:

shell> git add hello.go
shell> git commit -m "Adding a first hello world"
...
shell> git push
...

Présentation du code et commentaires de documentation

Go fourni deux outils pratique go fmt et go doc pour formater votre code et récupérer la documentation d'un package.

Prenons l'exemple suivant, on travaillera dans un répertoire my_app peut importe où, c'est un exemple.

/*
  my_app: a simple demo
*/
package main
 
import (
"math/rand"
"fmt"
)
 
// Foo(n): prints n returns it
func Foo(n int) int {
fmt.Println(n)
return n
}
 
func main() {
fmt.Println("Hello, World !")
Foo(42)
fmt.Println(rand.Int())
}

On peut mettre en forme notre code avec go fmt et obtenir:

/*
  my_app: a simple demo
*/
package main
 
import (
	"fmt"
	"math/rand"
)
 
// Foo(n): prints n returns it
func Foo(n int) int {
	fmt.Println(n)
	return n
}
 
func main() {
	fmt.Println("Hello, World !")
	Foo(42)
	fmt.Println(rand.Int())
}

Maintenant testons go doc:

shell> go doc
my_app: a simple demo
shell> go doc Foo
func Foo(n int) int
    Foo(n): prints n returns it
shell> go doc -cmd
package main // import "tutorial01/my_app"

my_app: a simple demo

func Foo(n int) int
shell>

Le principe est simple: les commentaires précédants une définition (sans saut de ligne) sont considérés comme de la doc pour cette définition. Ça marche pour les fonctions comme pour les packages. Dans la suite, les bouts de code fournis sont accompagnés des commentaires de documentation et vous pouvez donc essayer go doc dessus.

Un peu de code

Nous allons implémenter une fonction simple (guess what: factorielle) et nous en profiterons pour accéder aux arguments de votre programme.

Pour cet exercice nous travaillerons dans le répertoire tutorial20160314/fact.

Un petit factorielle pour la route

  • Implémenter la fonction suivante:
// Fact(n) computes factorial of n
// n should be a positive integer, if not, behavior is undefined.
func Fact(n int) (r int)

Nous verrons le choix du package

Tester

Pour tester, nous allons construire un package main contenant votre fonction Fact et une fonction main qui va récupérer un entier dans ses arguments et appeler correctement votre fonction.

Un peu de structure pour commencer, voici le squelette du fichier fact.go:

/*
  An simple factorial implementation in Go.
 
  Use os.Args to take input value for the function.
*/
package main
 
// imports
import (
	"fmt"
	"log"
	"os"
	"strconv"
)
 
// Fact(n) computes factorial of n
// n should be a positive integer, if not, behavior is undefined.
func Fact(n int) (r int) {
  // FIX ME
}
 
// main(): read an integer on the input and call Fact with it.
func main() {
  // FIX ME
}

Pour la gestion d'erreur, nous utiliserons la fonction Fatalln(...) du package log [1], cette fonction fonctionne comme fmt.Println(...) sauf qu'elle quite le programme. Voici un petit exemple:

func main() {
	if len(os.Args) < 2 {
		log.Fatalln("missing arguments")
	}
        fmt.Println(os.Args)
}

Pour récupérer les arguments du main, le package os fourni le classique tableau d'arguments: os.Args. On peut récupérer sa longueur via len(os.Args) et comme pour argv en C celui-ci contient le nom du programme suivi des ses arguments.

Comme en C, les arguments sont des chaînes de caractères et il faut donc convertir le premier argument (s'il existe) en entier. Pour ça nous utiliserons strconv.ParseInt(str, base, bit_size) [2]. Cette fonction renvoie une paire formée de l'entier lu et d'une valeur d'erreur. On l'utilise donc comme suit:

        n, err := strconv.ParseInt(os.Args[1], 10, 32)

Si err ne vaut pas nil (l'équivalent de NULL en C) il y a eu un problème.

  • Compléter le fichier fact.go

Binary Search

Nous allons maintenant faire un premier exercice avec des slices: Binary Search (dichotomie).

Comme pour factorielle, ce sera l'occasion de découvrir un autre package de Go: flag [3] qui permet de gérer simplement plusieurs paramêtres sur la ligne de commande.

Le bout de code suivant présente un exemple d'utilisation:

// flagex [-flag int] [-help|--help|-h]
//
//   flagex takes a flag (default value 0) and displays it.
package main
 
import (
	"flag"
	"fmt"
)
 
func main() {
	var f int
	flag.IntVar(&f, "flag", 0, "a simple integer flag")
	flag.Parse()
	fmt.Println("flag:", f)
}

Cet exercice est à réaliser dans le répertoire tutorial20160314/bin_search.

Voici le squelette pour votre programme bin_search.go:

// bin_search.go: simple binary search in sorted array of integer.
package main
 
import (
	"flag"
	"fmt"
	"math/rand"
)
 
// BinSearch(tab, x) search for x in tab using efficient binary searching
// algorithm
// Returns a pair made of a position and a boolean, the boolean is false if x
// is not in the array, true otherwise.
func BinSearch(tab []int, x int) (int, bool) {
	// ** FIX ME ***
}
 
// genSortedArray(size) build a random sorted array of len size
func genSortedArray(size int) []int {
	tab := make([]int, size)
	cur := rand.Intn(32)
	for i := 0; i < size; i++ {
		tab[i] = cur
		cur += rand.Intn(16) + 1
	}
	return tab
}
 
// bin_search: test binary searching
func main() {
	var size int
	var seed int64 // mandatory for rand.Seed()
	// Flags
 
	// ** FIX ME **
	// take 2 flags: size and seed setting the value for variable size and seed
	// use flag.IntVar() et flag.Int64Var()
	// use some reasonable default values for size and seed.
	// see https://golang.org/pkg/flag
 
	// Init random generator
	rand.Seed(seed)
 
	tab := genSortedArray(size)
	fmt.Println(tab)
	pos := rand.Intn(size)
	x := tab[pos]
	fmt.Println("\nSearch for existing element:", x)
	pos_res, found := BinSearch(tab, x)
	fmt.Printf("BinSearch(tab, x): (%v, %v)\n", pos_res, found)
 
	x = tab[0] + rand.Intn(tab[len(tab)-1]-tab[0])
	fmt.Println("\nSearch for random element:", x)
	pos_res, found = BinSearch(tab, x)
	fmt.Printf("BinSearch(tab, x): (%v, %v)\n", pos_res, found)
}
  • Compléter le code …

Quick Sort

Sur le même principe que l'exercice précédent, vous devez implémenter un quick sort avec le programme de tests correspondant.

  • Compléter le squelette de code fourni

Le répertoire est tutorial20160314/quick_sort. Voici le squelette du code:

/*
quick_sort.go: a simple quick sort on slice of int
 
  usage: quick_sort [-size size] [-seed seed]
 
    builds a random vector of integers, sorts the vector using QuickSort
    function and checks that it's really sorted, if size < 20 also prints the
    vector
 
  see quicksort -help for details
*/
package main
 
import (
	"flag"
	"fmt"
	"math/rand"
)
 
// smax(v, a, b) returns which index betweem a and b corresponds to a greater value in v
func smax(v []int, a, b int) int {
	if v[a] > v[b] {
		return a
	}
	return b
}
 
// smin(v, a, b) returns which index betweem a and b corresponds to a smaller value in v
func smin(v []int, a, b int) int {
	if v[a] < v[b] {
		return a
	}
	return b
}
 
// choose_pivot(v) chooses a pivot a pivot index in v
func choose_pivot(v []int) int {
	fst, med, last := 0, len(v)/2, len(v)-1
	a, b, c := smax(v, fst, med), smax(v, fst, last), smax(v, med, last)
	return smin(v, smin(v, a, b), c)
}
 
// partition(v) partitions v and returns the pivot index
func partition(v []int) (pivot int) {
	// ** FIX ME **
}
 
// QuickSort(v) sorts in place v
func QuickSort(v []int) {
	// ** FIX ME **
}
 
// random_vector(size) builds a random vector of size integer
// values are in the range [0, 1000)
func random_vector(size int) (v []int) {
	v = make([]int, size)
	for i, _ := range v {
		v[i] = rand.Intn(1000)
	}
	return
}
 
// is_sorted(v) returns true if v is sorted in increasing order
func is_sorted(v []int) bool {
	// ** FIX ME **
}
 
func main() {
	size := 8
	seed := int64(42)
	// ** FIX ME **
 
	rand.Seed(seed)
	v := random_vector(size)
 
	if size < 20 {
		fmt.Println(v)
	}
 
	fmt.Println("Running quick sort ...")
 
	QuickSort(v)
 
	if size < 20 {
		fmt.Println(v)
	}
 
	fmt.Println("is sorted: ", is_sorted(v))
}

Rappel: algorithme du quick sort, dans tous les algos left est inclu et right est exclue.

choose_pivot(v, left, right): returns a "good" pivot index

partition(v, left, right):
  pivot = choose_pivot(v, left, right)
  pval = v[pivot]
  swap(v[pivot], v[right - 1])
  pivot = left
  for i = left to right - 2 do:
    if v[i] < pval:
      swap(v[i], v[pivot])
      pivot += 1
    end if
  done
  swap(v[pivot], v[right - 1])
  return pivot

QuickSort(v, left, right):
  if right - left > 1:
    pivot = partition(v, left, right)
    QuickSort(v, left, pivot)
    QuickSort(v, pivot + 1, right)
  end if