How to get the index of an integer from a list if

2020-06-01 10:42发布

问题:

I am just starting with Python.

How to get index of integer 1 from a list if the list contains a boolean True object before the 1?

>>> lst = [True, False, 1, 3]
>>> lst.index(1)
0
>>> lst.index(True)
0
>>> lst.index(0)
1

I think Python considers 0 as False and 1 as True in the argument of the index method. How can I get the index of integer 1 (i.e. 2)?

Also what is the reasoning or logic behind treating boolean object this way in list? As from the solutions, I can see it is not so straightforward.

回答1:

The documentation says that

Lists are mutable sequences, typically used to store collections of homogeneous items (where the precise degree of similarity will vary by application).

You shouldn't store heterogeneous data in lists. The implementation of list.index only performs the comparison using Py_EQ (== operator). In your case that comparison returns truthy value because True and False have values of the integers 1 and 0, respectively (the bool class is a subclass of int after all).

However, you could use generator expression and the built-in next function (to get the first value from the generator) like this:

In [4]: next(i for i, x in enumerate(lst) if not isinstance(x, bool) and x == 1)
Out[4]: 2

Here we check if x is an instance of bool before comparing x to 1.

Keep in mind that next can raise StopIteration, in that case it may be desired to (re-)raise ValueError (to mimic the behavior of list.index).

Wrapping this all in a function:

def index_same_type(it, val):
    gen = (i for i, x in enumerate(it) if type(x) is type(val) and x == val)
    try:
        return next(gen)
    except StopIteration:
        raise ValueError('{!r} is not in iterable'.format(val)) from None

Some examples:

In [34]: index_same_type(lst, 1)
Out[34]: 2

In [35]: index_same_type(lst, True)
Out[35]: 0

In [37]: index_same_type(lst, 42)
ValueError: 42 is not in iterable


回答2:

Booleans are integers in Python, and this is why you can use them just like any integer:

>>> 1 + True
2
>>> [1][False]
1

[this doesn't mean you should :)]

This is due to the fact that bool is a subclass of int, and almost always a boolean will behave just like 0 or 1 (except when it is cast to string - you will get "False" and "True" instead).

Here is one more idea how you can achieve what you want (however, try to rethink you logic taking into account information above):

>>> class force_int(int):
...     def __eq__(self, other):
...         return int(self) == other and not isinstance(other, bool)
... 
>>> force_int(1) == True
False
>>> lst.index(force_int(1))
2

This code redefines int's method, which is used to compare elements in the index method, to ignore booleans.



回答3:

Here is a very simple naive one-liner solution using map and zip:

>>> zip(map(type, lst), lst).index((int, 1))
2

Here we map the type of each element and create a new list by zipping the types with the elements and ask for the index of (type, value).

And here is a generic iterative solution using the same technique:

>>> from itertools import imap, izip
>>> def index(xs, x):
...     it = (i for i, (t, e) in enumerate(izip(imap(type, xs), xs)) if (t, e) == x)
...     try:
...             return next(it)
...     except StopIteration:
...             raise ValueError(x)
... 
>>> index(lst, (int, 1))
2

Here we basically do the same thing but iteratively so as to not cost us much in terms of memory/space efficiency. We an iterator of the same expression from above but using imap and izip instead and build a custom index function that returns the next value from the iterator or a raise a ValueError if there is no match.



回答4:

Try to this.

for i, j in enumerate([True, False, 1, 3]):
    if not isinstance(j, bool) and j == 1:
        print i

Output:

2