Skip to content

Instantly share code, notes, and snippets.

@leg0ffant
Last active August 9, 2022 12:33
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save leg0ffant/3bee4829ce2ad8fd026c to your computer and use it in GitHub Desktop.
Save leg0ffant/3bee4829ce2ad8fd026c to your computer and use it in GitHub Desktop.
Golang introduction [FR]

Cours langage GO - annotation - Anthony Le Goff


#INTRO ALGORITHME SOUS GO De l'écriture du premier programme à la logique informatique golang

http://golang.org

Premier programme et présentation de "hello world"

code hello.go

package main
 
import "fmt" // Implémentation formatage I/O module package main

/* Début du bloc avec accolade fonction main afficher qqchose */
func main() {
        fmt.Printf("Hello, chrome os\n have fun こんにちは世界\n")
 }
Installation

sous UNIX Shell (crosh exemple après shell access mode développeur)

-xzf est le flag de la commande tar, plus d'information shell tar --help

Vous pouvez faire le téléchargement en ligne de commande via

wget url/fichier.tar.gz

Vérifier le hashage de l'archive, si les données ne sont pas corrompu durant le téléchargement algorithme cryptographique sha1 .

sha1sum fichier.tar.gz

La suite de chiffre / lettre doit correspondre avec le lien sur le site / server

Enfin décompresser l'archive dans /usr/local via la commande

sudo tar -C /usr/local -xzf ~/Downloads/<fichier>.tar.gz

Une fois installer en lançant /usr/local/go/bin/go accès aide cmd go

  • Créer dossier de travail gocode inclus dossier bin pour binaire et src pour code source. Ce positionner dans dossier travail avec la commande cd

  • Configurer Gopath

Gopath permet de gérer la persistance redémarrage et la gestion package et de les installer dans le système. Via éditeur ligne de cmd nano ou vi:

$ vi ~/.bashrc

Ajoutez en mode insertion vim en tapant i

export PATH=$PATH:/usr/local/go/bin
export GOPATH=~/Downloads/gocode
export PATH=$PATH:$GOPATH/bin
sudo mount -i -o remount,exec /home/chronos/user/

Sauvegardez et quitter le fichier en lançant échap et cmd :wq! sous vim

Redémarrer ou taper ces 4 lignes commandes dans le shell prise en compte immédiate

  • Compile et test dans src via go build hello.go puis ./hello
  • Installer package créer le dossier dans src puis go install hello & lancer en faisant hello

Introduction

La programmation en GO est un langage informatique développé à l'origine par Robert Griesemer, Rob Pike, Ken Thompson, Ian Taylor, Google en 2007. Le public qui est familiarisé avec certain type de langage tels que:

  • C
  • C++
  • Perl
  • Java
  • Erlang
  • Scala
  • Haskel

Aurons moins de mal à appréhender la programmation Go dont il est inspiré. Ex: Docker est un programme jeune et populaire développé en Go sous Github. Go est un langage orienté pour "systèmes et les réseaux" prisé par le C & C++ . Cela n'empêche pas de développer en Go web server ou encore apps smartphone. Il est possible de trouver bien de la documentation en Go sur le site officiel à l'adresse http://golang.org/doc/ Egalement la documentation spécifique est disponible sous un programme nommé godoc exemple d'utilisation avec les opérateurs et les fonctions built-in:

% godoc builtin

/*ou en local dand le navigateur via shell:*/

% godoc -http=":6060"

/*Puis dans la barre du navigateur:*/

http://localhost:6060/pkg/builtin

Go est-il un langage orienté-objet? Oui et non à la fois, Go introduit la notion d'interface qui est un ensemble de méthodes, une fonction peut être attachée à un type, ou encore l'héritage multiple ainsi que pour définir une structure de Tache grâce au constructeur.

Clair et Simple Go à pour but en quelques lignes de code de pouvoir faire beaucoup de chose

Concurrent Go permet de parallelisé des taches à l'intérieur d'espace d'adressage, à travers des processus et des coeurs multiple de processeur grâce au goroutine

Canaux La communication à travers des Goroutines est faîte via canal

Rapidité La compilation est rapide, plus rapide que la compilation en C mesuré en Seconde.

Format standard La règle est simple, la sortie du filtre gofmt est le format officiel

Type postfix Le Type est donnée après le nom variable comme var a int au lieu de int a; comme on retrouve en C.

UTF-8 UTF-8 est partout, dans les strings et le code. Final il est possible utiliser dans le code source Φ = Φ + 1

Open Source La licence Go est entièrement open source, voir le fichier LICENSE dans le code source de la distribution Go.

Définition Algorithme

La langage Go est abordé en parallèle à introduire l'algorithme informatique au niveau des notes prise pour le lecteur. L’algorithmique est l’ensemble des règles et des techniques qui sont impliquées dans la définition et la conception d’algorithmes, c’est-à-dire de processus systématiques de résolution d’un problème permettant de décrire les étapes vers le résultat. En d’autres termes, un algorithme est une suite finie et non-ambiguë d’instructions permettant de donner la réponse à un problème.

L’information est la matière première de l’informatique . Les algorithmes, qui sont constitués d’informations, stockent, manipulent et transforment d’autres informations, à l’aide de machines traité sous forme de programme avec son code source. Tout, en informatique, est représenté comme une séquence de 0 et de 1 : les algorithmes, ou plutôt les programmes,le contenu d’un livre, une photo, une vidéo...

Vous avez dit binaire?

La base binaire est le langage machine, il ne comprend que des suites de 001011 il y a une raison théorique et technique de l'utilisation du binaire:

  • On ne peut pas faire plus simple que 2 symboles (0, 1) avec un symbole plus rien ne fonctionne
  • Les symboles 0 et 1 sont transposables électroniquement par: le courant passe ou ne passe pas. Réprésente donc toutes les informations sous forme de bits ( binary digit = chiffre binaire). La quantité d'information est justement le nombre de bit nécessaires pour représenter cette information.
  • Le stockage de l'information binaire progresse niveau technique et l'utilisation du Qbit comme base computation ou le spin de l'électron dans la théorie quantique est "up" = 1 ou "down" = 0 ou l'Etat du système admet une information intriquée.
Base et unité
  • Le bit (binary digit) est l'unité d'information: par 0 ou 1
  • L'octet est un groupe de 8 bits (octet en anglais = bytes)

La mémoire (disque dur, mémoire flash...) sa taille est indiqué en système SI internationnal multiplicateur 10³ (symbole k,M,G,T pour kilos, Mega, Giga et Tera) et pour les multiplicateurs 2¹⁰ etc symbole Ki, Mi, Gi,etc pour kibi, mébi et gibi particulièrement en informatique. Base 2.

Les bases ont retrouve plusieurs familles et conversion en informatique:

  • Décimale base 10 (0,1,2,3,4,5,6,7,8,9)
  • Binaire base 2
  • Hexadécimal base 16 (0,1,2...A,B,C,D,E,F)
La théorie systémique

Point a abordé et la notion de machine introduit par la cybernétique et englobant un domaine bien plus large tels que la théorie des systèmes commencé année 1950 avec von bertalanffy sur les systèmes biologiques. Trois directions:

  • Le systémisme qui apporte le principe de complexité
  • Un autre systémisme abordé sur quelques choses de plus vague, sur quelques vérité holistiques sans devenir opérantes (ni réelle, ni formel, ambigu & fantome)
  • Il y a le system analysis qui est le correspondant systémique de l'engineering cybernétique en opération réductrice mais transdisciplinaire.
