Suite visiteur pattern : Visiteur Booleen

Introduction:

Nous allons refaire la même chose que notre interpréteur d’expressions. Mais avec des expressions booléennes. Par exemple

1
$expression = new Or( new And(New False(), New True()), new False);

Nous allons ensuite rajouter les comparaisons ==, <, etc ..

1
$expression = new Not(new NotEqual(new Constant(5), new Variable('i')));

Beaucoup de code. mais si vous avez compris la première partie. cela devrait aller.

Expression Booléenne

Nous définissons l’interface suivante.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * Une expression Booléenne
 */
interface BoolExpression
{
    public function accept(VisitorBoolExpression $v);
}
/**
 * Une classe abstraite.
 */
abstract class Unary implements BoolExpression
{
    public function accept(VisitorBoolExpression $v)
    {
        return $v->visit($this);
    }
}

Pour faire l’algèbre booléen j’ai besoin de False et de True

Voici le code.

1
2
3
class True extends Unary {}

class False extends Unary {}

J’ai aussi besoin de la négation

1
2
3
4
5
6
7
8
9
class Not extends Unary {
    private $value;
    public function __construct(BoolExpression $expr) {
        $this->value = $expr;
    }
    public function getValue() {
        return $this->value;
    }
}

Je vais rajouter la condition And, Or, Nand (No-et), Nor(Non-ou)

Je définis une classe avec deux arguments dans le constructeur.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Binary extends Unary
{
    private $left;
    private $right;
    public function __construct(BoolExpression $left, BoolExpression $right) {
        $this->left = $left;
        $this->right = $right;
    }
    public function getLeft() {
        return $this->left;
    }
    public function getRight() {
        return $this->left;
    }
}

Les classes sont alors très simples.

1
2
3
4
5
// Or et And sont des mots réservés en Php.
class BinaryOr extends Binary{}
class BinaryAnd extends Binary{}
class BinaryNand extends Binary{}
class BinaryNor extends Binary{}

J’ai un peu près tout.

On peut passer au Visiteur.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
interface VisitorBoolExpression{
    public function visit(BoolExpression $expr);
}


class VisitorBoolEvaluation implements VisitorBoolExpression {
    protected $context;
    function __construct($context){
        $this->context = $context;
    }

    public function visit(BoolExpression $expr){
        $class = 'visit'.get_class($expr);
        return $this->$class($expr);
    }
    public function visitTrue(BoolExpression $expr)
    {
        return true;
    }
    public function visitFalse(BoolExpression $expr)
    {
        return false;
    }
    public function visitNot(BoolExpression $expr)
    {
        return !$expr->getValue()->accept($this);
    }
    public function visitBinaryOr(BoolExpression $expr)
    {
        return $expr->getLeft()->accept($this)||$expr->getRight()->accept($this);
    }
    public function visitBinaryAnd(BoolExpression $expr)
    {
        return $expr->getLeft()->accept($this)&&$expr->getRight()->accept($this);

    }
    public function visitBinaryNor(BoolExpression $expr)
    {
        return !($expr->getLeft()->accept($this)||$expr->getRight()->accept($this));

    }
    public function visitBinaryNand(BoolExpression $expr)
    {
        return !($expr->getLeft()->accept($this) && $expr->getRight()->accept($this));
    }
}

Quelques exemples.

On réutilise notre mémoire du billet précédent. On utilise aussi var_dump plutôt que echo car echo false ne renvoie rien.

1
2
3
4
5
6
7
8
9
10
11
12
$memory = new Memory();
$memory->write('i', 10);

$ve = new VisitorBoolEvaluation($memory);
// une expression
$expression =  new True();
// appelle le visiteur
var_dump($expression->accept($ve)) // affiche bool(true);
$expression =  new Not(new False());
var_dump($expression->accept($ve)) // affiche bool(true);
$expression =  new BinaryAnd(new Not(new False()), new BinaryOr(new True(), new False()));
var_dump($expression->accept($ve)) //Affiche bool(true);

bien sur on peux refaire un autre visiteur pour traduire en chaînes de caractères

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class VisitorBoolPrint implements VisitorBoolExpression {
    protected $context;
    function __construct($context){
        $this->context = $context;
    }

    public function visit(BoolExpression $expr){
        $class = 'visit'.get_class($expr);
        return $this->$class($expr);
    }
    public function visitTrue(BoolExpression $expr)
    {
        return "true";
    }
    public function visitFalse(BoolExpression $expr)
    {
        return "false";
    }
    public function visitNot(BoolExpression $expr)
    {
        return "!" . $epr->getValue()->accept($this);
    }
    public function visitBinaryOr(BoolExpression $expr)
    {
        return "(" . $expr->getLeft()->accept($this)
        .'||' .$expr->getRight()->accept($this). ")";
    }
    ...
    ...
 }

