10 mai 2025

[Dynamo += Python] Liste pense-bête




Un article qui était resté au fond du tiroir sur des opérations de listes Python

#DynamoBIM #Python #AutodeskExpertElite #AutodeskCommunity 

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


Voici une série d'exemples d'opération sur des listes qui pourront être utiles aux débutants, peut-être même aux plus confirmés.


  • Aplatir une liste

# Flatten Example 1
import sys
#sys.setrecursionlimit(20000)
def flattenGen(*args):
    for x in args:
        if hasattr(x, "__iter__") and not isinstance(x, str):
            for y in flattenGen(*x) : yield y
        else: yield x


def flattenList(l):
	if hasattr(l, "__iter__") and not isinstance(l, (str, bytes, System.String)):
		return [item for sublist in l for item in flattenList(sublist)]
	else:
		return [l]

test_lst = ['1','2','3',['4','5','6'],['6','7',['10','20']],'5','6'] 
test_lst2 = [1,2,3,[4,5,6],[6,7]]   
for x in flattenGen(test_lst):
    print  (x) 

# Flatten Example 2
import re

L = [[[1, 2, 3], [4, 5]], 6]
flattened_list = re.sub("[[]]", "", str(L)).replace(" ", "").split(",")
new_list = list(map(int, flattened_list))
print(new_list)

# Flatten Example 3 with PythonNet3 or IronPython
import sys
import clr
import System
from System.Collections.Generic import List, IList, Dictionary
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

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

import traceback

def FlattenWithLinq(*args):
    for x in args:
        if hasattr(x, "__iter__") and not isinstance(x, (str, System.String)):
            iList = List[System.Object](x)
            iList = iList.SelectMany[System.Object, System.Object](
                                                    System.Func[System.Object, List[System.Object]](
                                                            lambda p : FlattenWithLinq(p) if hasattr(p, "__iter__") \
                                                                and not isinstance(p, (str, System.String)) else [p])).ToList()
            yield from iList
        else:
            yield x

iList = IN[0]

test = list(FlattenWithLinq(iList))

OUT = test

  • Découper (split) une liste

my_list=[1,2,3,1,5,6,7,5,9,9]
# split a list by maximum items in sublist
n=4 # number items by sublist
final = [my_list[i:i+n] for i in range(0, len(my_list), n)] 
print (final)

# split a list by count of sublist
n=4 # number of sublist
import numpy as np
split_list = np.array_split(my_list, n)
final2 = [list(arr) for arr in split_list]
print (final2)

  • Ordonner une liste suivant plusieurs clés

# sort by multiple keys
s = sorted(s, key = lambda x: (x[1], x[2]))
s = sorted(my_list, key=lambda i: ( criteria_1(i), criteria_2(i) ), reverse=True)s = sorted(my_list, key=lambda i: ( criteria_1(i), criteria_2(i) ), reverse=True) 
  • Transposer une liste 

Exemple 1

dataEnteringNode = IN
#transpose if list contain sublist
if all(hasattr(i, '__iter__') for i in dataEnteringNode):
	dataEnteringNode = [i for i in zip(*dataEnteringNode)]
else:
	dataEnteringNode = [dataEnteringNode]
    
Exemple 2

l=[[1,2,3],[4,5,6],[7,8,9]]
[list(i) for i in zip(*l)]
#result
#[[1, 4, 7], [2, 5, 8], [3, 6, 9]]
  • Ordonner une liste de chaine de caractère avec des nombres (Natural Sort V1)

import re

def natural_sort(l): 
    convert = lambda text: int(text) if text.isdigit() else text.lower()
    alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
    return sorted(l, key=alphanum_key)
  • Ordonner une liste de chaine de caractère avec des nombres (Natural Sort V2)

import re
lst = ["var1", "var3", "var11", "var4"]
def sort_Str_Numb(lst):
  lst.sort(key = lambda x: re.search(r'.*(\d)', x).group(1))
  return lst
  • Ordonner une liste suivant une liste d'index

a = ['c', 'd', 'B', 'a', 'e']
b = ['a001', 'B002', 'c003', 'd004', 'e005']
c = sorted(b, key = lambda x: a.index(x[0])) # ['c003', 'd004', 'b002', 'a001', 'e005']
#
#example to reverse values
textToReverse = "ZDA.01/ZDM.01"
sortFilter = ["ZDM", "ZDA"]
lstTemp = textToReverse.split("/")
lstTemp.sort(key = lambda x : sortFilter.index(x[:2]))
goodText = "/".join(lstTemp)

Parfois, il peut s'avérer que list.index() ne fonctionne pas, car l'item recherché ne se trouve pas dans la liste.

Penser à utiliser les méthodes .Net dans ce cas 

