Addition of Predicate-based findIndex and findLastIndex methods to java.util.List

David Alayachew davidalayachew at gmail.com
Fri Apr 19 22:16:45 UTC 2024


> Any stream can be "indexed", having an index is not an
> intrisinc property of a stream of List, so adding an
> index is more an intermediate operation than a source
> operation.
>
> So more like
>   list.stream().withIndex()
> than like
>   list.streamWithIndex()

I will start of by conceding this point -- this idea is much better than
mine.

> Could you give an example of why it is more error prone ?

It is error-prone because you are mixing indexing logic with predicate
logic. Those are 2 separate tasks that should be done separately. If
anything, it is for this exact reason why your suggestion of .withIndex()
is better than my .streamWithIndex() -- it separates the streaming from the
indexing. Users grab what they need as the need arises.

> Correctness is not an issue athat's why i've used
> ofSequential() when creating the Gatherer.
> You are right that performance can be an issue, the
> Gatherer API is more limited compared to what a
> Spliterator can do.
>
> This custom Gatherer can be declared in Gatherers (with
> an 's' at the end).

If your intent is to toss this into Gatherers, then I sort of see your
point. But is this a good fit for Gatherers? I thought the entire point of
Gatherers was to provide intermediate logic that is difficult to provide
via normal streaming features?

If anything, I feel like .withIndex() would be the feature to add to
Gatherers. But I think it is too good of a method to put into Gatherers. It
should be on the Stream base class, as you suggested.

> Technically, here, you want the result to be an index, so
> you may not want to pay the price of creating an index
> (your WithIndex objetcs) for every elements but just one
> when the predicate is true.
> This point is moot if the WithIndex is a value type and
> Stream is a specialized generics but as I said above,
> where are not yet at that point.

Ok yes, I also concede that there is missing details in my original
suggestion. Like you said, I am depending on features that aren't here yet,
but this problem is here and now.

Valhalla when?

> With your proposal, you still need to write something like

>   list
>       .withIndexStream()
>       .filter(withIndex -> predicate(withIndex.element()))
>       .mapToInt(WithIndex::index)
>       .findFirst()
>       .orElse(-1)

Come on Rémi, give me some credit here.

If we are making a WithIndex object, you think it is a simple no-body
record? You think nothing else in the ecosystem will change in response to
this inclusion?

Try this instead. I will also include your suggestion to use .withIndex().

list
    .stream()
    .withIndex()
    .filter(WithIndex.filter(predicate))
    .mapToIndex()
    .findFirst()
    .orElse(-1)

On Fri, Apr 19, 2024 at 5:47 PM <forax at univ-mlv.fr> wrote:

