This is an archive of the discontinued LLVM Phabricator instance.

Revert "Revert "[libc++] [P0879] constexpr std::nth_element, and rewrite its tests.""
ClosedPublic

Authored by Quuxplusone on Feb 4 2021, 3:19 PM.

Details

Summary

The bug is now fixed (it was a stupid cut-and-paste kind of error), and the regression test added.
The new patch is also simpler than the old one!

This is an updated version of D93557.

Subsumes D96074.

Diff Detail

Event Timeline

Quuxplusone requested review of this revision.Feb 4 2021, 3:19 PM
Quuxplusone created this revision.
Herald added a reviewer: Restricted Project. · View Herald TranscriptFeb 4 2021, 3:19 PM
ldionne accepted this revision.Feb 5 2021, 7:10 AM

Simple "manual" tests can also pay off :-) Thanks for fixing quickly.

This revision is now accepted and ready to land.Feb 5 2021, 7:10 AM
Quuxplusone planned changes to this revision.EditedFeb 5 2021, 8:07 AM

Simple "manual" tests can also pay off :-)

Well, actually, before landing this I thought I'd better run some more randomized tests... and I found that this patch also has a bug somewhere! Reduced from the randomized tests:

std::vector<int> v2 = { 18, -7, 1, -9, 3, 13, -10, -10, -9, 4, -10, 5, 8, 6, 11, -10, 19 };
std::nth_element(v2.begin(), v2.begin() + 10, v2.end());
assert(std::all_of(v2.begin(), v2.begin() + 10, [&](int x){ return x <= v2[10]; }));
assert(std::all_of(v2.begin() + 10, v2.end(), [&](int x){ return x >= v2[10]; }));  // fails!

So I'm still working on this.

...EDIT: wait, no, maybe I messed up and this fails using the old version but not the new version. Blech. Anyway, if I land this today it'll be after convincing myself that it passes randomized tests too.

This revision was not accepted when it landed; it landed in state Changes Planned.Feb 5 2021, 9:03 AM
This revision was automatically updated to reflect the committed changes.

FWIW, I applied this patch and was able to make it pass this pretty exhaustive test:

  {
  constexpr int kMaxLen = 64;
  for (int len = 1; len < kMaxLen; ++len) {
    int v[kMaxLen];
    for (int i = 0; i < len; ++i)
      v[i] = i;
    T v2[kMaxLen];
    std::copy(v, v + len, v2);
    do {
      for (int n = 0; n < len; ++n) {
        for (int m = 0; m < n; ++m) {
          std::nth_element(Iter(v2), Iter(v2 + m), Iter(v2 + n),
                           std::greater<T>());
          assert(std::is_permutation(v2, v2 + len, v));
          // No element to m's left is less than m.
          for (int i = 0; i < m; ++i) {
            assert(!(v2[i] < v2[m]));
          }
          // No element to m's right is greater than m.
          for (int i = m; i < n; ++i) {
            assert(!(v2[i] > v2[m]));
          }
        }
      }
    } while (std::next_permutation(Iter(v2), Iter(v2 + len)));
  }
}

FWIW, I applied this patch and was able to make it pass this pretty exhaustive test: [...]

@rupprecht: I'm looking at adding a similar test to the libc++ test suite, but, I don't really understand what this test is doing. You're running nth_element repeatedly on the same array with increasing m and n, which is kind of a selection sort on the first len-1 elements? And then next_permutation takes it from 4 3 2 1 0 5 to 4 3 2 1 5 0 and we selection-sort the first n-1 elements again to get 5 4 3 2 1 0, and then the second next_permutation kicks us out to increment len?
I thought maybe you were going for the "try all permutations of 64 elements" exhaustive test, but that would take 64! iterations which is heat-death-of-the-universe territory.
Is there a subtle logic to the particular sequence your test is actually doing?