How do keys work in min and max? - python

I run through the following sequence of statements:
>>> a = range(10)
>>> min(a, key=lambda x: x < 5.3)
6
>>> max(a, key=lambda x: x < 5.3)
0
The min and max give the exact opposite of what I was expecting.
The python documentation on min and max is pretty sketchy.
Can anyone explain to me how the "key" works?

Explanation of the key argument
Key works like this:
a_list = ['apple', 'banana', 'canary', 'doll', 'elephant']
min(a_list, key=len)
returns 'doll', and
max(a_list, key=len)
returns 'elephant'
You provide it with a function, and it uses the minimum or maximum of the results of the function applied to each item to determine which item to return in the result.
Application
If your function returns a boolean, like yours, for min it'll return the first of the minimum of True or False, (which is False) which would be 6 or the first of the max (True) which would be 0.
To see this:
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> import pprint
>>> pprint.pprint(dict((i, i<5.3) for i in a))
{0: True,
1: True,
2: True,
3: True,
4: True,
5: True,
6: False,
7: False,
8: False,
9: False}
Why?
>>> min([True, False])
False
>>> max([True, False])
True
Explanation
Why is True greater than False?
>>> True == 1
True
>>> False == 0
True
>>> issubclass(bool, int)
True
It turns out that True and False are very closely related to 1 and 0. They even evaluate the same respectively.

because all values are either True or False. So the first one of each is chosen.
min([True, 1, False, 0])
will return False instead of 0 because both have the same value but False happens first
Your code uses a key function with only boolean values so, for min, the first False
happens at the number 6. So it is the chosen one.
What you probably want to do is:
min(x for x in range(10) if x < 5.3)

Related

Find intervals of true values in vector

I am looking for a quick way to find the start and end indexes of each "block" of consecutive trues in a Vector.
Both julia or python would do the job for me. I'll write my example in julia syntax:
Say I have a vector
a = [false, true, true, true, false, true, false, true, true, false]
what I want to get is something like this (with 1-based indexing):
[[2, 4], [6, 6], [8, 9]]
The exact form/type of the returned value does not matter, I am mostly looking for a quick and syntactically easy solution. Single trues surrounded by falses should also be detected, as given in my example.
My use-case with this is that I want to find intervals in a Vector of data where the values are below a certain threshold. So I get a boolean array from my data where this is true. Ultimately I want to shade these intervals in a plot, for which I need the start and end indeces of each interval.
function intervals(a)
jumps = diff([false; a; false])
zip(findall(jumps .== 1), findall(jumps .== -1) .- 1)
end
Quick in terms of keystrokes, maybe not in performance or readability :)
My use-case with this is that I want to find intervals in a Vector of data where the values are below a certain threshold.
Let's say your vector is v and your threshold is 7:
julia> println(v); threshold
[9, 6, 1, 9, 5, 9, 4, 5, 6, 1]
7
You can use findall to get the indices where the value is below the threshold, and get the boundaries from that:
julia> let start = 1, f = findall(<(threshold), v), intervals = Tuple{Int, Int}[]
for i in Iterators.drop(eachindex(f), 1)
if f[i] - f[i - 1] > 1
push!(intervals, (f[start], f[i - 1]))
start = i
end
end
push!(intervals, (f[start], last(f)))
end
3-element Vector{Tuple{Int64, Int64}}:
(2, 3)
(5, 5)
(7, 10)
Here's a version that avoids running findall first, and is a bit faster as a consequence:
function intervals(v)
ints = UnitRange{Int}[]
i = firstindex(v)
while i <= lastindex(v)
j = findnext(v, i) # find next true
isnothing(j) && break
k = findnext(!, v, j+1) # find next false
isnothing(k) && (k = lastindex(v)+1)
push!(ints, j:k-1)
i = k+1
end
return ints
end
It also returns a vector of UnitRanges, since that seemed a bit more natural to me.
try this:
a = [False, True, True, True, False, True, False, True, True, False]
index = 0
foundTrue = False
booleanList = []
sublist = []
for i in a:
index += 1
if foundTrue:
if i == False:
foundTrue = False
sublist.append(index-1)
booleanList.append(sublist)
sublist = []
else:
if i == True:
foundTrue = True
sublist.append(index)
print(booleanList)
output should be: [[2, 4], [6, 6], [8, 9]]
This iterates in the a list and when it finds a True it marks a flag (foundTrue) and stores its index on sublist. Now with the maked flag (foundTrue), if it finds a False, then we store the previous index from that False into sublist, appends it to the booleanList and resets sublist.
This is not the shortest but very fast without using any find functions.
function find_intervals(v)
i = 0
res = Tuple{Int64, Int64}[]
while (i+=1) <= length(v)
v[i] || continue
s = f = i
while i < length(v) && v[i+=1]
f = i
end
push!(res, (s,f))
end
res
end
For a = [false, true, true, true, false, true, false, true, true, false], it gives:
find_intervals(a)
3-element Vector{Tuple{Int64, Int64}}:
(2, 4)
(6, 6)
(8, 9)

