All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.landawn.abacus.util.BiIterator Maven / Gradle / Ivy

/*
 * Copyright (c) 2018, Haiyang Li.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.landawn.abacus.util;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Supplier;

import com.landawn.abacus.util.Fn.Fnn;
import com.landawn.abacus.util.Fn.Suppliers;
import com.landawn.abacus.util.u.Optional;
import com.landawn.abacus.util.function.IntObjConsumer;
import com.landawn.abacus.util.stream.EntryStream;
import com.landawn.abacus.util.stream.Stream;

/**
 * The BiIterator class is an abstract class that extends ImmutableIterator.
 * It represents an iterator over a pair of values of type A and B.
 * This class provides a blueprint for classes that need to implement a bidirectional iterator.
 *
 * @param  the first type of elements returned by this iterator
 * @param  the second type of elements returned by this iterator
 *
 * @see com.landawn.abacus.util.Iterators
 * @see com.landawn.abacus.util.Enumerations
 *
 */
@SuppressWarnings({ "java:S6548" })
public abstract class BiIterator extends ImmutableIterator> {

    @SuppressWarnings("rawtypes")
    private static final BiIterator EMPTY = new BiIterator() {
        @Override
        public boolean hasNext() {
            return false;
        }

        @Override
        public Object next() {
            throw new NoSuchElementException(InternalUtil.ERROR_MSG_FOR_NO_SUCH_EX);
        }

        @Override
        protected void next(final Throwables.BiConsumer action) throws NoSuchElementException {
            throw new NoSuchElementException(InternalUtil.ERROR_MSG_FOR_NO_SUCH_EX);
        }

        @Override
        public void forEachRemaining(final BiConsumer action) throws IllegalArgumentException {
            N.checkArgNotNull(action);
        }

        @Override
        public void foreachRemaining(final Throwables.BiConsumer action) throws IllegalArgumentException {
            N.checkArgNotNull(action);
        }

        @Override
        public ObjIterator map(final BiFunction mapper) throws IllegalArgumentException {
            N.checkArgNotNull(mapper, cs.mapper);

            return ObjIterator.empty();
        }
    };

    @SuppressWarnings("rawtypes")
    private static final Throwables.BiConsumer DO_NOTHING = (a, b) -> {
        // do nothing;
    };

    /**
     * Returns an empty BiIterator instance.
     *
     * @param  the first type of elements returned by this iterator
     * @param  the second type of elements returned by this iterator
     * @return an empty BiIterator instance
     */
    public static  BiIterator empty() {
        return EMPTY;
    }

    /**
     * Returns a BiIterator for the given map.
     * If the map is empty, returns an empty BiIterator.
     *
     * @param  the type of keys in the map
     * @param  the type of values in the map
     * @param map the map to create the BiIterator from
     * @return a BiIterator over the entries of the map
     */
    public static  BiIterator of(final Map map) {
        if (N.isEmpty(map)) {
            return empty();
        }

        return of(map.entrySet().iterator());
    }

    /**
     * Returns a BiIterator for the given iterator of map entries.
     * If the iterator is {@code null}, returns an empty BiIterator.
     *
     * @param  the type of keys in the map entries
     * @param  the type of values in the map entries
     * @param iter the iterator of map entries to create the BiIterator from
     * @return a BiIterator over the entries of the iterator
     */
    public static  BiIterator of(final Iterator> iter) {
        if (iter == null) {
            return empty();
        }

        return new BiIterator<>() {

            @Override
            public boolean hasNext() {
                return iter.hasNext();
            }

            @Override
            public Pair next() {
                return Pair.create(iter.next());
            }

            @Override
            protected  void next(final Throwables.BiConsumer action) throws NoSuchElementException, E {
                // N.checkArgNotNull(action);

                final Map.Entry entry = iter.next();

                action.accept(entry.getKey(), entry.getValue());
            }

            @Override
            public void forEachRemaining(final BiConsumer action) throws IllegalArgumentException {
                N.checkArgNotNull(action);

                Map.Entry entry = null;

                while (iter.hasNext()) {
                    entry = iter.next();
                    action.accept(entry.getKey(), entry.getValue());
                }
            }

            @Override
            public  void foreachRemaining(final Throwables.BiConsumer action) throws IllegalArgumentException, E {
                N.checkArgNotNull(action);

                Map.Entry entry = null;

                while (iter.hasNext()) {
                    entry = iter.next();
                    action.accept(entry.getKey(), entry.getValue());
                }
            }

            @Override
            public  ObjIterator map(final BiFunction mapper) throws IllegalArgumentException {
                N.checkArgNotNull(mapper);

                return new ObjIterator<>() {
                    private Map.Entry entry = null;

                    @Override
                    public boolean hasNext() {
                        return iter.hasNext();
                    }

                    @Override
                    public R next() {
                        entry = iter.next();

                        return mapper.apply(entry.getKey(), entry.getValue());
                    }
                };
            }
        };
    }

