static void remove(Object[] input, int i) {
    int j = i + 2;
    for (; j < input.length; i += 2, j += 2) {
      if (input[j] == null) break; // found null key
      input[i] = input[j];
      input[i + 1] = input[j + 1];
    }
    input[i] = input[i + 1] = null;
  }
@Test void equalsAndHashCode() {
    for (Supplier<MutableSpan> constructor : PERMUTATIONS) {
      // same instance are equivalent
      MutableSpan span = constructor.get();
      assertEqualWithSameHashCode(span, span);
      assertThat(span).usingRecursiveComparison().isEqualTo(span); // double check

      // same fields are equivalent
      MutableSpan sameFields = constructor.get();
      assertEqualWithSameHashCode(sameFields, span);
      assertThat(sameFields).usingRecursiveComparison().isEqualTo(span); // double check our impl

      // copy constructor are equivalent
      MutableSpan copy = new MutableSpan(span);
      assertEqualWithSameHashCode(copy, span);
      assertThat(copy).usingRecursiveComparison().isEqualTo(span); // double check our impl

      // This seems redundant, and mostly is, but the order of equals matters
      List<Supplier<MutableSpan>> exceptMe = new ArrayList<>(PERMUTATIONS);
      exceptMe.remove(constructor);
      for (Supplier<MutableSpan> otherConstructor : exceptMe) {
        MutableSpan other = otherConstructor.get();
        assertThat(span).isNotSameAs(other); // sanity
        assertNeitherEqualNorShareHashCode(span, other);
        assertThat(Objects.deepEquals(span, other)).isFalse(); // double check our impl
      }
    }
  }