2

TL DR: How can I best use map to filter a list based on logical indexing?

Given a list:

values = ['1', '2', '3', '5', 'N/A', '5']

I would like to map the following function and use the result to filter my list. I could do this with filter and other methods but mostly looking to learn if this can be done solely using map.

The function:

def is_int(val):
    try:
        x = int(val)
        return True
    except ValueError:
        return False

Attempted solution:

[x for x in list(map(is_int, values)) if x is False]

The above gives me the values I need. However, it does not return the index or allow logical indexing. I have tried to do other ridiculous things like:

[values[x] for x  in list(map(is_int, values)) if x is False]

and many others that obviously don't work.

What I thought I could do:

values[[x for x in list(map(is_int, values)) if x is False]]

Expected outcome:

['N/A']
NelsonGon
  • 13,015
  • 7
  • 27
  • 57
  • 1
    You cannot use `map()` alone to perform a reduction. By its very definition, `map()` preserves the number of items https://en.wikipedia.org/wiki/Map_(higher-order_function) . On the other hand, reduce operations are meant to be doing what you want. In Python these may be implemented normally with a generator expression or for the more functional-style inclined programmers, with `filter()`. Other non-primitive approach may exist, but they essentially boil down to one of the two. – norok2 Jul 09 '19 at 12:29
  • Thanks, I actually meant to use the result of `map` like so: `values[result of map]` to return the result not literally use `map` to filter. – NelsonGon Jul 09 '19 at 12:31
  • 1
    Oh OK, that is a bit different then. Check out my answer. – norok2 Jul 09 '19 at 12:47

6 Answers6

5
[v for v in values if not is_int(v)]

If you have a parallel list of booleans:

[v for v, b in zip(values, [is_int(x) for x in values]) if not b]
Dan D.
  • 73,243
  • 15
  • 104
  • 123
  • Thanks, is there a way to use `map`? I am mostly asking to learn about how flexible `map` is since I'm new to `python`. If not, why can't we use `map` here? Sorry if it's too broad. – NelsonGon Jul 09 '19 at 12:09
  • 2
    @NelsonGon In Python, [list comprehensions are generally preferred to `map`s](https://stackoverflow.com/questions/1247486/list-comprehension-vs-map); if you are new to Python I would encourage you to "get on with it" even if other languages prefer a map-like construct. (There should be a way to make it through `map`, though.) – Leporello Jul 09 '19 at 12:17
  • 1
    Thanks @Leporello I will get used to comprehensions then. – NelsonGon Jul 09 '19 at 12:18
  • Or `[v for v, b in zip(values, map(is_int, values)) if not b]`. – tobias_k Jul 09 '19 at 13:30
4

you can get the expected outcome using the simple snippet written below which does not involve any map function

[x for x in values if is_int(x) is False]

And, if you want to strictly use map function then the snippet below will help you

[values[i] for i,y in enumerate(list(map(is_int,values))) if y is False]
Bibyutatsu
  • 241
  • 1
  • 6
  • Ah, I just thought of using enumerate right now. Is the latter solution computationally efficient though? – NelsonGon Jul 09 '19 at 12:16
  • 1
    Yeah, enumerate will be the most simple and helpful tool for this question – Bibyutatsu Jul 09 '19 at 12:18
  • 1
    How is `enumerate` more simple than a simple list comprehension? Also, better do `if not x` instead of `if x is False`. Also, you can `enumerate` the `map` directly, no need for `list`. – tobias_k Jul 09 '19 at 13:28
1

map is just not the right tool for the job, as that would transform the values, whereas you just want to check them. If anything, you are looking for filter, but you have to "inverse" the filter-function first:

>>> values = ['1', '2', "foo", '3', '5', 'N/A', '5']                       
>>> not_an_int = lambda x: not is_int(x)                                   
>>> list(filter(not_an_int, values))                                       
['foo', 'N/A']

In practice, however, I would rather use a list comprehension with a condition.

tobias_k
  • 81,265
  • 12
  • 120
  • 179
1

You can do this using a bit of help from itertools and by negating the output of your original function since we want it to return True where it is not an int.

from itertools import compress
from operator import not_

list(compress(values, map(not_, map(is_int, values))))

['N/A']
gold_cy
  • 13,648
  • 3
  • 23
  • 45
1

You cannot use map() alone to perform a reduction. By its very definition, map() preserves the number of items (see e.g. here). On the other hand, reduce operations are meant to be doing what you want. In Python these may be implemented normally with a generator expression or for the more functional-style inclined programmers, with filter(). Other non-primitive approach may exist, but they essentially boil down to one of the two, e.g.:

values = ['1', '2', '3', '5', 'N/A', '5']

list(filter(lambda x: not is_int(x), values))
# ['N/A']

Yet, if what you want is to combine the result of map() to use it for within slicing, this cannot be done with Python alone. However, NumPy supports precisely what you want except that the result will not be a list:

import numpy as np

np.array(values)[list(map(lambda x: not is_int(x), values))]
# array(['N/A'], dtype='<U3')

(Or you could have your own container defined in such a way as to implement this behavior).


That being said, it is quite common to use the following generator expression in Python in place of map() / filter().

filter(func, items)

is roughly equivalent to:

item for item in items if func(item)

while

map(func, items)

is roughly equivalent to:

func(item) for item in items

and their combination:

filter(f_func, map(m_func, items))

is roughly equivalent to:

m_func(item) for item in items if f_func(item)
norok2
  • 25,683
  • 4
  • 73
  • 99
  • 1
    The `numpy` solution looks great. I have unfortunately already accepted an answer(don't like de-accepting if that's a word) but thanks. – NelsonGon Jul 09 '19 at 12:51
  • 1
    Shouldn't it be `not is_int(x)` in the first `filter`? You are missing the `(x)`. – tobias_k Jul 09 '19 at 13:31
  • @tobias_k Yes, sorry it just went missing while typing. Thanks for spotting it. – norok2 Jul 09 '19 at 13:36
0

Not exactly what I had in mind but something I learnt from this problem, we could do the following(which might be computationally less efficient). This is almost similar to @aws_apprentice 's answer. Clearly one is better off using filter and/or list comprehension:

from itertools import compress
list(compress(values, list(map(lambda x: not is_int(x), values))))

Or as suggested by @aws_apprentice simply:

from itertools import compress
list(compress(values, map(lambda x: not is_int(x), values)))
NelsonGon
  • 13,015
  • 7
  • 27
  • 57