Repository: mars-project/mars

Issue:
Add support for `mt.setdiff1d`
<!--
Thank you for your contribution!

Please review https://github.com/mars-project/mars/blob/master/CONTRIBUTING.rst before opening an issue.
-->

**Is your feature request related to a problem? Please describe.**

`mt.setdiff1d` could be added support.



Discussion:

Resolving patch:
diff --git a/mars/tensor/__init__.py b/mars/tensor/__init__.py
--- a/mars/tensor/__init__.py
+++ b/mars/tensor/__init__.py
@@ -106,6 +106,7 @@
     insert,
     delete,
     in1d,
+    setdiff1d,
 )
 from .arithmetic import (
     add,
diff --git a/mars/tensor/base/__init__.py b/mars/tensor/base/__init__.py
--- a/mars/tensor/base/__init__.py
+++ b/mars/tensor/base/__init__.py
@@ -63,6 +63,7 @@
 from .insert import insert
 from .delete import delete
 from .in1d import in1d
+from .setdiff1d import setdiff1d
 
 
 def _install():
diff --git a/mars/tensor/base/setdiff1d.py b/mars/tensor/base/setdiff1d.py
new file mode 100644
--- /dev/null
+++ b/mars/tensor/base/setdiff1d.py
@@ -0,0 +1,58 @@
+# Copyright 1999-2022 Alibaba Group Holding Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+def setdiff1d(ar1, ar2, assume_unique=False):
+    """
+    Find the set difference of two tensors.
+
+    Return the unique values in `ar1` that are not in `ar2`.
+
+    Parameters
+    ----------
+    ar1 : array_like
+        Input tensor.
+    ar2 : array_like
+        Input comparison tensor.
+    assume_unique : bool
+        If True, the input tensors are both assumed to be unique, which
+        can speed up the calculation.  Default is False.
+
+    Returns
+    -------
+    setdiff1d : Tensor
+        1D tensor of values in `ar1` that are not in `ar2`. The result
+        is sorted when `assume_unique=False`, but otherwise only sorted
+        if the input is sorted.
+
+    Examples
+    --------
+    >>> import mars.tensor as mt
+    >>> a = mt.array([1, 2, 3, 2, 4, 1])
+    >>> b = mt.array([3, 4, 5, 6])
+    >>> mt.setdiff1d(a, b).execute()
+    array([1, 2])
+
+    """
+
+    from ..datasource.array import asarray
+    from .in1d import in1d
+    from .unique import unique
+
+    if assume_unique:
+        ar1 = asarray(ar1).ravel()
+    else:
+        ar1 = unique(ar1)
+        ar2 = unique(ar2)
+    return ar1[in1d(ar1, ar2, assume_unique=True, invert=True)]


Test patch:
diff --git a/mars/tensor/base/tests/test_base_execution.py b/mars/tensor/base/tests/test_base_execution.py
--- a/mars/tensor/base/tests/test_base_execution.py
+++ b/mars/tensor/base/tests/test_base_execution.py
@@ -1957,3 +1957,23 @@ def test_in1d_execute(setup, chunk_size, invert):
     result = ar.execute().fetch()
     expected = np.in1d(raw1, raw2, invert=invert)
     np.testing.assert_array_equal(result, expected)
+
+
+@pytest.mark.parametrize("chunk_size", [3, 5])
+def test_setdiff1d_execute(setup, chunk_size):
+    rs = np.random.RandomState(0)
+    raw1 = rs.randint(10, size=10)
+    ar1 = mt.tensor(raw1, chunk_size=5)
+    raw2 = np.arange(5)
+    ar2 = mt.tensor(raw2, chunk_size=chunk_size)
+    ar = mt.setdiff1d(ar1, ar2)
+    result = ar.execute().fetch()
+    expected = np.setdiff1d(raw1, raw2)
+    np.testing.assert_array_equal(result, expected)
+
+    raw3 = rs.shuffle(rs.choice(np.arange(100), 10))
+    ar3 = mt.tensor(raw3, chunk_size=5)
+    ar = mt.setdiff1d(ar3, ar2, assume_unique=True)
+    result = ar.execute().fetch()
+    expected = np.setdiff1d(raw3, raw2, assume_unique=True)
+    np.testing.assert_array_equal(result, expected)