Le même exemple .

1
2
3
4
5
6
7
8
9
10
11
$memory = new Memory();
$memory->write('i', 10);
$ve = new VisitorBoolPrint($memory);
// une expression
$expression =  new True();
// appelle le visiteur
var_dump($expression->accept($ve)) // affiche true;
$expression =  new Not(new False());
var_dump($expression->accept($ve)) // affiche !false;
$expression =  new BinaryAnd(new Not(new False()), new BinaryOr(new True(), new False()));
var_dump($expression->accept($ve)) //Affiche (!false&&(true||false));

Les comparaisons

Nous pouvons rajouter le ==, !=, > , < !

ajoutons de nouveau objet. les object prennent en entrée des expressions mais sortent des boléens.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class BinaryComparaison extends Unary
{
    private $left;
    private $right;
    public function __construct(Expression $left, Expression $right) {
        $this->left = $left;
        $this->right = $right;
    }
    public function getLeft() {
        return $this->left;
    }
    public function getRight() {
        return $this->right;
    }
}

class Equal extends BinaryComparaison{}
class NotEqual extends BinaryComparaison{}
//Greater Than Equal
class Gte extends BinaryComparaison{}
// Lesser Than Equal
class Lte extends BinaryComparaison{}
// Lesser Than
class Lt extends BinaryComparaison{}
// Greater Than
class Gt extends BinaryComparaison{}

Pour mon visiteur je vais utiliser mon visiteur d’expression du post précédent.

donc je modifie le constructeur.

1
2
3
4
function __construct($context, $ve){
    $this->context = $context;
    $this->ve = $ve;
}

je ne montre que le égal, mais vous avez un peu près l’idée pour le reste.

1
2
3
4
public function visitEqual(BoolExpression $expr)
{
    return ($expr->getLeft()->accept($this->ve)  == $expr->getRight()->accept($this->ve));
}

Le visiteur booléen utilise un autre visiteur pour évaluer une expression.

Un exemple d’utilisation

1
2
3
4
5
6
7
8
9
$memory = new Memory();
$memory->write('i', 10);
// une visiteur d'expression
$ve = new VisitorEvaluation($memory);
// un visiteur pour les expressions booléennes
$vb = new VisitorBoolEvaluation($memory, $ve);
// une expression
$expression =  new Not(new Equal( new Constant(10), new Addition(new Constant(5), new Variable('i'))));
var_dump ( $expression->accept($vb) );// affiche bool(true)

si je reprend mon autre visiteur VisitorToPhp avec le visitorBoolPrint

1
2
3
4
5
6
7
$memory = new Memory();
$memory->write('i', 10);
$ve = new VisitorToPhp($memory);
$vb = new VisitorBoolPrint($memory, $ve);
// une expression
$expression =  new Not(new Equal( new Constant(10), new Addition(new Constant(5), new Variable('i'))));
var_dump ( $expression->accept($vb) ); //affiche  "!(10==(5+$i))"

Une conclusion.

  • Dans le premier post : On a vu le visiteur pour évaluer/afficher des expressions.
  • dans le second post : le visiteur pour les expressions booléennes et les comparaisons. Celui-ci utilise le premier visiteur pour faire les calculs.

dans un prochain post, je vais montrer un troisième visiteur visitorInstruction pour évaluer des instructions d’un langage très simple. Mais cela est un peu long à écrire. Il y a un peu de théorie et des figures à faire.

Merci de m’avoir lu.

Interpréteur et Visiteur Pattern

Introduction

Nous allons voir ensemble sur une série trois posts

  • le design-pattern interpréteur
  • les limitations et une solution qui va introduire le visiteur pattern

Mise en place

Nous allons créer un simple calculatrice.

Nous définissons l’interface suivante

1
2
3
4
5
6
7
/**
 * Une expression arithmétique
 */
interface Expression
{
    public function interpret(Context $context = null);
}

Evaluer des constantes

Voici le code pour évaluer des constantes

1
2
3
4
5
6
7
8
9
10
11
12
13
Class Constant implements Expression
{
    private $value;
    public function __construct($value)
    {
        $this->value = $value;
    }

    public function interpret(Context $context = null)
    {
        return $this->value;
    }
}

Un exemple

1
2
$constante = new Constant(5);
echo $constante->interpret(); // affiche 5

jusqu’ici rien de complexe. Si j’interprète la constante que j’ai définie à 5, j’obtiens 5.

Evaluer des additions

voici le code pour interpréter les additions

1
2
3
4
5
6
7
8
9
10
11
12
13
Class Addition Implements Expression
{
    private $left;
    private $right;
    public function __construct(Expression $left, Expression $right) {
        $this->right = $right;
        $this->left = $left;
    }
    public function interpret(Context $context = null) {
        return $this->left->interpret($context) + $this->right->interpret($context);
    }

}

