python - Length of a list with the reduce() function - python

I need some help to count the numbers of elements in a list by using the reduce function.
def lenReduce(L):
return reduce(lambda x: x + 1, L)
With this one I get the following error message:
TypeError: <lambda>() takes 1 positional argument but 2 were given
Greetings from Berlin. ;-)

lenReduce([5,3,1])
returns 7
What this means is, for the very first time when the lambda function is invoked, count is set to 5 and item is set to 3 which are the first two elemets of the list. From the next invocation of the lambda function, count is incremented. Therefore the solution does not work.
The solution is to set the count to a value of our choosing rather than the first element of the list. To do that, invoke reduce with three arguments.
def lenReduce(L):
return reduce(lambda count, item: count + 1, L, 0)
In the above call of reduce, count is set to 0 and item will be set to the elements of the list starting from index 0 on each iteration.
lenReduce([3,2,1])
outputs 3 which is the desired result.

The function argument to reduce takes two arguments: the return value of the previous call, and an item from the list.
def counter(count, item):
return count + 1
In this case, you don't really care what the value of item is; simply passing it to counter means you want to return the current value of the counter plus 1.
def lenReduce(L):
return reduce(counter, L)
or, using a lambda expression,
def lenReduce(L):
return reduce(lambda count, item: count + 1, L)
Even though your function ignores the second argument, reduce still expects to be able to pass it to the function, so it must be defined to take two arguments.

All previous answers seemed not work since it will add 1 to the first element of the given list every iteration. Try this trick, add 0 as the first element of the list, then decrease the return value by 1.
lenReduce = lambda L: reduce(lambda x, y: x+1, [0]+L, 0) - 1

Related

Error handling while searching Python lists

I'm trying to calculate a few metrics around an orderbook, and looking for how deep I need to go in to fill an order of $x. I have created a list of lists that shows the price and cumulative asks, and then am finding how deep I need to go under cumulative asks >= x. However, depending on the orderbook and x, sometimes I don't have enough data and end up with a list index out of range error. How can I set some sort of default, None, N/A, whatever, when this is the case.
Note: I feel like there may be a better way than creating a list with the >= and then filtering for first elements of that list, so if that makes solving the index out of range error easier, that'd be great, too.
cum_ob = [[1.54325, 4296.408], [1.5449, 5862.9366], [1.54495, 7679.7978], [1.545, 7695.2478], [1.5467, 7696.7945]]
usd_5k = ([sublist for sublist in cum_ob if sublist[-1] >= 5000][0][0])
usd_10k = ([sublist for sublist in cum_ob if sublist[-1] >= 10000][0][0])
So usd_5k would return 1.5449, while usd_10k throws the error since I've exhausted these levels without reaching 10000.
Maybe you should write a function to do it rather than using list comprehension that it not so flexible for your case :
def search_for_amount(cumulative_list, threshold):
for order in cumulative_list:
if order[1] >= threshold:
return order # You return a tuple with (element, cumulative sum)
# If nothing was found, return None
return None
Then you would be able to get your previous values as follow :
usd_5k = search_for_amount(cum_ob, 5000) # Returns (1.5449, 5862)
usd_10k = search_for_amount(cum_ob, 10000) # Returns None
One advantage of this technique compared to yours is that you do not need to go through the whole list each time. You just iterate until you find a value that matches the threshold condition. With your technique, you have to iterate over every elements of the list, every time, even though the first element matches the condition.
You can use the more_itertools recipe first_true, pasted below. It takes 3 arguments: the iterable (your list), a default for what should be returned if the condition is not met and your list is exhausted; and a predicate (condition) which will be the >=5/10k check.
Since your list has pairs of values, I've set the default to [None, None] so when you index [0] of the returned value, you'll get None, or the price.
usd_5k = first_true(cum_ob, default=[None, None], pred=lambda sub: sub[1] >= 5_000)[0]
print(usd_5k)
# 1.5449
usd_10k = first_true(cum_ob, default=[None, None], pred=lambda sub: sub[1] >= 10_000)[0]
print(usd_10k)
# None
For the first_true code, you can either copy-paste the code or install more_itertools - the body is just one line.
# from https://docs.python.org/3/library/itertools.html#itertools-recipes
def first_true(iterable, default=False, pred=None):
"""Returns the first true value in the iterable.
If no true value is found, returns *default*
If *pred* is not None, returns the first item
for which pred(item) is true.
"""
# first_true([a,b,c], x) --> a or b or c or x
# first_true([a,b], x, f) --> a if f(a) else b if f(b) else x
return next(filter(pred, iterable), default)

