4
\$\begingroup\$

The util class to find all the indexes on an element into a Collection supports forward and reverse lookup starting from a given index along with changing the Collection the element’s index is to be found into.

A convoluted implementation of Builder design pattern starting from the simplest implementation in the NextIndex class having a static factory method returning a NextIndex.Lookup instance that is a variation of factory design pattern implementation exposing forward and reverse methods altering its internal state, to setup the lookup direction, without exposing the change outside of the instance, methods that build and set in a filed a NextIndex.Explorer instance using fixed or flexible build semantic supported by the NextIndex.Explorer’s available variations of builder design pattern implementations to support defining exploring attributes in a fixed order...

Explorer.fixed().semantic().builder()
        .from(() -> this.fromIndex)
        .to(() -> this.source.size())
        .bounder((index, bound) -> index < bound)
        .incrementer((index) -> ++index)
        .absent(() -> -1)
        .newIt()
        .get();

...or randomly...

Explorer.from(() -> this.fromIndex > 0 ? this.fromIndex : this.source.size() - 1)
        .absent(() -> 1)
        .incrementer((index) -> --index)
        .bounder((index, bound) -> index >= bound)
        .to(() ->  0)
        .newIt()
        .get();

...in any possible sequence...

Explorer.from(() -> this.fromIndex > 0 ? this.fromIndex : this.source.size() - 1)
        .incrementer((index) -> --index)
        .absent(() -> 1)
        .bounder((index, bound) -> index >= bound)
        .to(() ->  0)
        .newIt()
        .get();

Thread safeness concern could be addressed by each Thread using its own instance of NextIndex.Lookup class...

//TODO add package statement

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

public class NextIndex {

    public static <T> Lookup<T> of(T element) {
        return new NextIndex.Lookup<T>(element);
    }

    public static class Lookup<T> {

        private T element;

        private List<T> source = new ArrayList<T>();

        private Integer fromIndex = 0;

        private Explorer<Integer> explorer = forwardExplorer();

        public Lookup(T element) { this.element = element; }

        public <U extends Collection<T>> Lookup<T> into(U source) {
            if (source == null) { return this; }
            this.source = new ArrayList<>(source);
            return this;
        }

        public Lookup<T> fromIndex(int index) {
            this.fromIndex = index;
            return this;
        }

        private Explorer<Integer> forwardExplorer() {
            return Explorer.fixed().semantic().builder()
                           .from(() -> this.fromIndex)
                           .to(() -> this.source.size())
                           .bounder((index, bound) -> index < bound)
                           .incrementer((index) -> ++index)
                           .absent(() -> -1)
                           .newIt()
                           .get();
        }

        public Lookup<T> forward() {
            this.explorer = forwardExplorer();
            return this;
        }

        public Lookup<T> reverse() {
            this.explorer = Explorer.from(() -> this.fromIndex > 0 ? this.fromIndex 
                                                                   : this.source.size() - 1)
                                    .to(() ->  0)
                                    .bounder((index, bound) -> index >= bound)
                                    .incrementer((index) -> --index)
                                    .absent(() -> 1)
                                    .newIt()
                                    .get();
            return this;
        }

        public int absolute() {
            int index = this.explorer.absent();
            if (fromIndex < 0 || fromIndex > this.source.size()) { return index; }

            for (int i = this.explorer.from()
                    ; this.explorer.inbound(i, this.explorer.to())
                    ; i = this.explorer.increment(i)) {
                if (this.element == this.source.get(i) 
                    || this.element.equals(this.source.get(i))) {
                    index = i;
                    break;
                }
            }

            return index;
        }

        public int relative() {
            int index = this.explorer.absent();
            if (fromIndex < 0 || fromIndex > this.source.size()) { return index; }

            int i = this.explorer.from();
            while (this.explorer.inbound(i, this.explorer.to())) {
                if (this.element == this.source.get(i) 
                    || this.element.equals(this.source.get(i))) {
                    index = i - this.fromIndex;
                    break;
                }
                i = this.explorer.increment(i);
            }

            return index;
        }
    }

    public static class Explorer<U> {

        private Supplier<U> from;

        private Supplier<U> to;

        private Supplier<U> absent;

        private BiFunction<U, U, Boolean> bounder;

        private Function<U, U> incrementer;

