Skip to content

Commit 5060fc4

Browse files
committed
More refactors
1 parent acc1414 commit 5060fc4

9 files changed

Lines changed: 149 additions & 92 deletions

core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/AbstractLongAverageSlot.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,16 @@ public AbstractLongAverageSlot(State state) {
2424
protected void addMapped(long input) {
2525
cachedInput = input;
2626
state.count++;
27-
state.sum += input;
27+
state.sum = Math.addExact(state.sum, input);
2828
}
2929

3030
protected void updateMapped(long input) {
31-
state.sum += input - cachedInput;
31+
state.sum += Math.subtractExact(input, cachedInput);
3232
cachedInput = input;
3333
}
3434

3535
protected void removeMapped() {
3636
state.count--;
37-
state.sum -= cachedInput;
37+
state.sum = Math.subtractExact(state.sum, cachedInput);
3838
}
3939
}

core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/AbstractLongSumSlot.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ public AbstractLongSumSlot(State state) {
1919

2020
protected void addMapped(long input) {
2121
cachedInput = input;
22-
state.sum += input;
22+
state.sum = Math.addExact(state.sum, input);
2323
}
2424

2525
protected void updateMapped(long input) {
26-
state.sum += input - cachedInput;
26+
state.sum += Math.subtractExact(input, cachedInput);
2727
cachedInput = input;
2828
}
2929

3030
protected void removeMapped() {
31-
state.sum -= cachedInput;
31+
state.sum = Math.subtractExact(state.sum, cachedInput);
3232
}
3333
}

core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/AbstractReferenceSumSlot.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package ai.timefold.solver.core.impl.score.stream.collector;
22

3+
import java.util.Objects;
34
import java.util.function.BinaryOperator;
45

56
import org.jspecify.annotations.Nullable;
@@ -35,7 +36,7 @@ protected void addMapped(Result_ input) {
3536
}
3637

