3 janv. 2025

[Dynamo += Python] Aplatir des Listes

 



Après ces fêtes de fin d'années, aplatissons… des listes

#DynamoBIM #Python #List #Flatten #AutodeskExpertElite #AutodeskCommunity 

if this article is not in your language, use the Google Translate widget ⬈ (bottom of page for Mobile version )


Il existe plusieurs méthodes en Python pour aplatir une liste, voyons en revus quelques méthodes disponibles dans l'environnement DynamoBim

  • l'utilisation de l'API Dynamo avec la méthode DSCore.List.Flatten()
  • l'utilisation d'un générateur python (fonction récursive)
  • l'utilisation de la méthode d'extension Linq SelectMany (fonction récursive)
  • l'utilisation du package Matplotlib avec la méthode matplotlib.cbook.flatten


Pour les tests, nous utiliserons ici le nouveau moteur PythonNet3, avec comme entrée de tests une liste Python et une Collection .Net qui contiennent plus de 30000 objets repartis dans différentes listes imbriquées.

Nous utilisons ici une liste non homogène







code Python pour les Tests


import sys
import clr
import System
#import net library
from System import Array, GC
from System.Collections.Generic import List, IList, Dictionary
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

clr.AddReference('DSCoreNodes')
from DSCore import List as DSCoreList

clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)

net_clr_runtime = "Net Runtime : " + str(System.Environment.Version)

import traceback
import datetime
import matplotlib

def decotimeit(n_try):
    def timeit(func):
        def timeit_wrapper(*args, **kwargs):
            a = datetime.datetime.now()
            for i in range(n_try):
                result = func(*args, **kwargs)
            b = datetime.datetime.now() - a
            mean_time = (b.microseconds / n_try) * 0.001
            print(f"""Function {func.__name__} with input type : {type(args[0])}
                    | result length : {len(result)} 
                    | average time {mean_time:.3f} milliseconds ({n_try} tests) 
                    | {net_clr_runtime}""")
            return result
        return timeit_wrapper
    return timeit
    
@decotimeit(n_try = 10)
def Test_flatten_with_DSCoreList(input_lst):
    if sys.implementation.name == "cpython" and clr.__version__.startswith("3.") and isinstance(input_lst, list):
        # currently with DSPythonNet3 need to convert input_lst to NetList
        return DSCoreList.Flatten(convert_input_lst_to_NetList(input_lst))
    else:
        return DSCoreList.Flatten(input_lst)
    
@decotimeit(n_try = 10)
def Test_flatten_with_Matplotlib(input_lst):
    return list(matplotlib.cbook.flatten(input_lst))
    

@decotimeit(n_try = 10)
def Test_flatten_with_RecurseGenerator(input_lst):
    # sub function
    def flattenWithRecurseGen(*args):
        for x in args:
            if hasattr(x, "__iter__") and not isinstance(x, (str, System.String)):
                for y in flattenWithRecurseGen(*x) : yield y
            else: yield x
    # main
    return list(flattenWithRecurseGen(input_lst))
        
    
@decotimeit(n_try = 10)
def Test_flatten_with_RecurseLinq(input_lst):
    # sub function
    def flattenWithRecurseLinq(iList):
        if isinstance(iList, list):
            iList = List[System.Object](iList) 
        iList = iList.SelectMany[System.Object, System.Object](
                                            System.Func[System.Object, List[System.Object]](
                                                    lambda p : flattenWithRecurseLinq(p) if hasattr(p, "__iter__")\
                                                        and not isinstance(p, (str, System.String)) \
                                                    else List[System.Object]([p])
                                                        )
                                                    ).Select(System.Func[System.Object, System.Object](lambda p : p))\
                                                    .ToList()
        return iList

    # main
    return flattenWithRecurseLinq(input_lst)