La théorie des constructeurs

La programmation évolue et l'on voit l'accès à l'informatique quantique grand public. Dans cet évolution une théorie de l'information quantique a évolué de l'information classique et l'avènement de l'utilisation du Qbit quantum-bits. Le caclcul quantique via RF SQUID et l'information quantique appartient notion de super-information, de paires d'attributs non discernables. Il y a impredictibilité de processus déterministes. La cohérence de la paire d'attribut admet une information localement inaccessible.

Pour plus d'information sur la programmation informatique quantique un détour sur le langage Quipper.

Variables, Types et mot-clés

Pour introduire les bases et les types petit rappel sur l'entier et sa définition par la variable int

Type int
>>> 2009 # décimal
2009
>>> 0b11111011001 # binaire
2009
>>> 0o3731 # octal
2009
>>> 0x7d9 # hexadecimal
2009

Important que de ce rappeler que les bases ont un préfixe 0b, Oo, 0x

En langage Go la syntaxe ce réfère énormément au langage C avec un point qui rend fou les codeurs pour débuggué le programme: la fin du ";" et le calvaire trouver celui qui était manquant dans le programme le rendant impossible à compiler. La différence majeur au niveau des variables par rapport aux autres langages, elle est specifié après le nom variable tels que:

Pas int a, mais plutôt a int

Donc après var a int sa valeur assigné est de 0. Avec var s string la valeur zéro de la string vaut "". Déclaré et assigné en Go est deux process.

Déclaration avec =

var a int
var b bool
a = 15
b = false

Déclaration avec :=

a := 15
b := false

L'utilisation de := est essentiellement pour les variables déclarés dans les fonctions. Multiple déclaration de variable peuvent être groupé et const & import autorise cela. Ecriture:

var (
    x int
    b bool
)

Multiple variable peuvent être assigné sur une ligne également:var x,y int Ou encore un assignement en parallèle:

a, b := 20, 16

Les booléens sont de type bool et prennent la valeur de test de condition tout ou rien "TOR" true ou false Les variables numériques, il faut prendre en compte le cas des virgules flottantes. La déclaration de nombre réel à virgule flottante ce fait de cet manière soit en float32 ou float64 au niveau du type au besoin de l'architecture.

// déclaration de la constante Pi
const Pi = 3.14159
// déclaration variable rayon en virgule flottante
var rayon float32 = 1.0
Chaine de caractère Strings

L'un des principaux buit-in type est string. Affectation valeur chaine de caractère ce fait de cet façon rapide s := "Hello World!". En Go string prend une valeur d'encodage en UTF-8 entre double citation. Un petit exemple pour changer la première valeur de la string "Hello World" en Go.

s := "hello World"
c := []rune(s)
c[0] = 'c'
s2 := string(c)
fmt.Printf("%s\n", s2)

Explication:

  • Converti s en une array de runes
  • Change le premier élement de cet array
  • Créer une nouvelle string s2 avec modification
  • Enfin print la valeur de la string dans fmt.Printf

Multiple string

Ecrire des strings et assemblage sur plusieurs lignes:

s := "Starting part" +
     "Ending part"

// OU utilisant raw string et (`)

s := `Starting part
     Ending part`
Runes

Rune est un alias pour int32 encodé en UTF-8. Permet d'instancer, itération de caractère de string. Possible de faire loop sur chaque octets. Problème encodage string en 8-bit ASCII ne sont pas intégré en Go.

Nombres complexes

Go a en natif le support des nombres complexes sans librairies. Le type de variable est nommé complex128(64 bit réel et partie imaginaire) ou complex64 (32 bit réel et partie imaginaire). Règle:

Complexe écrit comme re + imi ou re est partie réel et im imaginaire

var c complex64 = 5+5i;fmt.Printf("Value is: %v", c) // %v défault format valeur

/* Après compilation et lancement */
print: (5+5i)
Errors

Dans des programmes il est nécessaire de reporter des erreurs, le built-in Go permettant cela s'appel error et ce déclare var e error valeur 'nil'. Ce type est une interface.

Opérateur et fonction built-in

Opérateur arithmétique

20 + 3 # 23
20 - 3 # 17
20 * 3 # 60
20 / 3 # 6.666666666666667
20 % 3 # 2 (modulo)
abs(3 - 20) # valeur absolue

Type booléen

Deux valeurs possibles : False, True.
• Opérateurs de comparaison : == (egal), != (strict different), >, >=, < et <= :
2 > 8 # False
2 <= 8 < 15 # True

/*Cette optimisation est appelée « principe du shortcut »*/

(3 == 3) || (9 > 24) # True (dès le premier membre)
(9 > 24) && (3 == 3) # False (dès le premier membre)

/* Opérateurs logiques */

&& ET logique,
|| OU logique,
!  négation logique

Opérateur bit à bit

& ET logique bit à bit,
| OU logique bit à bit,
^ OU EXCLUSIF bit à bit,
~ complément binaire ou négation bit à bit,

/* opérateurs il faut ajouter*/

<< décalage binaire à gauche
>> décalage binaire à droite

Algorithme d'Euclide

L’algorithmique est bien plus ancienne que l’informatique, que l’ordinateur, et que le langage Go, utilisé dans ces notes. Les exemples les plus anciens et célèbres sont :

  • les calculs d’impôts Babyloniens (il y a 4000 ans)
  • le calcul du plus grand diviseur commun (Euclide, vers −350)
  • les première méthodes de résolution systématique d’équations (Al Khawarizmi, ixe siècle)

Les algorithmes étaient donc d’abord utilisés «à la main». Aujourd’hui on entend généralement par algorithmique la réflexion préliminaire à l’écriture d’un programme d’ordinateur, c’est à dire la recherche d’une méthode systématique et non ambiguë pour résoudre un problème. C’est la partie conceptuelle de la programmation, l’abstraction 1 d’un programme d’ordinateur. L’algorithmique est parfois considérée comme une branche des mathématiques, parfois comme une branche de l’informatique. Les notions d’algorithme, puis d’ordinateur ont été formalisées dans les années 30 et 40 par : Kurt Gödel, Alan Turing, John von Neumann... Avoir des notions en algorithmique permet de développer soi-même des programmes, et/ou d’avoir une discussion constructive avec une équipe de développeurs. Les compétences en algorithmique font aussi partie du bagage minimum d’un scientifique, qu’il soit technicien, chercheur, ingénieur ou simplement curieux.

Ecriture exemple Algorithme d'Euclide

Étant donnés deux entiers, retrancher le plus petit au plus grand et recommencer jusqu’à ce que les deux nombres soient égaux. La valeur obtenue est le plus grand diviseur commun.

Dans cet idée, on introduit le principe des structures de controle c-a-d on repart sur la base si et sinon [if, else] permettant de faire des boucles. Le while équivaut à "répéter" en français disparait en langage Go au profit de for et switch