    /**
     * Generates an infinite {@code BiIterator} instance with the provided output Consumer.
     * The output Consumer is responsible for producing the next Pair on each iteration.
     * This method is typically used for generating a BiIterator with custom logic.
     *
     * @param  the first type of elements returned by this iterator
     * @param  the second type of elements returned by this iterator
     * @param output A Consumer that accepts a Pair and produces the next Pair on each iteration.
     * @return A BiIterator that uses the provided output Consumer to generate its elements.
     * @see #generate(BooleanSupplier, Consumer)
     */
    public static  BiIterator generate(final Consumer> output) {
        return generate(com.landawn.abacus.util.function.BooleanSupplier.TRUE, output);
    }

    /**
     * Generates a BiIterator instance with the provided hasNext BooleanSupplier and output Consumer.
     * The hasNext BooleanSupplier is used to determine if the iterator has more elements.
     * The output Consumer is responsible for producing the next Pair on each iteration.
     *
     * @param  the first type of elements returned by this iterator
     * @param  the second type of elements returned by this iterator
     * @param hasNext A BooleanSupplier that returns {@code true} if the iterator has more elements.
     * @param output A Consumer that accepts a Pair and produces the next Pair on each iteration.
     * @return A BiIterator that uses the provided hasNext BooleanSupplier and output Consumer to generate its elements.
     * @throws IllegalArgumentException If hasNext or output is {@code null}.
     */
    public static  BiIterator generate(final BooleanSupplier hasNext, final Consumer> output) throws IllegalArgumentException {
        N.checkArgNotNull(hasNext);
        N.checkArgNotNull(output);

        return new BiIterator<>() {
            private final Pair tmp = new Pair<>();

            @Override
            public boolean hasNext() {
                return hasNext.getAsBoolean();
            }

            @Override
            public Pair next() {
                if (!hasNext()) {
                    throw new NoSuchElementException(InternalUtil.ERROR_MSG_FOR_NO_SUCH_EX);
                }

                output.accept(tmp);

                return Pair.of(tmp.left, tmp.right);
            }

            @Override
            protected  void next(final Throwables.BiConsumer action) throws NoSuchElementException, E {
                // N.checkArgNotNull(action);

                if (!hasNext()) {
                    throw new NoSuchElementException(InternalUtil.ERROR_MSG_FOR_NO_SUCH_EX);
                }

                output.accept(tmp);

                action.accept(tmp.left, tmp.right);
            }

            @Override
            public void forEachRemaining(final BiConsumer action) throws IllegalArgumentException {
                N.checkArgNotNull(action);

                while (hasNext.getAsBoolean()) {
                    output.accept(tmp);

                    action.accept(tmp.left, tmp.right);
                }
            }

            @Override
            public  void foreachRemaining(final Throwables.BiConsumer action) throws IllegalArgumentException, E {
                N.checkArgNotNull(action);

                while (hasNext.getAsBoolean()) {
                    output.accept(tmp);

                    action.accept(tmp.left, tmp.right);
                }
            }

            @Override
            public  ObjIterator map(final BiFunction mapper) throws IllegalArgumentException {
                N.checkArgNotNull(mapper);

                return new ObjIterator<>() {
                    @Override
                    public boolean hasNext() {
                        return hasNext.getAsBoolean();
                    }

                    @Override
                    public R next() {
                        if (!hasNext()) {
                            throw new NoSuchElementException(InternalUtil.ERROR_MSG_FOR_NO_SUCH_EX);
                        }

                        output.accept(tmp);

                        return mapper.apply(tmp.left, tmp.right);
                    }
                };
            }
        };
    }