def convert_input_lst_to_NetList(lst):
    """Convert python list and nested list to Net List"""
    newlst = List[System.Object](lst) # convert to Net List
    for idx, elem in enumerate(lst):
        if isinstance(elem, list) and len(elem) > 0:
            newlst[idx] = convert_input_lst_to_NetList(lst[idx])
        elif isinstance(elem, list) and len(elem) == 0:
            newlst[idx] = List[System.Object]([])
        else:
            newlst[idx] = lst[idx]
    return newlst

python_input_lst = IN[0]
net_input_lst = convert_input_lst_to_NetList(IN[0])
# clear Gabarge collector for tests
GC.Collect()

print("-" * 10)
result_A1 = Test_flatten_with_DSCoreList(python_input_lst)
result_A2 = Test_flatten_with_DSCoreList(net_input_lst)
print("-" * 10)
result_B1 = Test_flatten_with_RecurseGenerator(python_input_lst)
result_B2 = Test_flatten_with_RecurseGenerator(net_input_lst)
print("-" * 10)
result_C1 = Test_flatten_with_RecurseLinq(python_input_lst)
result_C2 = Test_flatten_with_RecurseLinq(net_input_lst)
print("-" * 10)
result_D1 = Test_flatten_with_Matplotlib(python_input_lst)
result_D2 = Test_flatten_with_Matplotlib(net_input_lst)
print("-" * 10)


OUT = result_C2

résultats

Function Input Type Average Result Time for 10 tests (ms) Compatibility Engine
Test_flatten_with_DSCoreList list (python) 48.386 CPython3, PythonNet3, IronPython3
Test_flatten_with_DSCoreList System.Collections.Generic.List 0.400 CPython3, PythonNet3, IronPython3
Test_flatten_with_RecurseGenerator list (python) 62.672 CPython3, PythonNet3, IronPython3
Test_flatten_with_RecurseGenerator System.Collections.Generic.List 93.577 CPython3, PythonNet3, IronPython3
Test_flatten_with_RecurseLinq list (python) 14.761 IronPython3, PythonNet3
Test_flatten_with_RecurseLinq System.Collections.Generic.List 8.638 IronPython3, PythonNet3
Test_flatten_with_Matplotlib list (python) 10.495 CPython3, PythonNet3
Test_flatten_with_Matplotlib System.Collections.Generic.List 38.399 CPython3, PythonNet3
.
Il en résulte que suivant le type de liste que vous avez en entrée (python ou .Net Collection), une même méthode sera plus ou moins efficace.

Je vous souhaite une Bonne nouvelle Année et plein de bonnes choses


PS : cet article n'a pas été entièrement rédigé par une IA 😝


Related Posts:

  • [Revit] De l'Elec dans Revit 2021 ! Revit 2021 vient de sortir, voici quelques nouveautés intéressantes autour du domaine de l'électricité et plus. De l'ingénierie électrique au menu dans cette version ! L'utilisation de Revit ne se limite pas au… Read More
  • [Dynamo+=Python] Copier les états des calques CAO A défaut de travailler qu'avec des maquettes en liens, travailler avec des liens CAO sous Revit peut poser quelques difficultés La difficulté n'étant pas dans l'importation du lien CAO ou dans le géo-référencement du DWG n… Read More
  • [Dynamo+=Python] Transfert de Légendes L'outil d'insertion  de vue à partir de fichier de Revit ne permet pas à ce jour de copier des Légendes, mais... Il existe quelques utilitaires qui permettent de réaliser cette tâche sans avoir à faire du "cop… Read More
  • [Dynamo+=Python] Voyez le Changement! Au sommaire de la visibilité des changements entre 2 modèles et la gestion du changement de méthodes entre plusieurs versions de SDK (API Revit)  Ne travaillant essentiellement qu'avec des modèles liés et d… Read More
  • [Dynamo+=Python] Retrouver l'élément hôte (Maquette liée)Par défaut lorsqu'une instance de famille est hébergée sur une face d'une maquette liée, Revit n'affiche que la maquette hôte dans les propriétés. Voici une petite fonction pour retrouver l'élément hôte d'une instance lo… Read More

0 commentaires:

Enregistrer un commentaire