Recherche hiérarchique dans un index

Si nous cherchons à détecter la présence de n éléments dans les entrées d’un index, ce qui peut se traduire par une recherche de chaines dans tous les nœuds qui constituent une hiérarchie. Par exemple filtrer tous les items qui incluent 'usr' et 'bin', ce qui va faire ressortir les arborescences qui sont sous /usr/bin ou /bin/usr, /bin, /usr, /etc/bin … suivant que l’on soit en mode AND (les deux premiers) ou OR (les autres).
Mais nous pouvons aussi nous intéresser à une recherche directionnalisée, qui ne va sélectionner les arguments que dans un sens donné. Par exemple, à partir de [tag1, tag2, tag3], la recherche ne matchera (égalité ou sous-chaine) que si le nœud  tag1 est présent dans l’index. Si c’est le cas, il faudra qu’il inclue le sous nœud tag2 et si celui ci existe, il faudra qu’à son tour il inclue le sous(sous) nœud tag3 (s’il existe). Concrètement, nous aurons les moyens de discriminer /usr/bin vis à vis de /bin/usr, par exemple.

1. Trois modes et trois options

La fonction index_search_include du module index_incude.py se charge de faire ce type de recherches. Comme les autres fonctions de l’écosystème index_, elle utilise une liste de tags pour spécifier la recherche, un index (de répertoires ou complet: répertoires et fichiers) mais ne prends en compte que le format dirmatrix pour aller plus vite. Elle ne retourne qu’une liste python, mais celle ci peut être convertie au format dirmatrix ou simple, au moyen de fonctions de conversion (par exemple index_converter). Elle n’utilise que trois modes  (128, 32, 16) : OR-EQUAL-STRICT, OR-EQUAL-NOT_STRICT et OR-FIND-NOT_STRICT, ce qui permet de faire des recherches en tenant compte de la casse, en mode sous-chaine ou égalité. Elle utilise également trois directions '>'|'='|'>' de recherche spécifiées dans l’argument opmode. Une séquence de test typique, qui ferait une recherche en combinant les répertoires api et test (tags=['api', 'test' ]) et les 3 valeurs de opmode, avec conversion finale en dirmatrix, pourrait s’écrire :

Si l’argument mode est positionné sur >, c’est l’enchaînement tag1 > tag2 > tag3 qui est spécifié, donc un chemin dans le sens …/tag1/…/tag2/…/tag3/… la fonction n’utilise pas d’argument de proximité.

2. Combinaison des paramètres de recherche

Ce que l’on peut attendre d’une telle fonction est assez intéressant, parfois surprenant, suivant les arguments mode et opmode. Par exemple, si nous partons d’un index dont la racine test_dir est positionnée sur C:\Python3\envs\prod\Lib\site-packages\ (de l’ordre de 30000 entrées), nous pourrions avoir :

Mode Opmode Hits Exemples
OR-FIND-NOT_STRICT
[‘api’, ‘test’]
= 5646 ..\traits\tests\test_ui_notifiers.py
< 76 ..\sklearn\datasets\tests\data\openml\id_561\api-v1-jdl-dn-cpu-l-2-dv-1.json.gz
> 1 ..\pkg_resources\api_tests.txt
OR-EQUAL-NOT_STRICT
[‘api’, ‘test’]
= 847 ..\joblib\test\data\joblib_0.10.0_pickle_py33_np18.pkl
< 0
> 0
OR-EQUAL-STRICT
[‘api’, ‘test’]
= 847 ..\buildez\chem\xscore_interface\test\csvm\mol507_xscore.mol2
> 0
< 0

