site2wouf.fr : Dénombrer des triangles

Ce qui est affirmé sans preuve peut être nié sans preuve.

Euclide de Mégare (Nouveau design!)

Partager :

Facebook X (Twitter) LinkedIn Email WhatsApp

Combien comptez-vous de vrais triangles dans cette figure ?

Figure géométrique complexe avec 10 points (A,M,U,O,E,G,T,S,R,I) - 37 triangles à dénombrer

C'est une activité ancienne et toujours à la mode. Parfois proposée par le prof de Mathématiques, ou en guise d'énigme sur internet, le dénombrement de triangles (ou quadrilatères,...) n'est pas aussi facile que l'on pense ! Sans technique ni méthode, c'est peine perdue !

Pour vous aider à résoudre ce type d'énigme, j'ai créé deux outils complémentaires :

Les deux outils utilisent le même algorithme optimisé avec le module itertools.combinations pour garantir des résultats identiques et performants.

Mais avant de les utiliser, commençons par un exemple simple pour comprendre le principe...

🔺 Dénombrement de Triangles

Application interactive pour compter les triangles dans une figure géométrique

📚 Comment ça fonctionne ?

Cette application compte automatiquement le nombre de vrais triangles (non plats) dans une figure géométrique. Vous devez fournir deux informations :

  • Le graphe : liste des segments tracés
  • Les alignements (optionnel) : liste des points alignés

Exemple : Quadrilatère avec diagonales

Quadrilatère ABCD avec diagonales

Figure : Quadrilatère ABCD avec ses diagonales [AC] et [BD] sécantes en E

Fichier graphe.txt :
ABCDE
BACDE
CABDE
DABCE
EABCD

Chaque ligne : le 1er point est relié à tous les suivants
Ex: "ABCDE" → segments [AB], [AC], [AD], [AE]

Fichier alignement.txt :
AEC
BED

Points alignés (triangles plats à exclure)
A, E et C sont sur la même droite (diagonale AC)
B, E et D sont sur la même droite (diagonale BD)

✓ Résultat attendu : 8 triangles
  • 4 petits : AEB, BEC, CED, DEA
  • 4 grands : ABC, BCD, CDA, DAB

🚀 Application Interactive

Format : chaque ligne contient un point suivi de tous les points auxquels il est relié. Exemple : "ABCD" signifie que A est relié à B, C et D.
Format : chaque ligne contient les points alignés. Exemple : "AEC" signifie que A, E et C sont sur la même droite.

Python ! AU SECOURS !

Comme souvent plutôt que de passer un certain temps à compter, avec concentration et méthode, je préfère coder quelques lignes ! Ceci a au moins l'avantage d'être réutilisable

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Application de dénombrement de triangles dans une figure géométrique
Auteur : Laurent Petitprez (Wouf) - site2wouf.fr
Date : 2025

Cette application compte le nombre de "vrais" triangles (non plats)
tracés dans une figure géométrique donnée.

