Repository: dask/dask

Issue:
Short-cut for rechunking an array into a single chunk along an axis?
Currently this requires writing something like `x.rechunk((x.shape[0], ...))` or `x.rechunk((1000000,))`, which is a little clunky.

Possible alternatives:
1. Use `-1` as an alias for "full dimension size", e.g., `x.rechunk((-1,))`.
2. Add a keyword argument, e.g., `rechunk(divisions=(1,))` for rechunking into equally sized divisions instead of chunks.


Discussion:
Using an alias makes sense to me.  Doesn't reshape have similar aliases already?

Resolving patch:
diff --git a/dask/array/core.py b/dask/array/core.py
--- a/dask/array/core.py
+++ b/dask/array/core.py
@@ -1823,6 +1823,9 @@ def normalize_chunks(chunks, shape=None):
     >>> normalize_chunks(10, shape=(30, 5))  # Supports integer inputs
     ((10, 10, 10), (5,))
 
+    >>> normalize_chunks((-1,), shape=(10,))  # -1 gets mapped to full size
+    ((10,),)
+
     >>> normalize_chunks((), shape=(0, 0))  #  respects null dimensions
     ((0,), (0,))
     """
@@ -1843,7 +1846,8 @@ def normalize_chunks(chunks, shape=None):
                 "Got chunks=%s, shape=%s" % (chunks, shape))
 
     if shape is not None:
-        chunks = tuple(c if c is not None else s for c, s in zip(chunks, shape))
+        chunks = tuple(c if c not in {None, -1} else s
+                       for c, s in zip(chunks, shape))
 
     if chunks and shape is not None:
         chunks = sum((blockdims_from_blockshape((s,), (c,))
@@ -1872,6 +1876,8 @@ def from_array(x, chunks, name=None, lock=False, asarray=True, fancy=True,
         - A blockshape like (1000, 1000).
         - Explicit sizes of all blocks along all dimensions
           like ((1000, 1000, 500), (400, 400)).
+
+        -1 as a blocksize indicates the size of the corresponding dimension.
     name : str, optional
         The key name to use for the array. Defaults to a hash of ``x``.
         Use ``name=False`` to generate a random name instead of hashing (fast)
diff --git a/dask/array/ghost.py b/dask/array/ghost.py
--- a/dask/array/ghost.py
+++ b/dask/array/ghost.py
@@ -375,7 +375,7 @@ def add_dummy_padding(x, depth, boundary):
             ax_chunks = list(out_chunks[k])
             ax_chunks[0] += d
             ax_chunks[-1] += d
-            out_chunks[k] = ax_chunks
+            out_chunks[k] = tuple(ax_chunks)
 
             x = concatenate([empty, x, empty], axis=k)
             x = x.rechunk(out_chunks)


Test patch:
diff --git a/dask/array/tests/test_array_core.py b/dask/array/tests/test_array_core.py
--- a/dask/array/tests/test_array_core.py
+++ b/dask/array/tests/test_array_core.py
@@ -1414,6 +1414,13 @@ def my_getitem(x, ind):
     assert_eq(x, y)
 
 
+def test_from_array_minus_one():
+    x = np.arange(10)
+    y = da.from_array(x, -1)
+    assert y.chunks == ((10,),)
+    assert_eq(x, y)
+
+
 def test_asarray():
     assert_eq(da.asarray([1, 2, 3]), np.asarray([1, 2, 3]))
 
diff --git a/dask/array/tests/test_rechunk.py b/dask/array/tests/test_rechunk.py
--- a/dask/array/tests/test_rechunk.py
+++ b/dask/array/tests/test_rechunk.py
@@ -198,6 +198,13 @@ def test_rechunk_same():
     assert x is y
 
 
+def test_rechunk_minus_one():
+    x = da.ones((24, 24), chunks=(4, 8))
+    y = x.rechunk((-1, 8))
+    assert y.chunks == ((24,), (8, 8, 8))
+    assert_eq(x, y)
+
+
 def test_rechunk_intermediates():
     x = da.random.normal(10, 0.1, (10, 10), chunks=(10, 1))
     y = x.rechunk((1, 10))