Lookup

Typed Stores

Keyed lookup for Pine Script without choosing the data structure yourself. A store maps integer keys to one value each, and picks the fastest lookup for the keys it was given.

Updated Publisher Alien_AlgorithmsPine Script v6

One value per key

There is one store per value type: op.intIntStore(), op.intFloatStore(), op.intBoolStore(), op.intStringStore() and op.intColorStore(). build() pairs the entries at matching positions of a key array and a value array:

Build once, query by keypine
var op.IntFloatStore scores = op.intFloatStore()

if barstate.isfirst
    // Four entries are shown for readability; both arrays may be much larger.
    scores.build(
      array.from(410, 411, 412, 413),
      array.from(0.80, 0.30, 0.95, 0.50))

float selected = scores.get(412)

// Output: integer key 412 resolves to float value 0.95.

Lookup is one-way: get(412) returns 0.95, but values may repeat, so there is no general reverse lookup. Batch-build IDs to scores, states or metadata, then query them without committing to a representation.

Build before the first query
A store that was never built, or was emptied with clear(), has no lookup yet. In strict mode, the default, querying it stops the script with find() before buildFinish(). Building with empty arrays is fine and gives an empty store.

What automatic mode chooses

Every store owns an IntIndex. When the build completes, it looks at the keys and selects a lookup. The rules apply in this order:

KeysLookupCost per query
Consecutive and ascending, such as 410, 411, 412Direct arithmetic indexing, no table at allO(1)
A compact range: the span fits within twice the entry count and 16,384A dense lookup tableO(1)
Unordered, up to 50,000 keysA native mapO(1)
Ascending but sparseBinary search, or a map when the expected queries justify building oneO(log n), or O(1)

In our tests, direct integer addressing ran 21% faster than a map, and a map ran 91% faster than repeated linear search with 32 entries. Beyond Pine's 50,000-pair map limit, the store keeps working through binary search, up to 100,000 keys.

A linear scan is available for workloads that rebuild far more often than they query. Automatic mode only picks it when linearMaxEntries is configured, since it is 0 by default.

The expectedQueries hint

build(keys, values, expectedQueries) takes an optional third argument: how many lookups to expect before the next build. A map costs more to build than binary search, so for ascending sparse keys the store builds one only when the queries pay for it, at least 32 or a tenth of the entry count, whichever is larger. Leaving the hint out favours the cheaper build.

When future query volume is unknown, create the store with op.indexConfigDynamic(). It starts with binary search and promotes itself to a map once the counted queries reach the same threshold.

A store that adapts to its query volumepine
var op.IntStringStore names = op.intStringStore(op.indexConfigDynamic())

Misses and batch reads

  • get(key, fallback) returns the fallback on a miss: na by default, or false for a bool store.
  • When a stored value may equal the fallback, read(key) returns [found, value], and contains(key) tests the key alone.
  • getMany(queryKeys, output) resolves a whole batch with one dispatch, in query order, with the empty value marking each miss.
Several keys at oncepine
var op.IntFloatStore scores = op.intFloatStore()
var array<float> picked = array.new<float>()

if barstate.isfirst
    scores.build(
      array.from(410, 411, 412, 413),
      array.from(0.80, 0.30, 0.95, 0.50))

    scores.getMany(array.from(413, 999, 410), picked)

// Output: picked is [0.50, na, 0.80]. Key 999 is a miss.

Pass the same var output array every time; it is cleared and refilled instead of allocated.

Changing values

  • set(key, value) overwrites the value of a key that exists and returns true. It cannot add a key: the key set changes only through another build(), which replaces the whole contents.
  • Each store keeps a content revision. It advances on every build, on a set() that changes a value, and on a clear() that discarded something. Writing the same value again leaves it alone.
  • Pass revision() to watch.watchInt() or gate.dueWhenChanged() to rebuild something only when the store changed. See Revision & CadenceGate.
  • valuesView() is the stored values in slot order, as a read-only view valid until the next build.

Store, IntIndex or IntBuckets

You needUse
One primitive value per keyA typed store
Several payload fields per key, in arrays you ownIntIndex, which returns the slot for a key
Several integers per key, such as the objects in a price cellIntBuckets
Keys that appear and disappear one at a timeStablePool

Calls

Every store type has the same calls. Each constructor takes an optional IndexConfig.

CallWhat it does
op.intFloatStore(settings = na)Creates a store. Also intIntStore(), intBoolStore(), intStringStore() and intColorStore().
store.build(keys, values, expectedQueries = na)Replaces the contents and returns the lookup it chose. Both arrays must be the same length.
store.get(key, fallback = na)One value, or the fallback on a miss.
store.read(key)Returns [found, value].
store.contains(key)True when the key is present.
store.getMany(queryKeys, output = na)Values for a batch of keys, in query order.
store.set(key, value)Overwrites an existing key. True when it existed.
store.size(), store.revision(), store.valuesView(), store.clear()Entry count, content revision, borrowed values, and emptying.

Every signature is in the API Reference. Next: IntIndex.

OptiPine is a performance library for Pine Script. TradingView's memory, execution and collection limits still apply. © Alien_Algorithms.