Elle utilise deux fichiers d'entrée :
- graphe.txt : définit les segments tracés
- alignement.txt : définit les points alignés (triangles plats à exclure)
"""

import sys
from itertools import combinations


def lire_graphe():
    """
    Lit le fichier graphe.txt et renvoie les lignes non vides.

    Format attendu : chaque ligne contient un point suivi de tous les points
    auxquels il est relié. Par exemple, "ABCD" signifie que le point A est
    relié aux points B, C et D (segments [AB], [AC] et [AD]).

    Returns:
        list: Liste des lignes du fichier graphe.txt

    Raises:
        SystemExit: Si le fichier est introuvable
    """
    try:
        with open("graphe.txt") as fichier:
            lignes = fichier.read().split()
            return lignes
    except FileNotFoundError:
        print("❌ Erreur : Le fichier graphe.txt est introuvable")
        sys.exit(1)


def lire_alignements():
    """
    Lit le fichier alignement.txt et renvoie les lignes non vides.

    Format attendu : chaque ligne contient des points alignés.
    Par exemple, "AEC" signifie que A, E et C sont sur la même droite.

    Returns:
        list: Liste des lignes du fichier alignement.txt, ou liste vide si absent
    """
    try:
        with open("alignement.txt") as fichier:
            lignes = fichier.read().split()
            return lignes
    except FileNotFoundError:
        # Le fichier alignement.txt est optionnel
        return []


def extraire_segments(lignes_graphe):
    """
    Extrait tous les segments à partir des lignes du graphe.

    Chaque segment est représenté par un set de 2 points (pour éviter les doublons
    comme {A,B} et {B,A} qui représentent le même segment).

    Args:
        lignes_graphe (list): Lignes du fichier graphe.txt

    Returns:
        list: Liste de sets représentant les segments (ex: [{A,B}, {A,C}, ...])

    Raises:
        SystemExit: Si une ligne du graphe est invalide (moins de 2 caractères)
    """
    segments = []

    # Filtrer les lignes vides
    lignes_valides = [ligne for ligne in lignes_graphe if len(ligne) > 0]

    for ligne in lignes_valides:
        if len(ligne) < 2:
            print(f"❌ Erreur : Ligne invalide dans graphe.txt : '{ligne}'")
            print("   Chaque ligne doit contenir au moins 2 points")
            sys.exit(1)

        # Le premier caractère est le point de départ
        point_depart = ligne[0]

        # Chaque caractère suivant représente une connexion
        for point_arrivee in ligne[1:]:
            segment = set([point_depart, point_arrivee])

            # Éviter les doublons (un segment {A,B} = {B,A})
            if segment not in segments:
                segments.append(segment)

    return segments


def extraire_points(segments):
    """
    Extrait tous les points uniques à partir de la liste des segments.

    Args:
        segments (list): Liste des segments (sets de 2 points)

    Returns:
        list: Liste triée des points uniques (ex: ['A', 'B', 'C', 'D', 'E'])
    """
    points = set()

    for segment in segments:
        for point in segment:
            points.add(point)

    return sorted(points)


def identifier_triangles_plats(lignes_alignements):
    """
    Identifie tous les triangles plats (3 points alignés).

    À partir des lignes d'alignement, génère tous les triplets possibles
    de points alignés. Ces triplets forment des "triangles plats" qui doivent
    être exclus du décompte final.

    Args:
        lignes_alignements (list): Lignes du fichier alignement.txt

    Returns:
        set: Ensemble de frozensets représentant les triangles plats
             (ex: {frozenset({'A','E','C'}), frozenset({'B','E','D'})})
    """
    triangles_plats = set()

    for ligne in lignes_alignements:
        # Extraire les points uniques de la ligne
        points_alignes = list(set(ligne))

        # Ignorer les lignes avec moins de 3 points
        if len(points_alignes) < 3:
            continue

        # Générer tous les triplets possibles de ces points alignés
        # Utilisation de frozenset pour permettre l'ajout dans un set
        for triplet in combinations(points_alignes, 3):
            triangles_plats.add(frozenset(triplet))

    return triangles_plats


def compter_triangles(points, segments, triangles_plats):
    """
    Compte le nombre de vrais triangles (non plats) dans la figure.

    Algorithme :
    1. Générer tous les triplets possibles de 3 points (combinaisons)
    2. Pour chaque triplet, vérifier que les 3 côtés existent
    3. Exclure les triangles plats (points alignés)

    Args:
        points (list): Liste de tous les points
        segments (list): Liste de tous les segments
        triangles_plats (set): Ensemble des triangles plats à exclure

    Returns:
        list: Liste des triangles valides (chaque triangle est un tuple de 3 points)
    """
    triangles_valides = []

    # Générer tous les triplets de 3 points parmi tous les points
    # Utilisation de combinations pour éviter les doublons et optimiser
    for triplet in combinations(points, 3):
        p1, p2, p3 = triplet

        # Créer les 3 côtés potentiels du triangle
        cote1 = set([p1, p2])
        cote2 = set([p1, p3])
        cote3 = set([p2, p3])

        # Vérifier que les 3 côtés existent dans la liste des segments
        if cote1 in segments and cote2 in segments and cote3 in segments:
            # Vérifier que ce n'est pas un triangle plat
            if frozenset(triplet) not in triangles_plats:
                triangles_valides.append(triplet)

    return triangles_valides


def afficher_resultats(points, segments, alignements, triangles):
    """
    Affiche les résultats de l'analyse de manière lisible.

    Args:
        points (list): Liste des points
        segments (list): Liste des segments
        alignements (list): Liste des lignes d'alignement
        triangles (list): Liste des triangles trouvés
    """
    print("\n" + "=" * 60)
    print("📊 RÉSULTATS DE L'ANALYSE")
    print("=" * 60)

    # Afficher les points
    print(f"\n📍 Points : {len(points)}")
    print(f"   {', '.join(points)}")

    # Afficher les segments
    segments_str = [f"[{''.join(sorted(seg))}]" for seg in sorted(segments)]
    print(f"\n📏 Segments : {len(segments)}")
    print(f"   {', '.join(segments_str)}")

    # Afficher les alignements si présents
    if alignements:
        print(f"\n⚠️  Alignements déclarés : {len(alignements)}")
        for alignement in alignements:
            print(f"   {alignement}")

    # Afficher le nombre de triangles
    print(f"\n🔺 Nombre de vrais triangles : {len(triangles)}")

    # Afficher la liste des triangles
    if triangles:
        print("\n📋 Liste des triangles :")
        triangles_tries = sorted([''.join(sorted(t)) for t in triangles])
        print(f"   {' '.join(triangles_tries)}")
    else:
        print("   Aucun triangle trouvé")

    print("\n" + "=" * 60 + "\n")


def main():
    """
    Fonction principale de l'application.
    """
    print("\n🔍 Dénombrement de triangles - Version moderne")
    print("   Application par Laurent Petitprez (site2wouf.fr)")
    print()

    # Étape 1 : Lire les fichiers d'entrée
    lignes_graphe = lire_graphe()
    lignes_alignements = lire_alignements()

    # Étape 2 : Extraire les segments et les points
    segments = extraire_segments(lignes_graphe)
    points = extraire_points(segments)

    # Étape 3 : Identifier les triangles plats
    triangles_plats = identifier_triangles_plats(lignes_alignements)

    # Étape 4 : Compter les triangles valides
    triangles = compter_triangles(points, segments, triangles_plats)

    # Étape 5 : Afficher les résultats
    afficher_resultats(points, segments, lignes_alignements, triangles)


if __name__ == "__main__":
    main()

Mode d'emploi

Cette application compte le nombres de (vrais) triangles tracés dans une figure donnée.

Elle prend en compte le fichier graphe.txt qui contient des lignes de la forme ABCD pour signifier que les segment [AB] [AC] et [AD] sont tracés et éventuellement alignement.txt qui contient des lignes de la forme ABCD pour signifier que A,B,C et D sont alignés.

Revenons aux exemples.

1. Le quadrilatère et ses diagonales

Quadrilatère ABCD avec diagonales AC et BD sécantes en E - exemple pédagogique pour compter 8 triangles

Figure : Quadrilatère ABCD avec ses diagonales [AC] et [BD] sécantes en E

Après avoir construit les fichiers graphe.txt et alignements.txt qui vont bien, Python nous livre son résultat :

Résultats de l'application nbtri.py

🔍 Dénombrement de triangles - Version moderne Application par Laurent Petitprez (site2wouf.fr)

2. La figure complexe du départ.

Figure géométrique complexe avec 10 points (A,M,U,O,E,G,T,S,R,I) - énigme avec 37 triangles à dénombrer

Figure : la figure qui est à l'origine de cette page.

le fichier graphe.txt :

AMUOEG
GAUMOTSREI
IGROME
EIRGOUATMS
SETOGM
MSTEORIUGA
TESMOG
RGIEOM
UAMOEG
OMUAGRIETS

le fichier alignement.txt :

MORI
STOG
EOUA
MTE
MUG
ERG

Et un extrait de l'analyse de notre ami Python qui vous donne enfin la réponse !

🔺 Nombre de vrais triangles : 37

📋 Liste des triangles :
AEG AEM AGM AGO AGU AMO AMU EGI EGM EGO
EGS EGT EGU EIM EIO EIR EMO EMR EMS EMU
EOR EOS EOT EST GIM GIO GIR GMO GMR GMS
GMT GOR GOU MOSMOT MOU MST

Je vous laisse vous convaincre qu'on a les mêmes résultats en utilisant l'application interactive! 😉
Pensez à copier le contenu des fichiers .txt.

📦 Autres ressources : Python apprendre par l'exemple

🛠️ Exemples sur site2wouf.fr

📖 Blog

📥 Téléchargements

// Remarques, codes, note de version etc...

Mon travail est sous licence Creative commons.

La version actuelle du code Python est la 3.0.0 (30/01/2026)

La version actuelle du code JS est la 1.0.0 (beta) (30/01/2026)

N'hésitez pas à me contacter si vous detectez la moindre imperfection, ou si vous imaginez une amélioration potentielle !

Open source et gratuité n'empêchent ni les dons ni les remerciements 😉
Un euro ou deux pour m'aider à payer le serveur ? 💙 Faire un don sur PayPal