Repository: cupy/cupy

Issue:
Add resize function and method
Would be nice to have an analogous function and method to NumPy's `resize`.

ref: https://docs.scipy.org/doc/numpy/reference/generated/numpy.resize.html
ref: https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.resize.html


Discussion:

Resolving patch:
diff --git a/cupy/__init__.py b/cupy/__init__.py
--- a/cupy/__init__.py
+++ b/cupy/__init__.py
@@ -309,6 +309,8 @@ def is_available():
 from cupy.manipulation.tiling import repeat  # NOQA
 from cupy.manipulation.tiling import tile  # NOQA
 
+from cupy.manipulation.add_remove import append  # NOQA
+from cupy.manipulation.add_remove import resize  # NOQA
 from cupy.manipulation.add_remove import unique  # NOQA
 
 from cupy.manipulation.rearrange import flip  # NOQA
diff --git a/cupy/manipulation/add_remove.py b/cupy/manipulation/add_remove.py
--- a/cupy/manipulation/add_remove.py
+++ b/cupy/manipulation/add_remove.py
@@ -1,4 +1,7 @@
+import numpy
+
 import cupy
+from cupy import core
 
 
 # TODO(okuta): Implement delete
@@ -7,10 +10,67 @@
 # TODO(okuta): Implement insert
 
 
-# TODO(okuta): Implement append
+def append(arr, values, axis=None):
+    """
+    Append values to the end of an array.
+
+    Args:
+        arr(array_like):
+            Values are appended to a copy of this array.
+        values(array_like):
+            These values are appended to a copy of `arr`.  It must be of the
+            correct shape (the same shape as `arr`, excluding `axis`).  If
+            `axis` is not specified, `values` can be any shape and will be
+            flattened before use.
+        axis(int, optional):
+            The axis along which `values` are appended.  If `axis` is not
+            given, both `arr` and `values` are flattened before use.
+
+    Returns:
+        ndarray
+            A copy of `arr` with `values` appended to `axis`.  Note that
+            `append` does not occur in-place: a new array is allocated and
+            filled.  If `axis` is None, `out` is a flattened array.
+
+    .. seealso:: :func:`numpy.append`
+    """
+    arr = cupy.asarray(arr)
+    values = cupy.asarray(values)
+    if axis is None:
+        return core.concatenate_method(
+            (arr.ravel(), values.ravel()), 0).ravel()
+    return core.concatenate_method((arr, values), axis)
 
 
-# TODO(okuta): Implement resize
+def resize(a, new_shape):
+    """Return a new array with the specified shape.
+
+    If the new array is larger than the original array, then the new
+    array is filled with repeated copies of `a`.  Note that this behavior
+    is different from a.resize(new_shape) which fills with zeros instead
+    of repeated copies of `a`.
+
+    Args:
+        a(array_like): Array to be resized.
+        new_shape(int or tuple of int): Shape of resized array.
+
+    Returns:
+        ndarray:
+            The new array is formed from the data in the old array, repeated
+            if necessary to fill out the required number of elements.  The
+            data are repeated in the order that they are stored in memory.
+
+    .. seealso:: :func:`numpy.resize`
+    """
+    if numpy.isscalar(a):
+        return cupy.full(new_shape, a)
+    if a.shape == () or len(a) == 0:
+        # 0-dim or empty array
+        return cupy.zeros(new_shape, dtype=a.dtype)
+    a = a.ravel()
+    reps, remainder = divmod(numpy.prod(new_shape), len(a))
+    return core.concatenate_method(
+        [a] * reps + [a[:remainder]], 0).reshape(new_shape)
 
 
 # TODO(okuta): Implement trim_zeros


Test patch:
diff --git a/tests/cupy_tests/manipulation_tests/test_add_remove.py b/tests/cupy_tests/manipulation_tests/test_add_remove.py
--- a/tests/cupy_tests/manipulation_tests/test_add_remove.py
+++ b/tests/cupy_tests/manipulation_tests/test_add_remove.py
@@ -3,6 +3,78 @@
 from cupy import testing
 
 
+@testing.gpu
+class TestAppend(unittest.TestCase):
+
+    @testing.numpy_cupy_array_equal()
+    def test(self, xp):
+        a = testing.shaped_random((3, 4, 5), xp, xp.float32)
+        b = testing.shaped_random((6, 7), xp, xp.float32)
+        return xp.append(a, b)
+
+    @testing.numpy_cupy_array_equal()
+    def test_scalar_lhs(self, xp):
+        return xp.append(10, xp.arange(20))
+
+    @testing.numpy_cupy_array_equal()
+    def test_scalar_rhs(self, xp):
+        return xp.append(xp.arange(20), 10)
+
+    @testing.numpy_cupy_array_equal()
+    def test_scalar_both(self, xp):
+        return xp.append(10, 10)
+
+    @testing.numpy_cupy_array_equal()
+    def test_axis(self, xp):
+        a = testing.shaped_random((3, 4, 5), xp, xp.float32)
+        b = testing.shaped_random((3, 10, 5), xp, xp.float32)
+        return xp.append(a, b, axis=1)
+
+    @testing.numpy_cupy_array_equal()
+    def test_zerodim(self, xp):
+        return xp.append(xp.array(0), xp.arange(10))
+
+    @testing.numpy_cupy_array_equal()
+    def test_empty(self, xp):
+        return xp.append(xp.array([]), xp.arange(10))
+
+
+@testing.gpu
+class TestResize(unittest.TestCase):
+
+    @testing.numpy_cupy_array_equal()
+    def test(self, xp):
+        return xp.resize(xp.arange(10), (10, 10))
+
+    @testing.numpy_cupy_array_equal()
+    def test_remainder(self, xp):
+        return xp.resize(xp.arange(8), (10, 10))
+
+    @testing.numpy_cupy_array_equal()
+    def test_shape_int(self, xp):
+        return xp.resize(xp.arange(10), 15)
+
+    @testing.numpy_cupy_array_equal()
+    def test_scalar(self, xp):
+        return xp.resize(2, (10, 10))
+
+    @testing.numpy_cupy_array_equal()
+    def test_scalar_shape_int(self, xp):
+        return xp.resize(2, 10)
+
+    @testing.numpy_cupy_array_equal()
+    def test_typed_scalar(self, xp):
+        return xp.resize(xp.float32(10.0), (10, 10))
+
+    @testing.numpy_cupy_array_equal()
+    def test_zerodim(self, xp):
+        return xp.resize(xp.array(0), (10, 10))
+
+    @testing.numpy_cupy_array_equal()
+    def test_empty(self, xp):
+        return xp.resize(xp.array([]), (10, 10))
+
+
 @testing.gpu
 class TestUnique(unittest.TestCase):