    /**
     * Generates a BiIterator instance with the provided fromIndex, toIndex, and output IntObjConsumer.
     * The fromIndex and toIndex define the size of the returned iterator.
     * The output IntObjConsumer is responsible for producing the next Pair on each iteration.
     *
     * @param  the first type of elements returned by this iterator
     * @param  the second type of elements returned by this iterator
     * @param fromIndex The starting index of the iterator.
     * @param toIndex The ending index of the iterator.
     * @param output An IntObjConsumer that accepts an integer and a Pair and produces the next Pair on each iteration.
     * @return A BiIterator that uses the provided fromIndex, toIndex, and output IntObjConsumer to generate its elements.
     * @throws IllegalArgumentException If fromIndex is greater than toIndex.
     * @throws IndexOutOfBoundsException If fromIndex or toIndex is out of range.
     */
    public static  BiIterator generate(final int fromIndex, final int toIndex, final IntObjConsumer> output)
            throws IllegalArgumentException, IndexOutOfBoundsException {
        N.checkFromToIndex(fromIndex, toIndex, Integer.MAX_VALUE);
        N.checkArgNotNull(output);

        return new BiIterator<>() {
            private final MutableInt cursor = MutableInt.of(fromIndex);
            private final Pair tmp = new Pair<>();

            @Override
            public boolean hasNext() {
                return cursor.value() < toIndex;
            }

            @Override
            public Pair next() {
                if (!hasNext()) {
                    throw new NoSuchElementException(InternalUtil.ERROR_MSG_FOR_NO_SUCH_EX);
                }

                output.accept(cursor.getAndIncrement(), tmp);

                return Pair.of(tmp.left, tmp.right);
            }

            @Override
            protected  void next(final Throwables.BiConsumer action) throws NoSuchElementException, E {
                // N.checkArgNotNull(action);

                if (!hasNext()) {
                    throw new NoSuchElementException(InternalUtil.ERROR_MSG_FOR_NO_SUCH_EX);
                }

                output.accept(cursor.getAndIncrement(), tmp);

                action.accept(tmp.left, tmp.right);
            }

            @Override
            public void forEachRemaining(final BiConsumer action) throws IllegalArgumentException {
                N.checkArgNotNull(action);

                while (cursor.value() < toIndex) {
                    output.accept(cursor.getAndIncrement(), tmp);

                    action.accept(tmp.left, tmp.right);
                }
            }

            @Override
            public  void foreachRemaining(final Throwables.BiConsumer action) throws IllegalArgumentException, E {
                N.checkArgNotNull(action);

                while (cursor.value() < toIndex) {
                    output.accept(cursor.getAndIncrement(), tmp);

                    action.accept(tmp.left, tmp.right);
                }
            }

            @Override
            public  ObjIterator map(final BiFunction mapper) throws IllegalArgumentException {
                N.checkArgNotNull(mapper);

                return new ObjIterator<>() {
                    @Override
                    public boolean hasNext() {
                        return cursor.value() < toIndex;
                    }

                    @Override
                    public R next() {
                        if (!hasNext()) {
                            throw new NoSuchElementException(InternalUtil.ERROR_MSG_FOR_NO_SUCH_EX);
                        }

                        output.accept(cursor.getAndIncrement(), tmp);

                        return mapper.apply(tmp.left, tmp.right);
                    }
                };
            }
        };
    }

    /**
     * Zips two arrays into a BiIterator.
     * The resulting BiIterator will iterate over pairs of elements from the two arrays.
     * If the arrays have different lengths, the resulting BiIterator will have the length of the shorter array.
     * If either arrays is {@code null}, returns an empty BiIterator.
     *
     * @param  the type of elements in the first array
     * @param  the type of elements in the second array
     * @param a the first array
     * @param b the second array
     * @return a BiIterator that iterates over pairs of elements from the two arrays
     */
    public static  BiIterator zip(final A[] a, final B[] b) {
        return zip(Array.asList(a), Array.asList(b));
    }

