perf: improve ConstraintCollectors.toList()#2285
Conversation
There was a problem hiding this comment.
Pull request overview
This PR refactors ElementAwareArrayList into an AbstractList-backed implementation optimized for frequent O(1) removals via entry handles, and updates constraint-stream/indexing code to use the new entry-based API (notably for ConstraintCollectors.toList()/group node workloads).
Changes:
- Reimplemented
ElementAwareArrayListusing flat arrays plus index maps, adding partial/full compaction paths andListIteratorsupport. - Switched several hot-path components (collectors, indexer backend, neighborhood dataset) to use
addEntry(...)/Entry.remove()for O(1) undo/removal. - Expanded and reorganized tests for compaction, null handling, iterator behavior, and fail-fast semantics; clarified collector docs about mutability.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| core/src/main/java/ai/timefold/solver/core/impl/util/ElementAwareArrayList.java | Major rewrite to an AbstractList implementation with entry-based O(1) removal and compaction logic. |
| core/src/test/java/ai/timefold/solver/core/impl/util/ElementAwareArrayListTest.java | Large test suite expansion to validate new compaction/iterator/null/entry semantics. |
| core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ListUndoableActionable.java | Uses ElementAwareArrayList entries to make undo removal O(1). |
| core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/AbstractLeftDatasetInstance.java | Stores entry handles and removes via Entry.remove() on retract. |
| core/src/main/java/ai/timefold/solver/core/impl/bavet/common/index/RandomAccessIndexerBackend.java | Uses addEntry(...) and removes via Entry.remove() instead of list-level removal. |
| core/src/main/java/ai/timefold/solver/core/impl/bavet/common/index/DefaultUniqueRandomIterator.java | Adjusted to ElementAwareArrayList<T> element-based get(int) semantics. |
| core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintCollectors.java | Documentation updates warning users not to mutate returned collections. |
|
Converting back to a draft for another experiment. |
Christopher-Chianelli
left a comment
There was a problem hiding this comment.
Changes look good upon initial review. Any benchmarks?
|
It seems a comment I made got loss, but in |
|
(line 261, GitHub not allowing me to comment) |
|
Also, does |
The underlying collection is now optimized for removals, as those operations will be frequent inside group nodes. It still retains fast random access to elements, as that is required to reuse it in Neighborhoods. The collection supports null values, and implements the full list interface; this makes it a suitable replacement to ArrayList. Compared to ArrayList, remove() is super-fast if done via the entry. This introduces small overhead in addition and iteration, as the arrays may need to be compacted. Some operations compare equally poorly in both collections, such as additions into the middle of the array.
That method (
I originally had it implemented like that, but then I decided that insertion order is important. You expect that out of a list. |
|



The underlying collection is now optimized for removals, as those operations will be frequent inside group nodes. It still retains fast random access to elements, as that is required to reuse it in Neighborhoods.
The collection supports null values, and implements the full list interface; this makes it a suitable replacement to ArrayList.
Compared to ArrayList, remove() is super-fast if done via the entry. This introduces small overhead in addition and iteration, as the arrays may need to be compacted. Some operations compare equally poorly in both collections, such as additions into the middle of the array.
Note: this was co-developed with Claude code. It was reviewed by 2 different LLMs, as well as by me.