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 😝


0 commentaires:

Enregistrer un commentaire