Util class to find into a collection the indexes of a given element with multiple occurrences from the first index or relative to a given index.
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
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 = Collections.emptyList();
private int fromIndex = 0;
public Lookup(T element) {
this.element = element;
}
public Lookup<T> into(Collection<T> source) {
this.source = new ArrayList<>(source);
return this;
}
public Lookup<T> fromIndex(int index) {
this.fromIndex = index;
return this;
}
public int absolute() {
int index = -1;
if (fromIndex > this.source.size()) {
return index;
}
for (int i = this.fromIndex; i < this.source.size(); 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 = -1;
if (fromIndex > this.source.size()) {
return index;
}
int i = this.fromIndex;
while (i < this.source.size()) {
if (this.element == this.source.get(i) || this.element.equals(this.source.get(i))) {
index = i - this.fromIndex;
break;
}
++i;
}
return index;
}
}
}
Usage sample:
import java.util.List;
import java.util.stream.Collectors;
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);
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();
System.out.println(String.format("list of elements [ %s ]",
list.stream().map(n -> n.toString()).collect(Collectors.joining(", "))));
System.out.println(String.format("1st absolute index of %s is %s", element, firstIndexOfIntoAList));
System.out.println(String.format("2st absolute index of %s is %s", element, secondAbsoluteIndexOfIntoAList));
System.out.println(String.format("2st relative index of %s is %s", element, secondRelativeIndexOfIntoAList));
System.out.println(String.format("different list of elements [ %s ]",
differentList.stream().map(n -> n.toString()).collect(Collectors.joining(", "))));
System.out.println(String.format("1st absolute index of %s into a different list from previous index is %s", element,
indexOfFromPreviousIndexIntoDifferentList));
System.out.println(String.format("1st absolute index of %s into a different list from the start is %s", element,
indexOfIntoDifferentList));
}
}
with the output:
list of elements [ 1, 2, 3, 1, 2, 3, 1, 2, 3 ]
1st absolute index of 1 is 0
2st absolute index of 1 is 3
2st relative index of 1 is 2
different list of elements [ 3, 2, 1, 3, 2, 1, 3, 2, 1 ]
1st absolute index of 1 into a different list from previous index is 2
1st absolute index of 1 into a different list from the start is 2
1 Answer 1
It's good that you wrote this to use generics.
of
and into
represent a half-baked builder pattern. Builders in Java are useful because Java doesn't support named or default parameters, but they need to be written carefully to behave well. As it stands, your NextIndex
- when constructed - is not useful, having an empty list; and can only be made useful via mutation (not good). You can also mutate source
at any point but fromIndex
will not be reset or validated (extremely not good).
A finer detail, but equally problematic, is that users familiar with the builder pattern expect to be able to reuse an intermediate builder before finalization; for instance, this sequence:
- User constructs a
NextIndex
. - User calls
into(...)
. - User stores instance to two variables,
fromStart
andfromMiddle
. - User calls
fromIndex(0)
on the first instance andfromIndex(5)
on the second instance.
The user will get a nasty surprise if they don't realize that those are the same instance and one fromIndex
has overwritten the other. Builders, before finalization, generally should return new instances after any parameter modification for this reason. Otherwise, surprise side-effects.
So - whether you separate your building logic into a different class or keep it in one class - make source
and element
final
, and return a new instance from any method that changes source
or element
. Or, and this would be my preference given how simple the class is - drop the builder pattern entirely, and accept the source and element on construction (still making them final
).
Don't name the member variable fromIndex
to be the same as your parameter set method fromIndex
; consider something like baseIndex
for the variable.
relative()
can be thin wrapper around absolute()
, the former subtracting the previous index. Better, though, is to simply use sublist views and don't write any of the iteration yourself. While using a view, it's important to ensure that it isn't invalidated by mutating the underlying list, so call Collections.unmodifiableList
on the constructor argument. This is cheap, and in fact a no-op if the list is already unmodifiable (which is the case for all lists in the demo).
Don't write an inner String.format
when calling printf
. Don't use %s
for integers; use %d
.
Write a utility method to format your lists, such as
private static <T> String fmtList(Collection<T> items) {
String inner = items.stream()
.map(Object::toString)
.collect(Collectors.joining(", "));
return "[ %s ]".formatted(inner);
}
But that produces output so close to the default List.toString()
that it isn't worth rolling your own; just toss the list in directly.
2st
is very funny, but should be written 2nd
.
Suggested (simple class)
The following, to the best of my knowledge, is equivalent:
import java.util.Collections;
import java.util.List;
public class Main {
public static class Lookup<T> {
public final List<T> source;
public final T element;
public int fromIndex;
private List<T> view;
public Lookup(List<T> source, T element) {
this(source, element, 0);
}
public Lookup(List<T> source, T element, int fromIndex) throws IndexOutOfBoundsException {
this.source = Collections.unmodifiableList(source);
this.element = element;
setIndex(fromIndex);
}
public void setIndex(int fromIndex) throws IndexOutOfBoundsException {
view = source.subList(fromIndex, source.size());
this.fromIndex = fromIndex;
}
public int relative() {
return view.indexOf(element);
}
public int absolute() {
int index = relative();
if (index == -1)
return -1;
return index + baseIndex;
}
}
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);
Lookup<Integer> lookup = new Lookup<>(list, element);
int firstIndexOfIntoAList = lookup.absolute();
lookup.setIndex(firstIndexOfIntoAList + 1);
int secondAbsoluteIndexOfIntoAList = lookup.absolute();
int secondRelativeIndexOfIntoAList = lookup.relative();
lookup = new Lookup<>(differentList, element, firstIndexOfIntoAList + 1);
int indexOfFromPreviousIndexIntoDifferentList = lookup.absolute();
lookup.setIndex(0);
int indexOfIntoDifferentList = lookup.absolute();
System.out.printf("list of elements %s%n", list);
System.out.printf("1st absolute index of %d is %d%n", element, firstIndexOfIntoAList);
System.out.printf("2nd absolute index of %d is %d%n", element, secondAbsoluteIndexOfIntoAList);
System.out.printf("2nd relative index of %d is %d%n", element, secondRelativeIndexOfIntoAList);
System.out.printf("different list of elements %s%n", differentList);
System.out.printf("1st absolute index of %d into a different list from previous index is %d%n",
element, indexOfFromPreviousIndexIntoDifferentList);
System.out.printf("1st absolute index of %d into a different list from the start is %d%n",
element, indexOfIntoDifferentList);
}
}
Build sugar
The following has build sugar kind of like what you were aiming for, but crucially, mutation semantics are different. Whenever you call a setter, a new instance is returned, with full validation of the new parameters and ability to reuse the previous instance if desired.
import java.util.Collections;
import java.util.List;
public class Main {
public static class Lookup<T> {
public final List<T> source;
public final T element;
public final int baseIndex;
private final List<T> view;
public Lookup(List<T> source, T element) {
this(source, element, 0);
}
public Lookup(List<T> source, T element, int baseIndex) throws IndexOutOfBoundsException {
this.source = Collections.unmodifiableList(source);
this.element = element;
this.baseIndex = baseIndex;
view = source.subList(baseIndex, source.size());
}
public Lookup<T> into(List<T> source) throws IndexOutOfBoundsException {
return new Lookup<>(source, element, baseIndex);
}
public Lookup<T> of(T element) {
return new Lookup<>(source, element, baseIndex);
}
public Lookup<T> fromIndex(int baseIndex) throws IndexOutOfBoundsException {
return new Lookup<>(source, element, baseIndex);
}
public int relative() {
return view.indexOf(element);
}
public int absolute() {
int index = relative();
if (index == -1)
return -1;
return index + baseIndex;
}
}
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);
Lookup<Integer> lookup = new Lookup<>(list, element);
int firstIndexOfIntoAList = lookup.absolute();
lookup = lookup.fromIndex(firstIndexOfIntoAList + 1);
int secondAbsoluteIndexOfIntoAList = lookup.absolute();
int secondRelativeIndexOfIntoAList = lookup.relative();
lookup = lookup.into(differentList);
int indexOfFromPreviousIndexIntoDifferentList = lookup.absolute();
int indexOfIntoDifferentList = lookup.fromIndex(0).absolute();
System.out.printf("list of elements %s%n", list);
System.out.printf("1st absolute index of %d is %d%n", element, firstIndexOfIntoAList);
System.out.printf("2nd absolute index of %d is %d%n", element, secondAbsoluteIndexOfIntoAList);
System.out.printf("2nd relative index of %d is %d%n", element, secondRelativeIndexOfIntoAList);
System.out.printf("different list of elements %s%n", differentList);
System.out.printf("1st absolute index of %d into a different list from previous index is %d%n",
element, indexOfFromPreviousIndexIntoDifferentList);
System.out.printf("1st absolute index of %d into a different list from the start is %d%n",
element, indexOfIntoDifferentList);
}
}
Output
list of elements [1, 2, 3, 1, 2, 3, 1, 2, 3]
1st absolute index of 1 is 0
2nd absolute index of 1 is 3
2nd relative index of 1 is 2
different list of elements [3, 2, 1, 3, 2, 1, 3, 2, 1]
1st absolute index of 1 into a different list from previous index is 2
1st absolute index of 1 into a different list from the start is 2
Collection
interface has no notion of indices. And order of traversal over some collections isn't guaranteed to be stable. The problem itself is contrived. \$\endgroup\$