class PairPermutationSpliterator<T, R> extends CloneableSpliterator<R, PairPermutationSpliterator<T, R>> {
    private long cur;
    private long limit;
    private final int size;
    private int idx1;
    private int idx2;
    private final List<T> list;
    private final BiFunction<? super T, ? super T, ? extends R> mapper;

    public PairPermutationSpliterator(List<T> list, BiFunction<? super T, ? super T, ? extends R> mapper) {
        this.list = list;
        this.size = list.size();
        this.idx2 = 1;
        this.limit = size * (size - 1L) / 2;
        this.mapper = mapper;
    }

    @Override
    public long estimateSize() {
        return limit - cur;
    }

    @Override
    public int characteristics() {
        return ORDERED | SIZED | SUBSIZED;
    }

    /*
     * Calculates (int) (Math.sqrt(8 * n + 1)-1)/2 Produces exact result for any
     * long input from 0 to 0x1FFFFFFFC0000000L (2^61-2^30).
     */
    static int isqrt(long n) {<FILL_FUNCTION_BODY>}

    @Override
    public Spliterator<R> trySplit() {
        long size = limit - cur;
        if (size >= 2) {
            PairPermutationSpliterator<T, R> clone = doClone();
            clone.limit = this.cur = this.cur + size / 2;
            int s = this.size;
            long rev = s * (s - 1L) / 2 - this.cur - 1;
            int row = isqrt(rev);
            int col = (int) (rev - (row) * (row + 1L) / 2);
            this.idx1 = s - row - 2;
            this.idx2 = s - col - 1;
            return clone;
        }
        return null;
    }

    @Override
    public boolean tryAdvance(Consumer<? super R> action) {
        if (cur == limit)
            return false;
        action.accept(mapper.apply(list.get(idx1), list.get(idx2)));
        cur++;
        if (++idx2 == size) {
            idx2 = ++idx1 + 1;
        }
        return true;
    }

    @Override
    public void forEachRemaining(Consumer<? super R> action) {
        int idx1 = this.idx1;
        int idx2 = this.idx2;
        int size = this.size;
        long cur = this.cur;
        long limit = this.limit;
        while (cur < limit) {
            T item1 = list.get(idx1++);
            while (cur < limit && idx2 < size) {
                T item2 = list.get(idx2++);
                action.accept(mapper.apply(item1, item2));
                cur++;
            }
            idx2 = idx1 + 1;
        }
        this.cur = this.limit;
    }
}

        int x = (int) ((Math.sqrt(8.0 * n + 1.0) - 1.0) / 2.0);
        if (x * (x + 1L) / 2 > n)
            x--;
        return x;
    
<methods>public non-sealed void <init>() <variables>