GroupByKey in Python

Hi all,

How can I group my list by keys in python like the Dynamo List.GroupByKey node?

import clr #.NET Laden
import sys #sys is de fundamentele Python bibliotheek
#de standaard IronPython-bibliotheken
#sys.path.append('C:\Program Files (x86)\IronPython 2.7\Lib') #Imports the
#standaard IronPython-bibliotheken, die alles dekken, van servers en
#encryptie tot reguliere expressies.
import System #The System namespace in de hoofdmap .NET
from System import Array #.NET class voor het verwerken van array-informatie
import System.Collections.Generic as MGen #Module kan nu benaderd worden met MGen.xxxx
#from System.Collections.Generic import * #Hiermee kunt u generieke afhandelen. Revit's API
#soms wil hard-getypte 'generieke' lijsten, genaamd ILists. Als je niet nodig hebt
#deze kunt u deze regel verwijderen.
clr.AddReference('ProtoGeometry')  #Een Dynamo-bibliotheek voor zijn proxygeometrie
#classes. Je hebt dit alleen nodig als je interactie hebt met geometrie.
import Autodesk.DesignScript.Geometry as AGeo #Module kan worden opgeroepen a=met AGeo.xxxx
#from Autodesk.DesignScript.Geometry import * #Laadt alles in Dynamo's
#geometriebibliotheek
clr.AddReference("RevitNodes") #Dynamo's nodes voor Revit
import Revit #Laad in de Revit-namespaces in RevitNodes
clr.ImportExtensions(Revit.Elements) #Meer laden van Dynamo's Revit-bibliotheken
clr.ImportExtensions(Revit.GeometryConversion) #Meer laden van Dynamo's
#Revit-bibliotheken. Je hebt dit alleen nodig als je interactie hebt met geometrie.
clr.AddReference("RevitServices") #Dynamo's classes voor het omgaan met Revit-documenten
import RevitServices 
from RevitServices.Persistence import DocumentManager #Een interne Dynamo class
#dat het document bijhoudt waaraan Dynamo momenteel is gekoppeld
from RevitServices.Transactions import TransactionManager #Een Dynamo class voor
#transacties openen en sluiten om de database van het Revit-document te wijzigen

clr.AddReference("RevitAPI") #Verwijzing naar Revit's API DLL's toevoegen
clr.AddReference("RevitAPIUI") #Verwijzing naar Revit's APIUI DLL's toevoegen

import Autodesk #Loads the Autodesk namespace
import Autodesk.Revit.DB as RDB #Loading Revit's API UI classes module kan nu worden aangeroepen met RDB.xxxx
#from Autodesk.Revit.DB import * #Loading Revit's API UI classes
import Autodesk.Revit.UI as RUI # Loading Revit's API UI classes als RUI.xxxx
#from Autodesk.Revit.UI import * #Loading Revit's API UI classes

doc = DocumentManager.Instance.CurrentDBDocument #Dit is het actieve Revit document
uiapp = DocumentManager.Instance.CurrentUIApplication #Een handle instellen voor het actieve Revit UI-document
app = uiapp.Application  #Een handle instellen op de momenteel geopende instantie van de Revit-toepassing
uidoc = uiapp.ActiveUIDocument #Een handle instellen op de momenteel geopende instantie van de Revit UI-toepassing
revit_version = int(doc.Application.VersionNumber)
# code omrekenen revit feet naar huidig ingestele document units


	
#einde code omrekenen revit feet naar huidig ingestele document units

#alle elementen van OST category ophalen
alleElementen = RDB.FilteredElementCollector(doc)
alleElementen.OfCategory(RDB.BuiltInCategory.OST_PipeCurves)
alleElementen.WhereElementIsNotElementType()

MyElem = alleElementen.ToElements()
SystemName=[] #lege lijst starten

for NE in MyElem:
	Section=NE.get_Parameter(RDB.BuiltInParameter.RBS_SYSTEM_NAME_PARAM).AsString()
	SystemName.append(Section)

gezipt = zip(MyElem, SystemName)

#wegschrijven naar uitvoer

OUT= gezipt


I want to group my pipes by the output of SystemName

This is my goto solution for python grouping, alternatively if you have itertools available its groupby() method works well if you turn your list into a dictionary.

I’m not entirely sure what’s going on in your specific python code, but this is an effective code to group things by keys:

def unique(items):
	ulist=[]
	for i in items:
		if i not in ulist:
			ulist.append(i)
	return ulist

items = IN[0]
keys = IN[1]

ukeys = unique(keys)

#create empty output list
grplist = []
for i in range(len(ukeys)):
	grplist.append([])