/*
 ----------------------
 | ALGORITHME EUCLIDE |
 ----------------------

 E1: Formuler l'idée
 ___________________
 Étant donnés deux entiers, retrancher le plus petit au plus
 grand et recommencer jusqu’à ce que les deux nombres
 soient égaux. La valeur obtenue est le plus grand diviseur
 commun.

 E2: Décrire étapes
 __________________
 a,b : deux nombres
 Répéter tant que a et b sont différents :
     si le plus grand est a :
         les deux nombres deviennent a−b et b
     sinon (b est donc le plus grand) :
         les deux nombres deviennent a et b−a
 le pgcd est a

 Les espaces equivaut à l'indentation, ici = 4 (test et
 répétition de tâche)

 E3: Algorithme formel
 _____________________
 fonction pgdc(a,b : entiers)
     repeter tant que a/=b
         si a>b
             a=a−b
         sinon
             b=b−a
     retourner a

 On retrouve premières bases algo avec les boucles (while) et
 test de condition (if, else) Notons que en Go While n'existe pas.
 Il faut donc remplacer par une boucle for et l'accolade = retourner a

---------------------------------------
*/

/* E4: Ecriture programme principale calcul plus grand
dénominateur commun */

package main
 
import "fmt"

func main() {
  a := 933
  b := 789
  
  for a != b {
    if a > b {
      a = a - b
    } else {
      b = b - a
    }
  }
  fmt.Println("le PGDC vaut", a,":", b)
}

La valeur retourner de votre programme après compilation et édition: Sauvegarder pgdc.go puis dans le dossier de travail lancer go build pgdc.go enfin pour lancer l'executable bin taper shell ./pgdc

Valeur de sortie: le PGDC vaut 3 : 3

Dans ce programme nous avions déclaré deux entiers a = 933 et b = 789. Le plus petit diviseur commun valant donc 3 par l'analyse algorithmique d'Euclide. La difficulté ici resulte à print via un Println de fmt. Cett fonction formate de manière auto tout type paramètre. Retourne la chaine formatée au lieu de l'afficher équivaut à utiliser formatage défaut %v. Le formatage de texte est le module fmt.

Programmer est l’activité qui consiste à :

  • traduire des algorithmes dans un langage de programmation :
  • afin d’obtenir des réponses effectives (souvent numériques) à des problèmes ;
  • afin de se distraire...
  • corriger des erreurs dans un programme ;
  • rester calme...

Pour aller plus loin l'idéal programme pouvoir insérer une valeur d'entrée à a & b par l'utilisateur et comparer nombres déterminant un pgdc. Non appliqué dans l'exemple mais scanf du module fmt et l'opérateur & le font.

Il est courant d'utiliser controle d'état return ou break à la fin tels que:

if err := Chmod(0664); err != nil {     // nil is like C’s NULL 
   fmt.Printf(err) // erreur limité au corps du if
   return err
}

On peut utiliser les opérateurs logiques dans les structures de contrôle:

if true && true {
    fmt.Println("true")
}
if ! false {
    fmt.Println("true")
}
GOTO

GO utilise le goto permet de sauter à un label prédifini dans la fonction courante tels que pour un loop:

func myfunc() {
        i := 0
Here: // label du goto LOOP
        println(i)
        i++ // iterateur compteur incrémentation
        goto Here // Jump
}
La boucle For

En langage Go le for s'utilise sous 3 formes:

for init; condition; post { } // Comme en C for
for condition { } // Comme un while
for { } // boucle infini

Example sous l'écriture en C for en Go et incrementation compteur

sum := 0
for i := 0; i < 10; i++ {  // init; condition; post
    sum += i // reduction écriture de sum = sum + i
} // i cesse d'exister à la fin de la boucle
Le mot-clé range

Range s'utilise dans le cadre d'un loop pour slices (tranches), arrays (tableau), strings, maps, et canaux. range est un itérateur qui appel paire de clé. Quand il boucle sur slice ou array, il retourne l'index dans le slice comme une clé et sa valeur.

Switch

Go mot-clé switch est flexible. Pas besoin de constante ou entiers, c'est un comparateur de valeur via le mot-clé case et default simplifie:

if i == 0 {
 fmt.Println("Zero")
} else if i == 1 {
 fmt.Println("One")
} else if i == 2 {
 fmt.Println("Two")
} else if i == 3 {
 fmt.Println("Three")
} else if i == 4 {
 fmt.Println("Four")
} else if i == 5 {
 fmt.Println("Five")
}

Ce programme indique le nom anglais associé à un nombre via switch cela donne

switch i {
case 0: fmt.Println("Zero")
case 1: fmt.Println("One")
case 2: fmt.Println("Two")
case 3: fmt.Println("Three")
case 4: fmt.Println("Four")
case 5: fmt.Println("Five")
default: fmt.Println("Unknown Number")
}

Fonctions built-in

Les built-in sont des fonctions inclus sans besoin de faire appel à des librairies ou des packages.

  • close utilisé canal de communication, ferme un canal
  • delete utilisé pour supprimer entrée maps
  • len & cap sont utilisés pour retourner longueur d'un string ou tableau et d'un slice
  • new permet d'allouer de la mémoire pour utilisateur défini types données
  • ** make** Utiliser allouer mémoire built-in types (maps, slices, canaux)
  • copy permet de copier un slice
  • ** append** concaténation de slice
  • panic, recover comme mécanisme d'exeption
  • print, println fonction d'impression bas niveau sans "fmt" debugging
  • complex, real, imag utilisation des nombres complexes

Tableaux, Slices et maps

Un tableau array de valeurs est une série consécutive de valeurs, accessibles par leur indice entier, 0,1,2,3... En Go on déclare un tableau en faisant précéder le type des éléments par la taille en crochets var x [5]int donc x nom du tableau composé de 5 entiers. Test de programme tableau

package main

import "fmt"

func main() {
  var x [5]int
  x[4] = 100 // Allouer 100 à l'élément 4 du tableau
  fmt.Println(x)
}

Afficher valeur print

[0 0 0 0 100]

Un tableau commence toujours par la valeur 0,1,2,3,4..... Zéro est inclus

Calcul de moyenne

Ici nous allons abordé les tableaux par le calcul de la somme arithmétique de valeur pour diviser par le nombre de valeur, équivaut nombre élément dans le tableau.

package main

import "fmt"

func main() {
  var x [5]float64
  x[0] = 17.5
  x[1] = 11
  x[2] = 9
  x[3] = 14.5
  x[4] = 13
  var total float64 = 0
  for i := 0; i < 5; i++ {
    total += x[i] // Incrémente les valeurs du tableau x sur chaque élément
  }
fmt.Println(total / 5) // Total sur le nombre de valeur
}

Quelques explications sur l'utilisation du tableau

  • On déclare un tableau de 5 élément en virgule flottante
  • Allocation pour chaque élément une valeur = nos notes
  • Déclare variable total en virgule flottante entière
  • Boucle for et init, condition, post (incrémente supérieur)
  • Calcul bloucle for total incrémenté à chaque élément du tableau x
  • Println du total divisé par 5 (nombre de valeur)

A partir de cet compréhension du tableau, on va amélioré le code de manière plus réduite (less is more) en place et optimiser le calcul de valeur moyenne. Nouveau programme

package main

import "fmt"

func main() {
  xs := []float64{17.5,11,9,14.5,13} // composite literale

  total := 0.0
  for _, v := range xs {
    total += v
  }
  fmt.Println(total / float64(len(xs)))
}
  • On découvre une nouvelle manière d'interprété un tableau sur une ligne
  • Une boucle for "_" (underscore) à la place "i" car non appelé itérateur + range
  • Conversion tableau défaut int println en float64 sur un len équivaut taille du tableau
Slices

Un Slice est un seqment d'un tableau array qui est indexable et a une longueur.

slice != array la longueur est autorisé à changer pour le slice

