2

I have a list of tuples with one element as NaN:

l = [('a', 7.0), ('b', float('nan'))]

And I want to find the index of tuple ('b', float('nan')) in the above list.

l.index(('b', float('nan')) is unable to find the element in the list even though its index is 1. It is raising ValueError exception as:

>>> l.index(('b', float('nan'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: ('b', nan) is not in list

Most likely this is due to the fact that each float('nan') is an independent NaN object, meaning that the two tuples are different objects as well.

How do I solve this problem in general?

Moinuddin Quadri
  • 46,825
  • 13
  • 96
  • 126
Francesco Cariaggi
  • 688
  • 2
  • 9
  • 23

1 Answers1

2

float('nan') == float('nan') returns False because it is designed to not to match with itself. That's why list.index() function is unable to find a match for NaN value and is raising ValueError exception.

Please read Why is NaN not equal to NaN? to know more about this behaviour.

Below is a custom function check_nan_match() to check whether passed objects are having same value or not. This function will be able to match for NaN objects too based on the above property i.e. NaNs return False when matched with itself.

# Function too check passed values are match, including `NaN`
def check_nan_match(a, b):
    return (b != b and a != a) or a == b
          # ^    ^ `NaN` property to return False when matched with itself

To get the index of tuple in the list containing NaN, here I am creating another custom function as get_nan_index. This function accepts my_list and my_tuple as param, iterates over the my_list to get the index of my_tuple. To check for the equality, I am using previously created check_nan_match function which is capable to match NaN values too.

# Get index from list of tuple , when tuple is passed
def get_nan_index(my_list, my_tuple):
    for i, t in enumerate(my_list):
        if all(check_nan_match(x, y) for x, y in zip(t, my_tuple)):
            return i
    else:
        raise ValueError  # Raise `ValueError` exception in case of no match.
                          # Similar to `list.index(...)` function

Sample run:

# check for tuple with `NaN` 
>>> get_nan_index([('a', 7.0), ('b', float('nan'))], ('b', float('nan')))
1

# check for tuple without `NaN`
>>> get_nan_index([('a', 1), ('b', 2)], ('b', 2))
1

# `ValueError` exception if no match
>>> get_nan_index([('a', 7.0), ('b', 3)], ('b', float('nan')))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in get_nan_index
ValueError
Moinuddin Quadri
  • 46,825
  • 13
  • 96
  • 126
  • If you just want the first index with short-circuiting, `next(i for i, (x, y) in enumerate(l) if x == 'b' and isnan(y))` will do it (also make sure to test both values). – ShadowRanger Jan 21 '21 at 12:01
  • @ShadowRanger Handling different edge cases in generator with `next()` won't be clean. Having it in form of custom functions I created above would be much cleaner approach in my opinion – Moinuddin Quadri Jan 22 '21 at 08:24