Indexing function for a string in python

I am trying to implement a function called say "Function" from scratch that counts how many times each parameter z of letters occurs sequentially in a string.
For example, Function('abcbcb', z=2)
should return ab:1, bc:2, cb: 2
or Function('abcbcb', z=3) should return
abc: 1, bcb: 2, cbc: 1
I have tried using loops and python string methods but I have not been able to write a working one yet.
Thank you!
First let's call the function another name because this one is confusing. I'll call it times.
iterable[n:k] will return the iterable from index n (inclusive) to index k(exclusive).
here is a code with explanations:
def times(str, z):
dict ={} # we create an empty dict
for i in range(len(str)-z+1): #we loop over the string a number of times depending on z so we can check all z length paramters
if str[i:i+z] in dict.keys(): #if the part of the string is in the keys of the dictionary then we add one
dict[str[i:i+z]] += 1
else:
dict[str[i:i+z]] = 1 # if it wasn't we set it to one
return dict
times('abcbcb',3)

In this short recursive function `list_sum(aList)`, the finish condition is `if not aList: return 0`. I see no logic in why this condition works

I am learning the recursive functions. I completed an exercise, but in a different way than proposed.
"Write a recursive function which takes a list argument and returns the sum of its integers."
L = [0, 1, 2, 3, 4] # The sum of elements will be 10
My solution is:
def list_sum(aList):
count = len(aList)
if count == 0:
return 0
count -= 1
return aList[0] + list_sum(aList[1:])
The proposed solution is:
def proposed_sum(aList):
if not aList:
return 0
return aList[0] + proposed_sum(aList[1:])
My solution is very clear in how it works.
The proposed solution is shorter, but it is not clear for me why does the function work. How does if not aList even happen? I mean, how would the rest of the code fulfill a not aList, if not aList means it checks for True/False, but how is it True/False here?
I understand that return 0 causes the recursion to stop.
As a side note, executing without if not aList throws IndexError: list index out of range.
Also, timeit-1million says my function is slower. It takes 3.32 seconds while the proposed takes 2.26. Which means I gotta understand the proposed solution.
On the call of the function, aList will have no elements. Or in other words, the only element it has is null. A list is like a string or array. When you create a variable you reserve some space in the memory for it. Lists and such have a null on the very last position which marks the end so nothing can be stored after that point. You keep cutting the first element in the list, so the only thing left is the null. When you reach it you know you're done.
If you don't use that condition the function will try to take a number that doesn't exist, so it throws that error.
You are counting the items in the list, and the proposed one check if it's empty with if not aList this is equals to len(aList) == 0, so both of you use the same logic.
But, you're doing count -= 1, this has no sense since when you use recursion, you pass the list quiting one element, so here you lose some time.
According to PEP 8, this is the proper way:
• For sequences, (strings, lists, tuples), use the fact that empty
sequences are false.
Yes: if not seq:
if seq:
No: if len(seq)
if not len(seq)
Here is my amateur thougts about why:
This implicit check will be faster than calling len, since len is a function to get the length of a collection, it works by calling an object's __len__ method. This will find up there is no item to check __len__.
So both will find up there is no item there, but one does it directly.
not aList
return True if there is no elements in aList. That if statement in the solution covers edge case and checks if input parameter is not empty list.
For understand this function, let's run it step by step :
step 0 :
L=[0,1,2,3,4]
proposed_sum([0,1,2,3,4])
L != []
return l[0] + proposed_sum([1,2,3,4])
step 1 calcul proposed_sum([1,2,3,4]):
proposed_sum([1,2,3,4])
L != []
return l[0] + sum([2,3,4])
step 2 calcul proposed_sum([2,3,4]):
proposed_sum([2,3,4])
L != []
return l[0] + sum([3,4])
step 3 calcul proposed_sum([3,4]):
proposed_sum([3,4])
L != []
return l[0] + sum([4])
step 4 calcul proposed_sum([4]):
proposed_sum([4])
L != []
return l[0] + sum([])
step 5 calcul proposed_sum([]):
proposed_sum([])
L == []
return 0
step 6 replace:
proposed_sum([0,1,2,3,4])
By
proposed_sum([]) + proposed_sum([4]) + proposed_sum([3,4]) + proposed_sum([2,3,4]) + proposed_sum([1,2,3,4])+ proposed_sum([0,1,2,3,4])
=
(0) + 4 + 3 + 2 + 1 + 0
Python considers as False multiple values:
False (of course)
0
None
empty collections (dictionaries, lists, tuples)
empty strings ('', "", '''''', """""", r'', u"", etc...)
any other object whose __nonzero__ method returns False
in your case, the list is evaluated as a boolean. If it is empty, it is considered as False, else it is considered as True. This is just a shorter way to write if len(aList) == 0:
in addition, concerning your new question in the comments, consider the last line of your function:
return aList[0] + proposed_sum(aList[1:])
This line call a new "instance" of the function but with a subset of the original list (the original list minus the first element). At each recursion, the list passed in argument looses an element and after a certain amount of recursions, the passed list is empty.