3738
protected void updateMapped(Result_ input) {
38-
if (cachedValue == input) {
39+
if (Objects.equals(cachedValue, input)) {
3940
return;
4041
}
4142
state.current = state.subtractor.apply(state.current, cachedValue);

core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/AbstractToMapSlot.java

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package ai.timefold.solver.core.impl.score.stream.collector;
22

33
import java.util.Map;
4-
import java.util.Objects;
54
import java.util.Set;
65
import java.util.function.BinaryOperator;
76
import java.util.function.IntFunction;
@@ -34,29 +33,26 @@ State<Key_, Value_, Set_, Result_> multiMapState(Supplier<Result_> resultSupplie
3433
}
3534

3635
private final State<Key_, Value_, ResultValue_, Result_> state;
37-
private @Nullable Key_ cachedKey;
38-
private @Nullable Value_ cachedValue;
36+
private @Nullable ToMapPerKeyCounter<Key_, Value_> cachedCounter;
37+
private @Nullable CountHolder<Value_> cachedHolder;
3938

4039
public AbstractToMapSlot(State<Key_, Value_, ResultValue_, Result_> state) {
4140
this.state = state;
4241
}
4342

4443
protected void addMapped(Key_ key, Value_ value) {
45-
this.cachedKey = key;
46-
this.cachedValue = value;
4744
state.container.add(key, value);
45+
cachedCounter = state.container.lastCounter();
46+
cachedHolder = state.container.lastHolder();
4847
}
4948

5049
protected void updateMapped(Key_ key, Value_ value) {
51-
if (Objects.equals(cachedKey, key) && Objects.equals(cachedValue, value)) {
52-
return;
53-
}
54-
state.container.update(cachedKey, cachedValue, key, value);
55-
cachedKey = key;
56-
cachedValue = value;
50+
state.container.update(cachedCounter, cachedHolder, key, value);
51+
cachedCounter = state.container.lastCounter();
52+
cachedHolder = state.container.lastHolder();
5753
}
5854

5955
protected void removeMapped() {
60-
state.container.remove(cachedKey, cachedValue);
56+
state.container.remove(cachedCounter, cachedHolder);
6157
}
6258
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package ai.timefold.solver.core.impl.score.stream.collector;
2+
3+
final class CountHolder<Value_> {
4+
5+
final Value_ value;
6+
long count;
7+
8+
CountHolder(Value_ value) {
9+
this.value = value;
10+
this.count = 1;
11+
}
12+
13+
}

core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ToMapPerKeyCounter.java

Lines changed: 28 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,51 +5,46 @@
55
import java.util.function.BinaryOperator;
66
import java.util.stream.Stream;
77

8-
final class ToMapPerKeyCounter<Value_> {
9-
10-
private final Map<Value_, Long> counts = new LinkedHashMap<>();
11-
12-
public long add(Value_ value) {
13-
return counts.compute(value, (k, currentCount) -> {
14-
if (currentCount == null) {
15-
return 1L;
16-
} else {
17-
return currentCount + 1;
18-
}
19-
});
8+
final class ToMapPerKeyCounter<Key_, Value_> {
9+
10+
final Key_ key;
11+
private final Map<Value_, CountHolder<Value_>> counts = new LinkedHashMap<>(0);
12+
13+
ToMapPerKeyCounter(Key_ key) {
14+
this.key = key;
2015
}
2116

22-
public long update(Value_ oldValue, Value_ newValue) {
23-
var removedCount = remove(oldValue);
24-
add(newValue);
25-
return removedCount;
17+
CountHolder<Value_> add(Value_ value) {
18+
var holder = counts.get(value);
19+
if (holder == null) {
20+
holder = new CountHolder<>(value);
21+
counts.put(value, holder);
22+
} else {
23+
holder.count++;
24+
}
25+
return holder;
2626
}
2727

28-
public long remove(Value_ value) {
29-
var newCount = counts.compute(value, (k, currentCount) -> {
30-
if (currentCount > 1L) {
31-
return currentCount - 1;
32-
} else {
33-
return null;
34-
}
35-
});
36-
return newCount == null ? 0L : newCount;
28+
void decrement(CountHolder<Value_> holder) {
29+
holder.count--;
30+
if (holder.count == 0) {
31+
counts.remove(holder.value);
32+
}
3733
}
3834

39-
public Value_ merge(BinaryOperator<Value_> mergeFunction) {
40-
// Rebuilding the value from the collection is not incremental.
41-
// The impact is negligible, assuming there are not too many values for the same key.
42-
return counts.entrySet()
35+
Value_ merge(BinaryOperator<Value_> mergeFunction) {
36+
return counts.values()
4337
.stream()
44-
.map(e -> Stream.generate(e::getKey)
45-
.limit(e.getValue())
38+
.map(h -> Stream.generate(() -> h.value)
39+
.limit(h.count)
4640
.reduce(mergeFunction)
47-
.orElseThrow(() -> new IllegalStateException("Impossible state: Should have had at least one value.")))
41+
.orElseThrow(
42+
() -> new IllegalStateException("Impossible state: Should have had at least one value.")))
4843
.reduce(mergeFunction)
4944
.orElseThrow(() -> new IllegalStateException("Impossible state: Should have had at least one value."));
5045
}
5146

52-
public boolean isEmpty() {
47+
boolean isEmpty() {
5348
return counts.isEmpty();
5449
}
5550

core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ToMapResultContainer.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,23 @@
22

33
import java.util.Map;
44

5+
import org.jspecify.annotations.Nullable;
6+
57
sealed interface ToMapResultContainer<Key_, Value_, ResultValue_, Result_ extends Map<Key_, ResultValue_>>
68
permits ToMultiMapResultContainer, ToSimpleMapResultContainer {
79

810
void add(Key_ key, Value_ value);
911

10-
void update(Key_ oldKey, Value_ oldValue, Key_ newKey, Value_ newValue);
12+
void update(ToMapPerKeyCounter<Key_, Value_> counter, CountHolder<Value_> holder,
13+
Key_ newKey, Value_ newValue);
14+
15+
void remove(ToMapPerKeyCounter<Key_, Value_> counter, CountHolder<Value_> holder);
16+
17+
@Nullable
18+
ToMapPerKeyCounter<Key_, Value_> lastCounter();
1119

12-
void remove(Key_ key, Value_ value);
20+
@Nullable
21+
CountHolder<Value_> lastHolder();
1322

1423
Result_ getResult();
1524

core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ToMultiMapResultContainer.java

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,55 +7,78 @@
77
import java.util.function.IntFunction;
88
import java.util.function.Supplier;
99

10+
import org.jspecify.annotations.Nullable;
11+
1012
final class ToMultiMapResultContainer<Key_, Value_, Set_ extends Set<Value_>, Result_ extends Map<Key_, Set_>>
1113
implements ToMapResultContainer<Key_, Value_, Set_, Result_> {
1214

1315
private final Supplier<Set_> setSupplier;
1416
private final Result_ result;
15-
private final Map<Key_, ToMapPerKeyCounter<Value_>> valueCounts = new HashMap<>();
17+
private final Map<Key_, ToMapPerKeyCounter<Key_, Value_>> valueCounts = new HashMap<>(0);
18+
private @Nullable ToMapPerKeyCounter<Key_, Value_> lastCounter;
19+
private @Nullable CountHolder<Value_> lastHolder;
1620

17-
public ToMultiMapResultContainer(Supplier<Result_> resultSupplier, IntFunction<Set_> setFunction) {
21+
ToMultiMapResultContainer(Supplier<Result_> resultSupplier, IntFunction<Set_> setFunction) {
1822
var nonNullSetFunction = Objects.requireNonNull(setFunction);
1923
this.setSupplier = () -> nonNullSetFunction.apply(0);
2024
this.result = Objects.requireNonNull(resultSupplier).get();
2125
}
2226

2327
@Override
2428
public void add(Key_ key, Value_ value) {
25-
var counter = valueCounts.computeIfAbsent(key, k -> new ToMapPerKeyCounter<>());
26-
counter.add(value);
27-
result.computeIfAbsent(key, k -> setSupplier.get())
28-
.add(value);
29+
lastCounter = valueCounts.computeIfAbsent(key, ToMapPerKeyCounter::new);
30+
lastHolder = lastCounter.add(value);
31+
if (lastHolder.count == 1) {
32+
result.computeIfAbsent(key, k -> setSupplier.get()).add(value);
33+
}
2934
}
3035

3136
@Override
32-
public void update(Key_ oldKey, Value_ oldValue, Key_ newKey, Value_ newValue) {
33-
if (Objects.equals(oldKey, newKey)) {
34-
var counter = valueCounts.get(oldKey);
35-
var removedCount = counter.update(oldValue, newValue);
36-
if (removedCount == 0) {
37-
result.get(oldKey).remove(oldValue);
37+
public void update(ToMapPerKeyCounter<Key_, Value_> counter, CountHolder<Value_> holder,
38+
Key_ newKey, Value_ newValue) {
39+
if (Objects.equals(counter.key, newKey) && Objects.equals(holder.value, newValue)) {
40+
lastCounter = counter;
41+
lastHolder = holder;
42+
return;
43+
}
44+
if (Objects.equals(counter.key, newKey)) {
45+
counter.decrement(holder);
46+
if (holder.count == 0) {
47+
result.get(counter.key).remove(holder.value);
3848
}
39-
result.computeIfAbsent(oldKey, k -> setSupplier.get()).add(newValue);
49+
lastHolder = counter.add(newValue);
50+
if (lastHolder.count == 1) {
51+
result.computeIfAbsent(counter.key, k -> setSupplier.get()).add(newValue);
52+
}
53+
lastCounter = counter;
4054
} else {
41-
remove(oldKey, oldValue);
55+
remove(counter, holder);
4256
add(newKey, newValue);
4357
}
4458
}
4559

4660
@Override
47-
public void remove(Key_ key, Value_ value) {
48-
var counter = valueCounts.get(key);
49-
var newCount = counter.remove(value);
50-
if (newCount == 0) {
51-
result.get(key).remove(value);
61+
public void remove(ToMapPerKeyCounter<Key_, Value_> counter, CountHolder<Value_> holder) {
62+
counter.decrement(holder);
63+
if (holder.count == 0) {
64+
result.get(counter.key).remove(holder.value);
5265
}
5366
if (counter.isEmpty()) {
54-
valueCounts.remove(key);
55-
result.remove(key);
67+
valueCounts.remove(counter.key);
68+
result.remove(counter.key);
5669
}
5770
}
5871

72+
@Override
73+
public @Nullable ToMapPerKeyCounter<Key_, Value_> lastCounter() {
74+
return lastCounter;
75+
}
76+
77+
@Override
78+
public @Nullable CountHolder<Value_> lastHolder() {
79+
return lastHolder;
80+
}
81+
5982
@Override
6083
public Result_ getResult() {
6184
return result;

0 commit comments

Comments
 (0)