    /**
     * Zips two arrays into a BiIterator with specified default values for missing elements.
     * The resulting BiIterator will iterate over pairs of elements from the two arrays.
     * If the arrays have different lengths, the resulting BiIterator will continue with the default values
     * for the shorter array until the longer array is exhausted.
     *
     * @param  the type of elements in the first array
     * @param  the type of elements in the second array
     * @param a the first array
     * @param b the second array
     * @param valueForNoneA the default value for missing elements in the first array
     * @param valueForNoneB the default value for missing elements in the second array
     * @return a BiIterator that iterates over pairs of elements from the two arrays
     */
    public static  BiIterator zip(final A[] a, final B[] b, final A valueForNoneA, final B valueForNoneB) {
        return zip(Array.asList(a), Array.asList(b), valueForNoneA, valueForNoneB);
    }

    /**
     * Zips two iterables into a BiIterator.
     * The resulting BiIterator will iterate over pairs of elements from the two iterables.
     * If the iterables have different lengths, the resulting BiIterator will have the length of the shorter iterable.
     * If either iterable is {@code null}, returns an empty BiIterator.
     *
     * @param  the type of elements in the first iterable
     * @param  the type of elements in the second iterable
     * @param a the first iterable
     * @param b the second iterable
     * @return a BiIterator that iterates over pairs of elements from the two iterables
     */
    public static  BiIterator zip(final Iterable a, final Iterable b) {
        return zip(a == null ? null : a.iterator(), b == null ? null : b.iterator());
    }

    /**
     * Zips two iterables into a BiIterator with specified default values for missing elements.
     * The resulting BiIterator will iterate over pairs of elements from the two iterables.
     * If the iterables have different lengths, the resulting BiIterator will continue with the default values
     * for the shorter iterable until the longer iterable is exhausted.
     *
     * @param  the type of elements in the first iterable
     * @param  the type of elements in the second iterable
     * @param a the first iterable
     * @param b the second iterable
     * @param valueForNoneA the default value for missing elements in the first iterable
     * @param valueForNoneB the default value for missing elements in the second iterable
     * @return a BiIterator that iterates over pairs of elements from the two iterables
     */
    public static  BiIterator zip(final Iterable a, final Iterable b, final A valueForNoneA, final B valueForNoneB) {
        return zip(a == null ? null : a.iterator(), b == null ? null : b.iterator(), valueForNoneA, valueForNoneB);
    }

    /**
     * Zips two iterators into a BiIterator.
     * The resulting BiIterator will iterate over pairs of elements from the two iterators.
     * If the iterators have different lengths, the resulting BiIterator will have the length of the shorter iterator.
     * If either iterator is {@code null}, returns an empty BiIterator.
     *
     * @param  the type of elements in the first iterator
     * @param  the type of elements in the second iterator
     * @param iterA the first iterator
     * @param iterB the second iterator
     * @return a BiIterator that iterates over pairs of elements from the two iterators
     */
    public static  BiIterator zip(final Iterator iterA, final Iterator iterB) {
        if (iterA == null || iterB == null) {
            return empty();
        }

        return new BiIterator<>() {
            @Override
            public boolean hasNext() {
                return iterA.hasNext() && iterB.hasNext();
            }

            @Override
            public Pair next() {
                if (!hasNext()) {
                    throw new NoSuchElementException(InternalUtil.ERROR_MSG_FOR_NO_SUCH_EX);
                }

                return Pair.of(iterA.next(), iterB.next());
            }

            @Override
            protected  void next(final Throwables.BiConsumer action) throws NoSuchElementException, E {
                // N.checkArgNotNull(action);

                action.accept(iterA.next(), iterB.next());
            }

            @Override
            public void forEachRemaining(final BiConsumer action) throws IllegalArgumentException {
                N.checkArgNotNull(action);

                while (iterA.hasNext() && iterB.hasNext()) {
                    action.accept(iterA.next(), iterB.next());
                }
            }

            @Override
            public  void foreachRemaining(final Throwables.BiConsumer action) throws IllegalArgumentException, E {
                N.checkArgNotNull(action);

                while (iterA.hasNext() && iterB.hasNext()) {
                    action.accept(iterA.next(), iterB.next());
                }
            }

            @Override
            public  ObjIterator map(final BiFunction mapper) throws IllegalArgumentException {
                N.checkArgNotNull(mapper);

                return new ObjIterator<>() {
                    @Override
                    public boolean hasNext() {
                        return iterA.hasNext() && iterB.hasNext();
                    }

                    @Override
                    public R next() {
                        if (!hasNext()) {
                            throw new NoSuchElementException(InternalUtil.ERROR_MSG_FOR_NO_SUCH_EX);
                        }

                        return mapper.apply(iterA.next(), iterB.next());
                    }
                };
            }
        };
    }

