This is an automated email from the ASF dual-hosted git repository.

jorisvandenbossche pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/master by this push:
     new 373812d0aa ARROW-18280: [C++][Python] Support slicing to end in 
list_slice kernel (#14749)
373812d0aa is described below

commit 373812d0aa2c8b1238ad8ed13c8a173fc2fef710
Author: Miles Granger <[email protected]>
AuthorDate: Tue Dec 6 10:41:07 2022 +0100

    ARROW-18280: [C++][Python] Support slicing to end in list_slice kernel 
(#14749)
    
    Will close [ARROW-18280](https://issues.apache.org/jira/browse/ARROW-18280)
    
    Lead-authored-by: Miles Granger <[email protected]>
    Co-authored-by: Will Jones <[email protected]>
    Signed-off-by: Joris Van den Bossche <[email protected]>
---
 cpp/src/arrow/compute/kernels/scalar_nested.cc     | 45 +++++++++++++++-------
 .../arrow/compute/kernels/scalar_nested_test.cc    | 37 ++++++++++++++----
 python/pyarrow/tests/test_compute.py               | 31 +++++++--------
 3 files changed, 75 insertions(+), 38 deletions(-)

diff --git a/cpp/src/arrow/compute/kernels/scalar_nested.cc 
b/cpp/src/arrow/compute/kernels/scalar_nested.cc
index 490ea73b16..1934212a08 100644
--- a/cpp/src/arrow/compute/kernels/scalar_nested.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_nested.cc
@@ -106,14 +106,7 @@ struct ListSlice {
     const auto opts = OptionsWrapper<ListSliceOptions>::Get(ctx);
 
     // Invariants
-    if (!opts.stop.has_value()) {
-      // TODO(ARROW-18280): Support slicing to arbitrary end
-      // For variable size list, this would be the largest difference in 
offsets
-      // For fixed size list, this would be the fixed size.
-      return Status::NotImplemented(
-          "Slicing to end not yet implemented, please set `stop` parameter.");
-    }
-    if (opts.start < 0 || opts.start >= opts.stop.value()) {
+    if (opts.start < 0 || (opts.stop.has_value() && opts.start >= 
opts.stop.value())) {
       // TODO(ARROW-18281): support start == stop which should give empty lists
       return Status::Invalid("`start`(", opts.start,
                              ") should be greater than 0 and smaller than 
`stop`(",
@@ -130,9 +123,24 @@ struct ListSlice {
         list_type->id() == arrow::Type::FIXED_SIZE_LIST);
     std::unique_ptr<ArrayBuilder> builder;
 
+    // should have been checked in resolver
+    // if stop not set, then cannot return fixed size list without input being 
fixed size
+    // list b/c we cannot determine the max list element in type resolving.
+    DCHECK(opts.stop.has_value() ||
+           (!opts.stop.has_value() && (!return_fixed_size_list ||
+                                       list_type->id() == 
arrow::Type::FIXED_SIZE_LIST)));
+
     // construct array values
     if (return_fixed_size_list) {
-      const auto length = bit_util::CeilDiv(opts.stop.value() - opts.start, 
opts.step);
+      int32_t stop;
+      if (opts.stop.has_value()) {
+        stop = static_cast<int32_t>(opts.stop.value());
+      } else {
+        DCHECK_EQ(list_type->id(), arrow::Type::FIXED_SIZE_LIST);
+        stop = reinterpret_cast<const 
FixedSizeListType*>(list_type)->list_size();
+      }
+      const auto size = std::max(stop - static_cast<int32_t>(opts.start), 0);
+      const auto length = bit_util::CeilDiv(size, opts.step);
       RETURN_NOT_OK(MakeBuilder(ctx->memory_pool(),
                                 fixed_size_list(value_type, 
static_cast<int32_t>(length)),
                                 &builder));
@@ -217,7 +225,9 @@ struct ListSlice {
     auto cursor = offset;
 
     RETURN_NOT_OK(list_builder->Append());
-    while (cursor < offset + ((opts->stop.value() - opts->start))) {
+    const auto size = opts->stop.has_value() ? (opts->stop.value() - 
opts->start)
+                                             : ((next_offset - opts->start) - 
offset);
+    while (cursor < offset + size) {
       if (cursor + opts->start >= next_offset) {
         if constexpr (!std::is_same_v<BuilderType, FixedSizeListBuilder>) {
           break;  // don't pad nulls for variable sized list output
@@ -241,14 +251,23 @@ Result<TypeHolder> MakeListSliceResolve(KernelContext* 
ctx,
   const auto return_fixed_size_list =
       opts.return_fixed_size_list.value_or(list_type->id() == 
Type::FIXED_SIZE_LIST);
   if (return_fixed_size_list) {
+    int32_t stop;
     if (!opts.stop.has_value()) {
-      return Status::NotImplemented(
-          "Unable to produce FixedSizeListArray without `stop` being set.");
+      if (list_type->id() == Type::FIXED_SIZE_LIST) {
+        stop = checked_cast<const FixedSizeListType*>(list_type)->list_size();
+      } else {
+        return Status::NotImplemented(
+            "Unable to produce FixedSizeListArray from non-FixedSizeListArray 
without "
+            "`stop` being set.");
+      }
+    } else {
+      stop = static_cast<int32_t>(opts.stop.value());
     }
+    const auto size = std::max(static_cast<int32_t>(stop - opts.start), 0);
     if (opts.step < 1) {
       return Status::Invalid("`step` must be >= 1, got: ", opts.step);
     }
-    const auto length = bit_util::CeilDiv(opts.stop.value() - opts.start, 
opts.step);
+    const auto length = bit_util::CeilDiv(size, opts.step);
     return fixed_size_list(value_type, static_cast<int32_t>(length));
   } else {
     // Returning large list if that's what we got in and didn't ask for fixed 
size
diff --git a/cpp/src/arrow/compute/kernels/scalar_nested_test.cc 
b/cpp/src/arrow/compute/kernels/scalar_nested_test.cc
index 8b655658a3..a72ec99620 100644
--- a/cpp/src/arrow/compute/kernels/scalar_nested_test.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_nested_test.cc
@@ -135,6 +135,11 @@ TEST(TestScalarNested, ListSliceVariableOutput) {
     expected = ArrayFromJSON(list(value_type), "[[3], [], [], null]");
     CheckScalarUnary("list_slice", input, expected, &args);
 
+    args.start = 1;
+    args.stop = std::nullopt;
+    expected = ArrayFromJSON(list(value_type), "[[2, 3], [5], [], null]");
+    CheckScalarUnary("list_slice", input, expected, &args);
+
     args.start = 0;
     args.stop = 4;
     args.step = 2;
@@ -174,6 +179,27 @@ TEST(TestScalarNested, ListSliceFixedOutput) {
                                "[[3, null], [null, null], [null, null], 
null]");
       CheckScalarUnary("list_slice", input, expected, &args);
 
+      args.start = 1;
+      args.stop = std::nullopt;
+      expected = ArrayFromJSON(fixed_size_list(value_type, 2),
+                               "[[2, 3], [5, null], [null, null], null]");
+      if (input->type()->id() == Type::FIXED_SIZE_LIST) {
+        CheckScalarUnary("list_slice", input, expected, &args);
+      } else {
+        EXPECT_RAISES_WITH_MESSAGE_THAT(
+            NotImplemented,
+            ::testing::HasSubstr("Unable to produce FixedSizeListArray from "
+                                 "non-FixedSizeListArray without `stop` being 
set."),
+            CallFunction("list_slice", {input}, &args));
+      }
+
+      args.start = 3;
+      args.stop = std::nullopt;
+      expected = ArrayFromJSON(fixed_size_list(value_type, 0), "[[], [], [], 
null]");
+      if (input->type()->id() == Type::FIXED_SIZE_LIST) {
+        CheckScalarUnary("list_slice", input, expected, &args);
+      }
+
       args.start = 0;
       args.stop = 4;
       args.step = 2;
@@ -276,19 +302,14 @@ TEST(TestScalarNested, ListSliceBadParameters) {
       ::testing::HasSubstr(
           "`start`(1) should be greater than 0 and smaller than `stop`(1)"),
       CallFunction("list_slice", {input}, &args));
-  // stop not set and FixedSizeList requested
+  // stop not set and FixedSizeList requested with variable sized input
   args.stop = std::nullopt;
   EXPECT_RAISES_WITH_MESSAGE_THAT(
       NotImplemented,
-      ::testing::HasSubstr("NotImplemented: Unable to produce 
FixedSizeListArray without "
+      ::testing::HasSubstr("NotImplemented: Unable to produce 
FixedSizeListArray from "
+                           "non-FixedSizeListArray without "
                            "`stop` being set."),
       CallFunction("list_slice", {input}, &args));
-  // stop not set and ListArray requested
-  args.stop = std::nullopt;
-  args.return_fixed_size_list = false;
-  EXPECT_RAISES_WITH_MESSAGE_THAT(
-      NotImplemented, ::testing::HasSubstr("Slicing to end not yet 
implemented"),
-      CallFunction("list_slice", {input}, &args));
   // Catch step must be >= 1
   args.start = 0;
   args.stop = 2;
diff --git a/python/pyarrow/tests/test_compute.py 
b/python/pyarrow/tests/test_compute.py
index 70439ac1b7..64114521a8 100644
--- a/python/pyarrow/tests/test_compute.py
+++ b/python/pyarrow/tests/test_compute.py
@@ -2950,6 +2950,7 @@ def test_cast_table_raises():
 
 
 @pytest.mark.parametrize("start,stop,expected", (
+    (0, None, [[1, 2, 3], [4, 5, None], [6, None, None], None]),
     (0, 1, [[1], [4], [6], None]),
     (0, 2, [[1, 2], [4, 5], [6, None], None]),
     (1, 2, [[2], [5], [None], None]),
@@ -2966,13 +2967,22 @@ def test_list_slice_output_fixed(start, stop, step, 
expected, value_type,
     else:
         arr = pa.array([[1, 2, 3], [4, 5], [6], None],
                        pa.list_(pa.int8())).cast(list_type(value_type()))
-    result = pc.list_slice(arr, start, stop, step, return_fixed_size_list=True)
-    pylist = result.cast(pa.list_(pa.int8(),
-                         result.type.list_size)).to_pylist()
-    assert pylist == [e[::step] if e else e for e in expected]
+
+    args = arr, start, stop, step, True
+    if stop is None and list_type != "fixed":
+        msg = ("Unable to produce FixedSizeListArray from "
+               "non-FixedSizeListArray without `stop` being set.")
+        with pytest.raises(pa.ArrowNotImplementedError, match=msg):
+            pc.list_slice(*args)
+    else:
+        result = pc.list_slice(*args)
+        pylist = result.cast(pa.list_(pa.int8(),
+                             result.type.list_size)).to_pylist()
+        assert pylist == [e[::step] if e else e for e in expected]
 
 
 @pytest.mark.parametrize("start,stop", (
+    (0, None,),
     (0, 1,),
     (0, 2,),
     (1, 2,),
@@ -3034,19 +3044,6 @@ def test_list_slice_bad_parameters():
     with pytest.raises(pa.ArrowInvalid, match=msg):
         pc.list_slice(arr, 0, 0)  # start == stop?
 
-    # TODO(ARROW-18280): support stop == None; slice to end
-    # This fails first at resolve, b/c it doesn't now how big the
-    # resulting FixedSizeListArray item size will be
-    msg = "Unable to produce FixedSizeListArray without `stop`"
-    with pytest.raises(NotImplementedError, match=msg):
-        pc.list_slice(arr, 0, return_fixed_size_list=True)
-
-    # cont. This fails inside of kernel function; resolver doesn't
-    # need to know the item size for ListArray.
-    msg = "Slicing to end not yet implemented*"
-    with pytest.raises(NotImplementedError, match=msg):
-        pc.list_slice(arr, 0, return_fixed_size_list=False)
-
     # Step not >= 1
     msg = "`step` must be >= 1, got: "
     with pytest.raises(pa.ArrowInvalid, match=msg + "0"):

Reply via email to