Un exemple

1
2
$addition = new Addition(new Constant(5), new Constant(6));
echo $constante->interpret(); // affiche 11

On utilise la récursion pour interpréter la partie droite et gauche

1
2
$addition = new Addition(new Addition( new Constant(5), new Constant(6)), new Constante(4));
echo $constante->interpret(); // affiche 15

Faire la multiplication, la soustraction, la division ne sont pas plus compliquées. Il suffit de changer le signe dans la fonction interpret()

1
2
3
4
// muliplication 
public function interpret(Context $context = null) {
    return $this->left->interpret($context) * $this->right->interpret($context);
}

Ajouter d’autres méthodes

Ajoutons la methode Abso qui renvoie la valeur absolue, la fonction min qui renvoie le minimum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Class Abso Implements Expression
{
    public function __construct($value) {
        $this->value = $value;
    }
    public function interpret(Context $context = null) {
        return abs($this->value->interpret($context));
    }

}

Class Minimum Implements Expression
{
    public function __construct(Expression $left, Expression $right) {
        $this->right = $right;
        $this->left = $left;
    }
    public function interpret(Context $context = null) {
        return min($this->right->interpret($context),$this->left->interpret($context));
    }
}

un exemple

1
2
$min = new Minimum(new Abso(-10), new Addition(new Constant(24), new Constant(2)));
echo $min->interpret(); // renvoie 10

Tout n’est qu’une question de contexte

Nous allons ajouter les variables.

Il nous faut d’abord implémenter le Context

Voici la définition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
 * Interface Context
 */
interface Context
{
    // write a value in memory
    public function write($name, $value);
    // get a value from the memory
    public function read($name);
    //return all the value
    public function getAll()
}

/**
 * A Memory
 */
class Memory implements Context
{
    private $memory = array();
    // write a value in memory
    public function write($name, $value)
    {
        $this->memory[$name] = $value;
        return $this;
    }

    // get a value from the memory
    public function read($name)
    {
        return $this->memory[$name];
    }

    public function getAll()
    {
        return $this->memory;
    }

}

Il ne nous reste plus qu’à implémenter la variable.

1
2
3
4
5
6
7
8
class Variable implements Expression
{    public function __construct($name) {
        $this->name = $name;
    }
    public function interpret(Context $context = null){
        return $context->read($this->name);
    }
}

On comprend l’intérêt du context. Il nous permet de passer un pseudo-scope..

Un exemple:

1
2
3
4
5
6
7
$memory = new Memory();
$memory->write('i', 10);
$expression = new Addition(new Constant(10), new Variable('i'));
echo $expression->interpret($memory); // 20

$memory->write('i', 0);
echo $expression->interpret($memory); // 10

On peux rajouter plein d’autre expression. L’avantage est qu’il suffit de rajouter une méthode ->interpret(..) pour chaque objet.

mais si on change le cahier des charges…

Changeons le cahier des charges. Je souhaite transformer mon Expression en chaine de caractères. Je peux m’en sortir en surchargeant la méthode __tostring

Par exemple :

1
2
$expression = new Addition (new Addition(new Constant(3), new Constant(4)), new Constante(4));
$expression->__toString() // me donne ((3 + 4) + 4);
1
2
3
4
5
6
7
8
9
10
// pour la constante
        public function __toString() {
            return $this->value;
        }

// pour l'addition
         public function __toString() {
                // this->left->__toString()
                return '(' . $this->left . ' + ' . $this->right .')';
         }

Rechangeons le cahier des charges : je veux la traduction en Php

1
2
$expression = new Addition (new Addition(new Variable('i'), new Constant(4)), new Constante(4));
$expression->__toPhp() // me donne (($i + 4) + 4);

je suis un peu bloqué, je dois rajouter à chaque fois une méthode dans chaque Object. Je perd un peu de la simplicité du pattern..

Visiteur Pattern à la rescousse !

Je vais définir une méthode accept(Visitor $visitor)

1
2
3
interface Expression{
     public function accept(VisitorExpression $v);
}

avec VisitorExpression définit ainsi

1
2
3
abstract class VisitorExpression{
    public abstract function visite(Expression $expr);
}

Voici comment se transforme l’addition, la constante et la variable (je ne mets pas tout..)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
Class Constant implements Expression
{
    private $value;
    public function __construct($value)
    {
        $this->value = $value;
    }
    public function getValue() {
        return $this->value;
    }
    public function accept(VisitorExpression $v)
    {
        return $v->visit($this);
    }

}