Returning the index of the largest element in an array in Python

I'm trying to create a function that returns the largest element of an array, I feel I have the correct code but my syntax is in the wrong order, I'm trying to use a for/while loop in order to do so. So far I have the following:
def manindex(arg):
ans = 0
while True:
for i in range (len(arg)):
if arg[i] > arg[ans]:
pass
ans = i
return ans
Not sure where I'm going wrong if anyone could provide some guidance, thanks
EDIT: So it's been pointing out I'm causing an infinite loop so if I take out the while statement I'm left with
def manindex(arg):
ans = 0
for i in range (len(arg)):
if arg[i] > arg[ans]:
ans = i
return ans
But I have a feeling it's still not correct
When you say array I think you mean list in Python, you don't need a for/loop or while/loop to achieve this at all.
You can also use index with max, like so:
xs.index(max(xs))
sample:
xs = [1,123,12,234,34,23,42,34]
xs.index(max(xs))
3
You could use max with the key parameter set to seq.__getitem__:
def argmax(seq):
return max(range(len(seq)), key=seq.__getitem__)
print(argmax([0,1,2,3,100,4,5]))
yields
4
The idea behind finding the largest index is always the same, iterating over the elements of the array, compare to the max value we have at the moment, if it's better, the index of the current element is the maximum now, if it's not, we keep looking for it.
enumerate approach:
def max_element_index(items):
max_index, max_value = None, None
for index, item in enumerate(items):
if item > max_value:
max_index, max_value = index, item
return max_index
functional approach:
def max_element_index(items):
return reduce(lambda x,y: x[1] > y[1] and x or y,
enumerate(items), (None, None))[0]
At the risk of looking cryptic, the functional approach uses the reduce function which takes two elements and decides what is the reduction. Those elements are tuples (index, element), which are the result of the enumerate function.
The reduce function, defined on the lambda body takes two elements and return the tuple of the largest. As the reduce function reduces until only one element in the result is encountered, the champion is the tuple containing the index of the largest and the largest element, so we only need to access the 0-index of the tuple to get the element.
On the other hand if the list is empty, None object is returned, which is granted on the third parameter of the reduce function.
Before I write a long winded explanation, let me give you the solution:
index, value = max(enumerate(list1), key=lambda x: x[1])
One line, efficient (single pass O(n)), and readable (I think).
Explanation
In general, it's a good idea to use as much of python's incredibly powerful built-in functions as possible.
In this instance, the two key functions are enumerate() and max().
enumerate() converts a list (or actually any iterable) into a sequence of indices and values. e.g.
>>> list1 = ['apple', 'banana', 'cherry']
>>> for tup in enumerate(list1):
... print tup
...
(0, 'apple')
(1, 'banana')
(2, 'cherry')
max() takes an iterable and returns the maximum element. Unfortunately, max(enumerate(list1)) doesn't work, because max() will sort based on the first element of the tuple created by enumerate(), which sadly is the index.
One lesser-known feature of max() is that it can take a second argument in the form max(list1, key=something). The key is a function that can be applied to each value in the list, and the output of that function is what gets used to determine the maximum. We can use this feature to tell max() that it should be ranking items by the second item of each tuple, which is the value contained in the list.
Combining enumerate() and max() with key (plus a little help from lambda to create a function that returns the second element of a tuple) gives you this solution.
index, value = max(enumerate(list1), key=lambda x: x[1])
I came up with this recently (and am sprinkling it everywhere in my code) after watching Raymond Hettinger's talk on Transforming Code into Beautiful, Idiomatic Python, where he suggests exorcising the for i in xrange(len(list1)): pattern from your code.
Alternatively, without resorting to lambda (Thanks #sweeneyrod!):
from operator import itemgetter
index, value = max(enumerate(list1), key=itemgetter(1))
I believe if you change your for loop to....
for i in range (len(arg)):
if arg[i] > ans:
ans = arg[i]
it should work.
You could try something like this. If the list is empty, then the function will return an error.
m is set to the first element of the list, we then iterate over the list comparing the value at ever step.
def findMax(xs):
m = xs[0]
for x in xs:
if x > m:
m = x
return m
findMax([]) # error
findMax([1]) # 1
findMax([2,1]) # 2
if you wanted to use a for loop and make it more generic, then:
def findGeneric(pred, xs):
m = xs[0]
for x in xs:
if pred(x,m):
m = x
return m
findGeneric(lambda a,b: len(a) > len(b), [[1],[1,1,1,1],[1,1]]) # [1,1,1,1]

Python aggregate on a generator

I have a generator that returns a list in each iteration. Each element of the list could be either 0 or 1. I want to count the total number of elements returned (including both 0 and 1) and the total number of 1 returned. I tried to implement this using reduce function like this :
t = reduce( (lambda x,y:(y[0]+1,y[1]+x)), gen_fn(), (0,0))
gen_fn() above is the generator that returns part of the list in each yield statement. I wanted to implement it by initializing with a tuple (0,0) for count. Given that the elements returned from generator are following :
[0, 1, 1, 0, 1]
My expected output for t is (5,3). But my code is failing with this error message :
TypeError: unsupported operand type(s) for +: 'int' and 'tuple'
Can anybody help me identify the problem? My lack of experience with reduce and lambda functions is preventing me from figuring out what I am doing wrong. Thanks in advance.
I think the best answer here is to keep it simple:
count = 0
total = 0
for item in gen_fn():
count += 1
total += item
Using reduce() here only makes your code less readable.
If your question is code golf and you want a one liner (while keeping lazy evaluation), then you want:
count, total = collections.deque(zip(itertools.count(1), itertools.accumulate(gen_fn())), maxlen=1).pop()
Of course, you'd be mad to pick such a construction over the simple solution.
Edit:
If the generator yields multiple smaller parts, then simply use itertools.chain.from_iterable(gen_fn()) to flatten it.
You have the lambda arguments the wrong way around; the first argument (x) is the total so far (the tuple) and the second (y) is the new value (the integer). Try:
t = reduce((lambda x, y: (x[0]+1, x[1]+y)), gen_fn(), (0,0))
Using a dummy function:
def gen_fn():
for x in [0, 1, 1, 0, 1]:
yield x
I get (5, 3).
This equivalent implementation of reduce from the docs might make things clearer:
def reduce(function, iterable, initializer=None):
it = iter(iterable)
if initializer is None:
try:
initializer = next(it)
except StopIteration:
raise TypeError('reduce() of empty sequence with no initial value')
accum_value = initializer
for x in it:
accum_value = function(accum_value, x) # note value so far is first arg
return accum_value
As jonrsharpe has pointed out, you are using your lambda arguments backwards, given the way reduce works. However, there may be a further issue with how you're adding things up, if each item yielded from your generator is a list.
This issue is that your y value (the item yielded by the generator) is not a single number, but a list. You need to count its length and the number of 1s it has, so you probably want your lambda function to be:
lambda x, y: (x[0]+len(y), x[1]+sum(y))
How about taking a completely different approach?
t = [(len(row), len(filter(lambda x: x == 1, row))) for row in gen_fn()]

Categories