Algorithmique avec CoffeeScript

niveau d'indentation 0

Sorties graphiques

Le tableau

Le graphique

Le fichier du graphique au format svg

On peut

  1. Modifier le contenu à l'aide du clavier (typiquement, changer la valeur de x pour un texte)
  2. Sélectionner le tout (Ctrl+A ou pomme+A ou appui long)
  3. Copier le svg (Ctrl+C ou pomme+C)
  4. Coller le contenu dans un éditeur de texte
  5. Sauvegarder dans un fichier ayant l'extension .svg
  6. Ce fichier peut alors être
    • être ouvert par Inkscape, puis exporté en pdf ou ps (pour import dans LaTeX)
    • être inséré dans un fichier odt sous Libre Office
    • être importé dans wikipedia
    • etc

aide

Affectation, entrée et sortie de données

Affectation

En coffeescript, l'affectation est notée par un "=".

Par exemple,

  1. Pour mettre 2 dans x:
    x = 2
  2. Pour le remplacer par 3:
    x = x+1

Une variable peut être affectée par un nombre mais aussi par du texte, une liste ou même une fonction.

Affectation simultanée

On peut affecter simultanément a par 1 et b par 2 avec

[a,b] = [1,2]

Raccourcis

Pour augmenter x de 1, on peut faire indifféremment

x = x+1
x += 1
x++

Entrée de données

Pour faire entrer un nombre x par l'utilisateur, il faut affecter x (donc écrire un signe "égal") et afficher un message d'invite rappelant ce que représente le nombre. Par exemple

x = entre "Allo non mais allo quoi! Tu veux mettre quoi dans x ?"

Affichage de données

Tant qu'on ne demande pas à CoffeeScript d'afficher le résultat d'un calcul, on ne peut pas connaître celui-ci. Il y a deux moyens de faire connaître le contenu de x:

  • créer une boîte modale d'affichage contenant x avec
    alerte x
  • afficher x dans le cadre prévu à cet effet à droite de la page avec
    affiche x

Simulation du hasard

Pour lancer un dé à 6 faces

affiche dé 6

Le nombre de faces doit être en entier, sans limite.

Pour vérifier, on peut lancer 10 fois le dé:

(dé 6 for n in [1..10])

Pile ou face

Pour jouer à pile ou face, on lance un dé à deux faces.