        private Explorer(Supplier<U> start, Supplier<U> bound) {
            this.from = start;
            this.to = bound;
        }

        private Explorer(Supplier<U> start
                       , Supplier<U> bound
                       , BiFunction<U, U, Boolean> bounder
                       , Function<U, U> incrementer
                       , Supplier<U> absent) {
            this.from = start;
            this.to = bound;
            this.bounder = bounder;
            this.incrementer = incrementer;
            this.absent = absent;
        }

        public U from() { return this.from.get(); }

        public U to() { return this.to.get(); }

        public U absent() { return this.absent.get(); }

        public boolean inbound(U index, U bound) {
            return this.bounder.apply(index, bound);
        }

        public U increment(U index) {
            return this.incrementer.apply(index);
        }

        public static <T> Explorer.Builder<T> from(Supplier<T> start) {
            return new Explorer.Builder<T>(start);
        }

        public static Explorer.SemanticSource fixed() { return new Explorer.SemanticSource(); }

        public static class SemanticSource {

            private SemanticSource() {}
            public Explorer.BuilderSource semantic() { return new Explorer.BuilderSource(); }
        }

        public static class BuilderSource {
            private BuilderSource() {}
            public BuilderFactory builder() { return new Explorer.BuilderFactory(); }
        }

        public static class BuilderFactory {

            private BuilderFactory() {}
            public  <U> Explorer.Boundry<U> from(Supplier<U> from) {
                return new Explorer.Boundry<U>(from);
            }
        }

        private static abstract class Newer<K> {
            abstract Optional<Explorer<K>> newIt();
        }

        public static class Boundry<K> extends Newer<K> {

            public static <U> Boundry<U> from(Supplier<U> from) {
                return new Explorer.Boundry<U>(from);
            }

            private Supplier<K> from;
            private Supplier<K> to;

            private Boundry(Supplier<K> from) { this.from = from; }

            public Explorer.Bounder<K> to(Supplier<K> to) {
                this.to = to;
                return Explorer.Bounder.bound(this);
            }

            Optional<Explorer<K>> newIt() {

                if (from == null || to == null ) { return Optional.empty(); }

                return Optional.of(new Explorer<K>(from, to));
            }
        }

        public static class Bounder<K> extends Newer<K> {

            public static <U> Bounder<U> bound(Explorer.Boundry<U> boundry) {
                return new Explorer.Bounder<>(boundry);
            }

            private Boundry<K> boundry;
            private BiFunction<K, K, Boolean> bounder;

            private Bounder(Boundry<K> boundry) {
                this.boundry = boundry;
            }

            public Explorer.Incrementer<K> bounder(BiFunction<K, K, Boolean> bounder) {
                this.bounder = bounder;
                return Explorer.Incrementer.incremented(this);
            }

            Optional<Explorer<K>> newIt() {
                if ( bounder == null) { return Optional.empty(); }
                return this.boundry
                           .newIt()
                           .map(explorer -> { 
                               explorer.bounder = this.bounder;
                               return explorer;
                            });
            }
        }

        public static class Incrementer<K> extends Newer<K> {

            public static <U> Explorer.Incrementer<U> incremented(Bounder<U> bounder) {
                return new Explorer.Incrementer<U>(bounder);
            }

            private Bounder<K> bounder;
            private Function<K, K> incrementer;

            private Incrementer(Bounder<K> bounder) { this.bounder = bounder; }

            public Explorer.Absent<K> incrementer(Function<K, K> incrementer) {
                this.incrementer = incrementer;
                return Explorer.Absent.of(this);
            }

            Optional<Explorer<K>> newIt() {
                if (incrementer == null) { return Optional.empty(); }
                return this.bounder
                           .newIt()
                           .map(explorer -> { 
                               explorer.incrementer = this.incrementer;
                               return explorer;
                            });
            }
        }

        public static class Absent<K> extends Newer<K> {

            public static <U> Explorer.Absent<U> of(Explorer.Incrementer<U> incrementer) {
                return new Explorer.Absent<U>(incrementer);
            }

            private Incrementer<K> incrementer;
            private Supplier<K> absent;

            private Absent(Incrementer<K> incrementer) {
                this.incrementer = incrementer;
            }

            public Explorer.FixedSemanticBuilder<K> absent(Supplier<K> absent) {
                this.absent = absent;
                return Explorer.FixedSemanticBuilder.of(this);
            }

