Transform a flat list to nested List - python

Using Python
I want the following:
[1, 2, 2, 1, 2, 3, 2, 3]
To be transformed into:
[1, 2, 2, [1, 2, 3], 2, 3]
Rules: Go through each item in the list. If we hit a 2 followed by a 1 created a list and include that 1 in that list until we hit a 3, include that 3, then close the list and continue. It's like if 1 was 3 were parenthesis.
I'm not very good with recursive algorithms which I think might be needed in this case.
Thanks as always.

Still keeping in mind that #Walter is correct in his comments to your question, this is a silly implementation of what you asked for, inspired by the final bit of your question, in which you suggest that 1 and 3 could just be replaced with [1 and 3].
>>> import re
>>> s = repr([1, 2, 2, 1, 2, 3, 2, 3])
>>> s = re.sub('1', '[1', s)
>>> s = re.sub('3', '3]', s)
>>> l = eval(s)
>>> l
[[1, 2, 2, [1, 2, 3], 2, 3]]
What it does is working on the representation of the list (a string) and substituting the way you suggested using regular expressions. Finally, it evaluate the string (getting back a list).
I call this implementation "silly" because it does the trick, but it's ugly and truly unpythonic. That said, it does the trick, so if you are simply using it for a one-off conversion of some data you need to use...
HTH!

def whatever(a):
b = []
tmp = []
last = None
for elem in a:
if tmp:
tmp.append(elem)
if elem == 3:
b.append(tmp)
tmp = []
elif last == 2 and elem == 1:
tmp.append(1)
else:
b.append(elem)
last = elem
return b
print whatever([1, 2, 2, 1, 2, 3, 2, 3])

That is an entertaining problem! Here is my solution:
def treeize(treeizable, tree=None, stopper=object()):
if tree is None:
tree = []
if treeizable[:1] == [stopper]:
tree.append(treeizable.pop(0))
return tree
elif treeizable[0:2] == [2, 1]:
tree.append(treeizable.pop(0))
subtree = []
treeize(treeizable, subtree, stopper=3)
tree.append(subtree)
return treeize(treeizable, tree, stopper)
elif treeizable:
tree.append(treeizable.pop(0))
return treeize(treeizable, tree, stopper)
else:
return tree
This function receives a flat list treeizable that should be converted to a nested list tree. The stopper parameter marks when the current list is done - being this a nested or the toplevel list. (Since the default value of stopper is an instance of object, it is impossible that there will be a stopper on a list called with the default value, because instances of object are different between themselves).
def treeize(treeizable, tree=None, stopper=object()):
For easing our work, the default value of tree is None and, if it has the default value, then it is set to a list. It is made because it is problematic to have mutable values as default parameter objects. Also, it would be annoying to have to type the function with an empty list everytime.
if tree is None:
tree = []
If the first value of the flat list is the "stopper", then it is added to the tree and the tree is returned. Note that, by using treeizable.pop(0) I am actually removing the value from the flat list. Since the stopper is only set when defining a nested list so, when we found it, no more need to be done: the "subtree" (that is, the nested list) is complete. Also, note that I get the first element of the list with a slice of the list. I made it because it is boring to have to type if treeizable and treeizable[0] == stopper. Since slicing does not have problems with inexistent indexes, I got the slice and compared it to another list made in place with only the stopper:
if treeizable[:1] == [stopper]:
tree.append(treeizable.pop(0))
return tree
If the beginning of the list is the "beginner", then I pop the first element from the list, append it to the tree and create a new tree - that is, a empty list. Now I call treeize() with the remaining list and the empty subtree, also passing 3 as the stopper value. treeize() will recursively generate a new tree that I append to my initial tree. After that, just call treeize() with the remaining of the list (which does not contain the elements of the subtree anymore) and the original list. Note that the stopper should be the same stopper received by the original call.
elif treeizable[0:2] == [2, 1]:
tree.append(treeizable.pop(0))
subtree = []
treeize(treeizable, subtree, stopper=3)
tree.append(subtree)
return treeize(treeizable, tree, stopper)
If none of the previous conditions (the first element is a stopper, the beginning of the list is [2, 1]) is true, then I verify if there is something in the list. In this case, I pop the first element, add to the tree and call treeize() with the rest of the list.
elif treeizable:
tree.append(treeizable.pop(0))
return treeize(treeizable, tree, stopper)
In the case that not even the previous condition is true... then we have an empty list. This means that all elements were put in the tree. Just return the tree to the user:
else:
return tree
This seems to have worked:
>>> treeize.treeize([1, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2, 3, 2, 4, 5, 3, 3, 2, 3, 4])
[1, 2, 2, [1, 2, 2, [1, 2, 2, [1, 2, 3], 2, 4, 5, 3], 3], 2, 3, 4]
Your question has a taste of homework. In principle, we should not answer it but it is so interesting that I couldn't help myself :) If it is a homework, however, do not try to use this solution as your because it would be wrong and your teacher would surely find it on Google :P

