Commit 9bc4b70b authored by CHAMONT David's avatar CHAMONT David
Browse files

vieux jeu de ficheirs scala

parent 1d1139f8
FROM hseeberger/scala-sbt
RUN apt-get update \
&& apt-get install -y apt-utils \
&& apt-get install -y vim \
&& apt-get install -y x11-apps \
&& apt-get install -y xemacs21 \
&& rm -rf /var/lib/apt/lists/*
ADD . /work
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
import scala.concurrent.duration._
object ExampleWithFutures extends App {
val start = System.currentTimeMillis()
val future1 = Future { Thread.sleep(10000); 1}
val future2 = Future { Thread.sleep(10000); "Finished"}
val future3 = for {
x <- future1
y <- future2
} yield println("Le résultat est " + (x,y))
//val future3 = future1.flatMap(x => future2.map(y => println("Le résultat est " + (x,y))))
//Await.result(future3, Duration.Inf)
Await.result(future3, 3 seconds)
val end = System.currentTimeMillis()
println("Le programme a mis " + (end-start)/1000 + " secondes")
}
import scala.annotation.tailrec
object Factorielle extends App {
def factorielle_non_terminale( n: Int ): Int = {
if (n==0) 1
else n*factorielle_non_terminale(n-1)
}
def factorielle_terminale( n: Int ): Long = {
@tailrec
def factorielle( res: Long, n: Int ): Long = {
if (n==0) res
else factorielle(res*n,n-1)
}
factorielle(1,n)
}
println(factorielle_non_terminale(0))
println(factorielle_non_terminale(1))
println(factorielle_non_terminale(2))
println(factorielle_non_terminale(3))
println(factorielle_terminale(0))
println(factorielle_terminale(1))
println(factorielle_terminale(2))
println(factorielle_terminale(3))
}
import scala.collection.immutable.IndexedSeq
object ForComprehension extends App {
val firstExample = for {
x <- 1 to 10
} yield x*2
val secondExample: IndexedSeq[Int] = for {
x <- 1 to 10
y <- 10 to 1 by -1
} yield x+y
val thirdExample: IndexedSeq[Int] = for {
x <- 1 to 10
if x % 3 == 0
} yield x*x
val fourthExample = (1 to 100).map(_/2)
val fifthExample = (1 to 100).filter(_<50).map(_+50)
val sixthExample = (1 to 50)
.flatMap( x => (x to 50).map( y => (x,y) ) )
.filter{ case (x,y) => Math.sqrt(x*x + y*y).isValidInt }
.map{ case (x,y) => (x, y, Math.sqrt(x*x + y*y).toInt) }
//println(firstExample)
//println(secondExample)
//println(thirdExample)
//println(fourthExample)
//println(fifthExample)
println(sixthExample)
}
class Person (
val name:String,
private[this] var gender:String,
private val birthYear:Int ) {
def isOlder(anotherPerson:Person) = birthYear > anotherPerson.birthYear
def getGender = gender
}
object Person {
def apply(name:String, gender:String, birthYear:Int) = new Person(name, gender, birthYear)
}
object Main {
def main(args: Array[String]):Unit = println("coucou") ;
}
import java.time.LocalDate
import java.io.File
import scala.util.Try
trait ValidationError
case class Person ( name: String, age: Int, birthDate: LocalDate )
object PersonImplicitSandbox {
def main(args: Array[String]): Unit = {
// TODO: conversion implicit de Person vers Int (via l'age)
val ryan = Person("ryan", 14, LocalDate.now())
isAdult(ryan)
ryan.isAdult
}
def isAdult(age: Int): Boolean = age>18
implicit def toInt(p: Person): Int = p.age
implicit class PimpMyAdult[T <: Person](person: T) {
def isAdult() = MainSandBox.isAdult(person.age)
}
// pour inliner :
implicit class PimpMyAdult[T <: Person](val person: T) extends AnyVal {
def isAdult() = MainSandBox.isAdult(person.age)
}
}
import java.time.LocalDate
import java.io.File
import scala.util.Try
trait ValidationError
case class Person ( name: String, age: Int, birthDate: LocalDate )
object Person {
type PersonOrValidationErrors = Either[Seq[ValidationError], Person]
def parse(file: File): Try[Seq[PersonOrValidationErrors]] = {
Try {
val lines: Seq[String] = scala.io.Source.fromFile(file).getLines.toSeq
lines.map(parseLine)
}
Try[Seq[PersonOrValidationErrors]]{Seq[PersonOrValidationErrors]()}
}
def parseLine( line: String ): PersonOrValidationErrors = {
// COMMENT LE COMPILO ETABLIT QUE LE TYPE DE SORTIE
// EST Try[Person] ????????????????????????
// pareil en flatmap...
val okCase = for {
age <- Try(array(1).toInt)
date <- Try(LocalDate.parse(array(2))
} yield Person(array(0),age,date)
okCase
}
def parseItem[A](item: String, converter: String => A): Try[A]= Try(converter(item))
}
object PersonParse extends App {
val file = new java.io.File("/Users/chamont/Code/PiscineJI/Scala/test-good.csv")
println(Person.parse(file))
}
/*import org.scalatest.{FlatSpec, FunSuite, Matchers }
class PersonTest extends FlatSpec with Matchers {
"parse" should "work with a file" in {
val file = new java.io.File("/Users/chamont/Code/PiscineJI/Scala/test-good.csv")
Person.parse(file).isSuccess should be (true)
}
}
*/
# Inférence de type
C++ est statiquement et fortement typé : le programmeur doit préciser au compilateur
le type de toutes les variables qu'il utilise. C'est généralement considéré comme nécessaire
pour garantir le bon fonctionnement des programmes de très grande taille, mais cela peut
conduire à un code difficile à écrire comme à lire. Pour alléger la tâche du
programmeur sans renoncer au typage, C++11 autorise le compilateur à deviner
certains types dans les cas les moins ambigus. Ce mécanisme, appelé **inférence de type**,
s'appuie en C++ sur le mot-clef `auto`.
* prérequis : connaissance du C++ historique.
* hauteur du plongeoir : 5m.
* préinstallation : Docker, image `piscineri3/gcc61:3`.
* fichiers : https://gitlab.in2p3.fr/MaitresNageurs/PiscineJI/tree/master/CppAuto
* maître(s) nageur(s) : [David Chamont](http://informatique.in2p3.fr/?q=user/3).
* <img src="img/david.jpeg" height=50>
---
## Vérification de votre environnement de travail
Nous vous proposons de modifier vos fichiers de code au sein du système
d'exploitation natif de votre ordinateur portable, avec vos outils d'édition
habituels, et d'utiliser une machine Docker uniquement pour compiler et exécuter
le programme résultant.
Vous devriez avoir déjà installé Docker et VirtualBox, conformément aux instructions
du [README.md](../README.md) principal de ce projet. Vous devriez également déjà
disposer de l'image `piscineri3/gcc61:3`. Si ce n'est pas le cas et que vous disposez
de réseau, tapez `docker pull piscineri3/gcc61:3`.
En partant de l'hypothèse que vous éditerez vos fichiers dans le répertoire
local <LOCAL>, nous vous proposons de monter ce répertoire en tant que
`/piscine` au sein de la machine Docker.
Créez le programme suivant au sein de <LOCAL> :
```c++
// Fichier hello_world.cpp
#include <iostream>
int main()
{
std::cout<<"Hello World !"<<std::endl ;
}
```
A présent, compilez le et exécutez le au sein d'une machine Docker
`piscineri3/gcc61:3` :
```shell
> docker run --rm -it -v <LOCAL>:/piscine piscineri3/gcc61:3 /bin/bash
>> cd /piscine
>> g++ -std=c++14 hello_world.cpp -o hello_world.exe
>> ./hello_world.exe
Hello World !
>> exit
```
Vous etes prêt.
---
## Inférence de type : le mot-clef `auto`
C++ est statiquement et fortement typé : le programmeur doit préciser au
compilateur le type de toutes les variables qu'il utilise, ce qui peut conduire
à un code difficile à écrire comme à lire.
```c++
// Fichier auto_introduction_1.cpp
#include <iostream>
#include <vector>
void affiche( std::vector<int> & col )
{
std::vector<int>::iterator itr = col.begin() ;
while (itr!=col.end())
{
std::cout<<i<<std::endl ;
++itr ;
}
}
int main()
{
std::vector<int> v {1,2,3,4,5} ;
affiche(v) ;
}
```
Traditionnellement, on allège les notations en ayant recours aux alias
de types :
```c++
// Fichier auto_introduction_2.cpp
#include <iostream>
#include <vector>
typedef std::vector<int> Collection ;
void affiche( Collection & col )
{
Collection::iterator itr = col.begin() ;
while (itr!=col.end())
{
std::cout<<i<<std::endl ;
++itr ;
}
}
int main()
{
Collection v {1,2,3,4,5} ;
affiche(v) ;
}
```
Cependant, on peut juger inutile de devoir préciser le type de la variable
`itr`, qui est le même que celui de l'expression servant à calculer
sa valeur initiale (`col.begin()`).
Dans ce cas de figure, C++11 permet d'utiliser le mot-clef `auto` à la place du type
de la variable, indiquant ainsi au compilateur de réutiliser le type de la valeur
initiale.
```c++
// Fichier auto_introduction_3.cpp
#include <iostream>
#include <vector>
typedef std::vector<int> Collection ;
void affiche( Collection & col )
{
auto itr = col.begin() ;
while (itr!=col.end())
{
std::cout<<i<<std::endl ;
++itr ;
}
}
int main()
{
Collection v {1,2,3,4,5} ;
affiche(v) ;
}
```
Attention : lorsque vous utilisez `auto`, évitez d'utiliser en même temps l'initialisation
par accolades introduite par C++11 . Il existe en effet une possibilité d'interaction indésirable
entre ces deux nouveautés (`auto` risque de retenir `std::initializer_list` comme
type). Il est prévu de résoudre ce problème dans la norme C++17.
On notera que `auto`, lorsqu'il est utilisé avec des patrons de fonction, permet
d'implémenter des choses impossibles auparavant :
```c++
// Fichier auto_introduction_4.cpp
#include <iostream>
template< typename LHS, typename RHS >
void affiche_division( LHS lhs, RHS rhs )
{
auto res = lhs/rhs ;
std::cout<<res<<std::endl ;
}
int main()
{
affiche_division(3.,2) ;
}
```
Tant que l'on ne connait pas `T1` et `T2`, impossible de connaitre le type de `lhs/rhs`.
Seul `auto` permet de déclarer `res` avec le type qui préservera toute la précision
utile du calcul.
---
## Formes modifiées de `auto`
Attention, le type déduit par `auto` ne conserve pas certaines caractéristiques du type
de la valeur initiale. Notamment :
* Les modificateurs de référence `&` sont ignorés,
* Les modificateurs `const` de plus haut niveau sont ignorés,
* Les tableaux et fonctions sont traités comme des pointeurs.
Quelques exemples :
```c++
const int a = 3 ;
const int const * p = & a ;
auto b = a ; // b est de type int
auto q = p ; // q est de type const int *
...
int d = 3 ;
int & e = d ;
auto f = e ; // f est une copie de d de type int, et non une référence vers d de type int &
...
int tab[3] ;
auto ptr = tab ; // ptr est de type int *
```
Cette caractéristique est volontaire. En fait, les [règles d'inférence de type]
(http://en.cppreference.com/w/cpp/language/template_argument_deduction#Other_contexts)
sont inspirées de celles utilisées par le compilateur pour déduire
automatiquement les paramètres des fonctions génériques (templates). Elles permettent,
entre autres choses, d'obtenir plus de flexibilité dans l'utilisation d'`auto`. Par exemple,
si le modificateur `const` était conservé, on ne pourrait pas utiliser `auto` pour créer une
variable entière ordinaire à partir d'une constante entière.
Si vous voulez restaurer certaines des caractéristiques perdues lors l'inférence de type, ou
pour créer des types légèrements différents du type original, il est possible d'ajouter des
modificateurs à `auto` :
```c++
const int a = 3 ;
const int const * p = &a ;
const auto b = a ; // b est de type const int
const auto q = p ; // c est de type const int const *
...
int d = 3 ;
int & e = d ;
auto & f = e ; // f est une référence vers d de type int &
const auto & g = e ; // g est une référence de type const int &, ne permettant pas de modifier d
```
---
## Déclaration à droite du type de retour
Une nouvelle façon de déclarer le type de retour s'appuie également sur
le mot-clef `auto`, placé avant la signature de la fonction, et l'opérateur `->`
placé après la signature :
```c++
// Fichier auto_return_1.cpp
#include <iostream>
auto division( double lhs, double rhs ) -> double
{ return lhs/rhs ; }
int main()
{
std::cout<<division(3.,2)<<std::endl ;
}
```
Certains programmeurs désapprouve le choix du comité de normalisation de
réutiliser le mot-clef `auto` pour ce mécanisme. En effet, il n'y
a ici aucune inférence de type.
---
## Déduction du type de retour
En fait, on peut omettre la partie `-> double`, auquel cas il y a effectivement
inférence : le compilateur cherche la ou les instructions `return` dans le
code, et en détruit le type de retour. Il faut bien sur que toutes ces instrutions
soient de même type.
Ci-dessous, cela nous permet de rétablir l'utilisation de types paramétrés
et l'utilisation d'un type de retour exactement adapté au besoins de
précision :
```c++
// Fichier auto_return_2.cpp
#include <iostream>
template< typename LHS, typename RHS >
auto division( LHS lhs, RHS rhs )
{ return lhs/rhs ; }
int main()
{
std::cout<<division(3.,2)<<std::endl ;
}
```
---
## Le mot-clé `decltype`
Plutôt que de laisser tout le travail au compilateur, on peut également
récupérer le type d'une expression quelconque grâce au mot-clef `decltype`,
et s'en servir pour déclarer une nouvelle variable :
```c++
int n ;
double x ;
decltype(2*x+n) z ; // z sera du type de 2*x+n, soit ici double
.....
std::map<int,std::string> m1 ;
decltype(m1) m2 ; // m2 aura le même type que m1 ;
```
En réalité, `decltype` est utilisable partout ou le compilateur attend un nom
de type, par exemple dans la définition d'un alias :
```c++
// Fichier auto_decltype_1.cpp
#include <iostream>
template< typename LHS, typename RHS >
void affiche_division( LHS lhs, RHS rhs )
{
typedef decltype(lhs/rhs) Resultat ;
Resultat res = lhs/rhs ;
std::cout<<res<<std::endl ;
}
int main()
{
affiche_division(3.,2) ;
}
```
Ou encore :
```c++
// Fichier auto_decltype_2.cpp
#include <iostream>
template< typename LHS, typename RHS >
auto division( LHS lhs, RHS rhs ) -> decltype(lhs/rhs)
{ return lhs/rhs ; }
int main()
{
std::cout<<division(3.,2)<<std::endl ;
}
```
Dans l'exemple ci-dessus, nous n'aurions pas pu placer `decltype(lhs/rhs)`
à la place de `auto`, à la gauche du nom de fonction, car à ce stade
les arguments `lhs` et `rhs` ne sont pas encore connus. Il est donc
nécessaire d'utiliser la déclaration à droite du type de retour...
ou ne rien déclarer du tout, car le compilateur peut déduire le
type de retour à partir de l'instruction `return`.
---
## `decltype` n'est pas `auto`
Attention : pour conclure avec le mot-clef `decltype`, notons qu'
**il n'est pas complètement équivalent** à `auto`. En effet il prend
l'exact type de l'expression passée en argument, sans retirer aucun
`const` et aucun `&`.
Si vous avez envie d'utiliser l'inférence de type, mais sans toucher
au type original d'aucune manière... utilisez `decltype(auto)` à la
place de `auto`.
---
## Afficher le type d'une variable
Lorsqu'interviennent `auto` et `decltype`, hors des cas triviaux,
il peut devenir difficile de connaitre à coup sûr le type d'une
variable. Comment s'en assurer ?
Pour soutirer certaines informations à un compilateur C++, il est courant
de lui fournir une instruction volontairement erronée, écrite de telle sorte
que le message d'erreur contiennent l'information recherchée.
Ci-dessous, nous déclarons un patron de classe qui n'est jamais défini, ce qui permet
indirectement d'en savoir plus sur le type déduit par auto pour x et y :
```c++
// Fichier auto_td_1.cpp
template <typename T> ;
class TypeDisplayer ;
int main()
{
const int the_answer = 42 ;
auto x = the_answer ;
auto y = & the_answer ;
TypeDisplayer<decltype(x)> x_type ; // errors containing
TypeDisplayer<decltype(y)> y_type ; // x's and y's types
}
```
Par ailleurs, certains outils d'édition de code tentent de vous informer de
façon interactive sur les types associés aux utilisations de `auto`
dans votre code, avec plus ou moins de succès.
Vous pourriez être tentés d'utiliser `typeid` et `std::type_info::name`, mais ces fonctions
sont requises d'éliminer les références, les const, et présentent souvent leur résultat
sous une forme mutilée (mangled).
Si vous voulez mettre des instructions d'affichage de type dans votre code, la piste la plus
aboutie est sans doute la bibliothèque Boost TypeIndex.
---