#find indices
inds = []
for key in keys:
	inds.append(ukeys.index(key))

#fill output list
for item, ind in zip(items,inds):
	grplist[ind].append(item)


OUT = grplist

I would absolutely not be surprised if there’s more elegant methods built into Python but this certainly works.

Hi,
an alternative using PipingNetwork property

import clr
import sys
import System
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 specify namespace
from Autodesk.Revit.DB.Plumbing import *


clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument

pipeSystems = DB.FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_PipingSystem).WhereElementIsNotElementType()

OUT = {p_sys.Name : [x for x in p_sys.PipingNetwork if isinstance(x, Pipe)] for p_sys in pipeSystems}

or with itertools.groupby mentioned by @GavinNicholls

Little update, you can just define this GroupByKey function.
It gives you an output with both the grouped lists and the unique keys, just like the node.

def GroupByKey(Items,Keys):
	UKeys=[]
	for Key in Keys:
		if Key not in UKeys:
			UKeys.append(Key)
	GrpList=[]
	for empt in range(len(UKeys)):
		GrpList.append([])
	Inds=[]
	for Key in Keys:
		Inds.append(UKeys.index(Key))
	for Item, Ind in zip(Items, Inds):
		GrpList[Ind].append(Item)
	return [GrpList,UKeys]

Thank you all guys for the solutions, unfortunately I can only give one solution so choose the one I used.

@PauLtus
How can I group inside a group?
Like levels in Dynamo?

	
def GroupByKey(Items,Keys):
	UKeys=[]
	for Key in Keys:
		if Key not in UKeys:
			UKeys.append(Key)
	GrpList=[]
	for empt in range(len(UKeys)):
		GrpList.append([])
	Inds=[]
	for Key in Keys:
		Inds.append(UKeys.index(Key))
	for Item, Ind in zip(Items, Inds):
		GrpList[Ind].append(Item)
	return [GrpList,UKeys]

	
#einde code omrekenen revit feet naar huidig ingestele document units

cat_list = [RDB.BuiltInCategory.OST_PipeCurves, RDB.BuiltInCategory.OST_PipeFitting]
typed_list = MGen.List[RDB.BuiltInCategory](cat_list)
filter = RDB.ElementMulticategoryFilter(typed_list)
output = RDB.FilteredElementCollector(doc).WherePasses(filter).WhereElementIsNotElementType().ToElements()


#systemname ophalen
SystemName=[] #lege lijst starten
for NE in output:
	SystemN=NE.get_Parameter(RDB.BuiltInParameter.RBS_SYSTEM_NAME_PARAM).AsString()
	SystemName.append(SystemN)

#Group list
grplist = GroupByKey(output, SystemName)

#Grouped list 0 = elements 1 = keys
groups = grplist[0]
uniquekeys = grplist [1]

#check parameter in de groep

Section = []
for NE in groups:
	try:
		SectionN= NE.get_Parameter(RDB.BuiltInParameter.RBS_SECTION).AsValueString()
		Section.append(SectionN)
	except:
		Section.append(None)

grplist_1 = GroupByKey(groups, Section)

OUT = Section

image

Something like that doesn’t exist in Python, you have to play around with for loops then.
When I started with Python I quite missed these levels, but doing it in Python is certainly more consistent.

Thanks, I had a python course today and the teacher told the same, use a for loop a loop for example

It’s possible to use levels if you put this inside a custom node. You can then work across a data structure in a similar manner to a normal node, and apply lacing behaviour as well.

Thanks Gavin, yes ofcourse that is a way. :slight_smile:

Can someone help me with this?

I want to group inside a grouped list, this is the grouped list:
image

The “section” return as a null:

image


import Autodesk #Loads the Autodesk namespace
import Autodesk.Revit.DB as RDB
	
def GroupByKey(Items,Keys):
	UKeys=[]
	for Key in Keys:
		if Key not in UKeys:
			UKeys.append(Key)
	GrpList=[]
	for empt in range(len(UKeys)):
		GrpList.append([])
	Inds=[]
	for Key in Keys:
		Inds.append(UKeys.index(Key))
	for Item, Ind in zip(Items, Inds):
		GrpList[Ind].append(Item)
	return [GrpList]

def GetName(ele):
	elename = None
 	try:
 		elename = ele.Name
 	except:
 		elename = RDB.Element.Name.__get__(ele)
 	return elename 

	
#einde code omrekenen revit feet naar huidig ingestele document units

#categoriëen ophalen
cat_list = [RDB.BuiltInCategory.OST_PipeCurves, RDB.BuiltInCategory.OST_PipeFitting]
typed_list = MGen.List[RDB.BuiltInCategory](cat_list)
filter = RDB.ElementMulticategoryFilter(typed_list)
elementen = RDB.FilteredElementCollector(doc).WherePasses(filter).WhereElementIsNotElementType().ToElements()

#pipes en fittings ophalen 
pipe_elem = []
for nw in elementen:
	p_name = GetName(nw)
	if p_name.Contains("Bochtstraal") or p_name.Contains("Alupex"):
		pipe_elem.append(nw)

#elementen groeperen op systemname
systems = []
grplist = []

for np in pipe_elem:
	system_name = np.get_Parameter(RDB.BuiltInParameter.RBS_SYSTEM_NAME_PARAM).AsString()
	systems.append(system_name)

grplist = GroupByKey(pipe_elem, systems)

#buizen uit de groep halen
section = []
grp_elem_system = grplist [0]
for np in grp_elem_system:
	try:
		section_name = np.get_Parameter(RDB.BuiltInParameter.RBS_SECTION).AsString()
		section.append(system_name)
	except:
		section.append(None)

OUT = section

BTW, I want only python :wink:

Anyone? :slightly_smiling_face:

You’ll need to take the logic one level deeper using a for loop I think. That or use a custom node with levels (if you want versatility that is my recommended approach).

Thanks for you’re reply

Do you have an example how to do this?

I know, but I try to build it without any custom nodes or input.
It is the hardest way but the best way to learn I guess

def GroupByKey(Items,Keys):
	UKeys=[]
	for Key in Keys:
		if Key not in UKeys:
			UKeys.append(Key)
	GrpList=[]
	for empt in range(len(UKeys)):
		GrpList.append([])
	Inds=[]
	for Key in Keys:
		Inds.append(UKeys.index(Key))
	for Item, Ind in zip(Items, Inds):
		GrpList[Ind].append(Item)
	return [GrpList,UKeys]
 
dataToGroup  = IN[0]
keysForGroup = IN[1]

groupedData = []

for d,k in zip(dataToGroup,keysForGroup):
	grouped = GroupByKey(d,k)
	groupedData.append(grouped[0])
	
OUT = groupedData

Thanks Gavin, almost the solution:

I need the keys 1 level deeper (@L2 and @L3)

import Autodesk.Revit.DB as RDB
import System.Collections.Generic as MGen

def GroupByKey(Items,Keys):
	UKeys=[]
	for Key in Keys:
		if Key not in UKeys:
			UKeys.append(Key)
	GrpList=[]
	for empt in range(len(UKeys)):
		GrpList.append([])
	Inds=[]
	for Key in Keys:
		Inds.append(UKeys.index(Key))
	for Item, Ind in zip(Items, Inds):
		GrpList[Ind].append(Item)
	return [GrpList, UKeys]

def GetName(ele):
	elename = None
 	try:
 		elename = ele.Name
 	except:
 		elename = RDB.Element.Name.__get__(ele)
 	return elename 

	
#einde code omrekenen revit feet naar huidig ingestele document units

#categoriëen ophalen
cat_list = [RDB.BuiltInCategory.OST_PipeCurves, RDB.BuiltInCategory.OST_PipeFitting]
typed_list = MGen.List[RDB.BuiltInCategory](cat_list)
filter = RDB.ElementMulticategoryFilter(typed_list)
elementen = RDB.FilteredElementCollector(doc).WherePasses(filter).WhereElementIsNotElementType().ToElements()

#pipes en fittings ophalen 
pipe_elem = []
for nw in elementen:
	p_name = GetName(nw)
	if p_name.Contains("Bochtstraal") or p_name.Contains("Alupex"):
		pipe_elem.append(nw)

#elementen groeperen op systemname
systems = []

for np in pipe_elem:
	system_name = np.get_Parameter(RDB.BuiltInParameter.RBS_SYSTEM_NAME_PARAM).AsString()
	systems.append(system_name)

grplist = GroupByKey(pipe_elem, systems)
#section ophalen en groeperen


dataToGroup  = grplist[0]
keysForGroup = []
for np in pipe_elem:
	try: 
		section_name = np.get_Parameter(RDB.BuiltInParameter.RBS_SECTION).AsValueString()
		keysForGroup.append(section_name)
	except:
		keysForGroup.append("0")

gezipt = zip(dataToGroup, keysForGroup)
groupedData = []

for d,k in gezipt:
	grouped = GroupByKey(d,k)
	groupedData.append(grouped[0])
	
OUT = keysForGroup, dataToGroup