I like state machines:
from itertools import izip, tee
def pairwise(iterable):
a, b = tee(iterable)
next(b)
return izip(a, b)
class Flat(object):
def append_next(self, alist, e0, e1):
alist.append(e0)
if e0 == 2 and e1 == 1:
alist.append([])
self.__class__ = Nested
def append_last(self, alist, e):
alist.append(e)
class Nested(object):
def append_next(self, alist, e0, e1):
alist[-1].append(e0)
if e0 == 3:
self.__class__ = Flat
def append_last(self, alist, e):
alist[-1].append(e)
def nested(flat_list):
if len(flat_list) <= 1:
return list(flat_list)
state = Flat()
nested_list = []
for x, y in pairwise(flat_list):
state.append_next(nested_list, x, y)
state.append_last(nested_list, y)
return nested_list
s = [1, 2, 2, 1, 2, 3, 2, 3]
print nested(s)
gives:
[1, 2, 2, [1, 2, 3], 2, 3]
But this might be more pythonic:
def nested(flat_list):
if len(flat_list) <= 1:
return list(flat_list)
pairs = pairwise(flat_list)
nested_list = []
while True:
for x, y in pairs:
nested_list.append(x)
if x == 2 and y == 1:
nested_list.append([])
break
else:
nested_list.append(y)
break
for x, y in pairs:
nested_list[-1].append(x)
if x == 3:
break
else:
nested_list[-1].append(y)
break
return nested_list

Pease bear with me - it is 2:50(night) - here is my version - not very beatiful but it works pretty well for me:
def buildNewList(inputList):
last = 0
res = []
for i,c in enumerate(inputList):
if i == 0:
prev = c
if i < last:
continue
if c == 1 and prev == 2:
if 3 in inputList[i:]:
last = i + 1 + inputList[i:].index(3)
res.append(buildNewList(inputList[i: last]))
else:
last = len(inputList)
res.append(buildNewList(inputList[i:len(inputList)]))
else:
res.append(c)
prev = c
return res
l1 = buildNewList([1, 2, 2, 1, 2, 3, 2, 3])
>>> [1, 2, 2, [1, 2, 3], 2, 3]
l2 = buildNewList([1, 2, 2, 1, 2, 3, 2, 1, 2, 3])
>>> [1, 2, 2, [1, 2, 3], 2, [1, 2, 3]]
l3 = buildNewList([1,2,3,1,2,3])
>>> [1, 2, 3, 1, 2, 3]
l4 = buildNewList([1,2,1,1,2,1])
>>> [1, 2, [1, 1, 2, [1]]]

Related

Why does every function make the reversed iterator empty?

I was writing code like this:
nums = [4, 1, 3, 2]
rev = reversed(nums)
rev2 = list(rev)
print(list(rev))
print(rev2)
the result:
[]
[2, 3, 1, 4]
So we can see that rev has no elements after the list function; it's not only Python's function does that; it also happened with the custom one:
def become_list(iter):
result = []
for i in iter:
result.append(i)
return result
nums = [4, 1, 3, 2]
rev = reversed(nums)
rev2 = become_list(rev)
print(list(rev))
print(rev2)
the result:
[]
[2, 3, 1, 4]
I wonder why this happened.
For the question, a code in my test showed that it was trying to call two sorted rev in one line:
nums = [4, 1, 3, 2]
rev = reversed(nums)
print(sorted(rev) == sorted(rev))
The answer is False. Because they are [1, 2, 3, 4] and []. Though I have figured it out, why they are not equal, I still get confused and wonder why this happened.