Ici IndexOf() retourne -1 quand l'index n'est pas trouvé au lieu d'une erreur

import sys
import clr
import System
from System.Collections.Generic import List
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

a = List[System.String](['c', 'd', 'B', 'a', 'e', 'k'])
b = ['a001', 'z002', 'B002', 'c003', 'd004', 'e005', ]
c = sorted(b, key = lambda x: float('inf') if a.IndexOf(x[0]) == -1 else a.IndexOf(x[0]) ) 
# ['c003', 'd004', 'b002', 'a001', 'e005', 'z002']

OUT = c






  • Grouper une liste avec itertools
la liste nécessite d'être ordonnée avant l'application de "groupby"
import sys
import clr
import System
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

import itertools
input_lst = IN[0]
input_lst.sort(key=lambda x : x.EndPoint.Z)

groups = [ list(_group) for (_key, _group) in itertools.groupby(input_lst, lambda x : x.EndPoint.Z)]

OUT = groups 
  • Grouper une liste avec un dictionnaire
La méthode de base avec (Python 2 et 3)

import sys
import clr
import System
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

from System.Collections.Generic import Dictionary
pyDict = {}

for lin in IN[0]:
	mykey = lin.EndPoint.Z.ToString()
	if mykey not in pyDict:
		pyDict[mykey] = [lin]
	else:	
		pyDict[mykey].append(lin)

OUT = Dictionary[str, object](pyDict)
La méthode avec defaultdict (Python 2 et 3)

import sys
import clr
import System
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
from collections import defaultdict
from System.Collections.Generic import Dictionary

pyDict = defaultdict(list)

for lin in IN[0]:
	mykey = lin.EndPoint.Z.ToString()
	pyDict[mykey].append(lin)

OUT = Dictionary[str, object](pyDict)
La méthode avec setdefault() (Python 3)

import sys
import clr
import System
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

from System.Collections.Generic import Dictionary
pyDict = {}

for lin in IN[0]:
	mykey = lin.EndPoint.Z.ToString()	
	pyDict.setdefault(mykey, []).append(lin)

OUT = Dictionary[str, object](pyDict)
  • Grouper une liste avec Linq GroupBy (.Net)
compatible avec Ironpython2, IronPython3, DSPythonNet3
          
    import sys
    import clr
    import System
    from System.Collections.Generic import List, IList, Dictionary
    clr.AddReference('ProtoGeometry')
    from Autodesk.DesignScript.Geometry import *
    
    clr.AddReference("System.Core")
    clr.ImportExtensions(System.Linq)
    
    lstPoints = List[Point](IN[0])
    resultGroup = lstPoints.GroupBy[Point, int](System.Func[Point, int](lambda p :  int(p.Z)))
    OUT = resultGroup
    
  • Remplacer une partie de chaine de caractère (Regex)

import re
varrr = "3f3839cc-a118-4525-91dc-ccfac34ef90d-000aedd9:RVTLINK/3f3839cc-a118-4525-91dc-ccfac34ef90d-000aedd8:274326:6:SURFACE"
var22 = re.sub(r"(^.*:)RVTLINK.*(:.*:.*:.*)", r"\g<1>0:RVTLINK\g<2>", varrr)
print(var22)
  • Ordonner une liste de Point suivant une autre liste de Point

import sys
import clr
import random
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
    
def sortFunc(ptb):
    global lstPtsA
    ptB = Point.ByCoordinates(ptb.X, ptb.Y, 0)
    for idx, pta in enumerate(lstPtsA):
        ptA = Point.ByCoordinates(pta.X, pta.Y, 0)
        if ptA.DistanceTo(ptB) < 0.01:
            return idx
    return float('inf')
            
pt1 = Point.ByCoordinates(-1385.574, -173.02607, 0)
pt2 = Point.ByCoordinates(-1385.57404, -98.026, 0)
pt3 = Point.ByCoordinates(-1445.574, -173.02608, 0)
pt4 = Point.ByCoordinates(-1445.57405, -98.026, 0)
pt5 = Point.ByCoordinates(-1445.574, -173.02609, 400)
pt6 = Point.ByCoordinates(-1445.574, -98.026, 400)
pt7 = Point.ByCoordinates(-1385.57403, -173.02609, 400)
pt8 = Point.ByCoordinates(-1385.57402, -98.026, 400)
lstPts = [pt1, pt2, pt3, pt4, pt5, pt6, pt7, pt8]       
# shuffle
lstPts = random.shuffle(lstPts)         
#lstPts = IN[0]