L’exemple est volontairement choisi pour être très discriminant et s’applique à des dossiers et des fichiers. Si l’on ne veut que traiter des noms de répertoires (nœuds), il faut constituer un index de répertoires (index_tree_dirs au lieu de index_tree_files). Un premier tri est fait pour réduire l’index à 20000 items (par exemple, filtrage des sous arborescences sous les nœuds __pycache__). Les temps d’exécution vont d’un peu moins de 3 s (opmode =) à 0.2 s (> et <) pour la recherche par sous-chaine (mode OR-FIND-NOT_STRICT). L’option opmode='=' reste présente mais ne doit pas être utilisée, cela revient à une à un appel  index_search_strfind qui ne serait pas optimisé. Les modes basés sur l’égalité (OR-EQUAL-...) sont plus rapides, de moins de 0.4 à 0.1 s.

3. Un exemple réel

Dans la pratique nous utilisons le sens > associé à une liste de 1 à 4 tags et aux modes: OR-FIND-NOT_STRICT, OR-EQUAL-NOT_STRICT . A partir de la même racine (C:\Python3\envs\prod\Lib\site-packages\buildez\ et filtrage pour passer de 30000 à 20000 items), nous faisons une recherche initiale à partir des mots clés pandas et api, puis nous précisons et nous voyons comment cela se passe :

Tags Mode, opmode Hits Temps Exemples
['pandas', 'api'] OR-FIND-NOT_STRICT
>
34 0.1 s ..\pandas\core\indexes\api.py
..\pandas\tests\api\test_api.py
OR-EQUAL-NOT_STRICT
>
16 0.1 s ..\pandas\tests\api\test_api.py
..\pandas\api\types
On élimine les compositions du type test_api.py mais qui restent si pandas et apis sont des noms exacts de répertoires.
['pandas', 'api', 'types'] OR-FIND-NOT_STRICT
>
3 0.17 s ..\pandas\tests\api\test_types.py
..\pandas\api\types\__init__.py
..\pandas\api\types
OR-EQUAL-NOT_STRICT
>
2 0.17 s ..\pandas\api\types\__init__.py
..\pandas\api\types
['pandas', 'api', 'types', '__init__'] OR-FIND-NOT_STRICT
>
1 0.23 s ..\pandas\api\types\__init__.py
OR-EQUAL-NOT_STRICT
>
0 0.17 s C’est __init__.py qu’il faut trouver et non __init__ !
['pandas', 'api', 'types', '__init__.py'] OR-FIND-NOT_STRICT
>
1 0.25 s ..\pandas\api\types\__init__.py
OR-EQUAL-NOT_STRICT
>
1 0.18 s ..\pandas\api\types\__init__.py

Nous constatons que dans la pratique les temps, sont acceptables, si on sait ce que l’on cherche et si on sait le paramétrer. On note aussi que plus la recherche est profonde (longueur de la liste tags) le temps va augmenter et nous commencerons à sentir la différence entre OR-EQUAL_NOT_STRICT (plus rapide) et OR-FIND-NOT_STRICT (plus lent c’est normal, il y a une recherche de type str_substr).

4. Conclusion

Une question qui se pose, c’est pourquoi j’ai adopté des modes OR dans cette fonction, alors que finalement nous cherchons la présence de plusieurs mots clés dans les nœuds ou les éléments terminaux d’une hiérarchie ? Si nous prenons les résultats du tableau précédent, en particulier la première ligne à 34 touches, je trouve que cela relève plutôt du OR que du AND. Une autre raison c’est qu’il s’agit de la reprise d’un vieux code qui était explicitement pense OR plutôt que AND. Mais je reconnais que c’est un peu troublant d’avoir un OR qui n’affiche que des résultats ou chaque mot clé est représenté par un nœud ou un élément terminal.
Au final, pour un coût assez modeste, nous disposons d’un outil qui peut naviguer sur les nœuds d’un arbre sérialisé, et trouver ce que l’on veut, même si on ne sait pas exactement ce que l’on cherche, ou si on doit avoir un code global qui marchera sur plusieurs OS (et qui va fouiller dans le contenu d’une sous-arborescence sans prendre en compte les différentiels liés à la racine de l’arbre). Ce qui est l’intérêt, parce que si nous savons exactement ce que nous cherchons, il suffit d’utiliser une fonction du module Python ospath, par exemple isdir(path), isfile(path), exist(path)

Retour en haut