Recursive function to count all items in a nested list? - python

I'm trying to create a method that will count all the items in a nested list. So count([[3, 2] , [2]]) == 3. However, it's a Class attribute so I can't just simply do:
def count(L, target):
s = 0
for i in L:
if isinstance(i, list):
s += count(i, target)
else:
if i == target:
s += 1
return s
Rather, I tried to do this, but I get a max recursion depth error. I'm not sure why. Before you look at the code, there's a few things to keep in mind: (1) I expect the base list given to only contain lists so it will have the format: [ [], ]. Also (2) the sub lists will not contain anything except items: [ [item, item], [item] ] :
def count(self, stack=None):
n = 0
if stack:
n += len(stack)
else:
for i in self._items:
if isinstance(i, list):
n += self.count(i)
return n

if stack:
Empty lists are considered false in a boolean context. You want if stack is not None.
Why use recursion, though? You don't need it.
def count(self):
return sum(len(item) for item in self._items)

If your lists are only nested one level deep, this is easy.
class MyClass:
def __init__(self, items):
self.items = items
def count(self):
return sum(len(x) for x in self.items)
a = MyClass([[3,2],[2]])
b = MyClass([[1,2,3],[4,5,6],[7],[]])
print(a.count()) # 3
print(b.count()) # 7

Related

How to find two items of a list with the same return value of a function on their attribute?

Given a basic class Item:
class Item(object):
def __init__(self, val):
self.val = val
a list of objects of this class (the number of items can be much larger):
items = [ Item(0), Item(11), Item(25), Item(16), Item(31) ]
and a function compute that process and return a value.
How to find two items of this list for which the function compute return the same value when using the attribute val? If nothing is found, an exception should be raised. If there are more than two items that match, simple return any two of them.
For example, let's define compute:
def compute( x ):
return x % 10
The excepted pair would be: (Item(11), Item(31)).
You can check the length of the set of resulting values:
class Item(object):
def __init__(self, val):
self.val = val
def __repr__(self):
return f'Item({self.val})'
def compute(x):
return x%10
items = [ Item(0), Item(11), Item(25), Item(16), Item(31)]
c = list(map(lambda x:compute(x.val), items))
if len(set(c)) == len(c): #no two or more equal values exist in the list
raise Exception("All elements have unique computational results")
To find values with similar computational results, a dictionary can be used:
from collections import Counter
new_d = {i:compute(i.val) for i in items}
d = Counter(new_d.values())
multiple = [a for a, b in new_d.items() if d[b] > 1]
Output:
[Item(11), Item(31)]
A slightly more efficient way to find if multiple objects of the same computational value exist is to use any, requiring a single pass over the Counter object, whereas using a set with len requires several iterations:
if all(b == 1 for b in d.values()):
raise Exception("All elements have unique computational results")
Assuming the values returned by compute are hashable (e.g., float values), you can use a dict to store results.
And you don't need to do anything fancy, like a multidict storing all items that produce a result. As soon as you see a duplicate, you're done. Besides being simpler, this also means we short-circuit the search as soon as we find a match, without even calling compute on the rest of the elements.
def find_pair(items, compute):
results = {}
for item in items:
result = compute(item.val)
if result in results:
return results[result], item
results[result] = item
raise ValueError('No pair of items')
A dictionary val_to_it that contains Items keyed by computed val can be used:
val_to_it = {}
for it in items:
computed_val = compute(it.val)
# Check if an Item in val_to_it has the same computed val
dict_it = val_to_it.get(computed_val)
if dict_it is None:
# If not, add it to val_to_it so it can be referred to
val_to_it[computed_val] = it
else:
# We found the two elements!
res = [dict_it, it]
break
else:
raise Exception( "Can't find two items" )
The for block can be rewrite to handle n number of elements:
for it in items:
computed_val = compute(it.val)
dict_lit = val_to_it.get(computed_val)
if dict_lit is None:
val_to_it[computed_val] = [it]
else:
dict_lit.append(it)
# Check if we have the expected number of elements
if len(dict_lit) == n:
# Found n elements!
res = dict_lit
break