Loi uniforme sur [0;1[

affiche alea()

donne un nombre aléatoire entre 0 et 1

Application

On peut alors simuler une variable aléatoire Z normale centrée (d'espérance 0) et réduite (d'écart-type 1) avec

Z = -6
for n in [0...12]
    Z += alea()
affiche Z

Essayer par exemple

gauss = () ->
    Z = -6
    Z += alea() for n in [0...12]
    Z

grosTableau = (gauss() for n in [1..1000])

histogramme grosTableau, -3, 3, 15, 200
-3-2,6-2,2-1,8-1,4-1-0,6-0,20,20,611,41,82,22,63

Logique

Égalité

Pour tester l'égalité entre deux objets, il y a deux manières:

  • avec un double égal comme dans 2+2 == 4
  • avec le verbe is ("est") comme dans 2+2 is 4

affiche 2+2 is 5

affiche false (ben oui!)

Négation

Pour obtenir le contraire d'une proposition, la précéder par un point d'exclamation

affiche not 2+2==5
affiche 2+2 isnt 5

Le symbole "≠ se note donc isnt ou !=, au choix.

Conjonction

Pour dire que x est à la fois positif et pair, on peut écrire

x > 0 and x%2 is 0

Disjonction

Pour dire que x est, ou bien positif, ou bien pair (ou les deux), on peut écrire

x > 0 or x%2 is 0

Une alternative est le double trait vertical.

Pour taper le symbole "trait vertical", appuyer avec la main droite sur et, sans lâcher ce bouton, actionner le en haut du clavier, de la main gauche.

Booléens

Tests

Test simple

Un test est une instruction conditionnelle; Pour n'effectuer quelque chose que lorsqu'une condition est vraie (par exemple seulement si le dé est tombé sur 6), faire

if dé(6) is 6
    affiche "gagné"

On remarque l'indentation qui précise que la partie affiche "gagné" ne doit s'effectuer que si le dé vaut 6.

affiche "gagné" if dé(6) is 6

a le même effet.

Test multiple

Que faire quand le dé ne tombe pas sur 6 ?

Si on ne s'intéresse pas au gain mais seulement à la défaite, on est pessimiste et on écrit

affiche "perdu" unless dé(6) is 6

Mais les deux cas peuvent être traités ensemble:

if dé(6)==6
    affiche "gagné"
else
    affiche "perdu"

Intervalles

On peut aussi faire un test dans un intervalle:

(x for x in [1..12] when x%2==0)

Intervalles

En CoffeeScript, les intervalles sont composés uniquement d'entiers. L'intervalle fermé [a;b] se note [a..b] avec deux points; L'intervalle ouvert à droite [a;b[ se note [a...b] avec trois points; L'appartenance se note in; Pour vérifier que 2 est compris entre 0 et 5:

affiche 2 in [0..5]
affiche 0 <= 2 <= 5
	

L'appartenance est aussi utilisée pour appliquer une fonction à un intervalle:

affiche (x*x for x in [0..5])

Ensembles

Ajout d'un élément

Pour ajouter un élement x à un ensemble A:

A.push x

Retrait d'un élément

Pour enlever le nombre 5 d'un ensemble A:

A = (x for x in A if x isnt 5)

Intervalles d'entiers

Ensembles

Intersection

L'intersection de deux ensembles A et B est formée des x de A qui sont aussi dans B:

I=(x for x in A when x in B)

Complémentaire

Une légère différence mais qui change tout:

(x for x in A when x not in B)

est le complémentaire de B dans A.

En probabilités, cette notion formalise le contraire d'un évènement.

Réunion

La réunion de deux intervalles n'est pas nécessairement un intervalle:

A=(x for x in [0..100] when 2 < x < 15 or 80 <= x < 90)
affiche A

Multiensembles

À la différence d'un ensemble, un multiensemble (ou sac) peut contenir plusieurs occurences d'un élément. On crée un sac à partir d'un tableau, on ajoute un élément dans un sac comme dans un ensemble, mais on peut aussi ajouter plusieurs fois un élement d'un coup. Ainsi, pour créer une urne contenant 7 boules rouges et 3 boules bleues, on peut faire, ou bien

urne = new Sac ['rouge','rouge','rouge','rouge','rouge','rouge','rouge','bleu','bleu','bleu']

ou bien

urne = new Sac []
urne.ajoute 'rouge' for n in [1..7]
urne.ajoute 'bleu' for n in [1..3]

ou enfin

urne = new Sac []
urne.ajouteFois 7, 'rouge'
urne.ajouteFois 3, 'bleu'

On peut calculer les intersection et réunion de deux multiensembles comme pour les ensembles, oter un élément d'un multiensemble avec .ote élément ou extraire un élément au hasard d'un multiensemble; comme pour un ensemble, le cardinal d'un multiensemble est le nombre d'éléments (comptés avec leur multiplicité) qu'il contient. sac.contient x teste si l'élément x se trouve ou non dans le sac.

l'Infini dans CoffeeScript

Si on divise 0 par 0 on obtient NaN (not a number) qui signifie que la division n'a pas de sens. Mais si on divise 1 (ou tout autre nombre non nul) par 0, on obtient Infinity, ce qui veut dire que lorsqu'une variable x tend vers 0, son inverse 1/x tend vers l'infini;

De même,

1/Infinity

donne 0: Si une variable x tend vers l'infini, son inverse 1/x tend vers 0.

Opérations

Addition

affiche Infinity+Infinity

La somme de deux fonctions qui tendent vers +∞ tend elle-même vers +∞

affiche 3+Infinity

La somme d'une fonction qui tend vers 3 et d'une fonction qui tend vers +∞ tend elle-même vers +∞

affiche Infinity-Infinity

On ne peut rien conclure sur la somme d'une fonction qui tend vers +∞ et d'une fonction qui tend vers -∞: Il s'agit d'une forme indéterminée.

Multiplication

affiche Infinity*Infinity

Le produit de deux fonctions qui tendent vers +∞ tend lui-même vers +∞

affiche 3*Infinity

Le produit d'une fonction qui tend vers 3 et d'une fonction qui tend vers +∞ tend lui-même vers +∞

affiche 0*Infinity

On ne peut pas conclure sur le produit d'une fonction qui tend vers ∞ et d'une fonction qui tend vers 0: Il s'agit d'une forme indéterminée.

Croissance comparée

affiche leLogarithmeDe 0
affiche leLogarithmeDe Infinity

La limte de ln en 0 est -∞ et la limite de ln en +∞ est +∞

affiche exp -Infinity
affiche exp Infinity

La limite de ex est 0 lorsque x tend vers -∞ et +∞ lorsque x tend vers +∞.

Fonctions

Une fonction associe à une variable d'entrée (ou plusieurs) appelée antécédent, une (ou plusieurs) variable de sortie appelée image; l'image de x par la fonction f est notée f(x); Aussi, en CoffeeScript, une fonction est-elle désignée par une flèche -> séparant l'antécédent, entre parenthèses, de l'image, qui est une expression. Après la flèche on peut insérer un algorithme destiné à calculer la fonction par algorithme.

La dernière ligne écrite dans la fonction est la valeur retournée.

Une fonction peut ne pas avoir d'antécédent, dans ce cas c'est une procédure.

Cas particuliers

Fonctions constantes

(x) -> 3

est une fonction constante.

Fonctions affines
(x) -> x/2+1

est une fonction affine.

Une variable peut être une fonction

On peut affecter une variable par une fonction comme dans

f = (x) ->
    x*x-2*x-1

Ensuite on peut entrer f(3) ou f 3 pour avoir l'image de 3 par f.

Fonctions prédéfinies

Parmi les fonctions prédéfinies, on trouve

  • sinus et cosinus en degrés (pour l es radians, utiliser sin et cos)
  • laRacineDe pour la racine carrée; leCarréDe, leCubeDe pour le carré et le cube d'un nombre; lInverseDe pour l'inverse d'un nombre (1 divisé par ce nombre)
  • leLogarithmeDe pour le logarithme népérien, et lExponentielleDe pour ex.
  • les fonctions de JavaScript sont disponibles dans CoffeeScript

Tableau de valeurs

Pour avoir un tableau de valeurs d'une fonction, on peut construire un tableau associatif dans lequel on associe à chaque valeur de x choisie, son image par la fonction. Ensuite on peut afficher ce tableau associatif dans le tableau ci-dessus ("sorties graphiques")

tableauAssoc = {}
for x in [1,2,3,5,10]
    tableauAssoc[x] = leCarréDe x
trierDansTableau tableauAssoc
    

Plus court: On peut afficher les x et leurs images dans l'affichage:

for x in [1,2,3,5,10]
    affiche "#{x},   #{leCarréDe x}"
    

Ensuite, on sélectionne à la souris les nombres, et on les copie-colle dans un fichier portant l'extension "csv" (par exemple carré.csv); il suffit ensuite d'ouvrir ce fichier avec un tableur pour avoir non seulement le tableau, mais aussi la représentation graphique.

Boucles

Boucler n fois

Pas à pas

Pour répéter 10 fois une action, on a besoin d'une variable appelée indice de la boucle. Ici on notera i cet indice, qui ira de 1 à 10.

Par exemple, pour lancer un dé 10 fois, on peut entrer

for i in [1..10]
    affiche dé 6

Un bon moyen de suivre l'indice d'une boucle est d'afficher celui-ci dans la boucle:

for in in [1..10]
    affiche i
Remarque:
affiche i for i in [1..10]

a le même effet...

Les nombres 1, 2, 3 etc parcourus forment une suite arithmétique de raison 1.

Autres suites arithmétiques

Pour faire descendre l'indice (compte à rebours) on peut faire

affiche i for i in [10..1]

Pour aller de 3 en 3 on peut faire

affiche i for i in [1..10] when i%3 is 1

ou, mieux:

affiche i for i in [1..10] by 3

Boucler sur les nombres premiers

Pour boucler sur les nombres premiers inférieurs à 20:

for i in [2,3,5,7,11,13]

Boucler jusqu'à une condition

Pour lancer un dé jusqu'à ce qu'on ait un 6 (et compter les lancers dans un indice i), on fait

Jusque

i=0
i++ until dé(6) is 6

Il y a plusieurs manières de faire ça; par exemple

i=1
i++ while dé(6) isnt 6

Tant que

On peut aussi faire comme ceci:

i=1
while dé(6) isnt 6
    i=i+1

Dé icosaédrique

Un dé icosaédrique, comme son nom l'indique, a 20 faces numérotées de 1 à 20. L'univers de probabilité est alors Ω={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20}; On peut le calculer en fabricant un ensemble à partir de la liste des nombres allant de 1 à 20 :

univers = new Ensemble [1..20]

affiche univers
	

Évènements

L'évènement «le résultat est pair» s'écrit A={2,4,6,8,10,12,14,16,18,20}; il peut se définir en compréhension par

A = new Ensemble (x for x in univers.support when x%2 is 0)
	

L'évènement «le résultat s'écrit avec un seul chiffre» peut se définir de façon analogue mais aussi en faisant comme pour l'univers; on obtient B={1,2,3,4,5,6,7,8,9}

B = new Ensemble [1..9]
	

Intersection, réunion, contraire

Le contraire de A est formé des résultats qui sont dans l'univers mais pas dans A; on l'obtient par

A.complémentDans univers

L'évènement «le résultat est pair, et en plus il s'écrit avec un seul chiffre», noté A ∩ B, s'obtient par

A.inter B

L'évènement «le résultat est pair, ou alors il s'écrit avec un seul chiffre (à moins que ce soit les deux en même temps)», noté A ∪ B, s'obtient par

A.union B

Crible d'Eratosthène

Pour chercher les nombres premiers inférieurs ou égaux à 200, on peut faire ainsi :

crible = new Ensemble [1..200]
for m in [2..200]
    poubelle = new Ensemble (x for x in crible.support when x>m and x%m is 0)
    crible = poubelle.complémentDans crible

affiche crible

En fait, on peut aussi implémenter cet algorithme avec des tableaux, ce qui permet alors d'afficher l'histogramme des nombres premiers :

N = 200
crible = [2..N]
for m in [2..N]
    poubelle = (x for x in crible when x>m and x%m is 0)
    crible = (x for x in crible when x not in poubelle)

histogramme crible, 0, 200, 10, 10
020406080100120140160180

Probabilité

Le nombre d'éléments que contient un ensemble est son cardinal; pour savoir combien d'éventualités se trouvent dans A, on fait

A.cardinal()

Si son cardinal est nul, A est l'évènement impossible (le contraire de l'univers); pour savoir si un ensemble est vide, on fait

A.estVide()

La probabilité d'un évènement A est définie comme le quotient de son cardinal par celui de l'univers; elle dépend donc du choix de l'univers, et se note PΩ(A), ou, lorsqu'il n'y a pas d'ambigüité sur Ω, simplement P(A):

univers = new Ensemble [1..20]
proba = (E) -> E.probaSachantQue univers
B = new Ensemble [1..9]
affiche proba(B)

Graphisme

La sortie graphique mesure 640 pixels de large et 480 pixels de haut. Les abscisses vont donc de 0 (tout à gauche) jusqu'à 640 (tout à droite); mais les ordonnées sont ordonnées de haut en bas: 0 pour un élement graphique tout en haut, et 480 pour un élément graphique tout en bas.

Gomme

Pour effacer le dessin, on entre

effaceDessin()

Pour exporter le dessin :

$("#sortieSVG").text $("#graphique").html()

Ensuite, copier-coller le contenu du champ texte qui est en-dessous du graphique, vers un éditeur de texte; sauvegarder le résultat avec une extension .svg.

Point

On dessine un point comme un cercle de rayon petit, par exemple

dessineCercle 240, 100, 2, 'red'

pour dessiner le point de coordonnées (240,100) (représenté comme un cercle de rayon 2 pixels).

Segment

Pour dessiner le segment joignant les points de coordonnées respectives (100, 200) et (400, 300) :

dessineSegment 100, 200, 400, 300, 'blue'

Rectangle

Pour dessiner le rectangle dont le coin supérieur gauche a pour coordonnées (200,50) et de largeur 40 pixels, et de hauteur 150 pixels:

dessineRectangle 200, 50, 40, 150, 'cyan'

Texte

Pour écrire sur le graphique (aux coordonnées (150,100) ici), on fait

dessineTexte "alcoffeethmique Mac", 150, 100, 'grey'

On peut choisir la couleur que l'on veut pour chacune de ces primitives graphiques

Polygone

Pour dessiner un polygone, il faut lui fournir la liste des coordonnées de ses sommets (chacun des éléments de cette liste est lui-même une liste de deux nombres), ainsi que la couleur du périmètre et celle du remplissage (none si on ne veut pas remplir)

dessinePolygone [[20,240],[600,20],[600,460]], 'black', 'yellow'

Graphiques élaborés

Axes

Axe des abscisses

Pour dessiner l'axe gradué des abscisses allant de -1 à 3, en mauve :

dessineAxeX -1, 3, 'magenta'

et de façon similaire pour l'axe des ordonnées (dessineAxeY); on peut dessiner d'un coup les deux axes avec

dessineAxes -1, 3, -10, 100, 'brown'

Ces fonctions sont utilisées dans les deux suivantes:

Représentation graphique d'une fonction

Pour représenter graphiquement la fonction carré sur [-10,10] :

dessineFonction carré, -10, 10, 0, 100, 'red'
-10-9-8-7-6-5-4-3-2-10123456789100102030405060708090100

Représentation graphique d'une suite

Pour représenter graphiquement une suite, il faut la calculer comme une liste de nombres; par exemple, pour représenter graphiquement la somme des n premiers entiers (c'est une suite d'entiers) pour n allant de 0 à 10, avec des points de rayon 3, en bleu :

dessineSuite (laSommeDe [1..k] for k in [0..20]), 10, 0, 60, 3, 'blue'
0123456789100102030405060

Graphiques statistiques

Diagramme en bâtons
diagrammeBatons {'pour': 25, 'contre': 60, 'sans opinion': 15}, 100

Le second paramètre est un facteur d'échelle sur l'axe des ordonnées

Variante si le caractère est quantitatif:

lancers = new Sac []
lancers.ajoute dé(6) for n in [1..100]
diagrammeBatonsTrie lancers.effectifs , 40
Histogramme

Pour dessiner un histogramme, il faut une liste de nombres, les bornes d'un intervalle et le nombre de rectangles :

carrés = (x*x for x in [0..100])
histogramme carrés, 0, 10000, 10, 40
0100020003000400050006000700080009000

Statistiques

Pour faire des statistiques sur une liste, il est parfois nécessaire de la trier (par exemple, pour calculer les quantiles). Comme "trier" se dit "sort" en anglais, on trie la liste L ainsi:

Tri dans l'ordre croissant

	L.sort (x,y)->(x > y)
	

Tri dans l'ordre décroissant

	L.sort (x,y)->(x < y)
	

Maximum et minimum

Math.max.apply  null,L
Math.min.apply  null,L

Pour connaître l'effectif total d'une liste, on peut utiliser sa "longueur" (en anglais, length):

Compter les éléments d'une liste

L.length

Somme des éléments d'une liste

laSommeDe L

Moyenne d'une liste

laMoyenneDe L

Variance et écart-type

laVarianceDe L
lEcartTypeDe L

Médiane

laMédianeDe

Matrices

Vecteurs

Un vecteur est une liste de nombres; on le crée par le raccourci $V:

v = $V [2,1]
affiche v.elements

On voit au passage comment afficher les coordonnées d'un vecteur. On peut également engendrer des vecteurs aléatoires dont les coordonnées sont uniformes sur [0;1]:

u = Vector.Random(2)
affiche u.inspect()

On voit au passage une autre méthode d'affichage des coordonnées du vecteur

Coordonnées

L'abscisse de v se note v.e(1) et son ordonnée se note v.e(2). Pour additionner ou soustraire deux vecteurs:

u.add v
u.subtract v

Pour multiplier un vecteur par 3:

u.x 3

Coordonnées polaires

Pour calculer le produit scalaire de deux vecteurs, on utilise dot:

liste = (Vector.Random(2).dot(Vector.Random(2)) for n in [0...10000])
histogramme liste, 0, 2, 10, 4000

Ce script donne l'histogramme des produits scalaires de deux vecteurs aléatoires de coordonnées uniformes sur [0;1]:

00,20,40,60,811,21,41,61,82

Pour calculer la longueur d'un vecteur, on utilise modulus. Pour afficher la distribution statistique de la longueur d'un vecteur de coordonnées uniformes sur [0;1]:

liste = (Vector.Random(2).modulus() for n in [0...10000])
histogramme liste, 0, 1, 10, 2000

L'histogramme est surprenant:

00,10,20,30,40,50,60,70,80,91

Pour calculer l'angle entre deux vecteurs, on utilise angleFrom():

liste = (Vector.Random(2).angleFrom(Vector.Random(2)) for n in [0...10000])
histogramme liste, 0, 2, 10, 4000

Ce script affiche l'histogramme des angles entre deux vecteurs:

00,20,40,60,811,21,41,61,82

Pour faire tourner un vecteur v d'un angle a (en radians), faire

u = $V [1,0]
a = pi/6
affiche (u.rotate a, $V [0,0]).inspect()

Matrices

Définition

Une matrice est un vecteur de vecteurs:

M = $M [[1,2], [3,4]]
affiche M.inspect()

On engendre donc une matrice avec $M suivi du tableau, et on l'affiche avec inspect(). Il y a d'autres moyens d'engendrer des matrices:

M = Matrix.Diagonal [1,2]
M = Matrix.Zero 2, 2
M = Matrix.I(2)
M = Matrix.Random 2, 2
M = Matrix.Rotation pi/3

On peut récupérer les élémentsd'une matrice avec e(i,j), les lignes avec row(i) et les colonnes avec col(j)

Opérations

Pour additionner deux matrices, on utilise add comme pour les vecteurs. De même on peut soustraire deux matrices avec subtract et multiplier une matrice par un nombre avec x; la syntaxe est la même que pour les vecteurs.

Pour multiplier deux matrices (et arrondir le résultat pour qu'il soit plus lisible):

M = Matrix.Rotation pi/3
P = Matrix.Rotation pi/6
affiche (M.multiply P).inspect()
affiche ((M.multiply P).snapTo 0).inspect()

Trace et déterminant

Pour afficher les éléments diagonaux d'une matrice:

M = Matrix.Rotation pi/3
affiche M.diagonal().inspect()

Pour afficher la transposée:

M = Matrix.Rotation pi/3
affiche M.transpose().inspect()

Pour afficher la trace et le déterminant:

M = Matrix.Rotation pi/3
affiche M.trace()
affiche M.determinant()

Pour obtenir l'inverse d'une matrice:

M = Matrix.Rotation pi/3
affiche M.inverse().inspect()

Systèmes d'équations

Le principe est le suivant: On écrit le système sous la forme AX=B, où X et B sont des vecteurs colonne, et A une matrice carrée. Alors la solution du système, écrite sous forme d'un vecteur colonne, est X=A'B où A' est l'inverse de A. Par exemple, pour résoudre le système formé par les deux équations 3x-2y=-1 et x+y=8:

A = $M [[3,-2],[1,1]]
B = $V [-1,8]
affiche (A.inv().x B).inspect()

Exemples

Syracuse

Personne ne sait encore si cet algorithme s'arrête au bout d'un temps fini pour toute valeur initiale entière u0:

suite de Collatz

u = 35
until u is 1
    if u%2 is 0
        u /= 2
    else
        u = 3*u+1
    affiche u
	

Graphique

u = 35
Collatz = [u]
until u is 1
    if u%2 is 0
        u /= 2
    else
        u = 3*u+1
    Collatz.push u

Syr = {}
for n in [0...Collatz.length]
    Syr[n]=Collatz[n]
trierDansTableau Syr

dessineSuite Collatz, Collatz.length-1, 0, 200, 3, 'red'

	
0123456789101112130102030405060708090100110120130140150160170180190200

temps de vol

vol = (n) ->
    [u, temps] = [n, 0]
    until u is 1
        if u%2 is 0
            u /= 2
        else
            u = 3*u+1
        temps++
    temps

affiche (vol n for n in [2..20])
	

Algorithme d'Euclide

Pour calculer le pgcd de deux nombres a et b, on remplace a et b par b et r jusqu'à ce que r soit nul (r est le reste de la division de a par b).

version classique:

pgcd = (a,b) ->
    [x,y]=[a,b]
    while y>0
        [x,y]=[y,x%y]
    x

affiche pgcd 55,34
	

Version CoffeeScript:

pgcd = (x,y) ->
    while y>0
        [x,y]=[y,x%y]
    x

affiche pgcd 55,34
	

Calcul de racines carrées par l'algorithme de Heron

Pour calculer la racine carrée de 5 par l'algorithme de Heron, on remplace une valeur approchée a (par défaut), par sa moyenne avec 5/a (valeur approchée par excès); et on recommence jusqu'à ce que la différence entre les valeurs approchées par défaut et par excès soit devenue imperceptible.

Calcul de √(5)

[a,b]=[1,5]
until (abs b-a)<1e-16
    a=(a+b)/2
    b=5/a
affiche a
	
	

Représentation graphique

En plaçant les approximations successives de √(2) dans un tableau, on peut les représenter graphiquement, pour vérifier la convergence :

u=[1]
for n in [1..20]
    u[n]=(u[n-1]+2/u[n-1])/2

dessineSuite u, 20, 0, 2, 5, 'red'
01234567891011121314151617181900,10,20,30,40,50,60,70,80,911,11,21,31,41,51,61,71,81,9

L'algorithme de Heron, appliqué à de grands nombres décimaux, permet de calculer 1000 décimales de √(5) :

Big.DP = 1000
[a,b]=[Big(1),Big(5)]
for n in [1..12]
    a=a.plus(b).div(2)
    b=Big(5).div(a)

affiche a

Pour trouver combien de fois il a fallu boucler, une méthode simple est de faire des essais en affichant affiche a.minus b jusqu'à ce que l'affichage donne 0. La méthode suivante est plus rapide :

Big.DP = 1000
a = Big(5).sqrt()
affiche a

Cette rapidité permet d'ailleurs de faire une étude statistique sur les décimales de √(5), mais ceci est une autre histoire :

0123456789

Le graphique ayant été obtenu avec ce script :

Big.DP = 1000
a = Big(5).sqrt()
décimales = a.toString()[2..]
stats = new Sac []
for x in décimales
    stats.ajoute parseInt(x)
diagrammeBatonsTrie stats.effectifs, 150

Fractions

Une autre version, avec l'utilisation de fractions:

deux = new Fraction 2
cinq = new Fraction 5
[a,b]=[new Fraction(1),new Fraction(5)]
for n in [1..12]
    a=a.plus(b).sur(deux)
    b=cinq.sur(a)
    affiche a.toFloat()

Une légère modification fournit une suite d'approximations rationnelles de la racine de 5:

deux = new Fraction 2
cinq = new Fraction 5
[a,b]=[new Fraction(1),new Fraction(5)]
for n in [1..12]
    a=a.plus(b).sur(deux)
    b=cinq.sur(a)
    affiche a

De même, l'algorithme est aisément transposable aux nombres complexes:

deux = new Complexe 2
cinq = new Complexe 5
[a,b]=[new Complexe(1),new Complexe(5)]
until b.moins(a).module()<1e-8
    a=a.plus(b).sur(deux)
    b=cinq.sur(a)
affiche a
	

Fonction racine carrée

racineDe = (x) ->
    [a,b]=[1,x]
    until (abs b-a) < 1e-16
        a=(a+b)/2
        b=x/a
    a
    
affiche racineDe 5
	
	
Erreur d'approximation

La différence entre racineDe(x) et la "vraie" racine carrée de x dépend fortement de x :

racineDe = (x) ->
    [a,b]=[1,x]
    until (abs b-a) < 1e-5
        a=(a+b)/2
        b=x/a
    a
    
erreur = (x) -> racineDe(x) - racine(x)
	
dessineFonction erreur, 0.1, 20, 0, 1e-5, 'blue'
01234567891011121314151617181900,0000010,0000020,0000030,0000040,0000050,0000060,0000070,0000080,000009

Cribles (Eratosthène et autres)

Pour avoir les nombres premiers jusqu'à 400, on enlève de la liste des entiers allant de 2 à 200, tous ceux qui ne sont pas premiers... Jusqu'à ce qu'il ne reste plus rien à enlever...

Cet algorithme se traduit ainsi en CoffeeScript:

crible = [2..400]
for diviseur in [2..20]
    crible = (x for x in crible when x <= diviseur or x%diviseur isnt 0)
	

Ensuite, pour savoir si un nombre est premier, on peut faire un test d'appartenance :

affiche 127 in crible
	

Exercice

Un cycliste très maladroit tourne autour d'une maison encerclée de fleurs, numérotées de 1 à 100. Il zigzague de telle manière que, chaque fois qu'il évite une fleur, il écrase la suivante. Et à chaque tour de la maison, il évite une fleur encore intacte sur deux pour écraser la fleur intacte qui suit. Quel est le numéro de la dernière fleur restante ?

On peut résoudre cet exercice avec un crible :

fleurs = [1..100]
until fleurs.length is 1
    fleurs = (fleurs[n] for n in [0...fleurs.length] when n%2 is 1)
affiche fleurs

Problème de Freudenthal

Énoncé

On choisit au hasard deux entiers x et y strictement supérieurs à 1, x étant le plus petit des deux, et on donne à Sam leur somme qui est inférieure à 100, et à Polly leur produit. Après un temps de réflexion suffisamment long, le dialogue suivant se déroule entre Sam et Polly:

  • Polly: Je ne sais pas qui sont x et y.
  • Sam: Je savais que tu ne savais pas!
  • Polly: Alors je sais qui sont x et y.
  • Sam: Alors je sais aussi!

On demande quels sont ces deux nombres

Produits à partir des sommes

Pour la suite on aura besoin, à partir d'une somme (comme celles données à Sam), de la liste des produits de ses deux termes. Pour cela on construit une fonction prod

prod = (n) ->
    (x*(n-x) for x in [2..n-2]).unique()

Polly

Si Polly, au début, ne peut pas savoir quels sont les nombres, c'est qu'il y a au moins 2 façons d'obtenir le produit qu'on lui a donné. Pour obtenir la liste des produits possibles donnés à Polly, on peut donc faire ainsi :

produits = []
for y in [3...100]
    for x in [2...y]
        if x+y <= 100
            produits.push x*y
polly = (p for p in produits when (produits.compteLes p) >= 2)

Sam

Sam ne sachant pas non plus quels sont les nombres, en déduit que sa somme est celle de nombres qui sont tous dans la liste de Polly. Donc la liste des produits données à Sam est :

sam = (n for n in [5...100] when  (_.intersection prod(n), polly).length is prod(n).length)

La somme

L'algorithme suivant permet de savoir quelle est la somme donnée à Sam (un seul produit lui est associé) :

produits = _.flatten (prod(s) for s in sam)

doublons = (p for p in produits when (produits.compteLes p) > 1 )

for s in sam
    affiche "La somme #{s} a #{(_.difference prod(s), doublons).length} produits"

La variante suivante affecte une variable par cette somme :

produits = _.flatten (prod(s) for s in sam)

doublons = (p for p in produits when (produits.compteLes p) > 1 )

polly = (s for s in sam when (_.difference prod(s), doublons).length is 1)

somme = polly[0]
	

Le produit

Une fois qu'on a la somme, on a aussi le produit, avec

produit = (_.difference prod(somme), doublons)[0]
affiche produit
	

Conclusion

On peut trouver les deux nombres en bouclant avec un test:

solution = new Ensemble

for y in [3...100]
    for x in [2...y]
        if x+y is somme and x*y is produit
            solution.ajoute [x,y]


affiche solution

En résumé, le script complet qui donne la solution est assez rapide :

prod = (n) ->
    (x*(n-x) for x in [2..n-2]).unique()

produits = []
for y in [3...100]
    for x in [2...y]
        if x+y <= 100
            produits.push x*y
polly = (p for p in produits when (produits.compteLes p) >= 2)

sam = (n for n in [5...100] when  (_.intersection prod(n), polly).length is prod(n).length)

produits = _.flatten (prod(s) for s in sam)

doublons = (p for p in produits when (produits.compteLes p) > 1 )

polly = (s for s in sam when (_.difference prod(s), doublons).length is 1)

somme = polly[0]

produit = (_.difference prod(somme), doublons)[0]

solution = new Ensemble

for y in [3...100]
    for x in [2...y]
        if x+y is somme and x*y is produit
            solution.ajoute [x,y]


affiche solution

Tables trigonométriques

Table de sinus

au degré près:

arr3 = (x) -> arrondi(1000*x)/1000

affiche "|angle|sinus|"
affiche "|#{n}    |#{arr3 sinus n} |" for n in [0..90]
	

à la minute près:

	arr3 = (x) -> arrondi(1000*x)/1000

affiche "|angle|sinus|"
affiche "|#{arr3 n}    |#{arr3 sinus n} |" for n in [0..1] by 1/60
	

Remarque: On peut aussi faire

tableauValeurs sinus, [0..20]
	

Table de cosinus

arr3 = (x) -> arrondi(1000*x)/1000

affiche "|angle|cosinus|"
affiche "|#{n}    |#{arr3 cosinus n} |" for n in [0..90]	
	

Équations

Dichotomie

L'algorithme de dichotomie est efficace pour résoudre l'équation f(x)=0

f = (x) -> ln(x) - 1

[a,b]=[1,10]
until b-a < 0.000001
    m=(a+b)/2
    if f(m)<0
        a=m
    else
        b=m
    affiche [a,b]
	

Équations du second degré

Résoudre une équation, c'est donner la liste de ses solutions; pour le second degré, il y a 0, 1 ou 2 solutions selon le signe du discriminant Δ :

SecondDegré = (a,b,c) ->
    S=new Ensemble []
    Delta = b*b-4*a*c
    r = laRacineDe Delta
    if r >=0
        S.ajoute (-b-r)/(2*a)
        S.ajoute (-b+r)/(2*a)
    S

affiche SecondDegré 1, 2, 1
	

Suites géométriques

Combien de fois faut-il lancer un dé équilibré pour que la probabilité d'avoir un 6 dépasse 0,99 ?

La condition de sortie se traduisant par le fait que la probabilité de ne pas avoir de 6 est en-dessous de 0,01; et comme si les lancers sont indépendants, cette probabilité suit une suite géométrique de raison 5/6:

proba = 1
nombreDeLancers=1
until proba < 0.01
    proba *= 5/6
    nombreDeLancers++
affiche "En lançant #{nombreDeLancers} fois le dé, la probabilité d'avoir un 6 est #{1-proba}"
	

Représentation graphique

On construit une liste (ou tableau) des termes successifs de la suite, pour représenter graphiquement celle-ci. On remarque comme comme sa raison est inférieure à 1, la valeur maximale est 1; donc on choisit 0 pour yMin et 1 pour yMax :

u=[1]
for n in [1..20]
    u[n]=5/6*u[n-1]

dessineSuite u, 20, 0, 1, 5, 'red'
01234567891011121314151617181900,10,20,30,40,50,60,70,80,9

Briggs

Algorithme de Briggs pour calculer des logarithmes

log = (x) ->
    if x > 0
        [r,n]=[x,0]
        until 0.999999 < r < 1.000001
            n++
            r=racine r
        r -= 1
        r *= 2 for k in [1..n]
        r


affiche log 2.7182818
	

Erreur d'approximation

En calculant un logarithme par l'algorithme de Briggs, on commet une erreur d'approximation (la différence entre log(x) et ln(x)) qui dépend de x; sa représentation graphique permet de voir comment :

log = (x) ->
    if x > 0
        [r,n]=[x,0]
        until 0.999999 < r < 1.000001
            n++
            r=racine r
        r -= 1
        r *= 2 for k in [1..n]
        r

erreur = (x) -> log(x)-ln(x)

dessineFonction erreur, 0.1, 10, 0, 0.000002, 'blue'

	
01234567891001e-72e-73e-74e-75e-76e-77e-78e-79e-70,0000010,00000110,00000120,00000130,00000140,00000150,00000160,00000170,00000180,00000190,000002

Suite de Fibonacci

La suite Fn est définie par la relation de récurrence Fn+2=Fn+1+Fn. Si F0=F1=1, la suite est entièrement déterminée.

Version classique

[a,b]=[1,1]
for n in [1..10]
    [a,b]=[a+b,a]
    affiche "F(#{n})=#{b}"
	

Formule de Binet

nombreDor = (1 + racine 5)/2 
for n in [1..10]
    b = puissance nombreDor, n
    b /= racine 5
    b = arrondi b
    affiche "F(#{n})=#{b}"

Représentation graphique

Pour représenter graphiquement la suite de Fibonacci, on met ses termes successifs dans un tableau. Ce qui revient à les pousser l'un après l'autre dedans :

u=[1,1]
for n in [2..30]
    u[n]=u[n-2]+u[n-1]

dessineSuite u, 10, 0, 100, 5, 'red'
0123456789100102030405060708090100

Problème du Grand-Duc de Toscane

Lorsque Galilee lui donnait des cours, Cosme de Medicis, futur Grand-Duc de Toscane, lui a demandé comment ça se fait que lorsqu'on additionne les résultats de 3 dés, le 10 sort plus souvent que le 9, alors que

  • 9=1+2+6=1+3+5=1+4+4=2+2+5=2+3+4=3+3+3
  • 10=1+3+6=1+4+5=2+2+6=2+3+5=2+4+4=3+3+4
soit 6 manières dans chaque cas.

Simulation

stats = new Sac []
for n in [1..100000]
    stats.ajoute dé(6)+dé(6)+dé(6)
trierDansTableau stats.effectifs
diagrammeBatonsTrie stats.effectifs, 20000
3456789101112131415161718

Calcul des probabilités

loi = new Sac []
loi.ajoute(a+b+c) for a in [1..6] for b in [1..6] for c in [1..6]
d = puissance 6, 3
affiche "La probabilité d'avoir un 9 est #{loi.effectifs[9]/d}"
affiche "La probabilité d'avoir un 10 est #{loi.effectifs[10]/d}"
	

Problème du Chevalier de Méré

Dans une lettre à Blaise Pascal, le Chevalier de Méré lui a posé la question suivante: Comment se fait-il qu'en lançant 4 dés, on a plus d'une chance sur 2 d'avoir au moins un 6 ?

Simulation du lancer de 4 dés

gains=0
for n in [1..100000]
    jeu=(dé(6) for n in [1..4])
    if (x for x in jeu when x is 6).length
        gains++
affiche gains/100000

Calcul de la probabilité

gains=0
for a in [1..6]
    for b in [1..6]
        for c in [1..6]
            for d in [1..6]
                if a is 6 or b is 6 or c is 6 or d is 6
                    gains++
d = puissance 6,4
affiche gains/d

Avec une suite géométrique

Comme la probabilité de ne pas avoir de 6 en lançant un dé est 5/6, et par indépendance des lancers de dés, la probabilité de na pas avoir de 6 en lançant 4 dés est le quatrième terme d'une suite géométrique de premier terme 5/6 et de raison 5/6:

u = 1
(u *= 5/6 for n in [1..4])
affiche 1-u

jeu de Dada

Au jeu de Dada (ou des "petits chevaux") on lance un dé jusqu'à ce qu'on ait un 6. Alors seulement, le petit cheval peut "sortir de l'écurie" (on peut commencer à jouer)

Loi géométrique

La variable aléatoire "nombre de fois qu'on doit lancer le dé pour avoir un 6" suit une loi géométrique de paramètre 1/6. Pour la simuler:

X = 0
X++ until dé(6) is 6
affiche X

Pour jouer plusieurs fois aux petits chevaux, on peut refaire tout ça dans une boucle; on en profite pour faire un tableau d'effectifs:

stats = new Sac []
for n in [1..100]
    X = 0
    X++ until dé(6) is 6
    stats.ajoute X

trierDansTableau stats.effectifs
diagrammeBatonsTrie stats.effectifs, 20
0123456789101113141516181923

Loi géométrique tronquée

La probabilité que l'algorithme précédent s'arrête au bout d'un temps fini est 1. Néanmoins, on prend moins de risque à tronquer X, en remplaçant tous les X supérieurs à 20 par des 0:

stats = new Sac []
for n in [1..100]
    X = 0
    X++ until dé(6) is 6
    X = 0 if X > 20
    stats.ajoute X

trierDansTableau stats.effectifs
diagrammeBatonsTrie stats.effectifs, 20
	
012345678910111213141516

Poker avec 32 cartes

Tirer 5 cartes (une "main") dans un jeu de 32 cartes, c'est constituer un échantillon d'effectif 5 dans une population totale de 32. C'est donc la base de la statistique.

On vérifie que les 4 couleurs sont équiprobables :

couleur=['♦','♥','♠','♣']
urne = new Sac
urne.ajoute prendreAuHasardDans couleur for n in [1..1000]

mettreDansTableau urne.effectifs
diagrammeBatons urne.effectifs
	

Les 4 couleurs ont bien l'air équiréparties :

Tirage de 5 cartes

Tirage avec remise

Si on répète 5 fois l'expérience de tirer une carte, on risque d'avoir plusieurs fois la même carte (tirage avec remise). Mais ce risque est minime:

couleur=['♦','♥','♠','♣']
valeur=['1','7','8','9','10','V','D','R']
jeu=[]
for c in couleur
    for v in valeur
      jeu.push "#{v}#{c}"

main = tirageAvecRemise 5, jeu
affiche main
Tirage sans remise
couleur=['♦','♥','♠','♣']
valeur=['1','7','8','9','10','V','D','R']
jeu=[]
for c in couleur
    for v in valeur
      jeu.push "#{v}#{c}"

main = tirageSansRemise 5, jeu
affiche main

Cas particulier: Pour mélanger le jeu, on fait un tirage sans remise de 32 cartes :

couleur=['♦','♥','♠','♣']
valeur=['1','7','8','9','10','V','D','R']
jeu=[]
for c in couleur
    for v in valeur
      jeu.push "#{v}#{c}"

affiche mélangée jeu

Calcul de probabilités

Pour savoir quelle est la probabilité d'avoir un carré d'as,

  • le nombre de cas possibles est le nombre de choix de 5 cartes parmi 32
  • le nombre de cas favorables est le nombre de choix de la cinquième carte (les 4 autres sont des as) parmi 28 (celles qui restent une fois qu'on a enlevé les as) :

casPossibles = combinaison 32, 5
casFavorables = combinaison 28, 1

affiche casFavorables/casPossibles

loi binomiale

La loi B(10,0.7) est la loi du nombre de boules rouges dans un tirage (avec remise) dans une urne contenant 70 % de boules rouges.

Simulation

On peut utiliser deux sacs:

  • Le premier, urne, contient les boules rouges et bleues: C'est l'urne !
  • Le second, stats, contient les effectifs des nombres de boules rouges obtenues dans 100 tirages avec remise de 10 boules extraites du sac précédent.

urne = new Sac []
urne.ajouteFois 70, 'rouge'
urne.ajouteFois 30, 'bleu'

stats = new Sac []
stats.ajoute (urne.extraireAuHasard() for n in [1..10]).compteLes 'rouge' for n in [1..1000]

trierDansTableau stats.effectifs
diagrammeBatonsTrie stats.effectifs
	
345678910

Calcul de la loi binomiale

Pour afficher les valeurs prises par la loi binomiale de paramètres 10 et 0,7 (les probabilités), on peut faire

affiche (binomiale 10, 0.7, k for k in [0..10])

Intervalles de fluctuation

En tirant 200 boules (avec remise) dans une urne contenant 70 % de boules rouges, un intervalle de fluctuation à 95 % pour le nombre de boules rouges parmi les 200 s'obtient par

affiche IntFluctBinom 200, 0.7

Intervalles de confiance

Pour calculer un intervalle de confiance sur une proportion p, on utilise de l'algorithmique "de base" (pas de boucles): Affectations, entrées, sorties et éventuellement un test si on veut améliorer.

Version bac général

Cet algorithme simplifié est vu en Seconde et en Terminales L/ES et S

N = entre "Quelle est la taille de l'échantillon ?"
ns = entre "Combien de succès dans l'échantillon ?"
p = ns/N
borneInférieure = p-1/laRacineDe N
borneSupérieure = p+1/laRacineDe N
affiche "L'intervalle de confiance à 95 % est [#{borneInférieure};#{borneSupérieure}]"
	

Amélioration de l'algorithme

Deux problèmes peuvent se poser:

  1. La borne inférieure peut être négative; dans ce cas, on la remplace par 0.
  2. La borne supérieure peut être plus grande que 1; dans ce cas, on la tronque à 1.
L'algorithme amélioré peut s'écrire ainsi:
N = entre "Quelle est la taille de l'échantillon ?"
ns = entre "Combien de succès dans l'échantillon ?"
p = ns/N
borneInférieure = p-1/laRacineDe N
borneInférieure = 0 if borneInférieure < 0
borneSupérieure = p+1/laRacineDe N
borneSupérieure = 1 if borneSupérieure > 1
if N > 24
    affiche "L'intervalle de confiance à 95 % est [#{borneInférieure};#{borneSupérieure}]"
	

Version bac technologique

Cet algorithme, plus précis notamment lorsque la proportion de succès dans l'échantillon est proche de 0 ou de 1, est au programme des Terminales STL/STI2D et des BTS :

N = entre "Quelle est la taille de l'échantillon ?"
ns = entre "Combien de succès dans l'échantillon ?"
p = ns/N
q = 1-p
borneInférieure = p-1.96*racine(p*q/N)
borneSupérieure = p+1.96*racine(p*q/N)
affiche "L'intervalle de confiance à 95 % est [#{borneInférieure};#{borneSupérieure}]"
	

Version améliorée de la version bac technologique

Cette fois-ci, la formule n'est appliquée que si (avec les notations de l'algorithme), N×p et N×q sont tous les deux supérieurs à 5. De plus, l'échantillon doit être suffisamment grand (N≥25):

N = entre "Quelle est la taille de l'échantillon ?"
ns = entre "Combien de succès dans l'échantillon ?"
p = ns/N
q = 1-p
borneInférieure = p-1.96*racine(p*q/N)
borneSupérieure = p+1.96*racine(p*q/N)
if N > 25 and N*p > 5 and N*q > 5
 affiche "L'intervalle de confiance à 95 % est [#{borneInférieure};#{borneSupérieure}]"
else
    affiche "Il n'est pas raisonnable de calculer un intervalle de confiance dans ces conditions"

Graphisme

Tableau de fils

for n in [1..100]
    x1 = 100+4*n
    y1 = 440
    x2 = 100
    y2 = 40+4*n
    dessineSegment x1, y1, x2, y2, 'black'

étoile

sommets = []
for n in [0...17]
    angle = n*8/17*360
    sommets.push [320+200*cosinus(angle),240+200*sinus(angle)]
dessinePolygone sommets, 'blue', 'green'

Néphroïde

N = 24
for n in [0...N]
    angle = n/N*360
    cx = 320 + 100*cosinus(angle)
    cy = 240 + 100*sinus(angle)
    dessineCercle cx, cy, abs(100*sinus(angle)), 'blue'

Triangle de Sierpinski

Version itérative (nuage de points)
A = new Point 50, 240
B = new Point 600, 20
C = new Point 600, 460
M = new Point dé(640), dé(480)
for n in [1..1000]
    switch dé(3)
        when 1
            M = M.milieu A
            dessineCercle M.x, M.y, 2, 'red'
        when 2
            M = M.milieu B
            dessineCercle M.x, M.y, 2, 'blue'
        else
            M = M.milieu C
            dessineCercle M.x, M.y, 2, 'green'
Version récursive
A = new Point 50, 240
B = new Point 600, 20
C = new Point 600, 460
gasket = (a,b,c,N) ->
    if N < 1
        dessinePolygone [[a.x,a.y],[b.x,b.y],[c.x,c.y]], 'black', 'none'
    else
        gasket(a, a.milieu(b), a.milieu(c), N-1)
        gasket(b, b.milieu(c), b.milieu(a), N-1)
        gasket(c, c.milieu(a), c.milieu(b), N-1)

gasket A, B, C, 6

Nuage de points gaussiens

L'algorithme de Box-Muler permet de simuler des variables aléatoires normales de paramètres respectifs (320;40) et (240;40) simultanément ce qui définit un nuage de 50 points, ici représenté en rouge, accompagné de son diagramme de Voronoi (le diagramme de Voronoi de deux points est leur médiatrice) :

nuage = []
for n in [1..50]
    T = alea()*2*pi
    R = -80*ln(alea())
    nuage.push [320+R*cos(T),240+R*sin(T)]
dessineVoronoi nuage, 'darkGreen', 0.5

Géométrie dans l'espace

Sujet du bac S Liban 2014

Pour chacune des propositions suivantes, indiquer si elle est vraie ou fausse et justifier chaque réponse. Une réponse non justifiée ne sera pas prise en compte.

On se place dans l’espace muni d’un repère orthonormé. On considère le plan P d’équation x − y + 3z + 1 = 0 et la droite D dont une représentation paramétrique est

  • x = 2t
  • y = 1+t
  • z = -5+3t

On donne les points A(1 ; 1; 0), B(3 ; 0 ; −1) et C (7 ; 1 ; −2).

Pour entrer les définitions, on découvre rapidement que le plan P passe par le point de coordonnées (0,-1,0) et on lit sur son équation les coordonnées d'un vecteur normal de P; de même, on lit dans la représentation paramétrique de D les coordonnées (0,1,-5) d'un point et les coordonnées (2,1,3) d'un vecteur directeur. On utilise les notations $V pour les points (qui sont traités comme des vecteurs), $L pour les droites (comme "line") et $P pour les plans:

P = $P [0,1,0], [1,-1,3]
D = $L [0,1,-5], [2,1,3]
A = $V [1,1,0]
B = $V [3,0,-1]
C = $V [7,1,-2]

Proposition 1 :

Une représentation paramétrique de la droite (AB) est
  • x = 5-2t
  • y = -1+t
  • z = -2+t

Pour vérifier ceci, on crée une droite qu'on appelle AB à partir de la représentation paramétrique puis on vérifie qu'elle passe par A et par B:

P = $P [0,1,0], [1,-1,3]
D = $L [0,1,-5], [2,1,3]
A = $V [1,1,0]
B = $V [3,0,-1]
C = $V [7,1,-2]
AB = $L [5,-1,-2], [-2,1,1]
affiche AB.contains A
affiche AB.contains B

Comme la droite "AB" passe par A et par B, c'est bien la droite (AB)...

Proposition 2 :

Les droites D et (AB) sont orthogonales.

On va vérifier que les vecteurs directeurs des deux droites sont perpendiculaires :

P = $P [0,1,0], [1,-1,3]
D = $L [0,1,-5], [2,1,3]
A = $V [1,1,0]
B = $V [3,0,-1]
C = $V [7,1,-2]
AB = $L [5,-1,-2], [-2,1,1]
affiche D.direction.isPerpendicularTo AB.direction

Proposition 3 :

Les droites D et (AB) sont coplanaires.

On regarde si elles sont parallèles (auquel cas elles sont coplanaires, mais on a vu dans la proposition 2 qu'elles ne sont pas parallèles), et si elles sont sécantes, auquel cas elles sont coplanaires :

P = $P [0,1,0], [1,-1,3]
D = $L [0,1,-5], [2,1,3]
A = $V [1,1,0]
B = $V [3,0,-1]
C = $V [7,1,-2]
AB = $L [5,-1,-2], [-2,1,1]
affiche D.isParallelTo AB
affiche D.intersects AB

Proposition 4 :

La droite D coupe le plan P au point E de coordonnées (8; −3; −4).

On va d'abord vérifier que D coupe P :

P = $P [0,1,0], [1,-1,3]
D = $L [0,1,-5], [2,1,3]
A = $V [1,1,0]
B = $V [3,0,-1]
C = $V [7,1,-2]
AB = $L [5,-1,-2], [-2,1,1]
affiche D.intersects P

Maintenant que c'est fait, on va chercher où ils se coupent :

P = $P [0,1,0], [1,-1,3]
D = $L [0,1,-5], [2,1,3]
A = $V [1,1,0]
B = $V [3,0,-1]
C = $V [7,1,-2]
AB = $L [5,-1,-2], [-2,1,1]
affiche ((D.intersectionWith P).map (x)-> arrOdg x, 6).inspect()

Apparemment, D et P se coupent mais pas en E, les coordonnées du point d'intersection étant (3, 2.5, -0.5). On va vérifier que, si P passe par E, ce n'est pas le cas de D :

P = $P [0,1,0], [1,-1,3]
D = $L [0,1,-5], [2,1,3]
A = $V [1,1,0]
B = $V [3,0,-1]
C = $V [7,1,-2]
AB = $L [5,-1,-2], [-2,1,1]
E = $V [8,-3,-4]
affiche D.contains E
affiche P.contains E

D'ailleurs, on peut avoir les coordonnées (approximatives) du point de D le plus proche de E :

P = $P [0,1,0], [1,-1,3]
D = $L [0,1,-5], [2,1,3]
A = $V [1,1,0]
B = $V [3,0,-1]
C = $V [7,1,-2]
AB = $L [5,-1,-2], [-2,1,1]
E = $V [8,-3,-4]
affiche (D.pointClosestTo E).inspect()

Proposition 5 :

Les plans P et (ABC ) sont parallèles.

On définit le plan (ABC) par un de ses points (le point A) et deux de ses vecteurs, ceux allant de A respectivement à B et à C. Puis on regarde si ce plan est parallèle à P :

P = $P [0,1,0], [1,-1,3]
D = $L [0,1,-5], [2,1,3]
A = $V [1,1,0]
B = $V [3,0,-1]
C = $V [7,1,-2]
ABC = $P A, B.subtract A, C.subtract A
affiche P.isParallelTo ABC

On peut aussi vérifier que les trois points A, B et C sont à la même distance de P :

P = $P [0,1,0], [1,-1,3]
D = $L [0,1,-5], [2,1,3]
A = $V [1,1,0]
B = $V [3,0,-1]
C = $V [7,1,-2]
ABC = $P A, B.subtract A, C.subtract A
affiche (P.distanceFrom point for point in [A,B,C])

Bien bien bien, ils ne sont pas parallèles. Mais alors, dans ce cas, ils sont sécants ! Et puisqu'ils sont sécants, leur intersection est une droite dI. On peut obtenir un point et un vecteur directeur de cette droite :

P = $P [0,1,0], [1,-1,3]
D = $L [0,1,-5], [2,1,3]
A = $V [1,1,0]
B = $V [3,0,-1]
C = $V [7,1,-2]
ABC = $P A, B.subtract A, C.subtract A
dI = P.intersectionWith ABC
affiche [dI.anchor.inspect(), dI.direction.x(1).inspect()]

On parle de récursivité lorsqu'un algorithme fait appel à lui-même.

Récursivité

Définition récursive de la puissance

La définition revient à dire que

  • a0=1
  • an=a×an-1 sinon

puissRec = (base, exposant) ->
    if exposant is 0
        1
    else
        base*puissRec base, exposant-1

affiche puissRec 2, 8

Définition récursive de la factorielle

La factorielle de 0 est définie comme étant 1; sinon, n! = n × (n-1)!

factorielleRec = (n) ->
    if n is 0
        1
    else
        n*factorielleRec n-1

affiche factorielleRec 10

Jeu des tours de Hanoi

Le jeu des tours de Hanoï, inventé par Édouard Lucas, comprend trois "tours", sur lesquelles sont empilés des cylindres (ou "disques" épais) de taille décroissante, et le but du jeu est d'amener les cylindres de la tour "source" vers la tour "destination" (à l'aide d'une tour "intermédiaire"), en respectant les règles suivantes:

  • On ne déplace qu'un disque à la fois.
  • Déplacer un disque consiste à l'extraire du sommet d'une tour (le disque le plus haut) et le placer au sommet d'une autre tour.
  • On ne place jamais de disque au-dessus d'un disque plus petit que lui.
Par exemple, ci-dessous, on doit faire passer les trois disques de la tour de gauche à celle de droite, en utilisant la tour du milieu comme tour intermédiaire. Mettre le petit disque au milieu puis le disque suivant est interdit puisque le disque moyen est plus grand que le petit et ne doit pas être superposé à lui.

Une solution de ce jeu peut se décrire récursivement, avec l'algorithme suivant:

  • On joue aux tours de Hanoï avec les n-1 disques de la tour de gauche, en les empilant sur la tour du milieu, et en utilisant la tour de droite comme tour intermédiaire.
  • Alors il ne reste que le disque le plus grand à déplacer de la tour de gauche vers la tour de droite (qui est alors vide)
  • Puis on joue au jeu des tours de Hanoï entre les tours du milieu et de droite (on empile les n-1 disques sur le plus grand qui est passé à droite), en utilisant la tour de gauche comme tour intermédiaire.
L'algorithme se traduit ainsi:

hanoi = (n, source, interm, destination) ->
    if n is 1
        affiche "Déplacer de #{source} vers #{destination}"
    else
        hanoi n-1, source, destination, interm
        hanoi 1, source, interm, destination
        hanoi n-1, interm, source, destination

hanoi 3, "gauche", "milieu", "droite"