how to use all to filter list above threshold

x=[255, 255, 255, 250, 250, 250, 250, 250, 250, 255, 255, 255]
y=all([i for i in x if i>260])
y1 = all([i for i in x if i>220])
y2 = all([True for i in x if i>260])
y3 = all([True for i in x if i>220])
y1==y2==y3==y #gives True
How does all handle empty lists, and how can I use to filter items above 220.
the only thing working for me now is
y=len([i for i in x if i>220])== len(x)
what I want to know if all list is above certain number i.e 220
You want this:
all([i > 220 for i in x])
all for an empty list should be True, because all of its elements - all 0 of them - are truthy
Edit: if it's desired to rule out the trivial case where x is empty, then as suggested in the comments just add an additional check for that, such as len(X) > 0
There are some small tweaks required to make your list comprehensions work. For instance, you don't handle the case in which the value in the list is not greater than x. For example, let's assume our list is:
>>> x = [1, 2, 3, 4, 5, 6]
If we use the code snipped from your question:
>>> [True for i in x if i > 3]
[True, True, True]
The result is probably not exactly what you expected. Therefore, calling all() on that list will always evaluate to True. One small tweak, however (note the addition of the else statement):
>>> [True if i > 3 else False for i in x]
[False, False, False, True, True, True]
This is the desired output. Now you can also apply all() to the output list.
>>> y1 = [True if i > 3 else False for i in x]
>>> all(y1)
False
And if we want to know whether all values are greater than 0, we expect all() to return True in the following case:
>>> y2 = [True if i > 0 else False for i in x]
>>> all(y2)
True
To answer your question about how all() handles empty lists:
>>> all([])
True
Depending on the structure of your code and the desired output you may want to have an edge case statement that handles empty lists. Hopefully this is enough input for you to adapt it to your problem statement.
Making the code more compact: As very kindly pointed out in the comments, the code above is a bit "wordy" to make a point and for the sake of the explanation. When writing list comprehensions and condition statements in Python (and perhaps other programming languages), you can actually shorten the list comprehension and condition statement:
>>> y1 = [i > 3 for i in x]
>>> y1
[False, False, False, True, True, True]
>>> all(y1)
False
i > 3 in the list comprehension will turn into True or False and therefore yield the same result as writing True if i > 3 else False.
Finally, if you want to know if all elements meet the threshold and there is at least one element in the list:
>>> y1 = [i > 3 for i in x]
>>> y1
[False, False, False, True, True, True]
>>> all(y1) and y1
[] # this will evaluate to False if you use it in a condition statement
Alternatively you can also convert it to a boolean:
>>> bool(all(y1) and y1)
False
Which does the right thing for empty lists as well:
>>> bool(all([]) and [])
False
instead of True for i in x if i>260, do i>260 for i in x
i>260 will result in a boolean: True, or False for each element.

How to perform logical operations OR on list [multiple elements]?