Seeking elements in nested lists

I am trying to make a function that is able to find a element in a nested list.
That is what if got so far:
def in_list(ele, lst, place):
if ele == lst[place]:
return True
else:
for i in range(len(lst)):
in_list(ele, lst[i], place)
This is what i input:
a=[[1,2],[3,4]]
if in_list(2,a,1)==True:
print("True")
the variable "place" is the place in the list where the element should be found...
Now somehow it doesn't understand this line if ele == lst[place]
this is the error message: TypeError: 'int' object is not subscriptable
Thanks in advance
There are two issues in the last line
def in_list(ele, lst, place):
if ele == lst[place]:
return True
else:
for i in range(len(lst)):
in_list(ele, lst[i], place)
lst[i] is an integer (assuming lst is a list of integers), which is why you get your error.
The other issue is that you're not returning anything from the else branch.
Something like this might work better in case of arbitrary, but uniform, nesting:
def recursive_contains(item, lst):
if len(lst) == 0:
return False
elif isinstance(lst[0], collections.Iterable):
return any(recursive_contains(item, sublist) for sublist in lst)
else:
return item in lst
for arbitrary non-uniform nesting, perhaps something like this:
def recursive_contains(item, lst):
if not isinstance(lst, collections.Iterable):
return item == lst
for val in lst:
if item == val:
return True
elif isinstance(val, collections.Iterable):
if recursive_contains(item, val):
return True
return False
of course if you only have 2 levels (all elements of lst are lists of int), you could simply say:
if ele in sum(lst, []):
...
which uses sum to flatten the list first.
The other answers well define the mistake in your code.
Just to reiterate that you were assuming each element in the list as a nested list and subscripting it like - elem[place].
You can't subscript a primitive type such as integer and hence the error.
Refer the below code to handle nesting.
Note - You Don't require the 3rd parameter of place, more appropriately you wouldn't the place if you are searching.*
def fetch(data, l):
for element in l:
if type(element) == list:
if fetch(data, element):
return True
else:
if element == data:
return True
return False
On further thought you are looking for an element that should be only at "place" index of any of the nested lists.
Refer to the snippet below for that-
def fetch(data, l,place):
if data == l[place]:
return True
else:
for element in l:
if type(element) == list and fetch(data,element,place):
return True
return False
Note- Only call fetch again if the element is a list.
a = [[1, 2], [3, 4]]
def inlist(e, l, p):
for lists in range(len(l)):
print("array:", l[lists])
print("Looking for", e)
print("In position", p)
print(l[lists][p])
if l[lists][p] == e:
print(True, ": The element ", e, " is in the list n.", lists, " at the place ", p)
return True
inlist(2, a, 1)
Output
array: [1, 2]
Looking for 2
In position 1
2
True : The element 2 is in the list n. 0 at the place 1

Python count list items through a function [duplicate]