    /**
     * Zips two iterators into a BiIterator with specified default values for missing elements.
     * The resulting BiIterator will iterate over pairs of elements from the two iterators.
     * If the iterators have different lengths, the resulting BiIterator will continue with the default values
     * for the shorter iterator until the longer iterator is exhausted.
     *
     * @param  the type of elements in the first iterator
     * @param  the type of elements in the second iterator
     * @param iterA the first iterator
     * @param iterB the second iterator
     * @param valueForNoneA the default value for missing elements in the first iterator
     * @param valueForNoneB the default value for missing elements in the second iterator
     * @return a BiIterator that iterates over pairs of elements from the two iterators
     */
    public static  BiIterator zip(final Iterator iterA, final Iterator iterB, final A valueForNoneA, final B valueForNoneB) {
        final Iterator iter1 = iterA == null ? ObjIterator.empty() : iterA;
        final Iterator iter2 = iterB == null ? ObjIterator.empty() : iterB;

        return new BiIterator<>() {
            @Override
            public boolean hasNext() {
                return iter1.hasNext() || iter2.hasNext();
            }

            @Override
            public Pair next() {
                if (!hasNext()) {
                    throw new NoSuchElementException(InternalUtil.ERROR_MSG_FOR_NO_SUCH_EX);
                }

                return Pair.of(iter1.hasNext() ? iter1.next() : valueForNoneA, iter2.hasNext() ? iter2.next() : valueForNoneB);
            }

            @Override
            protected  void next(final Throwables.BiConsumer action) throws NoSuchElementException, E {
                // N.checkArgNotNull(action);

                if (!hasNext()) {
                    throw new NoSuchElementException(InternalUtil.ERROR_MSG_FOR_NO_SUCH_EX);
                }

                action.accept(iter1.hasNext() ? iter1.next() : valueForNoneA, iter2.hasNext() ? iter2.next() : valueForNoneB);
            }

            @Override
            public void forEachRemaining(final BiConsumer action) throws IllegalArgumentException {
                N.checkArgNotNull(action);

                while (iter1.hasNext() || iter2.hasNext()) {
                    action.accept(iter1.hasNext() ? iter1.next() : valueForNoneA, iter2.hasNext() ? iter2.next() : valueForNoneB);
                }
            }

            @Override
            public  void foreachRemaining(final Throwables.BiConsumer action) throws IllegalArgumentException, E {
                N.checkArgNotNull(action);

                while (iter1.hasNext() || iter2.hasNext()) {
                    action.accept(iter1.hasNext() ? iter1.next() : valueForNoneA, iter2.hasNext() ? iter2.next() : valueForNoneB);
                }
            }

            @Override
            public  ObjIterator map(final BiFunction mapper) throws IllegalArgumentException {
                N.checkArgNotNull(mapper);

                return new ObjIterator<>() {
                    @Override
                    public boolean hasNext() {
                        return iter1.hasNext() || iter2.hasNext();
                    }

                    @Override
                    public R next() {
                        if (!hasNext()) {
                            throw new NoSuchElementException(InternalUtil.ERROR_MSG_FOR_NO_SUCH_EX);
                        }

                        return mapper.apply(iter1.hasNext() ? iter1.next() : valueForNoneA, iter2.hasNext() ? iter2.next() : valueForNoneB);
                    }
                };
            }
        };
    }

    /**
     * Unzips an iterable of elements into a BiIterator.
     * The resulting BiIterator will iterate over pairs of elements produced by the unzip function.
     * If the iterable is {@code null}, returns an empty BiIterator.
     *
     * @param  the type of elements in the iterable
     * @param  the first type of elements returned by the BiIterator
     * @param  the second type of elements returned by the BiIterator
     * @param iter the iterable to unzip
     * @param unzipFunc a BiConsumer that accepts an element of type T and a {@code Pair} and populates the pair with the unzipped values
     * @return a BiIterator that iterates over pairs of elements produced by the unzip function
     */
    public static  BiIterator unzip(final Iterable iter, final BiConsumer> unzipFunc) {
        if (iter == null) {
            return BiIterator.empty();
        }

        return unzip(iter.iterator(), unzipFunc);
    }

