Skip to content

Commit 9c6e785

Browse files
sarveswaran-mCopilotCopilot
authored
Mp 11740 introduce sorted map to fast utils wrapper (#21)
* MP-11740 Introduce Long2ShortMap * MP-11740 Introduce Long2ShortTreeMap * MP-11740 Fix intendations * MP-11740 Restrict TreeMap to 1 bucket * MP-11740 Update comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * MP-11740 Update comment in test Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * MP-11740 Remove unused constant Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * MP-11740 Fix indentation Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix formatting in LongShortTreeMap.java to match other map interfaces Agent-Logs-Url: https://github.com/trivago/fastutil-concurrent-wrapper/sessions/06e81fc3-7af9-46a9-9a6e-9afec03ed2d1 Co-authored-by: sarveswaran-m <34768285+sarveswaran-m@users.noreply.github.com> * MP-11740 Prevent instantiating TreeMap with more than 1 bucket * MP-11740 Update version --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent 8b7c395 commit 9c6e785

24 files changed

Lines changed: 1867 additions & 1 deletion

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ allprojects {
2323
}
2424

2525
group 'com.trivago'
26-
version '0.2.4-SNAPSHOT'
26+
version '0.3.0-SNAPSHOT'
2727

2828
mavenPublishing {
2929
coordinates("com.trivago", "fastutil-concurrent-wrapper", "0.2.3")
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.trivago.fastutilconcurrentwrapper;
2+
3+
import com.trivago.fastutilconcurrentwrapper.map.ConcurrentBusyWaitingLongShortMap;
4+
import com.trivago.fastutilconcurrentwrapper.map.ConcurrentLongShortMap;
5+
6+
public final class ConcurrentLongShortMapBuilder {
7+
private MapMode mapMode = MapMode.BLOCKING;
8+
private int buckets = 8;
9+
private int initialCapacity = 100_000;
10+
private float loadFactor = 0.8f;
11+
private short defaultValue = LongShortMap.DEFAULT_VALUE;
12+
13+
private ConcurrentLongShortMapBuilder() {
14+
15+
}
16+
17+
public static ConcurrentLongShortMapBuilder newBuilder() {
18+
return new ConcurrentLongShortMapBuilder();
19+
}
20+
21+
public ConcurrentLongShortMapBuilder withBuckets(int buckets) {
22+
this.buckets = buckets;
23+
return this;
24+
}
25+
26+
public ConcurrentLongShortMapBuilder withInitialCapacity(int initialCapacity) {
27+
this.initialCapacity = initialCapacity;
28+
return this;
29+
}
30+
31+
public ConcurrentLongShortMapBuilder withLoadFactor(float loadFactor) {
32+
this.loadFactor = loadFactor;
33+
return this;
34+
}
35+
36+
public ConcurrentLongShortMapBuilder withMode(MapMode mapMode) {
37+
this.mapMode = mapMode;
38+
return this;
39+
}
40+
41+
public ConcurrentLongShortMapBuilder withDefaultValue(short defaultValue) {
42+
this.defaultValue = defaultValue;
43+
return this;
44+
}
45+
46+
public LongShortMap build() {
47+
return mapMode.createMap(this);
48+
}
49+
50+
public enum MapMode {
51+
BUSY_WAITING {
52+
@Override
53+
LongShortMap createMap(ConcurrentLongShortMapBuilder builder) {
54+
return new ConcurrentBusyWaitingLongShortMap(
55+
builder.buckets,
56+
builder.initialCapacity,
57+
builder.loadFactor,
58+
builder.defaultValue);
59+
}
60+
},
61+
BLOCKING {
62+
@Override
63+
LongShortMap createMap(ConcurrentLongShortMapBuilder builder) {
64+
return new ConcurrentLongShortMap(
65+
builder.buckets,
66+
builder.initialCapacity,
67+
builder.loadFactor,
68+
builder.defaultValue);
69+
}
70+
};
71+
72+
abstract LongShortMap createMap(ConcurrentLongShortMapBuilder builder);
73+
}
74+
}
75+
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.trivago.fastutilconcurrentwrapper;
2+
3+
import com.trivago.fastutilconcurrentwrapper.map.ConcurrentBusyWaitingLongShortTreeMap;
4+
import com.trivago.fastutilconcurrentwrapper.map.ConcurrentLongShortTreeMap;
5+
6+
public final class ConcurrentLongShortTreeMapBuilder {
7+
private MapMode mapMode = MapMode.BLOCKING;
8+
private short defaultValue = LongShortMap.DEFAULT_VALUE;
9+
10+
private ConcurrentLongShortTreeMapBuilder() {
11+
}
12+
13+
public static ConcurrentLongShortTreeMapBuilder newBuilder() {
14+
return new ConcurrentLongShortTreeMapBuilder();
15+
}
16+
17+
18+
public ConcurrentLongShortTreeMapBuilder withMode(MapMode mapMode) {
19+
this.mapMode = mapMode;
20+
return this;
21+
}
22+
23+
public ConcurrentLongShortTreeMapBuilder withDefaultValue(short defaultValue) {
24+
this.defaultValue = defaultValue;
25+
return this;
26+
}
27+
28+
public LongShortTreeMap build() {
29+
return mapMode.createMap(this);
30+
}
31+
32+
public enum MapMode {
33+
BUSY_WAITING {
34+
@Override
35+
LongShortTreeMap createMap(ConcurrentLongShortTreeMapBuilder builder) {
36+
return new ConcurrentBusyWaitingLongShortTreeMap(
37+
builder.defaultValue);
38+
}
39+
},
40+
BLOCKING {
41+
@Override
42+
LongShortTreeMap createMap(ConcurrentLongShortTreeMapBuilder builder) {
43+
return new ConcurrentLongShortTreeMap(
44+
builder.defaultValue);
45+
}
46+
};
47+
48+
abstract LongShortTreeMap createMap(ConcurrentLongShortTreeMapBuilder builder);
49+
}
50+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.trivago.fastutilconcurrentwrapper;
2+
3+
import it.unimi.dsi.fastutil.longs.Long2ShortFunction;
4+
5+
import java.util.function.BiFunction;
6+
7+
public interface LongShortMap extends PrimitiveLongKeyMap {
8+
9+
short DEFAULT_VALUE = 0;
10+
11+
/**
12+
* @param key key to get
13+
* @return configured LongShortMap.getDefaultValue(), if the key is not present
14+
*/
15+
short get(long key);
16+
17+
short put(long key, short value);
18+
19+
short getDefaultValue();
20+
21+
short remove(long key);
22+
23+
boolean remove(long key, short value);
24+
25+
short computeIfAbsent(long key, Long2ShortFunction mappingFunction);
26+
27+
short computeIfPresent(long key, BiFunction<Long, Short, Short> mappingFunction);
28+
}
29+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.trivago.fastutilconcurrentwrapper;
2+
3+
import it.unimi.dsi.fastutil.longs.Long2ShortMap;
4+
5+
/**
6+
* A sorted {@link LongShortMap} that additionally supports a {@code floorEntry()} operation.
7+
*/
8+
public interface LongShortTreeMap extends LongShortMap {
9+
10+
// floorEntry() requires all keys to reside in a single bucket to return
11+
// correct results, so the bucket count is fixed at 1.
12+
// ToDo: Identify a bucketing methodology that works for TreeMap
13+
public static final int BUCKETS = 1;
14+
15+
/**
16+
* Returns the entry with the greatest key less than or equal to the given key,
17+
* or {@code null} if no such key exists.
18+
*
19+
* @param key the key to search for
20+
* @return an entry with the greatest key ≤ {@code key}, or {@code null}
21+
*/
22+
Long2ShortMap.Entry floorEntry(long key);
23+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.trivago.fastutilconcurrentwrapper.impl.longs;
2+
3+
import it.unimi.dsi.fastutil.longs.Long2ShortAVLTreeMap;
4+
import it.unimi.dsi.fastutil.longs.Long2ShortMap;
5+
import it.unimi.dsi.fastutil.longs.Long2ShortSortedMap;
6+
7+
/**
8+
* Extends {@link Long2ShortAVLTreeMap} with a {@code floorEntry()} method.
9+
*
10+
* <p>Fastutil's sorted maps do not provide NavigableMap-style floor/ceiling
11+
* operations. This class implements {@code floorEntry()} using the
12+
* {@link Long2ShortSortedMap#headMap(long)} workaround:
13+
*
14+
* <pre>
15+
* headMap(key + 1) → view of all keys &lt; (key + 1), i.e., keys ≤ key
16+
* lastLongKey() → largest key in that view = floor(key)
17+
* </pre>
18+
*
19+
* <p>The {@code headMap()} call returns a <em>view</em>, not a copy, so memory
20+
* overhead is O(1). The additional method-call frame adds negligible latency
21+
* (~1 ns) compared to the O(log n) tree traversal (~100–500 ns at 10M entries).
22+
*
23+
* <p>This wrapper lives in this project's implementation package and provides
24+
* floor-entry behavior without relying on fastutil package-private internals.
25+
*
26+
* @see Long2ShortAVLTreeMap
27+
* @see Long2ShortSortedMap#headMap(long)
28+
*/
29+
public class FloorableLongShortAVLTreeMap extends Long2ShortAVLTreeMap {
30+
31+
private static final long serialVersionUID = 1L;
32+
33+
/**
34+
* Returns the entry with the greatest key less than or equal to the given key,
35+
* or {@code null} if no such key exists.
36+
*
37+
* <p>Implementation uses {@code headMap(key + 1).lastLongKey()} to find the
38+
* floor key, then constructs an entry with that key and its associated value.
39+
*
40+
* <p><b>Edge cases:</b>
41+
* <ul>
42+
* <li>If {@code key} equals {@link Long#MAX_VALUE}, {@code key + 1} overflows
43+
* to {@link Long#MIN_VALUE}. In this case we fall back to checking the
44+
* entire map's last key.</li>
45+
* <li>If the map is empty or all keys are greater than {@code key}, returns
46+
* {@code null}.</li>
47+
* </ul>
48+
*
49+
* @param key the key to search for
50+
* @return an entry with the greatest key ≤ {@code key}, or {@code null}
51+
*/
52+
public Long2ShortMap.Entry floorEntry(long key) {
53+
if (isEmpty()) {
54+
return null;
55+
}
56+
57+
// Handle Long.MAX_VALUE overflow: key + 1 wraps to Long.MIN_VALUE
58+
if (key == Long.MAX_VALUE) {
59+
// If key is MAX_VALUE, floor is the map's last key (if it exists)
60+
long lastKey = lastLongKey();
61+
return new BasicEntry(lastKey, get(lastKey));
62+
}
63+
64+
// headMap(toKey) returns keys strictly < toKey
65+
// So headMap(key + 1) returns keys ≤ key
66+
Long2ShortSortedMap head = headMap(key + 1);
67+
68+
if (head.isEmpty()) {
69+
return null;
70+
}
71+
72+
long floorKey = head.lastLongKey();
73+
return new BasicEntry(floorKey, get(floorKey));
74+
}
75+
}

0 commit comments

Comments
 (0)