This question already has answers here:
How do I count the occurrences of a list item?
(29 answers)
Closed 5 years ago.
In short, I am making a function that takes 2 arguments 'sequence' and 'item'. The 'sequence' can be anything - an integer, a string, or a list containing integers or strings. I am trying to make a function that counts the amount of times 'item' occurs in the 'sequence'. Please take into account, I am still a newbie at Python. A simple answer would be very much appreciated.
This is what I have so far
def count(sequence, item):
found = 0
if sequence == item:
found += 1
return found
else:
for num in sequence:
if num == sequence:
found += 1
return found
else:
return False
print count([4,'foo',5,'hi'], 5)
The else part of the code is meant to be enabled if the sequence is something like a list. I was thinking I should loop through the list using for and do the same thing - but it's not working as it keeps returning False which follows the second 'else' statement. Any idea how I can do this? For clarification, the output in the example above should be 1 because '5' occurs once in the list.
len([i for i in sequence if item in i])
EDIT:
Changed return to return the number of occurrences instead of True/False
You are checking each item in sequence and evaluating, if it doesn't equal item it will return False right away, that is why you're getting False always.
You need to have your loop increment found and once the loop is done then use if/else to check whether found == 0 or not. A simple example:
def count(sequence, item):
found = 0
if sequence == item:
return 1
for num in sequence:
if num == item:
found += 1
return found
If you learned list-comprehension already, you can use it as the following:
def count(sequence, item):
if sequence == item:
return 1
return sum(1 for x in sequence if x == item)
If we use your code as a basis, we get the following if we want it to work.
You told it to loop over sequence, asking when num is equal to sequence, but one part of the list is not equal to the whole list. To fix this, we use an index. So we say, if item is equal to sequence[i], where is is an index, then we do found +=1. You also had the return statement in the for-loop, while you should get it outside of the for-loop, because it because breaks the loop. I hope this helps.
def count(sequence, item):
found = 0
if sequence == item:
found += 1
return found
else:
for num in range(len(sequence)):
if item == sequence[num]:
found += 1
if found > 0:
return found
else:
return False
You can use a recursive function to repeat calls on the count function when the first argument is a list, or use a simple == when it is not. This can equally handle nested lists by walking through the nesting recursively:
def count(sequence, item):
found = 0
if isinstance(sequence, list):
for x in sequence:
found += count(x, item)
else:
return int(item == sequence)
return found
print count([4,'foo',5,'hi', [5]], 5)
# 2
print count(5, 5)
# 1
I'd use collections.Sequence (or collections.abc.Sequence in Python 3) to determine if the items are sequences (lists, tuples, etc). If you just want to know if the item is in the list, which is what your code seems to be doing in spite of the name count:
from collections import Sequence
# In Python 3 it would be
# from collections.abc import Sequence
def count(sequence, item):
for i in sequence:
if item == i or (isinstance(i, Sequence) and item in i):
return True
return False
If you want to actually count the number of appearances of item in sequence:
from collections import Sequence
# In Python 3 it would be
# from collections.abc import Sequence
def count(sequence, item):
found = 0
for i in sequence:
if item == i or (isinstance(i, Sequence) and item in i):
found += 1
return found
There could be the possibility that a list within sequence contains in turn deeper sublists, and so on, in which case you may want a recursive algorithm. For finding if the element is contained you could go with:
from collections import Sequence
# In Python 3 it would be
# from collections.abc import Sequence
def count(sequence, item):
for i in sequence:
if item == i or (isinstance(i, Sequence) and count(i, item)):
return True
return False
For actually counting:
from collections import Sequence
# In Python 3 it would be
# from collections.abc import Sequence
def count(sequence, item):
found = 0
for i in sequence:
if item == i:
found += 1
elif isinstance(i, Sequence) and item in i:
found += count(i, item)
return found
PD: Note that strings are considered sequences, and therefore "b" in "abc" and "b" in ["a", "b", "c"] are both true. If you don't want "b" being considered an element of "abc" (i.e. you want to consider strings atomically, like numbers), you'd have to tune the code a bit. You could replace isinstance(i, Sequence) with isinstance(i, (list, tuple)), or with isinstance(i, Sequence) and not isinstance(i, basestring) (or, in Python 3, isinstance(i, Sequence) and not isinstance(i, (str, bytes))).

Python overwriting and resizing list

I want to create a list in python of a fixed size, let's so 3 to begin with. I have a method that writes data to this list every time the method is called, I want to add this to the list until the list is full, once the list is full it should start overwriting the data in the list in ascending order (e.g. starting with element 0). I also want to add a function which will increase the length of the array, i.e. if the method is called the array will increase from size 3 to size 4. How do I go about doing either of these?
This simple solution should do:
def add(l, item, max_len):
l.insert(0, item)
return l[:max_len]
l = ["banana", "peanut", "bicycle", "window"]
l = add(l, "monkey", 3)
print(newlist)
prints:
> ['monkey', 'banana', 'peanut']
The the list to edit (l), the item to add and the max size (max_len) of the list are arguments.
The item will then be added at index 0, while the list is limited to max_len.
This should do the trick. There are tons of prebuilt modules that have similar functionality but I thought it would be best if you could visualize the process!
class SizedList(list):
def __init__(self, size):
list().__init__(self)
self.__size = size
self.__wrap_location = 0
self.len = len(self)
def append(self, *args, **kwargs):
self.len = len(self)
if self.len >= self.__size:
if self.__wrap_location == self.__size-1:
self.__wrap_location = 0
self.__wrap_location += 1
self.pop(self.__wrap_location)
return self.insert(self.__wrap_location-1, *args)
return list.append(self, *args, **kwargs)
def increase_size(self, amount=1):
self.__size += 1
Here is how I went about it in the end. I set a variable called length_list which was originally set at 3.
def add_to_list():
if len(list) < length_list:
append
else:
del list[0]
self.add_to_list()
def increase_length():
length_list += 1