    /**
     * Unzips an iterator of elements into a BiIterator.
     * The resulting BiIterator will iterate over pairs of elements produced by the unzip function.
     * If the iterator is {@code null}, returns an empty BiIterator.
     *
     * @param  the type of elements in the iterator
     * @param  the first type of elements returned by the BiIterator
     * @param  the second type of elements returned by the BiIterator
     * @param iter the iterator to unzip
     * @param unzipFunc a BiConsumer that accepts an element of type T and a {@code Pair} and populates the pair with the unzipped values
     * @return a BiIterator that iterates over pairs of elements produced by the unzip function
     */
    public static  BiIterator unzip(final Iterator iter, final BiConsumer> unzipFunc) {
        if (iter == null) {
            return BiIterator.empty();
        }

        final BooleanSupplier hasNext = iter::hasNext;

        final Consumer> output = out -> unzipFunc.accept(iter.next(), out);

        return BiIterator.generate(hasNext, output);
    }

    /**
     * Processes the next pair of elements in the iterator using the provided action.
     *
     * @param  the type of exception that the action may throw
     * @param action a BiConsumer that processes the next pair of elements
     * @throws NoSuchElementException if there are no more elements in the iterator
     * @throws E if the action throws an exception
     */
    protected abstract  void next(final Throwables.BiConsumer action) throws NoSuchElementException, E;

    /**
     * Performs the given action for each remaining element in the iterator until all elements have been processed or the action throws an exception.
     *
     * @param action the action to be performed for each element
     * @deprecated use {@code forEachRemaining(BiConsumer)} to avoid creating the unnecessary {@code Pair} Objects.
     * @see #forEachRemaining(BiConsumer)
     */
    @Override
    @Deprecated
    public void forEachRemaining(final Consumer> action) {
        super.forEachRemaining(action);
    }

    /**
     * Performs the given action for each remaining element in the iterator until all elements have been processed or the action throws an exception.
     *
     * @param action the action to be performed for each element
     */
    public abstract void forEachRemaining(final BiConsumer action);

    /**
     * Performs the given action for each remaining element in the iterator until all elements
     * have been processed or the action throws an exception.
     *
     * @param  the type of exception that the action may throw
     * @param action a BiConsumer that processes the next pair of elements
     * @throws E if the action throws an exception
     */
    public abstract  void foreachRemaining(final Throwables.BiConsumer action) throws E; // NOSONAR

    /**
     * Returns a new BiIterator with n elements skipped from the beginning of this BiIterator.
     *
     * @param n the number of elements to skip
     * @return A new BiIterator that skips the first n elements.
     * @throws IllegalArgumentException If n is negative.
     */
    public BiIterator skip(final long n) throws IllegalArgumentException {
        N.checkArgNotNegative(n, cs.n);

        if (n <= 0) {
            return this;
        }

        final BiIterator iter = this;

        return new BiIterator<>() {
            private boolean skipped = false;

            @Override
            public boolean hasNext() {
                if (!skipped) {
                    skip();
                }

                return iter.hasNext();
            }

            @Override
            public Pair next() {
                if (!hasNext()) {
                    throw new NoSuchElementException(InternalUtil.ERROR_MSG_FOR_NO_SUCH_EX);
                }

                return iter.next();
            }

            @Override
            protected  void next(final Throwables.BiConsumer action) throws NoSuchElementException, E {
                if (!skipped) {
                    skip();
                }

                iter.next(action);
            }

            @Override
            public void forEachRemaining(final BiConsumer action) {
                if (!skipped) {
                    skip();
                }

                iter.forEachRemaining(action);
            }

            @Override
            public  void foreachRemaining(final Throwables.BiConsumer action) throws E {
                if (!skipped) {
                    skip();
                }

                iter.foreachRemaining(action);
            }

            @Override
            public  ObjIterator map(final BiFunction mapper) {
                N.checkArgNotNull(mapper, cs.mapper);

                if (!skipped) {
                    skip();
                }

                return iter.map(mapper);
            }

            private void skip() {
                long idx = 0;

                @SuppressWarnings("UnnecessaryLocalVariable")
                final Throwables.BiConsumer action = DO_NOTHING;

                while (idx++ < n && iter.hasNext()) {
                    iter.next(action);
                }

                skipped = true;
            }
        };
    }