            Optional<Explorer<K>> newIt() {
                if (absent == null) { return Optional.empty(); }
                return this.incrementer
                           .newIt()
                           .map(explorer -> { 
                               explorer.absent = this.absent;
                               return explorer;
                            });
            }
        }

        public static class FixedSemanticBuilder<K> extends Newer<K> {

            private Absent<K> absent;

            private FixedSemanticBuilder(Absent<K> absent) {
                this.absent = absent;
            }

            public static <R> FixedSemanticBuilder<R> of(Absent<R> absent) {
                return new Explorer.FixedSemanticBuilder<R>(absent);
            }

            public Optional<Explorer<K>> newIt() { return this.absent.newIt(); }
        }

        public static class Builder<U> {

            private Supplier<U> from;

            private Supplier<U> to;

            private Supplier<U> absent;

            private BiFunction<U, U, Boolean> bounder;

            private Function<U, U> incrementer;

            public Builder(Supplier<U> start) {
                this.from = start;
            }

            public Explorer.Builder<U> to(Supplier<U> bound) {
                this.to = bound;
                return this;
            }

            public Explorer.Builder<U> bounder(BiFunction<U, U, Boolean> bounder) {
                this.bounder = bounder;
                return this;
            }

            public Explorer.Builder<U> incrementer(Function<U, U> incrementer) {
                this.incrementer = incrementer;
                return this;
            }

            public Explorer.Builder<U> absent(Supplier<U> absent) {
                this.absent = absent;
                return this;
            }

            public Optional<Explorer<U>> newIt() {

                if (from == null || to == null || bounder == null || incrementer == null) {
                    return Optional.empty();
                }

                return Optional.of(new Explorer<U>(from, to, bounder, incrementer, absent));
            }
        }
    }
}

Usage depicted by...


//TODO add package statement

import java.util.ArrayList;
import java.util.List;

public class IndexLookupApplication {

    public static void main(String[] args) {

        int element = 1;
        List<Integer> list = List.of(1, 2, 3, 1, 2, 3, 1, 2, 3);
        List<Integer> differentList = List.of(3, 2, 1, 3, 2, 1, 3, 2, 1);

        forwardLookup(element, list, differentList);
        System.out.println("");
        System.out.println("");
        System.out.println("");
        reverseLookup(element, list, differentList);
    }

    private static void forwardLookup(int element, List<Integer> list, List<Integer> differentList) {

        System.out.println("********** Forward lookup **********");

        NextIndex.Lookup<Integer> indexLookup = NextIndex.of(element);

        int firstIndexOfIntoAList = indexLookup.into(list).absolute();
        int secondAbsoluteIndexOfIntoAList = indexLookup.fromIndex(firstIndexOfIntoAList + 1).absolute();
        int secondRelativeIndexOfIntoAList = indexLookup.relative();
        int indexOfFromPreviousIndexIntoDifferentList = indexLookup.into(differentList).absolute();
        int indexOfIntoDifferentList = indexLookup.into(differentList).fromIndex(0).absolute();

        String listToString = list.toString();
        System.out.println("list of elements " + listToString);
        System.out.println(String.format("1st absolute index of element %d is %d", element, firstIndexOfIntoAList));
        System.out.println(String.format("2nd absolute index of element %d is %d", element, secondAbsoluteIndexOfIntoAList));
        System.out.println(String.format("1st relative index of element %d starting from index %d is %d", element, (firstIndexOfIntoAList + 1), secondRelativeIndexOfIntoAList));
        System.out.println(String.format("different list of elements %s", differentList.toString()));
        System.out.println(String.format("1st absolute index of element %d into a different list from previous index is %d",
                element, indexOfFromPreviousIndexIntoDifferentList));
        System.out.println(String.format("1st absolute index of element %d into a different list from the start is %d", element,
                indexOfIntoDifferentList));

        Integer indexOf = indexLookup.into(list).fromIndex(0).absolute();
        List<Integer> indexes = new ArrayList<>();
        while (indexOf > -1) {

            indexes.add(indexOf);
            indexOf = indexLookup.fromIndex(indexOf + 1).absolute();
        }

        System.out.println(
                String.format("element's %d indexes in list %s is %s", element, listToString, indexes.toString()));
    }