Prenons l'exemple var x []float64 entre crochet valeur longueur prend 0 La taille du tableau est fixe sur une valeur [] donc créé un slice pour le modifier en utilisant le built-in make

x := make([]float64, 5)

Il y a un troisième paramètre. Correspond à la capacité du tableau dont le slice pointe.

x := make([]float64, 5, 10)

Dans cet exemple len(x) = 5 et cap(x) = 10 La règle qui différencie Array & Slice est donc que:

len(slice)== n ;
cap(slice)== m ;
len(array)== cap(array)== m .

Une autre manière de créer un slice est en utilisant expression [low : high]

arr := []float64{1,2,3,4,5}
x := arr[1:4]

Même si le tableau a 5 élément, notre slice défini [1:4] retourne [2,3,4] Ainsi arr[0:] équivaut à arr[0:len(arr)] et arr[:5] correspond à arr[0:5]

Copy & append

Si vous avez besoin d'étendre un slice il y a des built-in fait pour ça tels que append & copy. Append alloue un nouveau slice dans le slice éxistant

s0 := []int{0, 0}
s1 := append(s0, 2)
s2 := append(s1, 3, 4, 5, 7)
s3 := append(s2, s0...) // Noter 3 petits points

s3 retourne s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}

La fonction copy éléments de slice d'une source src à la destination dst et retourne le nombre d'élément copié.

var a = [...]int{0, 1, 2, 3, 4, 5, 6, 7}
var s = make([]int, 6)
n1 := copy(s, a[0:]) // n1 == 6,s == []int{0, 1, 2, 3, 4, 5}
n2 := copy(s, s[2:]) 

Le type map ce retrouve dans plusieurs langage. En C++ c'est la même chose, en Perl on trouve hashes et en Python les dictionnaires. Map est une collection de paire de clé en désordre. Notion de clé associée. Prenons exemple x["clé"] = 10 et supprimons la valeur de la clé.

x := make(map[int]int)
x[1] = 10
fmt.Println(x[1])

Quand on Print la valeur de clé "1" on obtient 10. Et pour supprimer la clé:

delete(x,1)

Tableau d'élement périodique en chimie et le type map comme exemple dans un programme. 10 élément chimique indexe leur symbole. Print sur la clé "Li" qui affiche sa valeur Lithium

package main

import "fmt"

func main() {
  elements := make(map[string]string)
  elements["H"] = "Hydrogen"
  elements["He"] = "Helium"
  elements["Li"] = "Lithium"
  elements["Be"] = "Beryllium"
  elements["B"] = "Boron"
  elements["C"] = "Carbon"
  elements["N"] = "Nitrogen"
  elements["O"] = "Oxygen"
  elements["F"] = "Fluorine"
  elements["Ne"] = "Neon"

  fmt.Println(elements["C"])
}

On peut raccourcir et simplifier le code de cet manière sur type map

elements := map[string]string{
"H": "Hydrogen",
}

Cela permet d'une manière général de stocker de l'information. Ajoutons à la paire de clé et valeur une autre entrée tels que quelques terres rares

func main() {
  LREE := map[string]map[string]string{
    "Y": map[string]string{
      "Nom":"Yttrium",
      "Numéro et masse atomique":"N°39 et ma 88.90",
    },
    "Nd": map[string]string{
      "Nom":"Neodyme",
      "Numéro et masse atomique":"N°60 et ma 144.24",
    },
    "Eu": map[string]string{
      "Nom":"Europium",
      "Numéro et masse atomique":"N°63 et ma 151.25",
    },
    "Dy": map[string]string{
      "Nom":"Dysprosium",
      "Numéro et masse atomique":"N°66 et ma 162.50",
    },
  }
  
  if el, ok := LREE["Y"]; ok {
    fmt.Println(el["Nom"], el["Numéro et masse atomique"])
  }
}

Le if est utilisé pour indéxer une recherche dans le tableau à travers une valeur de clé et print ces valeurs: test d'existence

Note sur la complexité la recherche dans map (dictionnaire) en fonction nombre de valeur à indéxer admettent un temps de performance des algorithmes. Ainsi dans la recherche dichotomique, l'algorithme en Θ(log(n)) ou logarithmique est à préviligié dans l'écriture de boucle de fonction.

Exercices

Développement selon John Zelle

Analysez le problème Comprenez le problème à résoudre et sa nature. Essayez d’en apprendre le plus possible sur lui. Vous ne pourrez pas le résoudre avant de le connaître parfaitement.

Spécifiez le programme Décrivez très exactement ce que votre programme va faire. Vous ne devez pas déjà vous soucier de la façon dont il va le faire, mais plutôt décider très exactement ce qu’il va faire. Pour des programmes simples, cela consistera à décrire ce que seront les entrées et les sorties du programme et ce qui les relie.

Concevez le programme Formulez la structure globale du programme. C’est à ce moment que vous traiterez de la façon dont le programme résoudra le problème. Le travail principal est de concevoir le ou les algorithmes qui rempliront les tâches préalablement spécifiées.

Codez Traduisez les algorithmes conçus dans un langage de programmation et entrez les sur un ordinateur. Dans ces notes, nous programmerons nos algorithmes en Go.

Testez/Débuggez le programme Essayez votre programme et voyez s’il fonctionne comme vous le souhaitiez. S’il y a des erreurs (souvent appelées bugs), revenez à l’étape précédente et corrigez les. L’activité qui consiste à débusquer et corriger les erreurs s’appelle le debuggage. Durant cette phase, votre objectif est de trouver des erreurs, et pour cela, vous devez tester tout ce qui vous passe par le tête et qui pourrait planter le programme. Il sera utile de garder à l’esprit la maxime :

Nothing is foolproof because fools are too ingenious

Faites de la maintenance Continuez à développer votre programme en répondant aux besoins des utilisateurs. La plupart des programmes ne sont jamais vraiment finis ; ils continuent d’évoluer au fil des années d’utilisation.

Calcul de puissance et Entrée[Input] / Sortie[Output]

Q1. On va partir sur un programme simple mathématique sur les puissances pour introduire la saisie utilisateur. L'idée du programme est de connaitre une valeur de la fonction inverse 1/r² ou r est défini par l'utilisateur en début de programme. Pour plus d'information sur la fonction 'input'

godoc fmt

Rechercher func scanf et utilisation de l'opérateur & pour trouver adresse de variable

A1. Solution au problème

package main

import "fmt"

func main() {
  fmt.Print("Entrée un nombre: ")
  var r float64
  fmt.Scanf("%f", &r) // %f précision pour virgule flottante
  
  x := 1 / ( r * r )
  
  fmt.Println("Valeur inverse² :", x)
}
Runes: Comptage de symbole strings

Q2. Un nouveau problème de poser et les chaines de caractère. Admettons une énigme cryptographique sous un langage inconnu de symbole. On ne sait pas ce que cela veut dire, mais on aimerait bien compter le nombre de caractère de la chaine. Et en addition compter le nombre d'Octet de la string via module "utf8" voir godoc unicode/utf8 chaine à analyser: ⢎𝚿⣟ ⣹𝚲⢪ ⢱𝚵⢧

A2. Solution pour compter des runes et des octets. En cherchant dans godoc et utf8 on trouve une fonction qui permet de compter les runes func RuneCount(p []byte)int Pour convertir string vers octets

str := "hello"
b := []byte(str)

On ce retrouve donc avec ce programme runes_count.go

package main

import (
  "fmt"
  "unicode/utf8"
)
  