    /**
     * Returns a new BiIterator with a limited number of elements.
     * The resulting BiIterator will contain at most the specified number of elements.
     *
     * @param count the maximum number of elements to include in the resulting BiIterator
     * @return a new BiIterator that contains at most the specified number of elements
     * @throws IllegalArgumentException If count is negative.
     */
    public BiIterator limit(final long count) throws IllegalArgumentException {
        N.checkArgNotNegative(count, cs.count);

        if (count == 0) {
            return BiIterator.empty();
        }

        final BiIterator iter = this;

        return new BiIterator<>() {
            private long cnt = count;

            @Override
            public boolean hasNext() {
                return cnt > 0 && iter.hasNext();
            }

            @Override
            public Pair next() {
                if (!hasNext()) {
                    throw new NoSuchElementException(InternalUtil.ERROR_MSG_FOR_NO_SUCH_EX);
                }

                cnt--;
                return iter.next();
            }

            @Override
            protected  void next(final Throwables.BiConsumer action) throws NoSuchElementException, E {
                if (!hasNext()) {
                    throw new NoSuchElementException(InternalUtil.ERROR_MSG_FOR_NO_SUCH_EX);
                }

                cnt--;
                iter.next(action);
            }

            @Override
            public void forEachRemaining(final BiConsumer action) {
                final Throwables.BiConsumer actionE = Fnn.from(action);

                while (hasNext()) {
                    cnt--;
                    iter.next(actionE);
                }
            }

            @Override
            public  void foreachRemaining(final Throwables.BiConsumer action) throws E {
                while (hasNext()) {
                    cnt--;
                    iter.next(action);
                }
            }

            @Override
            public  ObjIterator map(final BiFunction mapper) {
                N.checkArgNotNull(mapper, cs.mapper);

                if (cnt > 0) {
                    return iter. map(mapper).limit(cnt);
                } else {
                    return ObjIterator.empty();
                }
            }
        };
    }

    /**
     * Returns a new BiIterator that includes only the elements that satisfy the provided predicate.
     *
     * @param predicate the predicate to apply to each pair of elements
     * @return a new BiIterator containing only the elements that match the predicate
     */
    public BiIterator filter(final BiPredicate predicate) {
        N.checkArgNotNull(predicate, cs.Predicate);

        final BiIterator iter = this;

        return new BiIterator<>() {
            private final Pair next = new Pair<>();
            private final Throwables.BiConsumer setNext = next::set;

            private boolean hasNext = false;

            @Override
            public boolean hasNext() {
                if (!hasNext) {
                    while (iter.hasNext()) {
                        iter.next(setNext);

                        if (predicate.test(next.left, next.right)) {
                            hasNext = true;
                            break;
                        }
                    }
                }

                return hasNext;
            }

            @Override
            public Pair next() {
                if (!hasNext && !hasNext()) {
                    throw new NoSuchElementException(InternalUtil.ERROR_MSG_FOR_NO_SUCH_EX);
                }

                hasNext = false;

                return next.copy();
            }

            @Override
            protected  void next(final Throwables.BiConsumer action) throws NoSuchElementException, E {
                if (!hasNext && !hasNext()) {
                    throw new NoSuchElementException(InternalUtil.ERROR_MSG_FOR_NO_SUCH_EX);
                }

                hasNext = false;

                action.accept(next.left, next.right);
            }

            @Override
            public void forEachRemaining(final BiConsumer action) {
                while (hasNext()) {
                    hasNext = false;

                    action.accept(next.left, next.right);
                }
            }

            @Override
            public  void foreachRemaining(final Throwables.BiConsumer action) throws E {
                while (hasNext()) {
                    hasNext = false;

                    action.accept(next.left, next.right);
                }
            }

            @Override
            public  ObjIterator map(final BiFunction mapper) {
                N.checkArgNotNull(mapper, cs.mapper);

                return new ObjIterator<>() {
                    @Override
                    public boolean hasNext() {
                        if (!hasNext) {
                            while (iter.hasNext()) {
                                iter.next(setNext);

                                if (predicate.test(next.left, next.right)) {
                                    hasNext = true;
                                    break;
                                }
                            }
                        }

                        return hasNext;
                    }

                    @Override
                    public R next() {
                        if (!hasNext && !hasNext()) {
                            throw new NoSuchElementException(InternalUtil.ERROR_MSG_FOR_NO_SUCH_EX);
                        }

                        hasNext = false;

                        return mapper.apply(next.left, next.right);
                    }
                };
            }
        };
    }