    private static void reverseLookup(int element, List<Integer> list, List<Integer> differentList) {

        System.out.println("********** Reversed lookup **********");

        NextIndex.Lookup<Integer> indexLookup = NextIndex.of(element).reverse();

        int firstIndexOfIntoAList = indexLookup.into(list).absolute();
        int secondAbsoluteIndexOfIntoAList = indexLookup.fromIndex(firstIndexOfIntoAList - 1).absolute();
        int secondRelativeIndexOfIntoAList = indexLookup.relative();
        int indexOfFromPreviousIndexIntoDifferentList = indexLookup.into(differentList).absolute();
        int indexOfIntoDifferentList = indexLookup.into(differentList).fromIndex(0).absolute();

        String listToString = list.toString();
        System.out.println("list of elements " + listToString);
        System.out.println(String.format("1st absolute index of element %d is %d", element, firstIndexOfIntoAList));
        System.out.println(String.format("2nd absolute index of element %d is %d", element, secondAbsoluteIndexOfIntoAList));
        System.out.println(String.format("1st relative index of element %d starting from index %d is %d", element, (firstIndexOfIntoAList - 1), secondRelativeIndexOfIntoAList));
        System.out.println(String.format("different list of elements %s", differentList.toString()));
        System.out.println(String.format("1st absolute index of element %d into a different list from previous index is %d",
                element, indexOfFromPreviousIndexIntoDifferentList));
        System.out.println(String.format("1st absolute index of element %d into a different list from the start is %d", element,
                indexOfIntoDifferentList));

        Integer indexOf = indexLookup.into(list).fromIndex(0).absolute();
        List<Integer> indexes = new ArrayList<>();
        while (indexOf > 0) {
            indexes.add(indexOf);
            indexOf = indexLookup.fromIndex(indexOf - 1).absolute();
        }

        System.out.println(
                String.format("element's %d indexes in list %s is %s", element, listToString, indexes.toString()));
    }
}]

...with the output...

********** Forward lookup **********
list of elements [1, 2, 3, 1, 2, 3, 1, 2, 3]
1st absolute index of element 1 is 0
2nd absolute index of element 1 is 3
1st relative index of element 1 starting from index 1 is 2
different list of elements [3, 2, 1, 3, 2, 1, 3, 2, 1]
1st absolute index of element 1 into a different list from previous index is 2
1st absolute index of element 1 into a different list from the start is 2
element's 1 indexes in list [1, 2, 3, 1, 2, 3, 1, 2, 3] is [0, 3, 6]



********** Reversed lookup **********
list of elements [1, 2, 3, 1, 2, 3, 1, 2, 3]
1st absolute index of element 1 is 6
2nd absolute index of element 1 is 3
1st relative index of element 1 starting from index 5 is -2
different list of elements [3, 2, 1, 3, 2, 1, 3, 2, 1]
1st absolute index of element 1 into a different list from previous index is 5
1st absolute index of element 1 into a different list from the start is 8
element's 1 indexes in list [1, 2, 3, 1, 2, 3, 1, 2, 3] is [6, 3]

This code review request is an after thought of Sequentially bidirectional find the indexes of an element into a collection (second thought)

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Explorer.java overhaul

public class Explorer<T> {
    private final Supplier<T> from;
    private final Supplier<T> to;
    private final Supplier<T> absent;
    private final BiFunction<T, T, Boolean> bounder;
    private final Function<T, T> incrementer;

    private Explorer(Builder<T> builder) {
        this.from = builder.from;
        this.to = builder.to;
        this.absent = builder.absent;
        this.bounder = builder.bounder;
        this.incrementer = builder.incrementer;
    }

    public T from() { return from.get(); }
    public T to() { return to.get(); }
    public T absent() { return absent.get(); }
    public boolean inbound(T index, T bound) { return bounder.apply(index, bound); }
    public T increment(T index) { return incrementer.apply(index); }

    public static <T> Builder<T> builder() {
        return new Builder<>();
    }

    public static class Builder<T> {
        private Supplier<T> from;
        private Supplier<T> to;
        private Supplier<T> absent;
        private BiFunction<T, T, Boolean> bounder;
        private Function<T, T> incrementer;

        public Builder<T> from(Supplier<T> from) {
            this.from = from;
            return this;
        }

        public Builder<T> to(Supplier<T> to) {
            this.to = to;
            return this;
        }