lstPts = sorted(lstPts, key = lambda x : x.Z)
lstPtsA = lstPts[: len(lstPts) / 2]
lstPtsB = lstPts[len(lstPts) / 2 : ]
# sort lstPtsB by lstPtsA coordinnates
lstPtsB.sort(key = lambda x : sortFunc(x))
# draws lines
DSlines = [Line.ByStartPointEndPoint(pta, ptb) for pta, ptb in zip(lstPtsA, lstPtsB)]
OUT = DSlines
  • Exemple d'un générateur d'Objet Python (anonyme)

def genClap(nbrClap):
	hand = type('',(object,),{"Clap": lambda self : print("clap! " + self.name), "name" : "MySelf"})()
	for i in range(nbrClap):
		yield hand

for hand in genClap(10):
	hand.Clap()	
    
## OR

def genClap(nbrClap):
	hand = type('',(),{"Clap": lambda self : print("Clap! " + self.name), "name" : "My_Name"})()
	for i in range(nbrClap):
		yield hand

for hand in genClap(10):
	hand.Clap()	
.


  • Appliquer une fonction sur une multiple liste en conservant la structure d'entrée


  •  Une méthode similaire de 'Find' ou 'FirstOrDefault' (.Net) avec Python

import clr
import sys
import System

#import Revit API
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB
from Autodesk.Revit.DB.Electrical import *

clr.AddReference('RevitAPIUI')
from Autodesk.Revit.UI import *

clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.Elements)

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument

Output = []

Elec = ElectricalSetting.GetElectricalSettings(doc)
# equivalent of 'Find' or FirstOrDefault method (.Net Collection)
WireMaT = next((x for x in Elec.WireMaterialTypes if x.ToDSType(False).Name == IN[0]), None)

if WireMaT is not None:
	Names = "NEC-210-3A"
	TransactionManager.Instance.EnsureInTransaction(doc)
	NewMaterial = Elec.AddWireMaterialType(Names, WireMaT)
	Output.append(NewMaterial.ToDSType(True).Name)
	TransactionManager.Instance.TransactionTaskDone()

OUT = Output

  • Convertir des coordonnées Autocad en liste de Point

def AcDb2DPLineToDS(obj):
	coords = iter(obj.Coordinates)
	DSPoints = [Point.ByCoordinates(x,y,0) for x, y in zip(coords, coords)]
	return PolyCurve.ByPoints(DSPoints, True)
  • Incrémenter la partie finale d'un texte
import re
hostname01 = "hostname10"
print(re.sub(r'(\d+)', lambda x: str(int(x.group(0)) + 1).zfill(2), hostname01))
  • la méthode 'drange' avec IronPython
Équivalent à  numpy.arange(0,1,0.1) avec numpy

import sys
import clr

def drange(start, end, step):
	i = start
	while round(i, 10) < end:
		yield i
		i += step
		
OUT = list(drange(IN[0], IN[1], IN[2]))
# OR 
# Version version reversed increment 
def drange(start, end, step):
	i = start
	while eval("round(i, 10) {} end".format("<=" if step > 0 else ">=")):
		yield i
		i += step


print(list(drange(0, 1, 0.1)))
print(list(drange(1, 0, -0.1))) 
  • la méthode 'linspace' avec IronPython
Équivalent à  numpy.linspace(0, 10, num=50)  avec numpy

def py_linspace(start, stop, num=50, endpoint=True):
    num = int(num)
    start = start * 1.
    stop = stop * 1.

    if num == 1:
        yield stop
        return
    if endpoint:
        step = (stop - start) / float(num - 1)
    else:
        step = (stop - start) / float(num)

    for i in range(num):
        yield start + step * i

lst = [x for x in  py_linspace(1, 10, 22)]
...

  • Obtenir la forme d'un tableau (array ou list)




import clr
import sys
import System

def get_shape(lst, shape=()):
	"""
	returns the shape of nested lists 
	"""
	if not hasattr(lst, "__iter__") or isinstance(lst, (str, System.String)):
		# base case
		return {"shape":shape, "ndim":len(shape)}
	# peek ahead and assure all lists in the next depth
	# have the same length
	if hasattr(lst[0], "__iter__") and not isinstance(lst[0], (str, System.String)):
		l = len(lst[0])
		if not all(len(item) == l for item in lst):
			msg = 'not all lists have the same length'
			raise ValueError(msg)
	shape += (len(lst), )
	# recurse
	shape = get_shape(lst[0], shape)

	return shape #{"shape":shape, "ndim":len(shape)} 

datas_array = IN[0]
OUT = get_shape(datas_array), get_shape(datas_array)['ndim']
  • Fusionner des dictionnaires avec Python

# Load the Python Standard and DesignScript Libraries
import sys
import itertools

list_dict = IN[0]
OUT = dict(itertools.chain.from_iterable(dct.items() for dct in list_dict))
# OR 
OUT = {}
for d in list_dict: OUT = dict(OUT, **d)
# OR (Python 3.9+)
OUT = {}
for d in list_dict: OUT |= d 
  • Fusionner des dictionnaires avec Linq 

