IntBuckets
A one-to-many map for Pine Script. Each integer key owns a group of integers, such as the object slots in a price cell, packed into shared contiguous storage.
One key, many values
A Pine map stores one value per key, and does not allow an array as that value. Giving a key several values usually means a small wrapper type that holds an array, with one wrapper per key. IntBuckets provides the relationship directly: a price cell can own several zone slots, a graph node several neighbours, a category several record IDs. One query visits only that group.
It suits a batch build followed by repeated traversal. In a 64-key traversal workload it averaged 19% faster than the wrapper approach across four runs in our tests. When individual groups change constantly, the wrapper approach is more convenient.
Build from pairs
buildFromPairs() pairs each key with the value at the same array position. A repeated key adds another member to its group instead of replacing the previous one.
var op.IntBuckets cellMembers = op.intBuckets()
var array<int> matches = array.new<int>()
if barstate.isfirst
// Six (cell, object slot) pairs. Cell 7 appears three times.
array<int> cellKeys = array.from(7, 2, 7, 5, 2, 7)
array<int> objectSlots = array.from(101, 205, 412, 990, 777, 888)
cellMembers.buildFromPairs(cellKeys, objectSlots)
// Read cell 7's group from flat storage. matches is only demo output.
[start, count] = cellMembers.rangeByKey(7)
if count > 0
for position = start to start + count - 1
matches.push(cellMembers.valueAt(position))
// Output: matches contains [101, 412, 888], the object slots assigned to cell 7.Every build replaces all previous groups. The optional expectedQueries argument is passed to the key directory, an IntIndex, which chooses its lookup the same way a typed store does.
Read a group
rangeByKey(key)returns[start, count]: where the group begins in the flat value storage and how many values it holds. A missing key returns a count of 0.valueAt(start + offset)reads one value.bucketValueAt(slot, offset)does the same from a bucket slot, and returnsnaoutside the group.- Within a group, values keep the order in which their pairs were added.
find(key)returns the bucket slot, andrangeBySlot(),bucketSize()andkeyAt()work from it. Bucket slots follow the order in which each key first appeared.
for loop counts down when its end is below its start. For an empty group, for position = start to start + count - 1 would run backwards, so check count > 0 first, as the example does.Pairs that arrive one at a time
When the pairs are not in two arrays already, open a build explicitly:
// Pseudocode: zones is an array of the script's own Zone objects.
cellMembers.buildBegin()
for zone in zones
if zone.active
cellMembers.add(zone.cell, zone.slot)
cellMembers.buildFinish()addMany(keyArray, valueArray) adds a batch inside the same build. In strict mode, an add outside a build, a second buildBegin() while one is open, or a lookup before the first build stops the script. op.intBuckets(settings, strictLifecycle = false) makes the add open a build by itself instead.
How groups are stored
All groups share one flat value array, grouped by bucket slot. A boundary array with one more entry than there are buckets marks where each group starts, so group slot runs from offsets[slot] to offsets[slot + 1]. There is no per-key array and no wrapper object.
valuesView(),offsetsView()andkeysView()expose the three arrays read-only, valid until the next build orclear().- A build is O(pairs) plus the key directory build. A lookup is the directory's lookup plus O(1).
- Up to 100,000 pairs and 50,000 distinct keys.
Calls
| Call | What it does |
|---|---|
op.intBuckets(settings = na, strictLifecycle = true) | Creates an empty multimap. By default its directory uses automatic lookup. |
buckets.buildFromPairs(keyArray, valueArray, expectedQueries = na) | Builds every group from two arrays of the same length. |
buckets.buildBegin(), buckets.add(key, value), buckets.addMany(keyArray, valueArray), buckets.buildFinish(expectedQueries = na) | The explicit build. |
buckets.rangeByKey(key), buckets.rangeBySlot(slot) | Return [start, count] for one group. |
buckets.valueAt(flatIndex), buckets.bucketValueAt(slot, offset) | Read one value. |
buckets.find(key), buckets.contains(key), buckets.keyAt(slot) | Resolve a key to its bucket slot and back. |
buckets.bucketSize(slot), buckets.bucketCount(), buckets.pairCount(), buckets.isSealed() | Sizes and build state. |
buckets.valuesView(), buckets.offsetsView(), buckets.keysView() | Read-only views of the packed storage. |
buckets.clear() | Empties every group, keeping the allocations. |
Every signature is in the API Reference. Next: Buffers & Utilities.