<div dir="ltr"><div class="gmail_default" style="font-family:monospace">> Any stream can be "indexed", having an index is not an<br>> intrisinc property of a stream of List, so adding an<br>> index is more an intermediate operation than a source<br>> operation.<br>> <br>> So more like<br>> list.stream().withIndex()<br>> than like<br>> list.streamWithIndex()<br><br>I will start of by conceding this point -- this idea is much better than mine.<br><br>> Could you give an example of why it is more error prone ?<br><br>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.<br><br>> Correctness is not an issue athat's why i've used<br>> ofSequential() when creating the Gatherer.<br>> You are right that performance can be an issue, the<br>> Gatherer API is more limited compared to what a<br>> Spliterator can do.<br>> <br>> This custom Gatherer can be declared in Gatherers (with<br>> an 's' at the end).<br><br>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?<br><br>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.<br><br>> Technically, here, you want the result to be an index, so<br>> you may not want to pay the price of creating an index<br>> (your WithIndex objetcs) for every elements but just one<br>> when the predicate is true.<br>> This point is moot if the WithIndex is a value type and<br>> Stream is a specialized generics but as I said above,<br>> where are not yet at that point.<br><br>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.<br><br>Valhalla when?<br><br>> With your proposal, you still need to write something like<br><br>> list<br>> .withIndexStream()<br>> .filter(withIndex -> predicate(withIndex.element()))<br>> .mapToInt(WithIndex::index)<br>> .findFirst()<br>> .orElse(-1)<br><br>Come on Rémi, give me some credit here.<br><br>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?<br><br>Try this instead. I will also include your suggestion to use .withIndex().<br><br>list<br> .stream()<br>
.withIndex()<br>
.filter(WithIndex.filter(predicate))<br>
.mapToIndex()<br>
.findFirst()<br>
.orElse(-1)<br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Fri, Apr 19, 2024 at 5:47 PM <<a href="mailto:forax@univ-mlv.fr">forax@univ-mlv.fr</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div><div id="m_-3989365510641111012zimbraEditorContainer" style="font-family:arial,helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0)"><div><br></div><div><br></div><hr id="m_-3989365510641111012zwchr"><div><blockquote style="border-left:2px solid rgb(16,16,255);margin-left:5px;padding-left:5px;color:rgb(0,0,0);font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt"><b>From: </b>"David Alayachew" <<a href="mailto:davidalayachew@gmail.com" target="_blank">davidalayachew@gmail.com</a>><br><b>To: </b>"Remi Forax" <<a href="mailto:forax@univ-mlv.fr" target="_blank">forax@univ-mlv.fr</a>><br><b>Cc: </b>"ІП-24 Олександр Ротань" <<a href="mailto:rotan.olexandr@gmail.com" target="_blank">rotan.olexandr@gmail.com</a>>, "core-libs-dev" <<a href="mailto:core-libs-dev@openjdk.org" target="_blank">core-libs-dev@openjdk.org</a>><br><b>Sent: </b>Friday, April 19, 2024 11:02:12 PM<br><b>Subject: </b>Re: Addition of Predicate-based findIndex and findLastIndex methods to java.util.List<br></blockquote></div><div><blockquote style="border-left:2px solid rgb(16,16,255);margin-left:5px;padding-left:5px;color:rgb(0,0,0);font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt"><div dir="ltr"><span style="font-family:monospace"><br>No Rémi, I don't think your idea is the right approach. You are working on the wrong level of abstraction.</span></div></blockquote><div><br></div><div>Hello David,<br></div><div><br></div><blockquote style="border-left:2px solid rgb(16,16,255);margin-left:5px;padding-left:5px;color:rgb(0,0,0);font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt"><div dir="ltr"><span style="font-family:monospace"><br>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 <span class="gmail_default" style="font-family:monospace">the </span>IntStream suggestion that the user above requested.</span></div></blockquote><div><br></div><div>Could you give an example of why it is more error prone ?<br></div><div><br></div><blockquote style="border-left:2px solid rgb(16,16,255);margin-left:5px;padding-left:5px;color:rgb(0,0,0);font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt"><div dir="ltr"><span style="font-family:monospace"> 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.</span></div></blockquote><div><br></div><div>Correctness is not an issue athat's why i've used ofSequential() when creating the Gatherer.</div><div>You are right that performance can be an issue, the Gatherer API is more limited compared to what a Spliterator can do.<br></div><div><br></div><blockquote style="border-left:2px solid rgb(16,16,255);margin-left:5px;padding-left:5px;color:rgb(0,0,0);font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt"><div dir="ltr"><span style="font-family:monospace"><br>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>.</span></div></blockquote><div><br></div><div>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.<br></div><div>So more like<br></div><div> list.stream().withIndex()<br></div><div>than like<br></div><div> list.streamWithIndex()<br></div><div><br></div><div>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.<br></div><div>So you are right that withIndex() is a more general design but because of the performance issue we can not yet offer such design.<br></div><div><br></div><div>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.<br></div><div><br></div><blockquote style="border-left:2px solid rgb(16,16,255);margin-left:5px;padding-left:5px;color:rgb(0,0,0);font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt"><div dir="ltr"><span style="font-family:monospace"><br></span><div><span style="font-family:monospace">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.</span></div></div></blockquote><div><br></div><div>This custom Gatherer can be declared in Gatherers (with an 's' at the end).<br></div><div><br></div><blockquote style="border-left:2px solid rgb(16,16,255);margin-left:5px;padding-left:5px;color:rgb(0,0,0);font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt"><div dir="ltr"><div><span style="font-family:monospace"><br></span></div><div style="font-family:monospace" class="gmail_default">Furthermore, doing it this way makes the correct answer obvious. If I need to do something with an index, stream with the index.</div></div></blockquote><div><br></div><div>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.<br></div><div>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.<br></div><div><br></div><blockquote style="border-left:2px solid rgb(16,16,255);margin-left:5px;padding-left:5px;color:rgb(0,0,0);font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt"><div dir="ltr"><div style="font-family:monospace" class="gmail_default"><br></div><div style="font-family:monospace" class="gmail_default">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.</div></div></blockquote><div><br></div><div>With your proposal, you still need to write something like<br></div><div><br></div><div> list.withIndexStream().filter(withIndex -> predicate(withIndex.element())).mapToInt(WithIndex::index).findFirst().orElse(-1)<br></div><div><br></div><div>I do not see the enhancement in readability.<br></div><div><br></div><div>regards,<br></div><div>Rémi<br></div><div><br></div><blockquote style="border-left:2px solid rgb(16,16,255);margin-left:5px;padding-left:5px;color:rgb(0,0,0);font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt"><div dir="ltr"><div style="font-family:monospace" class="gmail_default"><br></div><br><br></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Fri, Apr 19, 2024, 1:47 PM Remi Forax <<a href="mailto:forax@univ-mlv.fr" target="_blank">forax@univ-mlv.fr</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div><div id="m_-3989365510641111012m_-4157093933715390893m_9119089240644209637zimbraEditorContainer" style="font-family:arial,helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0)"><div>Hello,</div><div>for me, it seems what you want is Collector on Stream which is able to short-circuit,<br></div><div>so you can write<br></div><div> list.stream().collect(Collectors.findFirst(s -> s.contains("o")))<br></div><div>and in reverse<br></div><div> list.reversed().stream().collect(Collectors.findFirst(s -> s.contains("o")))<br></div><div> <br></div><div>Using a Stream here is more general and will work with other collections like a LinkedHashSet for example.<br></div><div>Sadly, there is no way to define a short-circuiting collector :(<br></div><br><div>You can have a short-circuiting Gatherer like this<br></div><div> <T> Gatherer<T, ?, Integer> findIndex(Predicate<? super T> predicate) {<br> return Gatherer.ofSequential(<br> () -> new Object() { int index; },<br> Integrtor.ofGreedy((state, element, downstream) -> {<br> var index = state.index++;<br> if (predicate.test(element)) {<br> return downstream.push(index);<br> }<br> return true;<br> }));<br> }<br></div><br><div>and use it like this:<br></div><div> list.stream().gather(findIndex(s -> s.contains("o"))).findFirst().orElse(-1);<br></div><br><div>But it's more verbose.<br></div><br><div>I wonder if at the same time that the Gatherer API is introduced, the Collector API should be enhanced to support short-circuiting collectors ?<br></div><br><div>regards,<br></div><div>Rémi<br></div><br><br><hr id="m_-3989365510641111012m_-4157093933715390893m_9119089240644209637zwchr"><div><blockquote style="border-left:2px solid rgb(16,16,255);margin-left:5px;padding-left:5px;color:rgb(0,0,0);font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt"><b>From: </b>"ІП-24 Олександр Ротань" <<a href="mailto:rotan.olexandr@gmail.com" rel="noreferrer" target="_blank">rotan.olexandr@gmail.com</a>><br><b>To: </b>"core-libs-dev" <<a href="mailto:core-libs-dev@openjdk.org" rel="noreferrer" target="_blank">core-libs-dev@openjdk.org</a>><br><b>Sent: </b>Friday, April 19, 2024 5:59:39 PM<br><b>Subject: </b>Addition of Predicate-based findIndex and findLastIndex methods to java.util.List<br></blockquote></div><div><blockquote style="border-left:2px solid rgb(16,16,255);margin-left:5px;padding-left:5px;color:rgb(0,0,0);font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt"><div dir="ltr">Subject<br>Addition of Predicate-based findIndex and findLastIndex methods to java.util.List<br><br>Motivation<br>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.<br><br>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.<br><br>The changes I am proposing are implemented in this PR: <a href="https://github.com/openjdk/jdk/pull/18639" rel="noreferrer" target="_blank">https://github.com/openjdk/jdk/pull/18639</a>. Here is a brief overview of the changes made in this pull request:<br><br>Added the findIndex (Predicate<? super E> filter) method to the List interface.<br>Added the findLastIndex (Predicate<? super E> filter) method to the List interface.<br>Implemented these methods in all non-abstract classes that implement the List interface, as well as List itself (default impl).<br>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.<br><br>For example, consider the following test case:<br><br>List<String> list = new ArrayList<>();<br>list.add("Object one");<br>list.add("NotObject two");<br>list.add("NotObject three");<br><br>int index1 = list.findIndex(s -> s.contains("ct t"));<br>System.out.println(index1); // Expected output: 1<br>int index2 = list. findLastIndex(s -> s.startsWith("NotObject"));<br>System.out.println(index2); // Expected output: 2<br>Currently, to achieve the same result, we would have to use a more verbose approach:<br><br>int index1 = IntStream.range(0, list.size())<br> .filter(i -> list.get(i).contains("ct t"))<br> .findFirst()<br> .orElse(-1);<br>System.out.println(index1); // Output: 1<br>int index2 = IntStream.range(0, list.size())<br> .filter(i -> list.get(i).startsWith("NotObject"))<br> .reduce((first, second) -> second)<br> .orElse(-1);<br>System.out.println(index2); // Output: 2<br><div>Or other approaches that require additional instructions and, therefore, can`t be used in all scopes (like passing argument to method).</div><div><br>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.<br><br>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!</div><br><div>Best regards</div></div></blockquote></div></div></div></blockquote></div><br></blockquote></div></div></div></blockquote></div>