Java - How to get list of elements best on certain condition

Viewed 124
Solution{
  String question
  String answer

  public Solution(String question,String answer){
  ...
  }
}

List<Solution> solutions = new ArrayList<>();

Arrays.asList(
    new Solution("Index1","a"),
    new Solution("Index2","b"),
    new Solution("Index3","c"),
    new Solution("Index4","d"),
    new Solution("Index5","ae"),
    new Solution("Index1","afg"),
    new Solution("Index2","adfg"),
    new Solution("Index1","ag"),
    new Solution("Index2","a"),
    new Solution("Index3","a"),
    new Solution("Index4","a"),
    new Solution("Index5","a"),
    new Solution("Index1","arrr"),
    new Solution("Index2","a"),
    new Solution("Index3","a"));

I always want to get last two sets starting from Index1 which are

new Solution("Index1","ag"),
new Solution("Index2","a"),
new Solution("Index3","a"),
new Solution("Index4","a"),
new Solution("Index5","a"),
new Solution("Index1","arrr"),
new Solution("Index2","a"),
new Solution("Index3","a"))

but I am not sure what is the best way to do that. I can think of reversing the list and then have a counter on Index1 which starts with 0 then do while loop to add it in a list until counter reaches 2. Not sure if this is possible with streams.

3 Answers

This is quite a bit simpler with a normal for-loop than with streams. You can loop through the list and keep track of the indexes of the last two occurrences of Index1. When you know the second to last index, you can use the subList method to get your final list.

To make it faster, you can iterate backwards from the end and find the first two occurrences. You can then stop when you hit Index1 the second time.

This example iterates through from the beginning:

int firstIndex = -1;
int secondIndex = -1;

for (int i = 0; i < solutions.size(); i++) {
    if (solutions.get(i).getQuestion().equals("Index1")) {
        firstIndex = secondIndex;
        secondIndex = i;
    }
}

if (firstIndex == -1) {
    // There weren't two occurrences of "Index1", so I return the whole list.
    return solutions;
}

return solutions.subList(firstIndex, solutions.size());

Note that the subList method returns a view over your original list. This means that it won't iterate over your list a second time when you call it. It also means that if you mutate your original list, the changes will be reflected in the sublist.

I think the simplest way is to get the positions at which Index1 occurs in the list of solutions. These represent potential start position of the sublist. You can do this by using an IntStream over the indexes into the solutions list. Then, take the second-to-last start point as the starting of a sublist that goes to the end of the list.

    List<Integer> starts = IntStream.range(0, solutions.size())
                                    .filter(i -> solutions.get(i).getQuestion().equals("Index1"))
                                    .boxed()
                                    .collect(toList());
    if (starts.size() < 2) {
        // not sure what you want to do in this case
    } else {
        List<Solution> lastTwoSets = solutions.subList(starts.get(starts.size()-2), solutions.size());
        lastTwoSets.forEach(System.out::println);
    }

It occurs to me that using an int[] instead of List<Integer> makes things slightly more efficient as well as a bit more concise. The techique is otherwise essentially the same.

    int[] starts = IntStream.range(0, solutions.size())
                            .filter(i -> solutions.get(i).question.equals("Index1"))
                            .toArray();
    if (starts.length < 2) {
        // not sure what you want to do in this case
    } else {
        List<Solution> lastTwoSets = solutions.subList(starts[starts.length-2], solutions.size());
        lastTwoSets.forEach(System.out::println);
    }

This solution is a bit tricky because the filtering criteria is multi-part, but this can be accomplished by counting the number of times that a question index has been seen, at most adding only 2 solutions per question, and stopping once Index1 has been seen twice.

The filtering object for this criteria would be:

public class SolutionFilter implements Predicate<Solution> {

    private final Map<String, Integer> counter = new HashMap<>();

    @Override
    public boolean test(Solution solution) {

        Integer index1Count = counter.get("Index1");

        if (index1Count != null && index1Count == 2) {
            return false;
        }

        Integer count = counter.get(solution.getQuestion());

        if (count == null) {
            counter.put(solution.getQuestion(), 1);
            return true;
        }
        else if (count == 1) {
            counter.put(solution.getQuestion(), 2);
            return true;
        }
        else {
            return false;
        }
    }
}

To ensure that the solutions are added in reverse order, a loop is performed, starting at the end and progressing towards the beginning of the solutions list. Likewise, to ensure that the output list is not in reverse order, a Deque is used and the matching Solutions are added to the head of the Deque:

public static void main(final String[] args) {
    List<Solution> solutions = Arrays.asList(
        new Solution("Index1","a"),
        new Solution("Index2","b"),
        new Solution("Index3","c"),
        new Solution("Index4","d"),
        new Solution("Index5","ae"),
        new Solution("Index1","afg"),
        new Solution("Index2","adfg"),
        new Solution("Index1","ag"),
        new Solution("Index2","a"),
        new Solution("Index3","a"),
        new Solution("Index4","a"),
        new Solution("Index5","a"),
        new Solution("Index1","arrr"),
        new Solution("Index2","a"),
        new Solution("Index3","a")
    );

    SolutionFilter filter = new SolutionFilter();
    Deque<Solution> filteredSolutions = new LinkedList<>();

    for (int i = solutions.size() - 1; i > 0; i--) {

        Solution solution = solutions.get(i);

        if (filter.test(solution)) {
            filteredSolutions.addFirst(solution);
        }
    }

    System.out.println(filteredSolutions);
}

This results in the following output:

[{Index1: ag}, {Index2: a}, {Index3: a}, {Index4: a}, {Index5: a}, {Index1: arrr}, {Index2: a}, {Index3: a}]

This can be accomplished using a Stream, but it may be more complicated.

Related