func main() {
  str := "⢎𝚿⣟ ⣹𝚲⢪ ⢱𝚵⢧"
  fmt.Printf("String %s\nLength: %d, Runes: %d\n", str,
len([]byte(str)), utf8.RuneCount([]byte(str)))
}

Valeurs affichées

String ⢎𝚿⣟ ⣹𝚲⢪ ⢱𝚵⢧

Length: 32, Runes: 11

Quelques explications sur le formatage de texte de la fonction printf et l'opérateur %

  • suivi d'un “d”, il implique la présence d'un entier et le formate en décimal,
  • suivi d'un “s”, il implique la présence d'une chaîne,
  • suivi d'un “x”, il implique la présence d'un entier et le formate en hexadécimal,
  • suivi d'un “g”, il implique la présence d'un réel,
  • suivi d'un “v”, il formate de manière automatique n'importe quel type,
  • suivi d'un “T”, il affiche le type de la valeur fournie
  • \n renvoi à la ligne

D'ou

  • %s renvoi à str
  • premier %d renvoi à len([]byte(str))
  • second %d renvoi à utf8.RuneCount([]byte(str))

Q3. Enfin de compte les symboles cryptés on peut y insérer d'autre chaine de caractère à l'intérieur. Remplacer dans la chaine ⢎𝚿⣟ ⣹ ⢱𝚵⢧ ⣾𝛀⣀ ⣒𝚹⢕ à la position 10, 3 runes par ⣹𝚲⢪

A3. runes-copy.go

package main

import "fmt"

func main() {
  s := "⢎𝚿⣟ ⣹ ⢱𝚵⢧ ⣾𝛀⣀ ⣒𝚹⢕"
  r := []rune(s)
  copy(r[10:10+3], []rune("⣹𝚲⢪"))
  fmt.Printf("Script symbole original : %s\n", s);
  fmt.Printf("Modification symbolique : %s\n", string(r))
}

Q4. La personne faisant de la rétro-ingénierie sur cette série de symbole codé a remarqué qu'il était possible d'obtenir quelque chose de différent en inversant le string. Après tout cela dépend du sens que nous écrivons que nous faisons la compréhension d'un langage! Alors inversé la chaine ⢎𝚿⣟ ⣹ ⢱𝚵⢧ ⣹𝚲⢪ ⣒𝚹⢕

A4. programme reverse.go

package main

import "fmt"

func main() {
  s := "⢎𝚿⣟ ⣹ ⢱𝚵⢧ ⣹𝚲⢪ ⣒𝚹⢕"
  a := []rune(s) // Une conversion
  for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
    a[i], a[j] = a[j], a[i] // Assignement parallele
  }
  fmt.Printf("%s\n", string(a)) // Retour converti
}

Fonctions

Une fonction est une section indépendante de code qui maps zéro ou plusieurs paramètres d'entrées INPUT. Les fonctions sont également appelés procédures ou des subroutines. Les fonctions sont représenté par une boite noire ou un code est exécuté par la fonction. Noire car par exemple une voiture est une boite noire pour son utilisateur: Il appuie sur l'accélérateur INPUT et en sortie le véhicule ce déplace OUTPUT. Ce ne veut pas dire que vous savez comment marche le moteur et tout le processus mécanique derrière. D'ou concept de boite noire.

Les notions de boite noire et boite blanche sont très importante pour résoudre des niveaux d'abtraction et d'isolation de système, utilisé définir architecture ou encore en notation UML

Sur nos programmes Go nous utilisons seulement auparavant une fonction principale func main() {}

Schéma:        ---------------
       INPUT   |             | OUTPUT
       ----- > | Boite noire | ----- >
               |             |
               ---------------

// Déclaration de fonction:

type mytype int 

func (p mytype) funcname(q int) (r,s int) { return 0,0 }
  • func le mot-clé est utilisé pour déclarer une fonction;
  • (p mytype) possible de donnée un type specifique appelé receiver dont celui-ci défini la méthode;
  • funcname est le nom de votre fonction;
  • (q int) est la variable d'entrée et copié dans la fonction pass-by-value;
  • (r, s int) sont les paramètres de variable return ;
  • { return 0,0 } corps de la fonction entre accolade;

Reprenons l'exemple du calcul de moyenne un peu auparavant pour construire une fonction à partir du main()

func main() {
   xs := []float64{98,93,77,82,83}
   total := 0.0
   for _, v := range xs {
      total += v
   }
   fmt.Println(total / float64(len(xs)))
}

La moyenne average d'une série de nombre est un problème classique dans l' algorithminique. Nous allons appeler la fonction average avec un slice en *float64 ainsi qu'un return en float64. On insert le code avant le main()

func average(xs []float64) float64 {
  panic("Introuvable")
}

Cela est la signature de la fonction, incluant le paramètre (une liste de nombre) qui est nommé xs et le type return. Le built-in panic appel un erreur runtime, permet de casser le process, routine de la fonction pour debugger plus simplement.

Ajoutons maintenant l'algorithme dans la fonction

func average(xs []float64) float64 {
  total := 0.0
  for _, v := range xs {
    total += v
  }
  return total / float64(len(xs))
}

et modification sur le main(), la variable nommé xs pas obligatoire sous même nom.

func main() {
  xs := []float64{98,93,77,82,83}
  fmt.Println(average(xs))
}

Algorithme récursif

Le paradigme de la programmation fonctionnelle en informatique est important dans la manière d'aborder les itérateurs. On utilise la fonction factorielle de manière récursive, elle suffit à elle même en tant que fonction pour s'appeler.

Factoriel, symbole !

n!= | 1           si n=0
    | n(n - 1)!   si n >= 1

Code go

package main

import "fmt"

func factorial(x uint) uint {
  if x == 0 {  // cas de base
    return 1
  }            // cas récursif
  return x * factorial(x-1)
}

func main() {
  fmt.Println("Valeur vaut: ", factorial(5))  // calcul 5!
}

Dans cette fonction, la valeur de n! n'est pas connue tant que la condition terminale (ici x == 0) n'est pas atteinte. Le programme empile les appels récursifs jusqu'à atteindre la condition terminale puis dépile les valeurs. Exemple factorial(2)

  • Si x == 0? Non. (x vaut 2)
  • Trouver le factoriel de x – 1
  • Si x == 0? Non. (x vaut 1)
  • Trouver le factoriel de x – 1 * Si x == 0? Oui, return 1.
  • return 1 * 1
  • return 2 * 1

Closure

Il est possible de créer des fonctions à l'intérieur de fonctions

func main() {
  x := 0
  increment := func() int {
    x++
    return x
  }
  fmt.Println(increment())
  fmt.Println(increment())
}

increment ajoute 1 à la variable x défini main() Scope. Cet variable est accessible via fonction increment. Variable est non-local et appelé closure d'ou increment et x forme closure

Variable & Scope

package main

var a = 6

func main() {
  p()
  q()
  p()
}

func p() {
  println(a)
}

func q() {
  a := 9 
  println(a)
}

Entre les fonctions et le main() les variables sont local ou global. a := 5 est déclaré dans q() et la valeur Print vaudra : 696

Valeurs de retour multiple

Go utilise le variable return multiple pour une fonction tels que

func f() (int, int) {
  return 5, 6
}

func main() {
  x, y := f()
}

Cette notation est utilisé pour connaitre une valeur erreur à coté d'un résultat ou un booléen fonctionnant.

  • x, err := f()
  • x, ok := f()