>
>
> ------------------------------
>
> *From: *"David Alayachew" <davidalayachew at gmail.com>
> *To: *"Remi Forax" <forax at univ-mlv.fr>
> *Cc: *"ІП-24 Олександр Ротань" <rotan.olexandr at gmail.com>,
> "core-libs-dev" <core-libs-dev at openjdk.org>
> *Sent: *Friday, April 19, 2024 11:02:12 PM
> *Subject: *Re: Addition of Predicate-based findIndex and findLastIndex
> methods to java.util.List
>
>
> No Rémi, I don't think your idea is the right approach. You are working on
> the wrong level of abstraction.
>
>
> Hello David,
>
>
> Many users ask requests like this all the time, and what you are
> suggesting would be even more error-prone than the equivalent for loop or the
> IntStream suggestion that the user above requested.
>
>
> Could you give an example of why it is more error prone ?
>
> Not to mention that getting it to parallelize would be a task many users
> are likely to mess up -- either in correctness or in performance.
>
>
> Correctness is not an issue athat's why i've used ofSequential() when
> creating the Gatherer.
> You are right that performance can be an issue, the Gatherer API is more
> limited compared to what a Spliterator can do.
>
>
> I think you would get far more mileage from adding 2 methods on the list
> interface streamWithIndex() and parallelStreamWithIndex() that would return
> a Stream<WithIndex<T>>, as opposed to just Stream<T>.
>
>
> Any stream can be "indexed", having an index is not an intrisinc property
> of a stream of List, so adding an index is more an intermediate operation
> than a source operation.
> So more like
>   list.stream().withIndex()
> than like
>   list.streamWithIndex()
>
> Now, we (the lambda-util expert group) talked 10 years ago to add such
> design but decide to postpone it because WithIndex<T> should be a value
> type and the Stream API should be specialized to avoid unecessary
> allocations.
> So you are right that withIndex() is a more general design but because of
> the performance issue we can not yet offer such design.
>
> As a counter argument, you can say that we have the same issue with the
> Collector API and the Gatherer API which are both not specialized but at
> least for a Collector, the fact that the accumulator does a side effect
> which is usualyl a write in memory is slow anyway. And this is also true
> for a Gatherer, it works well when the Gatherer is itself followed by a
> Collector.
>
>
> That way, users are not writing a custom Gatherer each time they want to
> work with the index. They just have the index be a field in the object.
> They can work with it the same way they would any other object field.
>
>
> This custom Gatherer can be declared in Gatherers (with an 's' at the end).
>
>
> Furthermore, doing it this way makes the correct answer obvious. If I need
> to do something with an index, stream with the index.
>
>
> Technically, here, you want the result to be an index, so you may not want
> to pay the price of creating an index (your WithIndex objetcs) for every
> elements but just one when the predicate is true.
> This point is moot if the WithIndex is a value type and Stream is a
> specialized generics but as I said above, where are not yet at that point.
>
>
> On top of that, it significantly enhances readability by making it clear
> to the reader that, whatever this stream is doing will require use of the
> index, so watch out for that.
>
>
> With your proposal, you still need to write something like
>
>   list.withIndexStream().filter(withIndex ->
> predicate(withIndex.element())).mapToInt(WithIndex::index).findFirst().orElse(-1)
>
> I do not see the enhancement in readability.
>
> regards,
> Rémi
>
>
>
>
>
> On Fri, Apr 19, 2024, 1:47 PM Remi Forax <forax at univ-mlv.fr> wrote:
>
>> Hello,
>> for me, it seems what you want is  Collector on Stream which is able to
>> short-circuit,
>> so you can write
>>   list.stream().collect(Collectors.findFirst(s -> s.contains("o")))
>> and in reverse
>>   list.reversed().stream().collect(Collectors.findFirst(s ->
>> s.contains("o")))
>>
>> Using a Stream here is more general and will work with other collections
>> like a LinkedHashSet for example.
>> Sadly, there is no way to define a short-circuiting collector :(
>>
>> You can have a short-circuiting Gatherer like this
>>   <T> Gatherer<T, ?, Integer> findIndex(Predicate<? super T> predicate) {
>>     return Gatherer.ofSequential(
>>       () -> new Object() { int index; },
>>       Integrtor.ofGreedy((state, element, downstream) -> {
>>         var index = state.index++;
>>         if (predicate.test(element)) {
>>           return downstream.push(index);
>>         }
>>         return true;
>>       }));
>>   }
>>
>> and use it like this:
>>   list.stream().gather(findIndex(s ->
>> s.contains("o"))).findFirst().orElse(-1);
>>
>> But it's more verbose.
>>
>> I wonder if at the same time that the Gatherer API is introduced, the
>> Collector API should be enhanced to support short-circuiting collectors ?
>>
>> regards,
>> Rémi
>>
>>
>> ------------------------------
>>
>> *From: *"ІП-24 Олександр Ротань" <rotan.olexandr at gmail.com>
>> *To: *"core-libs-dev" <core-libs-dev at openjdk.org>
>> *Sent: *Friday, April 19, 2024 5:59:39 PM
>> *Subject: *Addition of Predicate-based findIndex and findLastIndex
>> methods to java.util.List
>>
>> Subject
>> Addition of Predicate-based findIndex and findLastIndex methods to
>> java.util.List
>>
>> Motivation
>> The motivation behind this proposal is to enhance the functionality of
>> the List interface by providing a more flexible way to find the index of an
>> element. Currently, the indexOf and lastIndexOf methods only accept an
>> object as a parameter. This limits the flexibility of these methods as they
>> can only find the index of exact object matches.
>>
>> Here I want to propose methods that would accept a Predicate as a
>> parameter, allowing users to define a condition that the desired element
>> must meet. This would provide a more flexible and powerful way to find the
>> index of an element in a list.
>>
>> The changes I am proposing are implemented in this PR:
>> https://github.com/openjdk/jdk/pull/18639. Here is a brief overview of
>> the changes made in this pull request:
>>
>> Added the findIndex  (Predicate<? super E> filter) method to the List
>> interface.
>> Added the findLastIndex  (Predicate<? super E> filter) method to the List
>> interface.
>> Implemented these methods in all non-abstract classes that implement the
>> List interface, as well as List itself (default impl).
>> The changes have been thoroughly tested to ensure they work as expected
>> and do not introduce any regressions. The test cases cover a variety of
>> scenarios to ensure the robustness of the implementation.
>>
>> For example, consider the following test case:
>>
>> List<String> list = new ArrayList<>();
>> list.add("Object one");
>> list.add("NotObject two");
>> list.add("NotObject three");
>>
>> int index1 = list.findIndex(s -> s.contains("ct t"));
>> System.out.println(index1); // Expected output: 1
>> int index2 = list. findLastIndex(s -> s.startsWith("NotObject"));
>> System.out.println(index2); // Expected output: 2
>> Currently, to achieve the same result, we would have to use a more
>> verbose approach:
>>
>> int index1 = IntStream.range(0, list.size())
>>                      .filter(i -> list.get(i).contains("ct t"))
>>                      .findFirst()
>>                      .orElse(-1);
>> System.out.println(index1); // Output: 1
>> int index2 = IntStream.range(0, list.size())
>>                          .filter(i -> list.get(i).startsWith("NotObject"))
>>                          .reduce((first, second) -> second)
>>                          .orElse(-1);
>> System.out.println(index2); // Output: 2
>> Or other approaches that require additional instructions and, therefore,
>> can`t be used in all scopes (like passing argument to method).
>>
>> I believe these additions would greatly enhance the functionality and
>> flexibility of the List interface, making it more powerful and
>> user-friendly. I look forward to your feedback and am open to making any
>> necessary changes based on your suggestions.
>>
>> The main reason I am publishing this proposal in the mailing system is to
>> gather feedback from the Java developers community, especially about
>> possible caveats related to backward compatibility of your projects. Would
>> appreciate every opinion!
>>
>> Best regards
>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/core-libs-dev/attachments/20240419/f2795580/attachment-0001.htm>


More information about the core-libs-dev mailing list