IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Voir le flux RSS

nothus

Un moteur "naïf" IFTTT

Noter ce billet
par , 21/07/2017 à 16h50 (1406 Affichages)
Bonjour à tous,

Dans la continuité d'un article précédent sur les nombres premiers, je me suis amusé à chercher ce qui serait possible de faire des résultats. Et pourquoi pas un IFTTT - If This Then That ? En bon français, la traduction serait une Programmation à Déclenchement Conditionnel.

Un site très connu existe pour une première approche opérationnelle et la mise en œuvre de ce type de moteur s'accorde très bien avec un concept d'imbrication de micro-services (rapidité + facilité).

IFTTT : Principe de base

La logique est facile : Si (If) un Evenement (This) survient alors (Then) on fait ça (That). Un outil IFTTT ne prend pas nécessairement une seule condition en compte, mais une cardinalité de 1 à n condition(s). Graphiquement, cela se représente ainsi :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
cond1 	|
  (+) 	|==(test)==> résultat
cond2 	|
  (+) 	|
  ... 	|
Il est aussi possible de panacher les conditions non avec des valeurs mais aussi des actions, que l'on représente sous la forme de fonctions appelées au moment de la vérification de la valeur. Idem pour le test lui-même, qui peut-être une fonction (que l'on peut qualifier de call back car elle s'exécute après, dans une liaison avec l'étape précédente / imbriquée).

L'ensemble est hérité de l'algèbre de Boole. Je n'évoque pas directement les tenants qui permettent de produire l'approche que j'évoque ici, qui n'est ni plus ni moins qu'une table de vérité par produit (c'est-à-dire où toutes les conditions doivent être réunies pour que le résultat soit Vrai).

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

--- EXEMPLE 1 --- 
action1() 	|
   (+)   	|==(test)==> résultat
cond2    	|
   (+)   	|
  ...    	|

--- EXEMPLE 2 --- 
action1() 	|
   (+)    	|==( action2() )==> résultat
cond2     	|
   (+)    	|
  ...     	|

--- EXEMPLE 3 --- 
action1() 	|
   (+)    	|==( action2() )==> résultat2
resultat1 	|
   (+)    	|
  ...     	|
L'imbrication action / condition (le premier était considéré comme une condition dont la valeur est fixé par son retour) est ici optimale car transparente pour celui qui utilisera la classe.

Le "poids" d'une variable

Rappelez-vous votre premier cours : le binaire se "traduit" (du moins la conversion vers d'autres bases se fait) grâce au poids qu'acquière un bit donné. Un octet, c'est finalement "un série de bits signés" au sens où ils sont ensembles significatifs individuellement et collectivement (je sais que le mot signé est ici malheureux car se rapproche d'un autre concept, mais je manque de synonymes...).

Exemple :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
BIT   -->   POIDS    -->    VARIABLE
1     -->   1        -->    a
2     -->   2        -->    b
3     -->   4        -->    c
4     -->   8        -->    d
(etc)
Ainsi sur 4 bits, nous pouvons former le nombre : 1+2+4+8 = 15. Pour avoir le chiffre 17, il faut créer un 5e bit et mettre le bit 1 et 5 (qui a un poids à 16) à la valeur "Vrai" (1+16). Hexadécimal, décimal, binaire ou même votre horloge : tout fonctionne sur ce principe.

Si je désire le nombre 5, il me faut donc mettre à Vrai les bits 1 et 3 (soit 1+4). En algèbre, cela représente donc :
8 = (a * 1) + (b * 0) + (c * 1) + (d * 0)

Problème : que faire si une condition est optionnelle ?

C'est là où rentre en jeu les nombres premiers ! Car les produits/multiplications permettent bien des astuces, que Python saura exploiter pleinement et sans difficulté particulière de programmation.

