Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions g3doc/op_wishlist.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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~~
Expand Down
50 changes: 50 additions & 0 deletions hwy/contrib/algo/find-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <class D, class Func, typename T = TFromD<D>>
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<D> match0 = func(d, LoadU(d, in + i));
const Mask<D> 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<size_t>(pos1);
return i + FindKnownLastTrue(d, match0);
}
}

if (remaining >= N) {
const size_t i = remaining - N;
const Mask<D> match = func(d, LoadU(d, in + i));
const intptr_t pos = FindLastTrue(d, match);
if (pos >= 0) return i + static_cast<size_t>(pos);
remaining -= N;
}

HWY_DASSERT(remaining < N);
if (remaining != 0) {
const Mask<D> valid = FirstN(d, remaining);
const Mask<D> match = And(valid, func(d, LoadN(d, in, remaining)));
const intptr_t pos = FindLastTrue(d, match);
if (pos >= 0) return static_cast<size_t>(pos);
}

return count; // not found
}

// Returns index of the last element equal to `value` in `in[0, count)`, or
// `count` if not found.
template <class D, typename T = TFromD<D>>
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.
Expand Down
31 changes: 31 additions & 0 deletions hwy/contrib/algo/find_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<T>(9), in, count));
HWY_ASSERT_EQ(count, Find(d, ConvertScalarTo<T>(-9), in, count));
HWY_ASSERT_EQ(count, FindLast(d, ConvertScalarTo<T>(9), in, count));
HWY_ASSERT_EQ(count, FindLast(d, ConvertScalarTo<T>(-9), in, count));
}
};

Expand Down Expand Up @@ -148,6 +159,7 @@ struct TestFindIf {
return Gt(v, Set(d2, ConvertScalarTo<T>(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;

Expand All @@ -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<T>(val)) {
expected_last = i - 1;
break;
}
}
HWY_ASSERT_EQ(expected_last, actual_last);
}

// We will always not-find something due to val=9.
Expand All @@ -172,6 +193,16 @@ struct TestFindIf {
if (count != 0 && in[0] != ConvertScalarTo<T>(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));
}
};

Expand Down
Loading