How to insert an element in a list after using del in python

def my_len(lst):
mylen = 0
for i in lst:
mylen += 1
return mylen
def insrt(lst, what, where):
length = my_len(lst)
tmp = [0] * (length + 1)
for i in range(0, where):
tmp[i] = lst[i]
tmp[where] = what
for i in range(where, length + 1):
tmp[i] = lst[i - 1]
return tmp
def move(lst, from_index, to_index):
a = lst[from_index]
del lst[from_index]
insrt(lst, a, to_index)
return lst
my intention was if the original list input is [1, 2, 3, 4, 5, 6], then print(move(lst, 3, 1) will give [1, 4, 2, 3, 5, 6], but my code gives [1, 2, 3, 5, 6]. Please explain how to fix it.
NB. See the end of the answer for an alternative interpretation of the move
If you want the item to end up in the defined to_index, you can simply use list.insert and list.pop:
def move(lst, from_index, to_index):
lst.insert(to_index, lst.pop(from_index))
examples:
l = [1, 2, 3, 4, 5, 6]
move(l, 1, 3)
print(l)
[1, 3, 4, 2, 5, 6]
l = [1, 2, 3, 4, 5, 6]
move(l, 3, 1)
print(l)
[1, 4, 2, 3, 5, 6]
l = [1, 2, 3, 4, 5, 6]
move(l, 3, 3)
print(l)
[1, 2, 3, 4, 5, 6]
NB. your function should probably either return a copy, or modify the list in place, not both. (here I chose to modify in place, see below for a variant).
copy variant:
def move(lst, from_index, to_index):
lst = lst.copy()
lst.insert(to_index, lst.pop(from_index))
return lst
l = [1, 2, 3, 4, 5, 6]
l2 = move(l, 1, 3)
print(l)
[1, 2, 3, 4, 5, 6]
print(l2)
[1, 3, 4, 2, 5, 6]
alternative interpretation: move to the position as defined before the move
In this different interpretation, the item moves to the position as defined if the insertion occured before the deletion.
In this case we need to correct the insertion position if it is greater than the initial position.
We can take advantage of the boolean/integer equivalence by subtracting to_index>from_index (1 if True, 0 otherwise)
def move(lst, from_index, to_index):
lst.insert(to_index-(to_index>from_index), lst.pop(from_index))
l = [1, 2, 3, 4, 5, 6]
move(l, 1, 3)
print(l)
# [1, 3, 2, 4, 5, 6]
This would work,
def move(lst, from_index, to_index):
new_lst = lst[:to_index] + [lst[from_index]] + [item for item in lst[to_index:]]
new_lst.pop(from_index + 1)
return new_lst
lst = [1, 2, 3, 4, 5, 6]
move(lst, 3, 1)
Output -
[1, 4, 2, 3, 5, 6]
The main problem you're seeing right now relates to how your insrt function works, as compared to how you're calling it.
The way you've written it, insert returns a new list containing the values from the old list plus one new value at a particular index. But when you call it in move, you seem to be expecting it to modify the given list in place. It doesn't do that. You need to change at least one of the two functions so that they match up. Either make insrt work in-place, or make move save the value returned by it rather than throwing it away.
Anyway, here's a very simple tweak to your move function that makes it work properly with the insrt function as you've shown it:
def move(lst, from_index, to_index):
a = lst[from_index]
del lst[from_index]
new_lst = insrt(lst, a, to_index) # save the new list after the insert
return new_lst # and then return it
I'd note that using del on the earlier line already modifies the lst passed in, so this design may not make a whole lot of sense. You might want to modify insrt to work in place instead, to be consistent. That would look something like this (which would work with your original move):
def insrt(lst, what, where):
length = my_len(lst)
lst.append(None) # add a dummy value at the end of the list
for i in range(length-1, where, -1): # iterate backwards from the end
lst[i] = lst[i - 1] # shift each value back
lst[where] = what # plug in the new value
# no return any more, this version works in place

Why is my code not following the command?

def pairs(x):
for num in x:
if num == num :
return x
y = [2, 2, 2, 2, 2, 4, 3, 3]
pairs (y)
print (y)
this is returning [2, 2, 2, 2, 2, 4, 3, 3]
but I want to return [2,2,3]
I've tried 3 codes already other than this one
help me
Your code seems to be intended to find all the numbers that exist in pairs in the list. The best way would be (for a sorted list) to just cycle through the list and check successive elements.
Your code just matches if the current number is the same as the current numbers always returns true and returns all elements. A correct Code might be:
y = [2, 2, 2, 2, 2, 4, 3, 3]
y=sorted(y) # Sorts the given list
def pairs(arr):
i = 0 # Counter variable
res = [] #result list
while i < len(arr)-1:
if arr[i] == arr[i+1]:
res.append(arr[i]) # If the successive elements are the same, add
# it to the result array and since we have already
# checked the next element, increment the counter by
# two
i+=2
else:
i+=1 # If the elements are different we need to check the
# next element as well so increment by 1
return res
print(pairs(y))
You are comparing the element with itself which is always true. Here is the correct logic
y = [2, 2, 2, 2, 2, 4, 3, 3]
filt = []
i=0
while (i< (len(y)-1)):
if y[i] == y[i+1]:
filt.append(y[i])
i+=1
i+=1
print(filt)

Python List remove multiple items

I am trying to remove multiple occurrences of a value from a list.The output does not remove any of the desired items.
def rem(a,li):
try:
while a in li == True:
li.remove(a)
print('Updated list: ',li)
except ValueError:
print(a,' is not located in the list ',li)
Example to trying out function:
L = [1,2,3,45,3,2,3,3,4,5]
rem(2,L)
Output: Updated list: [1, 2, 3, 45, 3, 2, 3, 3, 4, 5]
There are 2 mistakes in your code. The first one is
while a in li == True: In fact, this check always returns False since li == True is False.
It should actually be while (a in li) == True:, or while a in li:
Also, if you are trying to delete only repeated occurrences of a (i.e. leave the first occurrence of a in) then list comprehension won't suit your needs. You will have to add an additional check inside your rem() function that catches the first occurrence of a and then executes your loop:
def rem(a, li):
list_length = len(li)
i = 0
while (li[i] != a) and (i < list_length):
i += 1 # skip to the first occurrence of a in li
i += 1 # increment i
while i < list_length:
if li[i] == a:
del li[i]
print('Updated list: ', li)
list_length -= 1 # decrement list length after removing occurrence of a
else:
i += 1
The code snippet above does not cover the edge cases where the list is empty, or the case a is not in the list. I'll leave those exercises to you.
Try changing the while condition to while a in li
def rem(a,li):
try:
while a in li:
li.remove(a)
print('Updated list: ',li)
except ValueError:
print(a,' is not located in the list ',li)
L = [1,2,3,45,3,2,3,3,4,5]
rem(2,L)
In general, if you want to remove duplicates from a list then you can use the built-in set.
Assuming you want to remove all instances of a from L, you could also just use a simple list comprehension:
def rem(a,li):
return [x for x in li if x != a]
L = [1,2,3,45,3,2,3,3,4,5]
print(rem(2,L))
Which Outputs:
[1, 3, 45, 3, 3, 3, 4, 5]
That would be a better job for a list comprehension.
L[:] = [a for a in L if a not in (2,)]
Assigning to a slice will mutate the list in place.
I'm updating my answer to account for the various interpretations your question allows
and also to make it more general by accepting also strings and multiple values to be removed at once.
def removed(items, original_list, only_duplicates=False, inplace=False):
"""By default removes given items from original_list and returns
a new list. Optionally only removes duplicates of `items` or modifies
given list in place.
"""
if not hasattr(items, '__iter__') or isinstance(items, str):
items = [items]
if only_duplicates:
result = []
for item in original_list:
if item not in items or item not in result:
result.append(item)
else:
result = [item for item in original_list if item not in items]
if inplace:
original_list[:] = result
else:
return result
Docstring extension:
"""
Examples:
---------
>>>li1 = [1, 2, 3, 4, 4, 5, 5]
>>>removed(4, li1)
[1, 2, 3, 5, 5]
>>>removed((4,5), li1)
[1, 2, 3]
>>>removed((4,5), li1, only_duplicates=True)
[1, 2, 3, 4, 5]
# remove all duplicates by passing original_list also to `items`.:
>>>removed(li1, li1, only_duplicates=True)
[1, 2, 3, 4, 5]
# inplace:
>>>removed((4,5), li1, only_duplicates=True, inplace=True)
>>>li1
[1, 2, 3, 4, 5]
>>>li2 =['abc', 'def', 'def', 'ghi', 'ghi']
>>>removed(('def', 'ghi'), li2, only_duplicates=True, inplace=True)
>>>li2
['abc', 'def', 'ghi']
"""
You should be clear about what you really want to do, modify an existing list, or make a new list with
the specific items missing. It's important to make that distinction in case you have a second reference pointing
to the existing list. If you have, for example...
li1 = [1, 2, 3, 4, 4, 5, 5]
li2 = li1
# then rebind li1 to the new list without the value 4
li1 = removed(4, li1)
# you end up with two separate lists where li2 is still pointing to the
# original
li2
# [1, 2, 3, 4, 4, 5, 5]
li1
# [1, 2, 3, 5, 5]
This may or may not be the behaviour you want.
Just keep track of index no and use del
Simple approach:
L = [1,2,3,45,3,2,3,3,4,5]
def rem(a,li):
for j,i in enumerate(li):
if a==i:
del li[j]
return li
print(rem(2,L))
output:
[1, 3, 45, 3, 3, 3, 4, 5]

Python list popping and appending

I am learning python and have a working code (shown below). However, I would like to know if there's a better way to rewrite it below.
What I am trying to do is to match list A that is passed to a method against a predefined list B. If an item in list A contains an item in list B, I'd like to move it to the end of list A. Here's an example:
# example 1
a = [1, 2, 3, 4, 5]
sanitized_list = sanitize(a) # [3, 4, 5, 1, 2]
# example 2
a = [3, 6, 1, 7, 4]
sanitized_list = sanitize(a) # [3, 6, 7, 4, 1]
def sanitize(arg):
# predefined list
predefined_list = [1, 2]
for item in predefined_list:
try:
# check to see if 'arg' contain any item
# in the predefined list
i = arg.index(item)
# save the value of arg[i]
j = arg[i]
# remove "j" from "arg"
arg.pop(i)
# append item to end of "arg"
arg.append(j)
except ValueError:
pass
return arg
You can use sorting; simply sort on the result of a containment test; False is sorted before True, but your order otherwise remains stable. You can make b a set to make the containment test faster:
def sanitize(lst):
# predefined list
b = {1, 2}
return sorted(lst, key=lambda v: v in b)
I made b a set here, but that is optional (but faster if you do).
So for each element in lst, if that element is in b sort it after anything that is not in b, but keep their relative order otherwise.
Note that rather than list I used a different name for the argument here; you don't want to shadow the built-in type.
Demo:
>>> a = [1, 2, 3, 4, 5]
>>> def sanitize(lst):
... # predefined list
... b = {1, 2}
... return sorted(lst, key=lambda v: v in b)
...
>>> sanitize(a)
[3, 4, 5, 1, 2]
Better to create always a new list:
def sanitize(l):
b = set([1, 2])
result = ([], [])
for item in l:
result[item in b].append(item)
result[0].extend(result[1])
return result[0]
Operations between lists are easier if transform them into sets.
a = [1, 2, 3, 4, 5]
def sanitize(l):
b = [1, 2]
try:
sanitized = list(set(l) - set(b)) + b
return sanitized
except ValueError:
return None

Categories

Resources