import sys
import clr
import System
from System.Collections.Generic import List, Dictionary

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

list_dict = IN[0]

OUT = list_dict.SelectMany(lambda x : Dictionary[str, object](x))\
            .ToDictionary(lambda g : g.Key, lambda g : g.Value)
..


  • Retrouver l'index d'un item d'une liste suivant une propriété.
import sys
import clr
import System
from System.Collections.Generic import List
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

import random
random.seed(282)

lst_pts = [Point.ByCoordinates(*random.sample(range(0, 30), 3)) for i in range(10)]
# get one point from list
pta = random.choice(lst_pts)
# search the index of this point pta
index_pta = next((i for i, pt in enumerate(lst_pts) if pt.IsAlmostEqualTo(pta)), -1)
# OR 
func  =System.Predicate[Point](lambda pt : pt.IsAlmostEqualTo(pta))
index_pta = List[Point](lst_pts).FindIndex(func)

OUT = lst_pts, pta, index_pta

  • Ordonner une liste de points suivant un vecteur


import clr
import sys
import System
#
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import Autodesk.DesignScript.Geometry as DS
    
lstPts = IN[0]
vector = IN[1]

# with Dynamo DesignScript API
sorted_points = sorted(lstPts, key = lambda p : p.AsVector().Dot(vector.Normalized()))
# with Revit API
# sorted_points = sorted(lstPts, key = lambda p : p.DotProduct(vector.Normalize()))

OUT = sorted_points


  • Utilisation de la librairie Bisect 

Le module bisect de la bibliothèque standard de Python fournit des fonctions basées sur la recherche binaire (« bissection ») pour :

  •     Trouver rapidement la position où insérer un élément dans une liste triée tout en gardant l’ordre.
  •     Insérer un élément dans la bonne position d’une liste triée.
  •     Rechercher la plage d’indices où un élément donné pourrait se trouver.

Fonction Description
bisect.bisect_left(a, x) Renvoie l'index où insérer x dans a pour garder la liste triée (avant les éléments égaux).
bisect.bisect_right(a, x)
bisect.bisect(a, x)
Renvoie l'index où insérer x après les éléments égaux (position à droite).
bisect.insort_left(a, x) Insère x dans a à l'index approprié (à gauche des égaux).
bisect.insort_right(a, x)
bisect.insort(a, x)
Insère x dans a à droite des égaux.



Un exemple avec Dynamo pour remplacer la couleur de l'élément en fonction des intervalles de valeurs des paramètres du projet 


.

import clr
import sys
import bisect
from bisect import bisect_left, bisect_right
import System
clr.AddReference("System.Numerics")

clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import Autodesk.DesignScript.Geometry as DS

#import Revit API
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB

#import net library
from System import Array
from System.Collections.Generic import List, IList, Dictionary

#import transactionManager and DocumentManager (RevitServices is specific to Dynamo)
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument


def get_color_by_interval(value, interval_values=[], color_values=[]):
    """
    Assigns color based on the input value interval.

    Args:
    value (float): The input value to assign a color to.
    interval_values (List): A list of interval values to determine color assignment.
    color_values (List): A list of color values corresponding to the interval values.

    Returns:
    str: The color assigned to the input value.
    """
    i = bisect_right(interval_values, value)
    return color_values[i-1]


def assign_colors_to_elements(lst_elems, parameterName, interval_values, color_values):
    """
    Assigns colors to elements based on the given parameter value.

    Args:
    lst_elems (List): List of elements to assign colors to.
    parameterName (str): The parameter name to retrieve values from elements.
    interval_values (List): A list of interval values to determine color assignment.
    color_values (List): A list of color values corresponding to the interval values.

    Returns:
    List: A list of elements with assigned colors.
    """
    out = []
    for elem in lst_elems:
        para = elem.LookupParameter(parameterName)
        if para is not None:
            paraValue = float(para.AsValueString())
            ds_color = get_color_by_interval(paraValue, interval_values, color_values)
            out.append([elem, paraValue, ds_color])
    return out


toList = lambda x : UnwrapElement(x) if isinstance(x, list) else UnwrapElement([x])
lst_elems = toList(IN[0])
parameterName = IN[1]
interval_values = toList(IN[2])
color_values = toList(IN[3])

OUT = assign_colors_to_elements(lst_elems, parameterName, interval_values, color_values)
.
À cet article, je vous recommande de regarder l'article sur l'utilisation des méthodes d'extension LINQ avec Dynamo PythonNet3
https://voltadynabim.blogspot.com/2025/03/dynamo-python-dspytonnet3-et-linq.html 

0 commentaires:

Enregistrer un commentaire