Defer, panic & recovering

Go a un état particulier entraine l'éxecution différée d'une commande à la sortie d'une fonction courante. Exemple après avoir ouvert fichier defer fermeture

f, _ := os.Open(filename)
defer f.Close()

defer est particulièrement utilisé pour libérer des ressources, que cela soit de la mémoire ou des processus.

Lorsque une erreur survient, durant appel de fonction, il vaut mieux indiqué l'erreur par un code return. Mais aussi irréversible qui entraine fin du programme, on appel la fonction panic() et récupéré l'exception lancé via panic avec recover() couplé à defer

package main

import "fmt"

func main() {
  defer func() {
    str := recover()
    fmt.Println(str)
  }()
  panic("PANIC")
}

Fonctions variadic

Les fonctions variadic en utilisant ... avant le type du dernier paramètre indique prend zéro ou plus paramètres. Tels que Zéro ou plus ints.

func myfunc(arg ...int) {}
func add(args ...int) int {
  total := 0
  for _, v := range args {
    total += v
  }
  return total
}
func main() {
  fmt.Println(add(1,2,3))
}

Exercices

Switch et moyenne arithmétique

Q5. Ecrire une fonction qui calcul moyenne average d'un float64 slice et switch

A5. Code Go average.go

func average(xs []float64) (avg float64) {
  sum := 0.0
  switch len(xs) {
  case 0: 
    avg = 0
  default:
    for _, v := range xs {
    sum += v
    }
    avg = sum / float64(len(xs)) 
  }
return 
}
Paire ou Impaire?

Q6. Exercice sur l'incrémentation. On veut afficher les dix premiers nombres de 1 à 10 et connaitre si ceux-ci sont soit paire ou impaire. Utiliser % modulo

A6. Correction. On retrouve deux structures controle for et if/else et l'incrémentation n+1 ce fait sur la ligne de condition par variable ++

package main

import "fmt"

func main() {
  for i := 1; i <= 10; i++ {
    if i % 2 == 0 {
      fmt.Println(i, "paire")
    } else {
      fmt.Println(i, "impair")
    }
  }
}
Récursion: suite de Fibonacci

Q7. La suite de Fibonacci commence tels que 1,1,2,3,5,8,13... ou en notation mathématique:

| F0 = 0
| F1 = 1
| Fn = Fn−1 + Fn−2 si n > 1

Ecrire une fonction prenant le type int donnant plusieurs termes de la suite de Fibonacci.

A7. fi.go

/*
Fibonacci sequence
*/

package main

import "fmt"

func fibonacci(value int) []int {  // variable "value" func, return slice
  x := make([]int, value)          // Init Slice en int et len(value)
  x[0], x[1] = 0, 1 
  for n := 2; n < value; n++ {
    x[n] = x[n-1] + x[n-2] 
  }
  return x
}

func main() {
  for _, term := range fibonacci(11) {  // Itérateur range de Fi(11)
    fmt.Printf("%v ", term)
  }
}
Une simple stack LIFO

En informatique la notion de stack empile revient régulièrement, web server, algo.


 i | k |<- [i++] --- push(k)
   | l |pop() -- [i--]-> l
 0 | m |
 
 Schéma stack LIFO 'last in, first out'
 
 Stack de la figure réprésenté par : [0:m] [1:l] [2:k]

Q8. Construire une stack LIFO avec un nombre fixe int. Définir push pour mettre quelques chose sur la stack et pop pour enlever de la fonction stack

A8. En premier nous définissons un nouveau type représente stack. Avec une array (gestion clé) et un index. 10 élements dans la stack

type stack struc {  // struc permet de construire des types avancés
  i int
  data [10]int
}

Maintenant nous devons créer les fonctions push et pop. En premier défini la solution fausse d'une solution de push et problème pointer.

func (s stack) push(k int) 
  if s.i+1 > 9 {
    return
  }
  s.data[s.i] = k
  s.i++
}

La fonction fonctionne variable s de type stack. En utilisant appel s.push(50) on empile un entier 50 sur a stack. Mais la fonction push a une copie de s et ne fonctionnera pas. Rien n'est empilé sur la stack:

var s stack // s comme stack variable
s.push(25)
fmt.Printf("stack %v\n", s);
s.push(14)
fmt.Printf("stack %v\n", s);
---
prints:
stack [0:0]
stack [0:0]

Pour résoudre cela on donne à la fonction push un pointer de la stack d'ou:

func (s stack)push(k int) → func (s *stack)push(k int)

Pour la création du pointer on fait une allocation avec new() tels que:

s := new(stack)

On ce retrouve avec le programme stack.go:

/*
Stack LIFO
*/

package main

import "fmt"

// Déclare un nouveau type stack, 10 éléments
type stack struct {
  i int
  data [10]int
}

// Fonction push sur une stack
func (s *stack) push(k int) {
  s.data[s.i] = k
  s.i++
}

// Fonction pop enlève une stack
func (s *stack) pop() int {
  s.i--
  return s.data[s.i]
}

// Déclaration dans main() variabe s comme stack et push valeur
func main() {
  var s stack
  s.push(25)
  s.push(14)
  fmt.Printf("stack %v\n", s)
}
Fonction de hashage MD5

Une introduction au algoritme cryptographique et MD5. Retro-ingénierie sur un morceau de code d'une fonction.

 /* MD5Hash
*/
package main

import (
  "crypto/md5"
  "fmt"
)

func main() {
  hash := md5.New()
  bytes := []byte("kenavo\n")
  hash.Write(bytes)
  hashValue := hash.Sum(nil)
  hashSize := hash.Size()
  for n := 0; n < hashSize; n += 4 {
    var val uint32
    val = uint32(hashValue[n])<<24 +
      uint32(hashValue[n+1])<<16 +
      uint32(hashValue[n+2])<<8 +
      uint32(hashValue[n+3])
    fmt.Printf("%x ", val)
  }
  fmt.Println()
}

Q9. Soit ce morceau de code source, comprendre son fonctionnement et définir les différentes étapes en sachant que le print vaut :

22917d0 d14dbfd0 ad38bb8b e1678dc4 
Algorithme de tri 'sort'
  Non trié  |  0 |  3 | 12 | -1 | -5 |  5 |
  Trié      | -5 | -1 |  0 |  3 |  5 | 12 |

Il fonctionne en répètant des étapes qu'une liste doit être trié, compare chaque paire d'item adjacent et le tri s'il est au mauvais endroit, passe l'étape s'il n'a pas besoin de trier l'item. La fonction s'appelle bubblesort

/*
Algorithme formel
-----------------

procedure bubbleSort( A : list of sortable items )
  do
    swapped = false
    for each i in 1 to length(A) - 1 inclusive do:
      if A[i-1] > A[i] then
        swap( A[i-1], A[i] )
        swapped = true
      end if
    end for
  while swapped
end procedure
*/

Q.10 Ecrire la fonction de l'algorithme quadratique complexité en Θ(n ² ) de tri bubblesort

A.10 code bubblesort.go

func main() {
  n := []int{5, -1, 0, 12, 3, 5}
  fmt.Printf("unsorted %v\n", n)
  bubblesort(n)
  fmt.Printf("sorted %v\n", n)
}

func bubblesort(n []int) {
  for i := 0; i < len(n) - 1; i++ {
    for j := i + 1; j < len(n); j++ {
      if n[j] < n[i] {
        n[i], n[j] = n[j], n[i]
      }
    }
  }
}

