3

I have a very simple use case

Given a list of letters with A and Bs, I want to get the sublist that contains the first N Bs, for example:

  • f(3, [A A A B A B A B A A]) = [A A A B A B A B]
  • f(2, [A A A B A B A B A A]) = [A A A B A B]
  • f(1, [A A B A B A]) = [A A B]
  • f(0, [A A B A B]) = []

By following an imperative approach, this is relatively easy, count until we find N Bs, and then get the sublist until that position.

However, I couldn't find any functional solution with lambdas, since the operation on every node seems to be independent from the others (which I guess make sense for parallelization).

Cœur
  • 37,241
  • 25
  • 195
  • 267
user3483969
  • 71
  • 1
  • 1
  • 3
  • What do you mean by _functional solution with lambdas_? Are you talking about streams? Can you give us some pseudo-code to let us know what you are after? – Keppil Aug 31 '15 at 11:14
  • I just want code using streams that does the following imperative function: – user3483969 Aug 31 '15 at 11:23
  • `private List getSubList(int maxAmount, List inputList) { ArrayList result = new ArrayList(); int itemsFound = 0; for (String item : inputList) { result.add(item); if (item.equals("B")) itemsFound++; if (itemsFound == maxAmount); break; } return result; }` – user3483969 Aug 31 '15 at 11:24
  • 1
    Wait for Java 9 and `Stream#takeWhile`. But your logic demands a stateful predicate, which will always be a stumbling block. – Marko Topolnik Aug 31 '15 at 11:27

3 Answers3

2

If your input is a List with fast random access, you can solve your problem using the stream of indices:

public static List<String> f(int n, List<String> input) {
    int fence = IntStream.range(0, input.size())
                         .filter(idx -> input.get(idx).equals("B")) // leave only B's
                         .skip(n-1)
                         .findFirst() // an index of n-th B
                         .getAsInt(); // with throw NoSuchElementException if not enough B's
    return input.subList(0, fence+1);
}

Usage example:

System.out.println(f(3, Arrays.asList("A", "A", "A", "B", "A", "B", "A", "B", "A", "A")));
System.out.println(f(2, Arrays.asList("A", "A", "A", "B", "A", "B", "A", "B", "A", "A")));
System.out.println(f(1, Arrays.asList("A", "A", "A", "B", "A", "B", "A", "B", "A", "A")));

However despite I love Stream API I would solve this problem imperatively.

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
  • Thanks Tagir, that is exactly what I was looking for. In any case, I have to agree with you, in this case the imperative approach seems to be much more explanatory, so I will leave it like now in the code. – user3483969 Aug 31 '15 at 12:46
  • However, your help is very valuable to go deeper in the functional understanding of the language. – user3483969 Aug 31 '15 at 12:47
0

This code snippet do the job.

public static void main(String[] args) {
    final String input = "A A A B A B A B A A";
    final String[] array = input.split(" ");
    final List<String> list = Arrays.asList(array);
    final List<String> result = getSubList(list, 3);
    System.out.println("Result=" + result);
}

public static List<String> getSubList(final List<String> list, final int limit) {
    final AtomicInteger counter = new AtomicInteger();
    return list.stream()
            .filter(ch -> {
                if (ch.equals("B") && counter.incrementAndGet() == limit) return true;
                return counter.get() < limit;
            })
            .collect(Collectors.toList());
}
Karol Król
  • 3,320
  • 1
  • 34
  • 37
  • 1
    Note that your solution violates [the contract](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#filter-java.util.function.Predicate-) of `filter` method: the supplied predicate must be stateless. – Tagir Valeev Sep 01 '15 at 03:00
  • @TagirValeev Not "must be" but "should be". This solution doesn't broke any restriction. Used operations on AtomicInteger are synchronized (and atomic). Anyway in this case there is no option to use parallelStream but simple stream - which use one thread. Do not be so orthodox - Java 8 this is not real functional programming language. – Karol Król Sep 01 '15 at 06:54
  • @KarolKról Thanks for your alternative. Even if it is not sure if it violates the contract of filter or not, I feel it carries a little bit more complexity (ie an AtomicInteger should be used instead of a regular one). In the end, I think that the imperative approach is the clearer in this case – user3483969 Sep 01 '15 at 12:10
  • @user3483969 I agree with you, but IMO Java Stream API is poor if you compare it with similar Scala API. Moreover TagirValeev approach is also not "so imperative" because it read/compare elements of mutable collection :/ In fact is even worse because get method on List are not atomic - I just defend my super solution :P Vote up man ;) https://www.youtube.com/watch?v=REUukm_WQJI – Karol Król Sep 01 '15 at 12:31
  • @KarolKról Yep, although I like having the possibility to write more "functional" style code, the API is not so flexible as Scala`s. – user3483969 Sep 01 '15 at 13:28
0

Here is code by abacus-common

List<String> input = N.asList("A", "A", "A", "B", "A", "B", "A", "B", "A", "A");
List<String> list = Stream.of(input).takeWhile(MutableInt.of(3), (e, cnt) -> cnt.getAndAdd(e.equals("B") ? -1 : 0) > 0).toList();
N.println(list);

Declaration: I'm the developer of abacus-common.

user_3380739
  • 1
  • 14
  • 14