Is there any way to compare the list elements and return the outcome value ?
Below is the python snippet, where it recieves two values and returns the value.
def logical_or_decision(a,b):
return a or b
value = logical_or_decision(1,0)
print value
I need to make it generic & scalable to more than 2 elements.How can i do it for more than 2 elements ?
There's a built-in function that does this: any.
>>> any([0,0,1,0])
True
>>> any([0,0,0])
False
best solution ^^above^^:
any([True, False, True])
# returns True
any is good because "short-circuits" (like "boolean fast evaluation" it doesn't iterate till the end).
If you want things alike but manually and eager - see reduce:
from operator import or_
from functools import reduce
reduce(or_, [True, False, True])
# returns True
You may use reduce to do it:
def logical_or_decision(*args):
return reduce(
lambda a, b: a or b,
args,
False
)
print logical_or_decision(1, 1, 1, 0) # False
Of course you could use any or all (for logical AND), but reduce could provide you the general way to build such operations (not for OR or AND).
The builtin function any() would be the correct way to go about this:
def logical_or_decision(*args):
return any(args)
value = logical_or_decision(1, 0, 0, 1, 0, 0)
print value
The relevant part from the link to the official documentation above:
Return True if any element of the iterable is true. If the iterable is empty, return False. Equivalent to:
def any(iterable):
for element in iterable:
if element:
return True
return False
There are two possible interpretations of the question:
1. OR & AND operations for all the elements of an iterable:
OR: any
AND: all
l1 = [True, False, False, True]
t1 = (True, True, True)
t2 = (False, False, False, False)
any(l1) # True
all(l1) # False
any(t1) # True
all(t1) # True
any(t2) # False
all(t2) # False
2. OR & AND operations for multiple pairs given as two iterables:
In this case the functions used are the same but you need to use the map and zip function to wrap them:
l = [True, True, False]
t = (True, False, False)
list(map(any, zip(l, t))) # [True, True, False]
tuple(map(all, zip(l, t))) # (True, False, False)
NOTE: I've used lists and tuples to prove that it can be done with different array-like structures. The list and tuple wrapper in the second example is Python3 as map returns an iterator instead of a list and that would give a non-human-readable answer.
From your comment:
say i've a list of [0,-1] , it should return -1. if [0,0,-1] , it should return -1
While most suggest using any and all, this does not seem to be what you actually want:
>>> lst1 = [0, False, [], -1, ""]
>>> lst2 = [4, True, "", 0]
>>> any(lst1)
True
>>> all(lst2)
False
Instead, you can use the reduce builtin (or in Python 3: functools.reduce) together with an accordant lambda function, applying or or and to the operands, to get the first "truthy" or "falsy" value in the list:
>>> reduce(lambda a, b: a or b, lst1)
-1
>>> reduce(lambda a, b: a and b, lst2)
''
Also, operator.or_ and and_ won't work, as those are bitwise | and & instead of logical or and and.
>>> reduce(operator.or_, lst1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for |: 'int' and 'list'
>>> lst = [1, 2, 4]
>>> reduce(operator.or_, lst)
7

Why does this expression evaluate to false?

I thought range() returns a list of numbers . why does the all() function return a false for this
all(range(100))
Your range(100) contains 0, and the boolean value of 0 is False:
In [1]: bool(0)
Out[1]: False
while for any other number it evaluates to True. The function all only returns True when all of the elements of its argument evaluate to True, see its manual (the if statement in the given example implicitly takes the boolean value of every item). See here the difference of including 0 or not:
In [2]: all(range(10)) # 0..9
Out[2]: False
In [3]: all(range(1,10)) # 1..9
Out[3]: True
Because the first number generated by range is 0.
>>>range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# 0 is evaluated to false in boolean expressions
>>>bool(0)
True
# making range start at 1
>>>all(range(1, 100))
True

Python: Mapping with Index of Previous Occurrence

I'm often met with an analog of the following problem, and have had trouble writing clean code to solve it. Usually, I have something involving a temporary variable and a for loop, but is there a more elegant way?
Suppose I have a list of booleans or values which evaluate to booleans:
[True, False, True, False, False, True]
How would I map this to a list of values, with the index of the previous True, inclusive?
[0, 0, 2, 2, 2, 5]
[EDIT] Have tried something along the lines of:
def example(lst):
rst, tmp = [], None
for i in range(len(lst)):
if lst[i]:
tmp = i
rst.append(tmp)
return rst
Assuming the first element of the list is always True.
While it still uses a for loop and a temporary variable, it's still relatively clean, I think. If you want, you could replace the yield and append to a list and return that.
def get_indexes(booleans):
previous = 0
for index, b in enumerate(booleans):
if b:
previous = index
yield previous
>>> b = [True, False, True, False, False, True]
>>> list(get_indexes(b))
[0, 0, 2, 2, 2, 5]
This is even shorter (although potentially less readable):
def get_indexes(booleans):
previous = 0
for index, b in enumerate(booleans):
previous = index if b else previous
yield previous
Try this:
index = 0
bools = [True, False, True, False, False, True]
result = []
for i in range(len(bools)):
index = i if bools[i] else index
result.append(index)
Not tested, but should work.
[i if b else i-lst[i::-1].index(True) for i,b in enumerate(lst)]

Categories

Resources