En reprenant mon tableau précédent, un poil modifié :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
BIT   -->   POIDS    -->    VARIABLE
1     -->   0        -->    a
2     -->   1        -->    b
3     -->   2        -->    c
4     -->   4        -->    d
Vous me direz que c'est idiot : pour le même nombre de bits, je ne peux "représenter" que moins de nombres entiers. C'est vrai, mais la valeur que j'ai fixé au premier bit annule systématiquement les autres. En somme, le tableau ci-dessous, quelque soit l'état des bits, est égal à... 0. Ce tableau ne représente donc pas plusieurs entiers mais un seul ! [ édition : Quelle erreur... une relecture trop rapide ! évidemment cela ne fait qu'annuler le premier bit et non tous, systématiquement, si l'on reprend l'exemple plus haut l Mon enchaînement était malheureux ]

Si maintenant je ne raisonne plus pour des bits mais comme une unique variable dont c'est la table des états possibles (avec 0 OU 1 OU X - où X est un nombre premier), et que je fais le produit avec plusieurs de ses congénères :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
VAR "A" => (0, 1, 2) 
VAR "B" => (0, 1, 3) 
VAR "C" => (0, 1, 5)
Alors je peux avoir les 9 combinaisons suivantes :
  • 0 (car 1 ou n VAR est/sont à 0)
  • 1 (car toutes les VAR sont à 1)
  • 2 ou 3 ou 5 (la valeur d'une VAR alors que les autres sont à 1)
  • 6 (A x B et C à 1)
  • 15 (B x C et A à 1)
  • 10 (A x C et B à 1)
  • 30 (toutes les VAR sont à leur valeur)


Si j'ajoute d'autres états possibles pour les VAR, toujours en respectant les nombres premiers comme état possible, je peux donc démultiplier les possibilités de mon état de sortie...

En quelque sorte, le résultat est un "contexte" unique, à un moment donné, où chaque appel le mettra à jour. La valeur 0 annule tout ; la valeur 1 rend l'état de la VAR indifférente au reste du produit (la VAR est donc devenue "optionnelle") et sinon c'est un nombre différent car la multiplication des nombres premiers rend unique le résultat.

En terme de programmation...

C'est d'une facilité assez enfantine : j'ai mes conditions stockées dans un tableau, sous la forme d'une référence à une fonction. Des paramètres peuvent être ajoutés. La vérification de l'état de sortie de la condition, déclenche la fonction et retourne un nombre non-premier.

J'ai déterminé mon action en fonction du résultat total. Par facilité, j'ai permis de déclarer une action avec un segment de valeur (range). Mon Schéma une fois initié, peut être essayé et l'objet Schéma représente "humainement" une séquence de possibles, d'actions, de contextes...

Enfin j'autorise la liaison entre plusieurs séquences car la classe Schéma détecte, lors de l'essai, si la référence qui lui est transmise est la même classe qu'elle-même : un résultat de séquence peut donc servir comme condition à une autre et sans préjuger de son état au préalable !

Code python : Sélectionner tout - Visualiser dans une fenêtre à part
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
class Schema: 
 
    def __init__(self):
        self.__conditions__ =  []
        self.__actions__ = {} 
 
    def condition(self, ref, params=list(), oui=3, non=2, erreur=0): # /!\ Mettre non=1 lors de l'appel pour rendre la condition optionnelle ! 
        self.__conditions__.append((
            ref,
            params,
            oui,
            non,
            erreur 
        )) 
 
    def action(self, etat, ref, params=list()):
        if isinstance(etat, int):
            etat = (etat,)
        for e in etat: 
            self.__actions__[e] = (
                ref,
                params
            ) 
 
    def essayer(self, oui=True, non=False, erreur=False):
        i = 1
        for condition in self.__conditions__:
            try:
 
                i *= self.__executer__(condition) 
            except Exception as err: 
                i *= int(
                    condition[4]() if callable(condition[4]) else condition[4]
                ) 
        try: 
            action = self.__actions__[i]
            if action[0](i, *action[1]): 
                return oui(self, i) if callable(oui) else oui 
            else: 
                return non(self,i) if callable(non) else non 
        except:
            return erreur(self,i) if callable(erreur) else erreur
 
    def __executer__(self,condition):
        if condition[0].__class__ is self.__class__:
            condition = list(condition) 
            condition[0] = condition[0].essayer
        return (
            condition[2]() if callable(condition[2]) else condition[2] 
        ) if condition[0](*condition[1]) is True else (
            condition[3]() if callable(condition[3]) else condition[3]
        )


Voici un code de démonstration à ajouter :

Code python : Sélectionner tout - Visualiser dans une fenêtre à part
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
53
from time import gmtime, strftime
 
def Calendrier(jours = { # Si la valeur d'aujourd'hui est sur False, le retour de la condition sera à 2 (non par défaut) ou sinon 3 (oui par défaut) 
    0: False, 
    1: False, 
    2: True, 
    3: True, 
    4: True, 
    5: True, 
    6: True
}):
    print("Calendrier : ", jours[int(strftime("%w", gmtime()))]) 
    return jours[int(strftime("%w", gmtime()))]
 
def Rien(i, echo=None): 
    print("---\ni =", i, "\n", type(echo), echo,"\n---") 
    return True  
 
if __name__=="__main__": 
 
    Sequence1 = ifttt.Schema() 
 
    Sequence1.condition(
        Calendrier 
    ) 
    Sequence1.action( 
        0, 
        Rien,
        ("Oups ! (1)",) 
    ) 
    Sequence1.action( 
        (2, 3), 
        Rien,
        ("Une première étape de franchie !",) 
    ) 
 
    Sequence2 = ifttt.Schema() 
 
    Sequence2.condition(
        Sequence1 
    ) 
    Sequence2.action( 
        0, 
        Rien,
        ("Oups ! (2)",) 
    ) 
    Sequence2.action( 
        (2,3), 
        Rien,
        ("La seconde étape tant attendue...",) 
    ) 
 
    Sequence2.essayer()


Ce code est d'une inutilité crasse mais peu importe : elle démontre que l'on peut avoir des conditions optionnelles ou d'autres au contraire impératives simplement et dont le résultat est donné par une simple multiplication.

Exemple de cas d'IFTTT

Si je suis connecté à ma boîte courriel, qu'il est entre 8h du matin et 18h, et que j'ai pas mis SPÉCIFIQUEMENT mon profil en pause, alors m'envoyer une alerte si j'ai un courriel...

Soit :
- connexion courriel + nouveau courriel arrivé = 2 (ou 0)
- horaire = 3 (ou 0)
- profil = 5 (ou 1 ou 0 -> c'est-à-dire la troisième valeur qu'est l'erreur)
--> j'émets une alerte si le résultat est 6 ou 30, indiquant un "contexte" différent (l'état de mon profil). Les autres valeurs sont ignorées et n'ont pas besoin d'être déclarées (2, 3, 15 ou 10). Je peux augmenter le nombre de conditions facilement (toujours renvoyant 0, 1 ou un nombre premier non-encore utilisé dans la séquence) et obtenir de nouveaux contextes uniques et prédéterminés ainsi créés.

Bon code à tous,

Julien.

Envoyer le billet « Un moteur "naïf" IFTTT » dans le blog Viadeo Envoyer le billet « Un moteur "naïf" IFTTT » dans le blog Twitter Envoyer le billet « Un moteur "naïf" IFTTT » dans le blog Google Envoyer le billet « Un moteur "naïf" IFTTT » dans le blog Facebook Envoyer le billet « Un moteur "naïf" IFTTT » dans le blog Digg Envoyer le billet « Un moteur "naïf" IFTTT » dans le blog Delicious Envoyer le billet « Un moteur "naïf" IFTTT » dans le blog MySpace Envoyer le billet « Un moteur "naïf" IFTTT » dans le blog Yahoo

Mis à jour 29/07/2018 à 18h20 par LittleWhite (Coloration du code)

Catégories
Programmation , Python

Commentaires