Class Addition Implements Expression
{
    public function __construct(Expression $left, Expression $right) {
        $this->right = $right;
        $this->left = $left;
    }
    public function getLeft() {
        return $this->left;
    }
    public function getRight() {
        return $this->right;
    }
    public function accept(VisitorExpression $v)
    {
        return $v->visit($this);
    }

}

class Variable implements Expression
{
    private $name;
    public function __construct($name) {
        $this->name = $name;
    }
    public function getName() {
        return $this->name;
    }
    public function accept(VisitorExpression $v)
    {
        return $v->visit($this);
    }
}

Voici l’implémentation de notre Visiteur

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class VisitorEvaluation extends VisitorExpression {
    protected $context;
    function __construct($context){
        $this->context = $context;
    }

    public function visit(Expression $expr){
        $class = 'visit'.get_class($expr);
        return $this->$class($expr);
    }
    public function visitAddition(Expression $expr)
    {
        return $expr->getLeft()->accept($this) +
            $expr->getRight()->accept($this);

    }
    public function visitConstant(Expression $expr)
    {
        return $expr->getValue();

    }
    public function visitVariable(Expression $expr)
    {
         return $this->context->read($expr->getName());
    }

en pratique. On appelle la méthode accept. Celle-ci appelle la methode visit($this). la méthode visit détermine la fonction à appeller. Si c’est une constante alors visistConstant() celle-ci résout la valeur. pour une addition c’est un plus compliqué on ré-appelle récursivement accept sur chaque partie de l’addition.

Voici comment s’en servir

1
2
3
4
5
6
7
8
9
// j'ai besoin d'une mémoire
$memory = new Memory();
$memory->write('i', 10);
// j'ai besoin d'un visiteur
$ve = new VisitorEvaluation($memory);
// une expression
$expression = new Addition(new Constant(10), new Variable('i'));
// appelle le visiteur
echo $expression->accept($ve); // 20

On se rend compte qu’il n’y a plus de logique dans mes objet. Tout est sous-traité dans le visiteur.

L’avantage de cette méthode est qu’il est très simple de changer le visiteur sans changer la logique.

Par exemple le visiteur qui convertit en php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
}
class VisitorToPhp extends VisitorEvaluation {
    public function visitAddition(Expression $expr)
    {
        return '(' .  $expr->getLeft()->accept($this) . '+'
            . $expr->getRight()->accept($this). ')';

    }
    public function visitVariable(Expression $expr)
    {
         return '$'. $expr->getName();
    }

    public function convertMemory()
    {
        $output = '';

        foreach($this->context->getAll() as $key => $value) {
            $output .= '$'.$key . ' = ' . $value . ';';
        }
        return $output;
    }

    public function getOutput()
    {
        return 'echo';
    }

    public function translate(Expression $exp) {
        return $this->convertMemory() . $this->getOutput() . $exp->accept($this);
    }
}