    /**
     * Transforms the elements of this BiIterator using the given mapper function.
     *
     * @param  the type of elements in the resulting ObjIterator
     * @param mapper the function to apply to each pair of elements
     * @return an ObjIterator containing the elements produced by the mapper function
     */
    public abstract  ObjIterator map(final BiFunction mapper);

    /**
     * Returns an Optional containing the first pair of elements in the iterator.
     * If the iterator is empty, returns an empty Optional.
     *
     * @return an Optional containing the first pair of elements, or an empty Optional if the iterator is empty
     */
    public Optional> first() {
        if (hasNext()) {
            return Optional.of(next());
        } else {
            return Optional.empty();
        }
    }

    /**
     * Returns an Optional containing the last pair of elements in the iterator.
     * If the iterator is empty, returns an empty Optional.
     *
     * @return an Optional containing the last pair of elements, or an empty Optional if the iterator is empty
     */
    public Optional> last() {
        if (hasNext()) {
            final Pair next = new Pair<>();
            final BiConsumer setNext = next::set;

            forEachRemaining(setNext);

            return Optional.of(next);
        } else {
            return Optional.empty();
        }
    }

    /**
     * Returns an EntryStream of the elements in this BiIterator.
     *
     * @return A Stream containing the remaining elements in the BiIterator.
     */
    public EntryStream stream() {
        return EntryStream.of(this);
    }

    /**
     * Returns a Stream of elements produced by applying the given mapper function to each pair of elements in this BiIterator.
     *
     * @param  the type of elements in the resulting Stream
     * @param mapper the function to apply to each pair of elements
     * @return a Stream containing the elements produced by the mapper function
     */
    public  Stream stream(final BiFunction mapper) {
        N.checkArgNotNull(mapper, cs.mapper);

        return Stream.of(map(mapper));
    }

    /**
     * Converts the elements in this BiIterator to an array of Pair objects.
     *
     * @return An array containing the remaining pairs of elements in this BiIterator.
     */
    public Pair[] toArray() {
        return toArray(new Pair[0]);
    }

    /**
     * Converts the elements in this BiIterator to an array of the specified type.
     *
     * @param  the type of the array elements. It should be super type of Pair.
     * @param a the array into which the elements of this BiIterator are to be stored, if it is big enough;
     *          otherwise, a new array of the same runtime type is allocated for this purpose.
     * @return an array containing the elements of this BiIterator
     * @deprecated This method is deprecated. Use {@link #toArray()} or {@link #toList()} instead.
     */
    @Deprecated
    public  T[] toArray(final T[] a) {
        return toList().toArray(a);
    }

    /**
     * Converts the elements in this BiIterator to a List of Pair objects.
     *
     * @return a List containing all pairs of elements in this BiIterator
     */
    public List> toList() {
        return toCollection(Suppliers.ofList());
    }

    /**
     * Converts the elements in this BiIterator to a Pair of Lists.
     * The first list contains all the first elements of the pairs, and the second list contains all the second elements of the pairs.
     *
     * @param supplier a Supplier that provides new instances of List
     * @return a Pair containing two Lists: one with the first elements and one with the second elements
     */
    public Pair, List> toMultiList(@SuppressWarnings("rawtypes") final Supplier supplier) {
        final List listA = supplier.get();
        final List listB = supplier.get();

        this.foreachRemaining((a, b) -> {
            listA.add(a);
            listB.add(b);
        });

        return Pair.of(listA, listB);
    }

    /**
     * Converts the elements in this BiIterator to a Pair of Sets.
     * The first set contains all the first elements of the pairs, and the second set contains all the second elements of the pairs.
     *
     * @param supplier a Supplier that provides new instances of Set
     * @return a Pair containing two Sets: one with the first elements and one with the second elements
     */
    public Pair, Set> toMultiSet(@SuppressWarnings("rawtypes") final Supplier supplier) {
        final Set listA = supplier.get();
        final Set listB = supplier.get();

        this.foreachRemaining((a, b) -> {
            listA.add(a);
            listB.add(b);
        });

        return Pair.of(listA, listB);
    }
}