Tri par sélection

Une première idée est de procéder ainsi :

  1. rechercher le plus petit, le mettre au début,
  2. rechercher le second plus petit, le mettre en deuxième position
  3. ...
Variadic & var args

Q.11 Ecrire une fonction qui prend un nombre variable int et print chacun d'entre eux sur une ligne séparée.

A.11 On utilise la syntaxe ... qui prend un nombre d'argument au hasard

package main

import "fmt"

func main() {
  prtthem(1, 4, 5, 7, 4)
  prtthem(1, 2, 4)
}
func prtthem(numbers... int) { // numbers : slice of ints
  for _, d := range numbers {
    fmt.Printf("%d\n", d)
  }
}

Pointeurs

Définition d'un pointeur

Les pointeurs sont un type important en langage Go. Il s'utilise comme l'adresse pointant une valeur. On peut retrouver la valeur et la modifier avec un pointeur.

Un pointeur est déclaré en faisant précéder le type de donnée pointée par une étoile* (ie: *int // pointeur entier). On prend l'adresse pointant sur une donnée en la faisant précédé opérateur &:

var i = 5
var j *int = &i // pointeur sur i

Exemple sur ce programme, pointeur référence & mémorise:

Sans pointeur

func zero(x int) {
  x = 0
}
func main() {
  x := 5
  zero(x)
  fmt.Println(x) // x vaut 5
}

Avec pointeur

func zero(xPtr *int) {
  *xPtr = 0
}
func main() {
  x := 5
  zero(&x)
  fmt.Println(x) // x vaut 0
}

Adressage &

L'opérateur & référence à l'adresse du pointeur ainsi il est possible de savoir l'adresse mémoire du pointeur tels que ce code:

var i int // Declare variable entier i
p = &i    // Faire p pointer vers i
fmt.Printf("%v", p)
----
Print 0x7ff96b81c000a

Le mot-clé nil

Le mot clé nil représente un pointeur qui ne pointe sur aucune valeur licite. Cela peut représenter la valeur initiale d'un pointeur qui ne pointe vers rien NULL

Allocation

fonction new()
func one(xPtr *int) {
  *xPtr = 1
}
func main() {
  xPtr := new(int)
  one(xPtr)
  fmt.Println(*xPtr) // x is 1
}

new prend un argument, alloue de la mémoire pour la valeur du type et retourne au pointeur. Les pointeurs sont utilisé en particulier avec struct le type structuré.

Utilisation de make pour allouer

New() alloues

make() initialises utilisé seulement pour slices, maps, canaux

Exemple

var p *[]int = new([]int) 
var v []int = make([]int, 100)

Retenez

v := make([]int, 100)

Constructeur

La définition d'un constructeur est une méthode particulière d'initialisation. Pour plus d'information liser la doc package container/ring et la fonction new défini un constructeur en Go.

Exemple avec le package os

func NewFile(fd int, name string) *File {
  if fd < 0 {
    return nil
  }
  f := File{fd, name, nil, 0} // Create a new File
  return &f    // Return the address of f
}

Type structuré

L'idéal serait de construire un programme seulement avec builtin type Go, ce qui arrive pas toujours avec des besoins spécifique de création. Considérer cet exemple intéragi avec des formes:

package main

import ("fmt"; "math")

func distance(x1, y1, x2, y2 float64) float64 {
  a := x2 - x1
  b := y2 - y1
  return math.Sqrt(a*a + b*b)
}

func rectangleArea(x1, y1, x2, y2 float64) float64 {
  l := distance(x1, y1, x1, y2)
  w := distance(x1, y1, x2, y1)
  return l * w
}

func circleArea(x, y, r float64) float64 {
  return math.Pi * r*r
}

func main() {
  var rx1, ry1 float64 = 0, 0
  var rx2, ry2 float64 = 10, 10
  var cx, cy, cr float64 = 0, 0, 5
  fmt.Println(rectangleArea(rx1, ry1, rx2, ry2))
  fmt.Println(circleArea(cx, cy, cr))
}

définir son type avec Struct

Une façon rapide d'optimiser le programme est d'utiliser struct et ces paramètres fields pour définir Circle

type Circle struct {
  x, y, r float64
}

Initialisation

Créer instance type Circle

var c Circle

Il est possible également d'utiliser la fonction new() pour struct

c := new(Circle)

Cela alloue mémoire pour les fields mise à zéro et un retour pointeur et ainsi redonner une valeur fields tels que:

c := Circle{x: 0, y: 0, r: 5}

Fields

On peut accéder aux fields en utilisant l'opérateur .

fmt.Println(c.x, c.y, c.r)
c.x = 10
c.y = 5

Modifiant function CircleArea avec Circle

func circleArea(c *Circle) float64 {
  return math.Pi * c.r*c.r
}

Dans le main()

c := Circle{0, 0, 5}
fmt.Println(circleArea(&c))

Méthodes

On peut encore écrire la fonction d'une autre manière en utilisant une fonction particulière nommée méthode , dans ce morceau de code on ajoute un receiver permettant d'appeler une fonction utilisant l'opérateur .

func (c *Circle) area() float64 {
  return math.Pi * c.r*c.r
}

Dans le print

fmt.Println(c.area())

On remarque que l'utilisation des pointeurs en Go en utilisant une méthode à changé. En Go sait automatiquement ajouter un pointeur pour Circle pour cette méthode défini. Change rectangle:

type Rectangle struct {
  x1, y1, x2, y2 float64
}
func (r *Rectangle) area() float64 {
  l := distance(r.x1, r.y1, r.x1, r.y2)
  w := distance(r.x1, r.y1, r.x2, r.y1)
  return l * w
}

Et dans le Main()

r := Rectangle{0, 0, 10, 10}
fmt.Println(r.area())

Exercices

Pointeurs

Q.12 . Suppose type structuré est défini:

type Person struc {
  name string
  age int
}

Quel est différence entre ces deux lignes?

var p1 Person
p2 := new(Person)

Quel est différence entre ces deux allocations?

func Set(t *T) {
  x = t
}

and

func Set(t T) {
  x= &t
}
Map Fonction
func Map(f func(int) int, l []int) []int {
  j := make([]int, len(l))
  for k, v := range l {
    j[k] = f(v)
  }
  return j
}

func main() {
  m := []int{1, 3, 4}
  f := func(i int) int {
    return i * i
  }
  fmt.Printf("%v", (Map(f, m)))
}

Une map()-function est une fonction attaché à une liste et une fonction tels que:

map(f(),(a1, a2, . . . , an−1, an)) = (f(a1), f(a2), . . . , f(an−1), f(an))

Le code ci-dessus est la solution à l'équation pour le type int

Q.13 Ré-écrire la map-fonction avec une interface générique, en typé int, str

A.13 code map-function-gen.go


package main 

import "fmt"

//* define the empty interface as a type
type e interface{}

func mult2(f e) e {
  switch f.(type) {
  case int:
    return f.(int) * 2
  case string:
    return f.(string) + f.(string) + f.(string)
      + f.(string)
  }
  return f
}

func Map(n []e, f func(e) e) []e {
  m := make([]e, len(n))
  for k, v := range n {
    m[k] = f(v)
  }
  return m
}

func main() {
  m := []e{1, 2, 3, 4}
  s := []e{"a", "b", "c", "d"}
  mf := Map(m, mult2)
  sf := Map(s, mult2)
  fmt.Printf("%v\n", mf)
  fmt.Printf("%v\n", sf)
}

Interfaces

Une interface est un ensemble de méthodes, c'est à dire des fonctions qu'il est possible d'implémenter pour une type défini. Ce morceau de code défini struct type s avec un field qui défini 2 méthodes pour s

type S struct { i int }
func (p *S) Get() int { return p.i }
func (p *S) Put(v int) { p.i = v }

En définissant type interface I de plusieurs méthodes

type I interface {
  Get() int
  Put(int)
}

Rappeler vous les méthodes communes pour Rectangle & Circle ont area. En définissant une interface nommé Shape:

type Shape interface {
  area() float64
}

L'interface implémenté en langage Go est une forme de duck typing, loin du pure duck typing car la compliation Go va checké statique le type implémenté niveau interface. La conversion checké au moment runtime. Interface est l'idée dans d'autres langages comme pure abstract virtual base classes en C++, typerclasses en Haskell ou duck typing en python.

Aucun langage combine comme en Go : Valeur interface, statique type checking, dynamique runtime conversion, et pas nécessité déclaré qu'un type satisfait une interface.

Concurrent

“Parallelisme est a propos de la performance;Concurrent est à propos du design de programme. Rob Pike

Quand on code de gros programme informatique il est nécessaire plusieurs sous-programme. L'exemple du web server dans l'attente réponse du navigateur et renvoi à une page HTML en réponse. Chaque requète est comme un sous-programme.

Il serait idéal pour de sous-programme comme ceux-ci de pouvoir les lancer en même temps en parallèle (surtout multiple requète server et de page web) Lancer du multi-tache simultanément s'appel des tâches concurrent. Go est riche dans l'utilisation concurrent avec goroutines et canaux

Goroutines

Une goroutine est une fonction capable de lancer en concurrent d'autres fonctions. Le mot clé pour utiliser une goroutine est go puis l'invocation de fonction.

ready("Green Tea", 2) // Appel fonction normal
go ready("Green Tea", 2) ← ready() lancé comme goroutine
package main

import "fmt"

func f(n int) {
  for i := 0; i < 10; i++ {
    fmt.Println(n, ":", i)
  }
}

func main() {
  go f(0) // goroutine de f(0)
  var input string
  fmt.Scanln(&input) // permet de tout print avant fin de programme et sortir
}

Ce programme utilise deux goroutines l'une implicite le main() et la seconde à l'appel de go f(0)

Utilisons la fonction time.Sleep pour donner un peu de delay execution tache

package main

import (
  "fmt"
  "time"
)

func ready(w string, sec int) { 
  time.Sleep(time.Duration(sec) * time.Second) 
  fmt.Println(w, "is ready!") 
} 

func main() { 
  go ready("Tea", 2) 
  go ready("Coffee", 1) 
  fmt.Println("I'm waiting") 
  time.Sleep(5 * time.Second) 
}

Dans le même style, il est possible d'éxecuter un temps aléatoire avec la fonction rand

import "math/rand"

amt := time.Duration(rand.Intn(250)) // sur un entier

Avant de sortir du programme on attend 5 seconde, l'exemple auparavant on demande à l'utilisateur un scanln. On peut fixer le problème en utilisant des canaux

Canaux

Un canal est comparable à un pipeline en UNIX shell. Le type est spécifique au canal. Donc un canal chan permet à deux goroutines go de communiquer en eux et de synchroniser leur execution. Créer canal avec make

ci := make(chan int)         // ci integer
cs := make(chan string)      // cs stringe
cf := make(chan interface{}) // cf interface

Pour recevoir et envoyer à un canal on utilise l'opérateur <-

ci <- 1   // Envoyer integer 1 vers canal ci
<-ci      // Recevoir un integer du canal ci
i := <-ci // Recevoir du canal ci et le stocker dans i

Fermer un channel

x, ok = <-ch

var c chan int  //déclare c variable entier canal

func ready(w string, sec int) {
  time.Sleep(time.Duration(sec) * time.Second)
  fmt.Println(w, "is ready!")
  c <- 1  // Envoi integer 1
}

func main() {
  c = make(chan int)        // init c
  go ready("Tea", 2)       // lancer goroutine
  go ready("Coffee", 1)
  fmt.Println("I'm waiting, but not too long")
  <-c  // Attendre de recevoir du canal 'sans valeur'
  <-c  // Deux goroutines donc deux canaux associer valeur. 
}

select

L'appel select fonctionne comme un switch mais dans l'utilisation d'un canal.

Modification du programme précédent avec select:

var c chan int  

func ready(w string, sec int) {
  time.Sleep(time.Duration(sec) * time.Second)
  fmt.Println(w, "is ready!")
  c <- 1  // Envoi integer 1
}

func main() {
  c = make(chan int)        
  go ready("Tea", 2)      
  go ready("Coffee", 1)
  fmt.Println("I'm waiting, but not too long")
  L: for {
    select {
    case <-c:
      i++
      if i > 1 { 
        break L 
      } 
    } 
  }
}

Parallelisme

Le langage Go utilise goroutine comme concurrent, pour améliorer le parallelisme utiliser la fonction runtime.GOMAXPROCS(n) qui paramètre le nombre maximum de CPU's processeurs utiliser pour lancer simultanément.

Communication

Parlonds rapidement de la communication avec le monde externe. Tels que fichiers, dossiers, réseaux ou éxecuter d'autre programme. GO's I/O avec les interfaces:

io.Reader et io.Writer

Lire à partir d'un fichier

package main
import "os"

func main() {
  buf := make([]byte, 1024)
  f, _ := os.Open("/etc/passwd") // Ouvrier le fichier
  defer f.Close() // sur de fermer le fichier
    for {
      n, _ := f.Read(buf) // Lecture 1024 octets à la fois
      if n == 0 { break } // fin fichier
      os.Stdout.Write(buf[:n]) // écrire contenu vers os.Stdout
    }
}

Version buffered

package main
import ( "os"; "bufio")

func main() {
  buf := make([]byte, 1024)
  f, _ := os.Open("/etc/passwd") 
  defer f.Close()
  r := bufio.NewReader(f)  
  w := bufio.NewWriter(os.Stdout)
  defer w.Flush()
  for {
    n, _ := r.Read(buf) 
    if n == 0 { break }
    w.Write(buf[0:n])
  }
}

Lancer commands

Le package os/exec a une fonction de lancer des commandes externes. Exemple en lançant la commande UNIX ls -l affichant chaque fichier, information, droit d'accès

Comparaison script shell UNIX et Go création dossier inexistant

Version UNIX Shell
------------------
if [ ! -e name ]; then
  mkdir name
else
  # error
fi

Version Go
----------
if f, e := os.Stat("name"); e !=
  nil {
  os.Mkdir("name", 0755)
} else {
  // error
}

Packages

Package essentiel en Go

  • fmt
  • io
  • io/ioutil
  • bufio
  • sort
  • strconv
  • strings
  • math/rand
  • os
  • sync
  • flag
  • time
  • sync/atomic
  • encoding/json
  • encoding/gob
  • html/template
  • net/http
  • net/rpc
  • unsafe
  • runtime
  • reflect
  • os/exec
  • path/filepath
  • errors
  • container/list
  • hash
  • crypto/aes
  • crypto/md5

Liens vrac

@oumaimabenabdelmalek
Copy link

svp comment on fait pour Inclure un fichier dans un autre avec le GoLang ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment