Slice a list every time it contains a list of items

Like this I think?

import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
#The inputs to this node will be stored as a list in the IN variables.

OUT = []

def tolist(obj):
	if hasattr(obj, "__iter__"):
		return obj
	else:
		return [obj]	

items = tolist(IN[0])
splittingItems = tolist(IN[1])

outList = []
for splitter in splittingItems:
	i = 0
	while i < len(items):
		if items[i] == splitter:
			items[i] = [items[i]]
			i += 1	
		else:
			i += 1
OUT = items

Hope that works :slight_smile:

Mark

1 Like

very close, I am expecting to slice the list when those strings appear, but the subgroup would be comprised between the string item found until the next found in the list, the subgroups would be what I marked in red. Many thanks

in designscript I did it like this, but I need to do it in Python because it kills all memory RAM when list output results over million items

#slice list by key
t2=key list
t3=list
slicedbykeytag = DSCore.List.Slice(t3, DSCore.List.AddItemToFront(0, t2), DSCore.List.AddItemToEnd(DSCore.List.Count(t3), t2), 1);

I tried to write this code in python node loading the designscript DSCore but I get a warning:

Warning: IronPythonEvaluator.EvaluateIronPythonScript operation failed. 
Traceback (most recent call last):
  File "<string>", line 134, in <module>
TypeError: expected Nullable[int], got ArrayList

I tried that as well but it feeds indices that are previously generated, and I got a warning

# Load the Python Standard and DesignScript Libraries
import sys
import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

# The inputs to this node will be stored as a list in the IN variables.
indices = IN[1]
input = IN[0]
splitbykey = []

reduce(lambda x, y: splitbykey.append(input[x:y]) or y, indices + [len(input)], 0)

OUT= splitbykey

There’s probably a more clever way to write this, but it should work:

# Enable Python support and load DesignScript library
import clr

# The inputs to this node will be stored as a list in the IN variables.
dataEnteringNode = IN

def tolist(obj):
	if hasattr(obj, "__iter__"):
		return obj
	else:
		return [obj]

def split_at_values(items, values):
    ot = []
    items = tolist(items)
    for index,item in enumerate(items):
        check = item in values
        if check and index==0:
            ot = [item]
            new_items = items[1:]
            break
        elif check and index > 0:
            ot = items[:index+1]
            new_items = items[index+1:]
            break
        else:
            new_items = [item]
    return ot, new_items
    
def split_list(items, values):
    ot = []
    i = 0
    while items and i < 100:
        i += 1
        check = [x for x in values if x in items]
        if check:
            split = split_at_values(items, values)
            ot.append(split[0])
            items = split[1]
        else:
            ot.append(items)
            break
    return ot

items = tolist(IN[0])
splitting_items = tolist(IN[1])

# Assign output
OUT = split_list(items, splitting_items)

1 Like

I found this python function, it works as charm:

indices = IN[1]
list = IN[0]

from itertools import izip, chain
def partition(list, indices):
    pairs = izip(chain([0], indices), chain(indices, [None]))
    return (list[i:j] for i, j in pairs)

OUT = partition(list, indices)
2 Likes

Similar situation found in this post. Might be simpler since you can define your “break” items separately.

1 Like

I tried it before writting this new post but it seems I did something wrong with it, many thanks

It would have to be modified to match your condition, but that’s fine. Just suggesting another option.

1 Like

well this last script is not doing all the process I wanted because I have to write more python code to feed the input of indexes, which match with a list of search inputs wanted, so in reality my code is quite longer but I would like it shortened, but still learning

That’s where I think you could bypass the index part and just look for matches directly.

what I see in the output list is not correct…because only a sublist can start with cat and dog strings, did not work

An alternative:

def group(lst,criteria):
    out = []
    it = iter(lst)
    for i in lst:
        if next(it) in criteria: out.append([i])
        else: out[-1].append(i)
    return out

OUT = group(IN[0],IN[1])
2 Likes

If I’m reading this right, it only works for groups of 2. However, I used your idea of using iter:

def split_list(items, values):
    ot = []
    idx = [i for i,v in enumerate(items) if v in values]+[None]
    it = iter(idx)
    next(it)
    return [items[i:next(it)] for j,i in enumerate(idx) if j != len(idx)-1]

I don’t have Revit readily available, so here’s a screenshot in the stock Python shell:

Actually I guess since I’m using enumerate the iter is superfluous. Also forgot to take out the ot from before I put in the list comprehension as the return value.

def split_list(items, values):
    idx = [i for i,v in enumerate(items) if v in values]+[None]
    return [items[i:idx[j+1]] for j,i in enumerate(idx) if j != len(idx)-1]

Edit:
Okay, okay, I’m done after this one. This is more straight forward to read.

def split_list(items, values):
    idx = [i for i,v in enumerate(items) if v in values]+[None]
    z = zip(idx,idx[1:])
    return [items[i:j] for i,j in z]
2 Likes

I get this warning
image

it seems it works great, but for some reason I noticed that the first sublist generated disappeared, and the rest of the original list seems same, I mean if your original list does not start with the key, you lose the subgroup with items until you get the first key, so I would have to start the original list with a key to do not lose items

Try this one:

def group(lst,criteria):
    out = []
    it = iter(lst)
    for i in lst:
        if next(it) in criteria:
            out.append([i])
        else:
            try: out[-1].append(i)
            except: out.append([i])
    return out
2 Likes

thank you very much, second time in a week you give me great answer

1 Like

hello,
another solutions with

  • a python generator
  • a recursive function

python generator

import sys

def r_split(lst):
    out = []
    for i in lst:
        out.append(i)
        if i in s_values:
            yield out
            out = []
    if out:
        yield out
        
input_lst = IN[0]
s_values = IN[1]

OUT = list(r_split(input_lst))

recursive function

import sys

def r_split2(lst):
    try:
        min_idx = min(lst.index(x) for x in s_values if lst.count(x) > 0)
    except Exception as ex:
        return [lst] if lst else []
    out, rest = lst[:min_idx  + 1], lst[min_idx  + 1:]
    return [out] + r_split2(rest)
    
input_lst = IN[0]
s_values = IN[1]

OUT = r_split2(input_lst)
2 Likes

hello, it looks amazing many thanks, but it is not doing what I was expecting. The original list is flatten and the sublists are created each time the key list is found on the list, so first item of the sublists must be a key of the list but it is not happening here