Say I have a series of entries in a list of tuples like this:
TRUE = 1
listOfTuples = [('selectable', 'frequency'), ('color', 'green'), ('item', '10 Hz'),
('value', 10), ('align', 'left'), ('hidden', TRUE), ('item', '20 Hz'),
('value', 20), ('align', 'right'), ('item', '50 Hz'), ('value', 50),
('item', '100 Hz'), ('value', 100), ('textColor', 0xFF0000)]
Now I want to extract a list with individual item entries like this:
[(('item', '10 Hz'), ('value', 10), ('align', 'left'), ('hidden', TRUE)),
(('item', '20 Hz'), ('value', 20), ('align', 'right')),
(('item', '50 Hz'), ('value', 50)),
(('item', '100 Hz'), ('value', 100), ('textColor', '0xFF0000'))]
The delimiting keyword to identify a sublist is always item or the end of the list. There can be an arbitrary number of tuples between two adjacent delimiters. The content of the list before the first occurrence of item is to be ignored. The detection of the keyword item should be case insensitive.
I am not a Python afficionado, so I don’t know how to apply something like list comprehension (if that’s actually possible), given that I need to extract lists between delimiters. Of course I can do it the pedestrian way by traversing through the list, identifying the positions of the keyword in the tuples and then extracting the sublists but I was hoping for a more elegant solution.
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
You can use itertools.groupby for the task:
from itertools import accumulate, groupby
TRUE = 1
listOfTuples = [
("selectable", "frequency"),
("color", "green"),
("item", "10 Hz"),
("value", 10),
("align", "left"),
("hidden", TRUE),
("item", "20 Hz"),
("value", 20),
("align", "right"),
("item", "50 Hz"),
("value", 50),
("item", "100 Hz"),
("value", 100),
("textColor", 0xFF0000),
]
a = accumulate(t == "item" for t, *_ in listOfTuples)
out = []
for _, g in groupby(zip(a, listOfTuples), lambda k: k[0]):
l = tuple(t for _, t in g)
if l[0][0] == "item":
out.append(l)
print(out)
Prints:
[
(("item", "10 Hz"), ("value", 10), ("align", "left"), ("hidden", 1)),
(("item", "20 Hz"), ("value", 20), ("align", "right")),
(("item", "50 Hz"), ("value", 50)),
(("item", "100 Hz"), ("value", 100), ("textColor", 16711680)),
]
Method 2
You can use tuple() function to transform lists into tuples, so you will be able to append all of the tuples inside listOfTuples variable into the output that you need:
TRUE = 1
lot = [('selectable', 'frequency'), ('color', 'green'), ('item', '10 Hz'),
('value', 10), ('align', 'left'), ('hidden', TRUE), ('item', '20 Hz'),
('value', 20), ('align', 'right'), ('item', '50 Hz'), ('value', 50),
('item', '100 Hz'), ('value', 100), ('textColor', 0xFF0000)]
l = [[]]
for i in lot:
if i[0]=='item':
l[-1] = tuple(l[-1])
l.append([])
l[-1].append(i)
print(l[1:])
Output:
[(('item', '10 Hz'), ('value', 10), ('align', 'left'), ('hidden', 1)), (('item', '20 Hz'), ('value', 20), ('align', 'right')), (('item', '50 Hz'), ('value', 50)), [('item', '100 Hz'), ('value', 100), ('textColor', 16711680)]]
The only disadvantage of this method is that you need to remove the first element of the output list of tuples, so it may doesn’t work in certain situations.
Method 3
OK, so in the meantime I learned more about list comprehension, the use of _, * and more than one index in a for loop. Besides, a good friend came up with the following solution:
indices = [i for i, value in enumerate(listOfTuples)
if value[0].casefold() == "item"] + [len(listOfTuples)]
out = [listOfTuples[i:j] for i,j in zip(indices[:-1], indices[1:])]
Indices contains all occurrences of item and the index of the last item in the list. Using indices in a shifted fashion (zip(indices[:-1], indices[1:])) allows then the straightforward construction of out.
Method 4
A constructive approach with no conditionals. Use itertools.groupby to group per 'item'. Grouping is a True/False classification process (ideally gives raise to an alternate sequence of True/False values!), so you can slice by block of 2 and chain them together.
The split-right (initial) condition means that the 1st group with item is found on the “right” and what is on the “left” can be forgotten ([1:]-part).
import itertools as it listOfTuples = # see question # group by item & discard 1st group due to split-right condition lst = tuple(tuple(grps) for _, grps in it.groupby(listOfTuples, lambda p: p[0] == 'item'))[1:] # chain the slices lst_new = [tuple(it.chain.from_iterable(lst[2*i:2*(i+1)])) for i in range(len(lst)//2)] print(lst_new)
Same idea but with generators
... lst = (tuple(grps) for _, grps in it.groupby(listOfTuples, lambda p: p[0] == 'item')) next(l) # split-right initial condition l1, l2 = it.tee(lst) n = len(tuple(l2)) lst_new = [tuple(it.chain.from_iterable(it.islice(l1, 0, 2, None))) for _ in range(n//2)]
A (logical different) index based approach
... # indices of the items iter_ = filter(None, (tuple(grps)[0][0] if check else None for check, grps in it.groupby(enumerate(listOfTuples), lambda p: p[1][0] == 'item'))) # zip-stuffs it1, it2 = it.tee(iter_) next(it2) it2 = it.chain(it2, iter((len(listOfTuples),))) # apply the slices and cast to tuples lst_new = list(map(tuple, map(listOfTuples.__getitem__, it.starmap(slice, zip(it1, it2))))) print(lst_new)
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0