Repository: numba/numba

Issue:
Add support for `ndarray` 'contains'
- [x] I have tried using the latest released version of Numba
- [x] I have included below a minimal working reproducer

In trying to replace numpy's setxor1d (which is unfortunately not supported by Numba), I came across an error that does occur for (integer) ndarrays but not for lists.
In my case the setxor1d can be replaced by [i for i in a if i not in b], but for the sake of testing, here is a different minimal working example:

```
@nb.njit()
def test():
    included = np.array([1, 2, 6, 8])
    return 6 in included
```
This will give the error:

```
No implementation of function Function(<built-in function contains>) found for signature:

 >>> contains(array(int64, 1d, C), Literal[int](6))
```

While the funtion:
```
@nb.njit()
def test():
    included = np.array([1, 2, 6, 8])
    return 6 in list(included)
```

Works fine. 
I'm assuming it is supposed to work for the array as well.
I came accross this previous issue that looks similar at first glance: [github.com/numba/numba/issues/4150](https://github.com/numba/numba/issues/4150)
But there it concerns an array of floats, so it is evident that 'int in array(float)' is not going to work.


Discussion:
There's actually another issue related to my stand-in setxor function:

```
@nb.njit()
def test():
    included = np.array([1, 2, 6, 8])
    not_included = [i for i in range(10) if i not in list(included)]
    return not_included
```
produces:
```
Type of variable '$18compare_op.7.7054' cannot be determined, operation: unary(fn=<built-in function not_>, value=$18compare_op.7.7054), location: <ipython-input-138-e85438f982c0> (4)
```
Which does not occur when the 'not' statement (in `if i not in list(included)]`) is not there
I had to get around this by instead doing:
```
@nb.njit()
def test():
    included = np.array([1, 2, 6, 8])
    not_included = [i for i in range(10) if (i in list(included)) is False]
    return not_included
```
Which is not very pretty. But I should probably open a new issue for this separately?
Thanks for the question! Marking this as a feature request, to add support for `contains` for NumPy arrays.

>  But I should probably open a new issue for this separately?

It would be helpful for issue / feature tracking to have a separate issue for this - many thanks!
The use of Numba's extension API `@overload` decorator is strongly recommended for this task. A guide to using `@overload` is [here](http://numba.pydata.org/numba-doc/dev/extending/overloading-guide.html?highlight=overload) and API documentation is [here](http://numba.pydata.org/numba-doc/latest/extending/high-level.html#implementing-functions).

It may be useful to take a look at examples of pull requests adding support for a NumPy function that have already been done if guidance is needed... here's a couple:

* `np.roll`: https://github.com/numba/numba/pull/3563/files
* `np.roots`: https://github.com/numba/numba/pull/2135/files

Resolving patch:
diff --git a/numba/np/arraymath.py b/numba/np/arraymath.py
--- a/numba/np/arraymath.py
+++ b/numba/np/arraymath.py
@@ -7,6 +7,7 @@
 from collections import namedtuple
 from enum import IntEnum
 from functools import partial
+import operator
 
 import numpy as np
 
@@ -3303,6 +3304,20 @@ def np_imag_impl(a):
 #----------------------------------------------------------------------------
 # Misc functions
 
+@overload(operator.contains)
+def np_contains(arr, key):
+    if not isinstance(arr, types.Array):
+        return
+
+    def np_contains_impl(arr, key):
+        for x in np.nditer(arr):
+            if x == key:
+                return True
+        return False
+
+    return np_contains_impl
+
+
 @overload(np.count_nonzero)
 def np_count_nonzero(arr, axis=None):
     if not type_can_asarray(arr):


Test patch:
diff --git a/numba/tests/test_np_functions.py b/numba/tests/test_np_functions.py
--- a/numba/tests/test_np_functions.py
+++ b/numba/tests/test_np_functions.py
@@ -321,6 +321,10 @@ def flip_ud(a):
     return np.flipud(a)
 
 
+def array_contains(a, key):
+    return key in a
+
+
 class TestNPFunctions(MemoryLeakMixin, TestCase):
     """
     Tests for various Numpy functions.
@@ -436,6 +440,62 @@ def check(x_types, x_values, **kwargs):
         x_types = [typeof(v) for v in x_values]
         check(x_types, x_values, ulps=2)
 
+    def test_contains(self):
+        def arrs():
+            a_0 = np.arange(10, 50)
+            k_0 = 20
+
+            yield a_0, k_0
+
+            a_1 = np.arange(6)
+            k_1 = 10
+
+            yield a_1, k_1
+
+            single_val_a = np.asarray([20])
+            k_in = 20
+            k_out = 13
+
+            yield single_val_a, k_in
+            yield single_val_a, k_out
+
+            empty_arr = np.asarray([])
+            yield empty_arr, k_out
+
+            # np scalars
+
+            bool_arr = np.array([True, False])
+            yield bool_arr, True
+            yield bool_arr, k_0
+
+            np.random.seed(2)
+            float_arr = np.random.rand(10)
+            np.random.seed(2)
+            rand_k = np.random.rand()
+            present_k = float_arr[0]
+
+            yield float_arr, rand_k
+            yield float_arr, present_k
+
+            complx_arr = float_arr.view(np.complex128)
+            yield complx_arr, complx_arr[0]
+            yield complx_arr, rand_k
+
+            np.random.seed(2)
+            uint_arr = np.random.randint(10, size=15, dtype=np.uint8)
+            yield uint_arr, 5
+            yield uint_arr, 25
+
+        pyfunc = array_contains
+
+        cfunc = jit(nopython=True)(pyfunc)
+
+        for arr, key in arrs():
+            expected = pyfunc(arr, key)
+            received = cfunc(arr, key)
+
+            self.assertPreciseEqual(expected, received)
+
     def test_angle(self, flags=no_pyobj_flags):
         """
         Tests the angle() function.