Et celui qui convertit en Javascript !

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class VisitorToJs extends VisitorToPhp {
    public function visitVariable(Expression $expr)
    {
         return $expr->getName();
    }

    public function convertMemory()
    {
        $output = '';
        foreach($this->context->getAll() as $key => $value) {
            $output .= 'var '. $key . ' = ' . $value . ';';
        }
        return $output;
    }
    public function getOutput()
    {
        return 'console.log';
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$memory = new Memory();
$memory->write('i', 10);
$ve = new VisitorEvaluation($memory);
// une expression
$expression = new Addition(new Constant(10), new Variable('i'));
// appelle le visiteur evaluation simple
echo $expression->accept($ve); // 20
// evaluation conversion php 
$php = new VisitorToPhp($memory);
echo $expression->accept($php); // (10 + $i)
$js = new VisitorToJs($memory);
echo $expression->accept($js); // (10 + i)
// j'ai rajouté une méthode translate qui est un raccourci
echo $php->translate($expression); // $i = 10;echo(10+$i)
echo $js->translate($expression); // var i = 10;console.log(10+i)

Les limitations du visiteur pattern

  • toute la logique est sur le visiteur. s’il y a un beaucoup de type d’expression (dans notre cas Addition, Constant, Variable, Abso, Multiplication ..) c’est autant de ligne à rajouter dans celui-ci.
  • rajouter un type, oblige à le ré-implementer partout.

Les avantages du visiteur pattern. On peut parfaitement imaginer un type document, et lui ajouter un visiteur toJson, toPdf, toEbook, toHtml. sans jamais changer le modèle.

Nous continuerons avec le visiteur pattern dans un prochain post. Nous ajouterons un visiteur pour les expressions booléenes. puis nous ajouterons un visiteur pour des instructions. nous allons créer un mini-langage..

Ce projet vient des notes que j’avais prise quand j’étais au CNAM sur le cours de Design-Pattern en Java. J’avais adoré!

Comprendre la ligne de commande de vi

Sous Vi quand on appuie sur : on a la ligne de commande

Tout le monde connaît

1
2
:wq // quitter et enregistrer
:q! // quitter sans enregistrer et sans confirmation !

Mais en pratique il existe plein de commandes.

Par exemple :

1
:1,10d 

efface la ligne 1 à 10 (d = delete)

1
:1,10m 10

bouge les lignes de 1 à 10 de 10 ligne (ici m = move)

le “pattern” est toujours le même

1
: (début, fin)action
mouvements traduction
1,10 entre la ligne 1 et la ligne 1 à 10
.,10 . signifie la ligne actuelle
10,$ $ signifie la dernière ligne
/mot1/,/mot2/ entre le mot1 et le mot2
., +5 entre la ligne actuelle (.) et les 5 lignes suivantes
% tout le fichier

quelques actions

racourcci traduction
d comme delete
j comme join
sort trier (sort) les lignes
w pour enregistrer
y comme yank

le plus connu substitute

vous avez souvent vu cette syntaxe dans les commits s/mot1/mot2

ici s signifie substitute.

par exemple

1
:%s/mot1/mot2/g

va remplacer le mot 1 par le mot 2 le g active le flag global et remplace si le mot apparait deux fois.

par exemple

1
2
3
4
5
6
7
$mot1 = $mot1 + 1;

// s/mot1/mot2 
$mot2 = $mot1 + 1 ; // on ne change que le premier mot

// s/mot1/mot2/g
$mot2 = $mot2 + 1 // tout les mots

Encore un peu plus loin

la commande suivante permet de grouper les mots

1
:g/mot/ #donne toute les lignes contenant mot

g ici signifie group

On peut chaîner les differentes actions par exemple

1
2
3
4
5
:g/pattern/s/mot/mot2/g # toutes les lignes qui contiennent le pattern, remplace mot1 par mot2.

:g/pattern/d # efface toute les lignes qui contiennent le pattern suivant

:g/pattern/p # 'print toutes lignes qui contienne le pattern suivant

la derniere ligne est la plus connue. pattern est le plus souvent une regex donc la traduction g/regex/p ->donne la commande grep sous linux.

en faite, toutes les commandes que j’ai données proviennent de sed. mais ce n’est pas un hasard. vi est l’abbreviation de Visual Interaction of Sed. un sed interactif.

J’espère que cela vous fera apprecier sed comme vi. on peut rester très longtemps sur toutes les commandes.

j’avais expliqué dans un précédent articles les mouvements en mode normal sont

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Action + Nombre de fois + Mouvement

Exemple:
d5w # *d*elete *5* word
yG  # copier jusqu'à la fin du fichier (G)
=4j # indenter (=) 4 lignes vers le bas 
di( # efface entre les parenthèses *d*elete *i*nside ( 

Dans le mode commande
: début, fin action

:%d # efface tout le fichier
:1,10y # copie dans le presse-papier la ligne 1 à 10
:%s/include_one/require_once/gc

Il me reste à vous parler des buffers et des macros. et on aura presque fait le tour de la magie de vi.

une commande de la vrai vie

1
:%s/\s\+$//

sur tout le fichier (%) remplace(s) un ou plus(+) espaces (\s) à la fin de la ligne ($) par du vide. cette commande supprime les espaces vides à la fin des lignes..

FizzBuzz sans boucle If

Bon, j’ai passé deux-trois entretiens ou on m’a demandé d’implémenter “fizzbuz”

Les règles sont simples.

  • Écrire un programme qui écrit les nombres de 1 à 100.
  • Si le chiffre est divisible par 3 afficher seulement “fizz”
  • Si le chiffre est divisible par 5 afficher seulement “buzz”
  • Si divisible par 3 et 5 afficher “fizzbuzz”
  • sinon afficher le chiffre tout seul

Il y a plein de solutions possibles

la plus littérale

1
2
3
4
5
6
7
8
9
10
11
for ($i = 1; $i <= 100; $i++) {
    if ($i % 15 == 0) {
        echo 'fizzbuzz'. "\n";
    } elseif ($i % 3 == 0) {
        echo 'buzz'. "\n";
    } elseif ($i % 5 == 0) {
        echo 'fizz'. "\n";
    } else {
    echo $i ."\n";
    }
}

la version de wikipedia

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for ($i = 1; $i <= 100; $i++) {
    $output = '';
    if ( $i%3 == 0) {
        $output .= 'fizz';
    }
    if ( $i%5 == 0) {
        $output .= 'buzz';
    }

    if ($output == '') {
        $output .= $i;
    }
    echo $output. "\n";
}

Ma version que j’avais programmé

Le continue n’est pas souvent utilisé. Mais je trouve qu’il remplit son rôle ici.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
for ($i = 1; $i <= 100; $i++) {
    if ($i % 15 == 0) {
        echo 'fizzbuzz'. "\n";
        continue;
    }
    if ($i % 3 == 0) {
        echo 'buzz'. "\n";
        continue;
    }
    if ($i % 5 == 0) {
        echo 'fizz'. "\n";
        continue;
    }
    echo $i ."\n";

}

Sans boucle if

Il existe une version qui n’utilise aucune boucle if.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$resp = array(
    'fizzbuzz',
    false,
    false,
    'fizz',
    false,
    'buzz',
    'fizz',
    false,
    false,
    'fizz',
    false,
    false,
    'fizz',
    false,
    false
);

for ($i = 1; $i <= 100; $i++) {
    ($output = $resp[$i%15]) || ($output = $i);
    echo $output. "\n";
}

En php on ne peux pas faire la commande suivante qui correcte en javascript;

1
var a = value || defautvalue ;
1
$output = $resp[i%15] || $i// => $output = true

C’est pour cela que l’on utilise cette ligne un peu bancale.

1
($output = $resp[$i%15]) || ($output = $i);

Il n’y a pas de boucle if. Si un jour on vous demande d’implémenter FizzBuzzb en essayer cette version.

MapReduce du pauvre

MapReduce du pauvre.

une commande linux

1
find -name '*.php' | xargs -p 4 grep "ma chaine de caractère" | wc -l

Cela ne se voit pas mais je viens de faire un map/reduce

  • je prend tout les fichiers php
  • je lance un grep avec le résultat que je passe à travers le pipe.
  • puis je compte le nombre de résultats.
1
[filter] -> [map] -> [reduce]

ici l’astuce tient dans une astuces xargs -p 4 qui ordonne à xargs d’utiliser 4 processeurs en parrallèle ! Donc qui me permet d’aller quatre fois plus vite. On comprend l’interet du map/reduce. Je peux dispatcher le travail sur plusieurs instances(ici processeurs). On retrouve ce fonctionnement dans beaucoup de logiciels actuels. L’avantage ici est que j’utilise linux et que je n’ai installé aucun programme et en multiprocesseur je fais pleinenement confiance au kernel de mon linux. Il existe aussi un programme parallel mais je ne m’en suis jamais servi.

En php ?

Il existe bien entendu les fonctions array_map, array_filter, array_reduce mais il ne sont pas spécialement plus rapide qu’une boucle foreach. Il existe un map pour les Collection de doctrine.

je vais montrer que l’on peux écrires toute les opérations possibles avec ‘array_reduce’.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
function mysum($array) {
    return array_reduce($array, function($acc, $item) {
        return $acc += $item;
    });
}
function mymult($array) {
    return array_reduce($array, function($acc, $item) {
        return $acc *= $item;
    },1);
}

function mymax($array) {
    return array_reduce($array, function($acc, $item) {
        return max($acc, $item);
    });
}// le min est un peu moins simple

function map($function, $array) {
    return array_reduce(
        $array,
        function ($acc, $input) use ($function) {
            $acc[] = $function($input);
            return $acc;
        });
}

function filter($function, $array) {
    return array_reduce(
        $array,
        function ($acc, $input) use ($function) {
            if ($function($input)) {
                $acc[] = $input;
            }
            return $acc;
        });
}

function mycount($array) {
    return array_reduce(
        $array,
        function ($acc, $input)  {
        return ++$acc;
},0);
}

$a = [1,2,3,4,5];
var_dump(mysum($a)); // 15
var_dump(mymult($a)); // 120
var_dump(mymax($a)); // 5
var_dump(mycount($a)); // 5
var_dump(map(function($input) { return 2 * $input;}, $a)); // [2,4,6,8,10]
var_dump(filter(function($input) { return ($input > 4); }, $a)); // [6]

Map et Reduce sont des fonctions un peu particulières, elle prenent comme argument des fonctions. On parle de fonctions d’ordre supérieure, parfois de functor (mais çà c’est une autre histoire ..)

Php en mode interactif

Je me baladai sous Github

Et je suis tombé sur ce dépôt

https://github.com/borisrepl/boris

(en fait c’est un des dépôts les plus actifs de ce mois pour Php)

C’est vraiment cool

Bien entendu il existe déjà un mode interactif sous Php

1
php -a

On peux le refaire en encore moins de ligne.

Explication : La documentation parle de REPL. c’est l’abréviation de Read Eval Print Loop

et voici comment on peut l’implémenter (difficile de faire plus simple)

1
2
3
while (true) {
  echo eval(fgets(STDIN));
}

avec les commentaires

1
2
3
while (true)/*Loop*/ {
  echo /*Print*/ eval(/*Read*/ fgets(STDIN));
}

Read Eval Print Loop. le nom donne le programme.

C’est une technique qui marche avec un peu tout les langages. voir http://repl.it

Comprendre les raccourcis claviers de vi/vim

Comprendre les raccourcis claviers Vim : la méthode des deux tableaux

Aujourd’hui nous allons essayer de comprendre les raccourcis de vi/vim. Je vous demande de prendre deux feuilles. Je vais essayer de vous monter comment se combinent les touches.

  • sur la feuille 1 dessinez deux colonnes: touche description.
  • Titre de la feuille 1 : Mouvement
  • Tire de la feuille 2 : Action deux colonnes : touche et description.

Lancez vim et ouvrez un fichier texte existant.

Les mouvements :

w signifie word appuyez sur w vous passez au mot suivant.

Ajouter à votre tableau mouvement w word passe au mots suivant

b signifie back. Vous allez au mot précédent

Vous ajoutez a la feuille b back

e signifie end va à la fin du mot

les directions

J’ai un peu peur de vous perdre ici. Dans le temps les clavier n’avait pas de touche de direction. Les touches sont h, j, k, l pour respectivement gauche, bas, haut, droite

Moyen mnémotechnique j va vers le bas, k va vers le haut.

Vous pouvez utiliser les flèches. Mais un des avantage d’utiliser les j et k est que vos doigts ne quitte jamais le milieu du clavier.

Vous avez maintenant 7 lignes à votre tableau

Un quiz !

Que se passe t’il si je tape 3w ou 5j ?

Réponse je me déplace de 3 mots ou 5 ligne vers le bas modifions notre tableau en rajoutant le (n).

Se déplacer dans le fichier.

gg vous ramène au début du fichier (g pour go) G vous emmène à la fin du fichier 50G vous emmène à la ligne 50 (variante :50 marche aussi)

Vous rajoutez les 3 lignes dans votre tableau.

Début de ligne, fin de ligne

si vous connaissez vos Regex ^a vous donnes tout les occurrences qui commencent par a et a$ qui finissent par a ^ début du texte sur la ligne actuelle $ fin de la ligne 0 colonne 0 (tout début de la ligne)

A la recherche du mot perdu

pour chercher de vi on utilise la touche /, vous mettez le mot que vous souhaitez, appuyez sur n pour suivant ou N pour précédent.

nous avons le tableau final

Touche Description
w word mot suivant
(n)w n word (ex: 3w 3 mots)
b back mots précédent
e end fin du mot
h,j, k,l gauche, bas, haut, droite
(n)j 3 fois bas
gg début du fichier
G fin du fichier
20G ligne 20
^ Début de ligne
$ fin de la ligne
/mot cherche mot. n pour suivant N pour précédent

Nous avons la feuille 1.

Un demi conclusion :

  • Il existe plus de mouvement.
  • mais le but ici n’est pas d’être exhaustif. J’ai besoin des mouvements pour introduire la feuille suivante.

Les Actions.

d signifie delete (en fait c’est un couper)

Petit quizz : que se passe t’il si j’appuie sur les touches suivantes ? d3w une traduction delete 3 words. J’efface trois mots, Vous comprenez pourquoi j’ai absolument besoin de la table d’avant. Car en fait et c’est une des vérités qui vous aidera dans Vi/Vim. Un combinaison dans Vi c’est action + mouvement.

Je donne les autres actions

  • ypour yank pour copier yw copie un mot. y3j copie 3 lignes. pour coller on utilise la touche p comme paste
  • c pour change. Vous souhaitez changer un mot. avant vous appuyer sur i pour vous effacer le mot, puis vous rajouter le votre. Si vous taper cw comme change word. vi supprime le mot et met directement en mode insertion.
  • > et < déplace à droite et à gauche le texte <4j déplace 4 ligne à gauche.
  • = re-indente le texte, c’est un pictogramme le deux lignes du égale sont alignées. Pour ré-indenter tout le texte gg=G soit gg va au début du fichier =G ré-indente jusqu’à la fin du fichier.

Table 2

Touche Description
d delete
c change efface et passe en mode insertion
y yank (copier) p pour paste coller
> décale le texte à droite
< décale à droite
= reindente le code
v visual selectionne le texte y copie d coupe etc ..

Conclusion

  • Pour faire une combinaison de touche il suffit de prendre une lettre de la table 2 + un mouvement de la table 1. par exemple je veux effacer 5 lignes: delete 5 lines -> d5j je veux changer un mot change word -> cw

  • Recopier les deux tableaux. L’astuce est vraiment d’avoir les feuilles sous les yeux.

  • Les touches sont les mêmes pour man et surtout less

Un peu de philosophie.

  • Je n’ai pas utilisé une seul fois la touche controle et alt.
  • En 1 sens, je n’utilise pas la souris. Je veux indenter le texte je dis indente le texte =G et pas sélectionner le texte, puis bouton droit ou un raccourci clavier. En un sens Vi est plus direct.
  • J’espère que cela démystifie un peu l’utilisation du clavier sur vi

Teaser

il existe d’autre mouvement ! Je ferai une version deluxe, l’idée ici est de comprendre la combinaison de mouvements. une touche apprise c’est de dizaine de nouvelles possibilités par exemple di( i signifie inside. donc delete inside parenthèse. Efface le texte entre les parenthèses.

Faq

j’appris que pour effacer une ligne c’est dd ? je ne le vois pas dans le tableau. en fait

  • dd efface une ligne.
  • yy copie la ligne.
  • cc change la ligne.

Il existe un mouvement qui s’appelle _ qui représente la ligne actuelle (c’est d’ailleurs le pictogramme d’une ligne) donc en fait si on tape d_ on efface la ligne actuelle (essayez !). Mais la plupart du temps c’est un peu compliqué à taper pour une opération plutôt courante (supprimer une ligne) . Donc il a été décidé que dd, yy , cc sont les raccourcis de d_, y_ et c_.

Il y a en fait une multitude de raccourcis par exemple:

  • c$ donne C(Change tout la ligne à partir du curseur).
  • d$ donne D(Efface toute la ligne à partir du Curseur).
  • ^i donne I insert au début de la ligne.
  • $a donne A ajoute à la fin de la ligne.

Quelques commandes Vim/Bash

Quelques commandes utiles

Quelques commandes utiles que j’utilise souvent

Quand je ne suis pas sudo

1
2
3
4
$ apt-get install npm
E: Could not open lock file /var/lib/dpkg/lock - open (13: Permission denied)
E: Unable to lock the administration directory (/var/lib/dpkg/), are you root?
$ sudo !!

!! est la dernière commande exécutée

Une variante

1
2
mkdir log/
cd !$ 

!$ est la dernière commande sans le premier argument ici log/

Vi/Vim

Bon il y a beaucoup de commandes relativement sympa et complexes mais je donne celle que tout le monde demande

Enregistrer un fichier en lecture seule

1
:w !sudo tee %

Dite oui pour recharger la page. Et dite oui si vous voulez quitter la page sans enregistrer.

D’ailleurs si vous utilisez tout le temps :wq vous pouvez essayer

  • :w enregistre sans quitter
  • :q quitte mais avec warning si vous n’avez pas sauvegarder
  • :q! quitte sans warning et sans sauver
  • :x raccourci de :wq. Il existe aussi le raccourci clavier ZZ comme pour aller dormir. quitte et enregistre.
  • :e fichier.txt ouvre le fichier (l’auto complétion avec tab fonctionne..)
  • :tabe fichier.txt ouvre dans un nouvel onglet (vim seulement). on peux se ballader d’onglet en onglet avec les touches gt et gT g est le diminutif de go et t pour tab. ou avec ctrl+<pageUp> et ctrl+<pageDown> ou encore tout simplement cliquer avec la souris (astuce suivante)

Active la souris

1
set mouse=a

une astuce est d’ajouter un .vimrc et d’ajouter cette ligne dans ce fichier de config.

le vimrc

Vous en avez besoin !! Il existe deux versions de vi: vi et vim vim démarre par défaut en mode vi compatible (ce qui n’est plus tout à fait vrai).Certaines fonctionnalités sont coupées. Dont les flèches de directions (et ca vous voulezles flèches haut, bas, gauche, droite) et surtout le annuler (u come undo) illimité (il est parfois limité à 1). Quel rapport avec le vimrc ? L’existence du fichier fera que votre vi démarre en mode non compatible. donc vous aurez les touches et le undo.

l’auto-complétion sous vim

Il existe une dizaine d’auto complétion sous vim. Certainement pas aussi puissantes que IDE mais cela rend service.

En mode insertion ctrl+p auto complétion en cherchant dans les fichiers ouverts. Cela résout 50 % des auto complétions.

l’auto-complétion est activé via la touche ctrl+x +crtl+lettre avec :

  • ctrl+x ctrl+f f pour file auto complétion suivant le fichier (marche nickel pour les hosts apache !)
  • ctrl+x ctrl+l l pour line auto complétion sur la ligne entière
  • ctrl+x ctrl+p c’est pareil que ctrl+p
  • ctrl+x ctrl+o o pour omni-completion auto complete php/python/bash suivant le fichier que vous éditez. (cela comprend les mots clés et peux vous donner la syntaxe)

Je montrerai dans un prochain post comment faire pareil qu’un IDE avec les fonctions. Ou se balader dans les classes et/ou auto compléter le code. Ce n’est pas encore le niveau des IDEs mais c’est souvent une fonctionnalité méconnue (pourtant c’est dans vi depuis des années)