diff --git a/g3doc/op_wishlist.md b/g3doc/op_wishlist.md index 3de0c8f49c..be4b0720bc 100644 --- a/g3doc/op_wishlist.md +++ b/g3doc/op_wishlist.md @@ -36,8 +36,6 @@ fmod, nexttoward * In-place Remove / RemoveIf, like CopyIf, but in-place. -* FindLast / FindLastIf (in find-inl.h) - can use FindLastTrue. - * index-returning Mismatch(d, a, b, count) (in find-inl.h) - like EqualSpan, but returns the first mismatched index. @@ -174,6 +172,7 @@ For SVE (svld1sb_u32)+WASM? Compiler can probably already fuse. * ~~Document Reduce/Min NaN behavior~~ * ~~IndexOfMin/Max~~ (algo) * ~~AllOf / AnyOf / NoneOf~~ (algo) +* ~~FindLast / FindLastIf~~ (algo) * ~~EqualSpan~~ (algo) * ~~ReverseSpan~~ (algo) * ~~NEON dot product~~ diff --git a/hwy/contrib/algo/find-inl.h b/hwy/contrib/algo/find-inl.h index ae7371bbd7..692a733c99 100644 --- a/hwy/contrib/algo/find-inl.h +++ b/hwy/contrib/algo/find-inl.h @@ -108,6 +108,56 @@ size_t FindIf(D d, const T* HWY_RESTRICT in, size_t count, const Func& func) { return count; // not found } +// Returns index of the last element in `in[0, count)` for which `func(d, vec)` +// returns true, or `count` if no element matches. +template > +size_t FindLastIf(D d, const T* HWY_RESTRICT in, size_t count, + const Func& func) { + HWY_LANES_CONSTEXPR size_t N = Lanes(d); + size_t remaining = count; + + // Visit pairs of vectors from the end so the highest matching index wins. + if (HWY_LIKELY(remaining >= 2 * N)) { + for (; remaining >= 2 * N; remaining -= 2 * N) { + const size_t i = remaining - 2 * N; + const Mask match0 = func(d, LoadU(d, in + i)); + const Mask match1 = func(d, LoadU(d, in + i + N)); + if (AllFalse(d, Or(match0, match1))) continue; + const intptr_t pos1 = FindLastTrue(d, match1); + if (pos1 >= 0) return i + N + static_cast(pos1); + return i + FindKnownLastTrue(d, match0); + } + } + + if (remaining >= N) { + const size_t i = remaining - N; + const Mask match = func(d, LoadU(d, in + i)); + const intptr_t pos = FindLastTrue(d, match); + if (pos >= 0) return i + static_cast(pos); + remaining -= N; + } + + HWY_DASSERT(remaining < N); + if (remaining != 0) { + const Mask valid = FirstN(d, remaining); + const Mask match = And(valid, func(d, LoadN(d, in, remaining))); + const intptr_t pos = FindLastTrue(d, match); + if (pos >= 0) return static_cast(pos); + } + + return count; // not found +} + +// Returns index of the last element equal to `value` in `in[0, count)`, or +// `count` if not found. +template > +size_t FindLast(D d, T value, const T* HWY_RESTRICT in, size_t count) { + const auto equal = [value](const auto d2, const auto v) HWY_ATTR { + return Eq(Set(d2, value), v); + }; + return FindLastIf(d, in, count, equal); +} + // AnyOf/AllOf/NoneOf unroll 4x, matching AllUnique below rather than the 2x in // Find/FindIf: none of them needs the position of a match, so an iteration is // just the loads, the predicate and one mask test, with no loop-carried state. diff --git a/hwy/contrib/algo/find_test.cc b/hwy/contrib/algo/find_test.cc index 4c4e310173..a9f98eb16a 100644 --- a/hwy/contrib/algo/find_test.cc +++ b/hwy/contrib/algo/find_test.cc @@ -89,6 +89,15 @@ struct TestFind { // For each position, search for that element (which we know is there) for (size_t pos = 0; pos < count; ++pos) { const size_t actual = Find(d, in[pos], in, count); + const size_t actual_last = FindLast(d, in[pos], in, count); + size_t expected_last = count; + for (size_t i = count; i != 0; --i) { + if (IsEqual(in[i - 1], in[pos])) { + expected_last = i - 1; + break; + } + } + HWY_ASSERT_EQ(expected_last, actual_last); // We may have found an earlier occurrence of the same value; ensure the // value is the same, and that it is the first. @@ -113,6 +122,8 @@ struct TestFind { // Also search for values we know not to be present (out of range) HWY_ASSERT_EQ(count, Find(d, ConvertScalarTo(9), in, count)); HWY_ASSERT_EQ(count, Find(d, ConvertScalarTo(-9), in, count)); + HWY_ASSERT_EQ(count, FindLast(d, ConvertScalarTo(9), in, count)); + HWY_ASSERT_EQ(count, FindLast(d, ConvertScalarTo(-9), in, count)); } }; @@ -148,6 +159,7 @@ struct TestFindIf { return Gt(v, Set(d2, ConvertScalarTo(val))); }; const size_t actual = FindIf(d, in, count, greater); + const size_t actual_last = FindLastIf(d, in, count, greater); found_any |= actual < count; not_found_any |= actual == count; @@ -163,6 +175,15 @@ struct TestFindIf { 0, count); HWY_ASSERT(false); } + + size_t expected_last = count; + for (size_t i = count; i != 0; --i) { + if (in[i - 1] > ConvertScalarTo(val)) { + expected_last = i - 1; + break; + } + } + HWY_ASSERT_EQ(expected_last, actual_last); } // We will always not-find something due to val=9. @@ -172,6 +193,16 @@ struct TestFindIf { if (count != 0 && in[0] != ConvertScalarTo(0)) { HWY_ASSERT(found_any); } + + const auto always = [](const auto /*d2*/, const auto v) HWY_ATTR { + return Eq(v, v); + }; + const auto never = [](const auto /*d2*/, const auto v) HWY_ATTR { + return Ne(v, v); + }; + HWY_ASSERT_EQ(count == 0 ? count : count - 1, + FindLastIf(d, in, count, always)); + HWY_ASSERT_EQ(count, FindLastIf(d, in, count, never)); } };