{"cells": [{"cell_type": "markdown", "metadata": {}, "source": ["# Algo - distance d'\u00e9diction\n", "\n", "La distance d'\u00e9dition ou distance de [Levenshtein](https://en.wikipedia.org/wiki/Levenshtein_distance) permet de calculer une distance entre deux mots et par extension entre deux s\u00e9quences."]}, {"cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [{"data": {"text/html": ["
run previous cell, wait for 2 seconds
\n", ""], "text/plain": [""]}, "execution_count": 2, "metadata": {}, "output_type": "execute_result"}], "source": ["from jyquickhelper import add_notebook_menu\n", "add_notebook_menu()"]}, {"cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": ["%matplotlib inline"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Enonc\u00e9\n"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Q1 : Distance simple entre deux mots de m\u00eame longueur\n", "\n", "Une distance entre deux mots... c'est plus simple si les deux mots ont la m\u00eame longueur, on calcule le nombre de diff\u00e9rences."]}, {"cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": []}, {"cell_type": "markdown", "metadata": {}, "source": ["### Q2 : Distance simple entre deux mots de longueur diff\u00e9rente\n", "\n", "On construit cette distance comme la diff\u00e9rence des longueurs + la distance entre le mot le plus court et toutes les sous-s\u00e9quences de m\u00eame longueur issues de la cha\u00eene la plus longue."]}, {"cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": []}, {"cell_type": "markdown", "metadata": {}, "source": ["### Q3 : Distance alambiqu\u00e9e...\n", "\n", "Cette fois-ci, on coupe chacun des deux mots en deux, au hasard. On applique la distance pr\u00e9c\u00e9dente sur chacun des deux tron\u00e7ons. On fait la somme. Il ne reste plus qu'\u00e0 minimiser cette somme sur l'ensemble des coupures possibles."]}, {"cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": []}, {"cell_type": "markdown", "metadata": {}, "source": ["### Q4 : impl\u00e9menter l'algorithme de la page wikipedia\n", "\n", "[Levenshtein](https://fr.wikipedia.org/wiki/Distance_de_Levenshtein)"]}, {"cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": []}, {"cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": []}, {"cell_type": "markdown", "metadata": {}, "source": ["## R\u00e9ponses"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Q1 : Distance simple entre deux mots de m\u00eame longueur\n", "\n", "Une distance entre deux mots... c'est plus simple si les deux mots ont la m\u00eame longueur, on calcule le nombre de diff\u00e9rences."]}, {"cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [{"data": {"text/plain": ["2"]}, "execution_count": 9, "metadata": {}, "output_type": "execute_result"}], "source": ["def distance_meme_longueur(m1, m2):\n", " if len(m1) != len(m2):\n", " raise ValueError(\"m1 et m2 sont de longueurs diff\u00e9rentes\")\n", " d = 0\n", " for c1, c2 in zip(m1, m2):\n", " if c1 != c2:\n", " d += 1\n", " return d\n", "\n", "distance_meme_longueur('abcef', 'abcde')"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On v\u00e9rifie que la fonctionne jette bien une exception lorsque les cha\u00eenes de caract\u00e8res sont de longueurs diff\u00e9rentes."]}, {"cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["m1 et m2 sont de longueurs diff\u00e9rentes\n"]}], "source": ["try:\n", " distance_meme_longueur('a', 'bb')\n", "except Exception as e:\n", " print(e)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Q2 : Distance simple entre deux mots de longueur diff\u00e9rente\n", "\n", "On construit cette distance comme la diff\u00e9rence des longueurs + la distance entre le mot le plus court et toutes les sous-s\u00e9quences de m\u00eame longueur issues de la cha\u00eene la plus longue."]}, {"cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [{"data": {"text/plain": ["(0, 1, 3)"]}, "execution_count": 11, "metadata": {}, "output_type": "execute_result"}], "source": ["def distance(m1, m2):\n", " if len(m1) < len(m2):\n", " return distance(m2, m1)\n", " if len(m1) == len(m2):\n", " return distance_meme_longueur(m1, m2)\n", " d = len(m1) - len(m2)\n", " mind = [distance_meme_longueur(m1[i:i+len(m2)], m2)\n", " for i in range(0, d)]\n", " return d + min(mind)\n", " \n", "distance('aa', 'aa'), distance('aa', 'aaa'), distance('aa', 'bbb')"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Q3 : Distance alambiqu\u00e9e...\n", "\n", "Cette fois-ci, on coupe chacun des deux mots en deux, au hasard. On applique la distance pr\u00e9c\u00e9dente sur chacun des deux tron\u00e7ons. On fait la somme. Il ne reste plus qu'\u00e0 minimiser cette somme sur l'ensemble des coupures possibles."]}, {"cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [{"data": {"text/plain": ["(2, 1, 1.5, 0)"]}, "execution_count": 12, "metadata": {}, "output_type": "execute_result"}], "source": ["def distance_alambiquee(m1, m2):\n", " mini = None\n", " for i in range(len(m1)):\n", " for j in range(len(m2)):\n", " d = distance(m1[:i], m2[:j]) + distance(m1[i:], m2[j:])\n", " if mini is None or d < mini:\n", " mini = d\n", " # Option verlan\n", " d = distance(m1[:i], m2[j:]) + distance(m1[i:], m2[:j]) + 0.5\n", " if d < mini:\n", " mini = d\n", " return mini\n", "\n", "(distance('abc', 'ac'),\n", " distance_alambiquee('abc', 'ac'),\n", " distance_alambiquee('abc', 'ca'),\n", " distance_alambiquee('b', 'b'))"]}, {"cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": []}, {"cell_type": "markdown", "metadata": {}, "source": ["### Q4 : impl\u00e9menter l'algorithme de la page wikipedia\n", "\n", "[Levenshtein](https://fr.wikipedia.org/wiki/Distance_de_Levenshtein)\n", "\n", "La premi\u00e8re impl\u00e9mentation reprend l'algorithme d\u00e9crit sur la page wikip\u00e9dia."]}, {"cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [{"data": {"text/plain": ["1"]}, "execution_count": 14, "metadata": {}, "output_type": "execute_result"}], "source": ["def levenstein(m1, m2):\n", " d = {}\n", " d[0,0] = 0\n", " for i in range(len(m1) + 1):\n", " d[i, 0] = i\n", " for j in range(len(m2) + 1):\n", " d[0, j] = j\n", " for i in range(1, len(m1) + 1):\n", " for j in range(1, len(m2) + 1):\n", " d[i, j] = min(d[i-1,j] +1, d[i,j-1] +1,\n", " d[i-1, j-1] + (1 if m1[i-1] != m2[j-1] else 0))\n", " return d[len(m1), len(m2)]\n", "\n", "levenstein('abc', 'ac')"]}, {"cell_type": "markdown", "metadata": {}, "source": ["La seconde version est plus alambiqu\u00e9e, elle modifie l\u00e9g\u00e8rement la version alambiqu\u00e9e. C'est une version r\u00e9cursive."]}, {"cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [{"data": {"text/plain": ["(3, 2, 2)"]}, "execution_count": 15, "metadata": {}, "output_type": "execute_result"}], "source": ["def distance_alambiquee_levenstein(m1, m2):\n", " mini = None\n", " for i in range(len(m1)):\n", " for j in range(len(m2)):\n", " if i > 0 and i < len(m1) - 1 and j > 0 and j < len(m2) - 1:\n", " d1 = distance_alambiquee_levenstein(m1[:i], m2[:j])\n", " d2 = distance_alambiquee_levenstein(m1[i:], m2[j:])\n", " else:\n", " d1 = distance(m1[:i], m2[:j])\n", " d2 = distance(m1[i:], m2[j:])\n", " d = d1 + d2\n", " if mini is None or d < mini:\n", " mini = d\n", " return mini\n", "\n", "(distance_alambiquee('abcde', 'ace'),\n", " levenstein('abcde', 'ace'),\n", " distance_alambiquee_levenstein('abcde', 'ace'))"]}, {"cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": []}], "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, "language_info": {"codemirror_mode": {"name": "ipython", "version": 3}, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2"}}, "nbformat": 4, "nbformat_minor": 2}