Sorting through a nested list

I have a list that describes a hierarchy, as such:
[obj1, obj2, [child1, child2, [gchild1, gchild2]] onemoreobject]
Where, child1 (and others) are children of obj2, while gchild1 and 2 are children of child 2.
Each of this objects has attributes like date, for example, and I want to sort them according to such attributes. In regular list I would go like this:
sorted(obj_list, key=attrgetter('date'))
In this case, nonetheless that method wont work, since lists don't have date attribute... Even if it did, if its attribute would be different of its parent, then the hierarchical ordering would be broken. Is there a simple and elegant way to do this in python?
I think you just need to put your key in the sort(key=None) functions and this will work. I tested it with strings and it seems to work. I wasn't sure of the structure of onemoreobject. This was sorted to the beginning with obj1 and obj2. I thought that onemoreobject might represent a new hierarchy so I enclosed each hierarchy into a list to keep like objects together.
def embededsort(alist):
islist = False
temp = []
for index, obj in enumerate(alist):
if isinstance(obj,list):
islist = True
embededsort(obj)
temp.append((index,obj))
if islist:
for lists in reversed(temp):
del alist[lists[0]]
alist.sort(key=None)
for lists in temp:
alist.append(lists[1])
else:
alist.sort(key=None)
return alist
>>>l=[['obj2', 'obj1', ['child2', 'child1', ['gchild2', 'gchild1']]], ['obj22', 'obj21', ['child22', 'child21', ['gchild22', 'gchild21']]]]
>>>print(embededsort(l))
[['obj1', 'obj2', ['child1', 'child2', ['gchild1', 'gchild2']]], ['obj21', 'obj22', ['child21', 'child22', ['gchild21', 'gchild22']]]]
This is an implementation of QuickSort algorithm using the polymorphism provided by Python. It should work for ints, floats, lists, nested lists, tuples and even dictionaries
def qsort(list):
if not list: return []
first = list[0]
lesser = filter( lambda x: x < first, list[1:] )
greater = filter( lambda x: x >= first, list[1:] )
return qsort(lesser) + [first] + qsort(greater)
thanks for the answers, as they gave me quite a few ideas, and new stuff to learn from. The final code, which seems to work looks like this. Not as shor and elegant as I imagined, but works:
def sort_by_date(element_list):
last_item = None
sorted_list = []
for item in element_list:
#if item is a list recurse and store it right below last item (parent)
if type(item) == list:
if last_comparisson:
if last_comparisson == 'greater':
sorted_list.append(sort_by_date(item))
else:
sorted_list.insert(1, sort_by_date(item))
#if not a list check if it is greater or smaller then last comparisson
else:
if last_item == None or item.date > last_item:
last_comparisson = 'greater'
sorted_list.append(item)
else:
last_comparisson = 'smaller'
sorted_list.insert(0, item)
last_item = item.date
return(sorted_list)
If you want to sort all children of a node without taking into consideration those nodes which are not siblings, go for a tree structure:
class Tree:
def __init__ (self, payload):
self.payload = payload
self.__children = []
def __iadd__ (self, child):
self.__children.append (child)
return self
def sort (self, attr):
self.__children = sorted (self.__children, key = lambda x: getattr (x.payload, attr) )
for child in self.__children: child.sort (attr)
def __repr__ (self):
return '{}: {}'.format (self.payload, self.__children)

Categories