        public Builder<T> absent(Supplier<T> absent) {
            this.absent = absent;
            return this;
        }

        public Builder<T> bounder(BiFunction<T, T, Boolean> bounder) {
            this.bounder = bounder;
            return this;
        }

        public Builder<T> incrementer(Function<T, T> incrementer) {
            this.incrementer = incrementer;
            return this;
        }

        public Explorer<T> build() {
            validate();
            return new Explorer<>(this);
        }

        private void validate() {
            if (from == null || to == null || bounder == null || incrementer == null || absent == null) {
                throw new IllegalStateException("All builder properties must be set");
            }
        }
    }
}

This enhanced version is based on the introduction of a Builder Pattern (with proper validation). Now the fields are final to ensure immutability and redundant methods are now refactored (in the original Explorer class there were several nested builder-related classes (SemanticSource, BuilderSource, BuilderFactory, Newer, Boundry, Bounder, Incrementer, Absent, FixedSemanticBuilder) that created a complex chain of method calls to build an Explorer instance). I also kept using generics to enhance type safety (now there's a consistent type parameter naming, all builder methods maintain the same type and all functional interfaces follow the same parametric structure).

NextIndex.java overhaul

public class NextIndex {
    public static <T> Lookup<T> of(T element) {
        return new Lookup<>(element);
    }

    public static class Lookup<T> {
        private final T element;
        private final List<T> source;
        private final int fromIndex;
        private final Direction direction;

        private Lookup(T element) {
            this(element, new ArrayList<>(), 0, Direction.FORWARD);
        }

        private Lookup(T element, List<T> source, int fromIndex, Direction direction) {
            this.element = element;
            this.source = new ArrayList<>(source);
            this.fromIndex = fromIndex;
            this.direction = direction;
        }

        public Lookup<T> into(Collection<T> source) {
            return new Lookup<>(element, new ArrayList<>(source), fromIndex, direction);
        }

        public Lookup<T> fromIndex(int index) {
            return new Lookup<>(element, source, index, direction);
        }

        public Lookup<T> forward() {
            return new Lookup<>(element, source, fromIndex, Direction.FORWARD);
        }

        public Lookup<T> reverse() {
            return new Lookup<>(element, source, fromIndex, Direction.REVERSE);
        }

        public int absolute() {
            Explorer<Integer> explorer = direction.createExplorer(fromIndex, source.size());
            return findIndex(explorer, false);
        }

        public int relative() {
            Explorer<Integer> explorer = direction.createExplorer(fromIndex, source.size());
            return findIndex(explorer, true);
        }

        private int findIndex(Explorer<Integer> explorer, boolean relative) {
            if (!isValidIndex(fromIndex)) {
                return explorer.absent();
            }

            int index = explorer.absent();
            for (int i = explorer.from(); explorer.inbound(i, explorer.to()); i = explorer.increment(i)) {
                if (Objects.equals(element, source.get(i))) {
                    index = relative ? i - fromIndex : i;
                    break;
                }
            }
            return index;
        }

        private boolean isValidIndex(int index) {
            return index >= 0 && index <= source.size();
        }
    }

    private enum Direction {
        FORWARD {
            @Override
            Explorer<Integer> createExplorer(int fromIndex, int size) {
                return Explorer.<Integer>builder()
                    .from(() -> fromIndex)
                    .to(() -> size)
                    .bounder((index, bound) -> index < bound)
                    .incrementer(index -> index + 1)
                    .absent(() -> -1)
                    .build();
            }
        },
        REVERSE {
            @Override
            Explorer<Integer> createExplorer(int fromIndex, int size) {
                return Explorer.<Integer>builder()
                    .from(() -> fromIndex > 0 ? fromIndex : size - 1)
                    .to(() -> 0)
                    .bounder((index, bound) -> index >= bound)
                    .incrementer(index -> index - 1)
                    .absent(() -> -1)
                    .build();
            }
        };

        abstract Explorer<Integer> createExplorer(int fromIndex, int size);
    }
}

NextIndex class now owns a Lookup instance with no state mutations and it also owns a Direction enum to handle forward/reverse operations. I've also implemented an improved encapsulation with private fields and methods, refactoring also the index finding logic. Now thread's safety of the code is improved by immutable features.

\$\endgroup\$
0

You must log in to answer this question.