Data structures
Data structures are data organization, management, and storage formats that are usually chosen for efficient access to data. More precisely, a data structure is a collection of data values, the relationships among them, and the functions or operations that can be applied to the data.
This page lists a handy collection of data structures implemented in Tact for your day-to-day needs and beyond.
All of the data structures listed here are made using the built-in map<K, V>
type. For the description and basic usage of maps see the dedicated page in the Book.
Array
An array is a data structure consisting of a continuous block of memory, which represents a collection of elements of same memory size, each identified by at least one array key or index.
Following example emulates an array using a map<Int, V>
wrapped in a Struct, where V
can be any of the allowed value types of the map:
import "@stdlib/deploy"; // for Deployable trait
struct Array { m: map<Int as uint16, Int>; // array of Int values as a map of Ints to Ints, // with serialization of its keys to uint16 to save space length: Int = 0; // length of the array, defaults to 0}
// Compile-time constant upper bound for our map representing an array.const MaxArraySize: Int = 5_000; // 5,000 entries max, to stay reasonably far from limits
// Extension mutation function for adding new entries to the end of the arrayextends mutates fun append(self: Array, item: Int) { require(self.length + 1 <= MaxArraySize, "No space in the array left for new items!");
self.m.set(self.length, item); // set the entry (key-value pair) self.length += 1; // increase the length field}
// Extension mutation function for inserting new entries at the given indexextends mutates fun insert(self: Array, item: Int, idx: Int) { require(self.length + 1 <= MaxArraySize, "No space in the array left for new items!"); require(idx >= 0, "Index of the item cannot be negative!"); require(idx < self.length, "Index is out of array bounds!");
// Move all items from idx to the right let i: Int = self.length; // not a typo, as we need to start from the non-existing place while (i > idx) { // Note, that we use !! operator as we know for sure that the value would be there self.m.set(i, self.m.get(i - 1)!!); i -= 1; }
// And put the new item in self.m.set(idx, item); // set the entry (key-value pair) self.length += 1; // increase the length field}
// Extension function for getting the value at the given indexextends fun getIdx(self: Array, idx: Int): Int { require(self.length > 0, "No items in the array!"); require(idx >= 0, "Index of the item cannot be negative!"); require(idx < self.length, "Index is out of array bounds!");
// Note, that we use !! operator as we know for sure that the value would be there return self.m.get(idx)!!;}
// Extension function for returning the last valueextends fun getLast(self: Array): Int { require(self.length > 0, "No items in the array!");
// Note, that we use !! operator as we know for sure that the value would be there return self.m.get(self.length - 1)!!;}
// Extension mutation function for deleting and entry at the given index and returning its valueextends mutates fun deleteIdx(self: Array, idx: Int): Int { require(self.length > 0, "No items in the array to delete!"); require(idx >= 0, "Index of the item cannot be negative!"); require(idx < self.length, "Index is out of array bounds!");
// Remember the value, which is going to be deleted let memorized: Int = self.m.get(idx)!!;
// Move all items from idx and including to the left let i: Int = idx; while (i + 1 < self.length) { // Note, that we use !! operator as we know for sure that the value would be there self.m.set(i, self.m.get(i + 1)!!); i += 1; }
self.m.set(self.length - 1, null); // delete the last entry self.length -= 1; // decrease the length field
return memorized;}
// Extension mutation function for deleting the last entry and returning its valueextends fun deleteLast(self: Array): Int { require(self.length > 0, "No items in the array!");
// Note, that we use !! operator as we know for sure that the value would be there let lastItem: Int = self.m.get(self.length - 1)!!; self.m.set(self.length - 1, null); // delete the entry self.length -= 1; // decrease the length field
return lastItem;}
// Extension function for deleting all items in the Arrayextends mutates fun deleteAll(self: Array) { self.m = emptyMap(); self.length = 0;}
// Global static function for creating an empty Arrayfun emptyArray(): Array { return Array{m: emptyMap(), length: 0}; // length defaults to 0}
// Contract, with emulating an Array using the mapcontract MapAsArray with Deployable { // Persistent state variables array: Array;
// Constructor (initialization) function of the contract init() { self.array = emptyArray(); }
// Internal message receiver, which responds to a String message "append" receive("append") { // Add a new item self.array.append(42); }
// Internal message receiver, which responds to a String message "delete_5h" receive("delete_5th") { // Remove the 5th item if it exists and reply back with its value, or raise an error self.reply(self.array.deleteIdx(4).toCoinsString().asComment()); // index offset 0 + 4 gives the 5th item }
// Internal message receiver, which responds to a String message "del_last" receive("del_last") { // Remove the last item and reply back with its value, or raise an error self.reply(self.array.deleteLast().toCoinsString().asComment()); }
// Internal message receiver, which responds to a String message "get_last" receive("get_last") { // Reply back with the latest item in the array if it exists, or raise an error self.reply(self.array.getLast().toCoinsString().asComment()); }
// Internal message receiver, which responds to a String message "delete_all" receive("delete_all") { self.array.deleteAll(); }}
Stack
A stack is a data structure consisting of a collection of elements with two main operations:
- push, which adds an element to the end of the collection
- pop, which removes the most recently added element
Following example emulates a stack using a map<Int, V>
wrapped in a Struct, where V
can be any of the allowed value types of the map:
import "@stdlib/deploy"; // for Deployable trait
struct Stack { m: map<Int as uint16, Int>; // stack of Int values as a map of Ints to Ints, // with serialization of its keys to uint16 to save space length: Int = 0; // length of the stack, defaults to 0}
// Compile-time constant upper bound for our map representing an stack.const MaxStackSize: Int = 5_000; // 5,000 entries max, to stay reasonably far from limits
// Extension mutation function for adding new entries to the end of the stackextends mutates fun push(self: Stack, item: Int) { require(self.length + 1 <= MaxStackSize, "No space in the stack left for new items!");
self.m.set(self.length, item); // set the entry (key-value pair) self.length += 1; // increase the length field}
// Extension mutation function for deleting the last entry and returning its valueextends mutates fun pop(self: Stack): Int { require(self.length > 0, "No items in the stack to delete!");
// Note, that we use !! operator as we know for sure that the value would be there let lastItem: Int = self.m.get(self.length - 1)!!; self.m.set(self.length - 1, null); // delete the entry self.length -= 1; // decrease the length field
return lastItem;}
// Extension function for returning the last valueextends fun peek(self: Stack): Int { require(self.length > 0, "No items in the stack!");
// Note, that we use !! operator as we know for sure that the value would be there return self.m.get(self.length - 1)!!;}
// Extension function for deleting all items in the Stackextends mutates fun deleteAll(self: Stack) { self.m = emptyMap(); self.length = 0;}
// Global static function for creating an empty Stackfun emptyStack(): Stack { return Stack{m: emptyMap(), length: 0}; // length defaults to 0}
contract MapAsStack with Deployable { // Persistent state variables stack: Stack; // our stack, which uses the map
// Constructor (initialization) function of the contract init() { self.stack = emptyStack(); }
// Internal message receiver, which responds to a String message "push" receive("push") { // Add a new item self.stack.push(42); }
// Internal message receiver, which responds to a String message "pop" receive("pop") { // Remove the last item and reply with it self.reply(self.stack.pop().toCoinsString().asComment()); }
// Internal message receiver, which responds to a String message "peek" receive("peek") { // Reply back with the latest item in the map if it exists, or raise an error self.reply(self.stack.peek().toCoinsString().asComment()); }
// Internal message receiver, which responds to a String message "delete_all" receive("delete_all") { self.stack.deleteAll(); }
// Getter function for obtaining the stack get fun stack(): map<Int as uint16, Int> { return self.stack.m; }
// Getter function for obtaining the current length of the stack get fun length(): Int { return self.stack.length; }}
Circular buffer
A circular buffer (circular queue, cyclic buffer or ring buffer) is a data structure, which uses a single, fixed-size buffer as it were connected end-to-end.
Following example emulates a circular buffer using a map<Int, V>
wrapped in a Struct, where V
can be any of the allowed value types of the map:
import "@stdlib/deploy"; // for Deployable trait
struct CircularBuffer { m: map<Int as uint8, Int>; // circular buffer of Int values as a map of Ints to Ints, // with serialization of its keys to uint8 to save space length: Int = 0; // length of the circular buffer, defaults to 0 start: Int = 0; // current index into the circular buffer, defaults to 0}
// Compile-time constant upper bound for our map representing a circular buffer.const MaxCircularBufferSize: Int = 5;
// Extension mutation function for putting new items to the circular bufferextends mutates fun put(self: CircularBuffer, item: Int) { if (self.length < MaxCircularBufferSize) { self.m.set(self.length, item); // store the item self.length += 1; // increase the length field } else { self.m.set(self.start, item); // store the item, overriding previous entry self.start = (self.start + 1) % MaxCircularBufferSize; // update starting position }}
// Extension mutation function for getting an item from the circular bufferextends mutates fun getIdx(self: CircularBuffer, idx: Int): Int { require(self.length > 0, "No items in the circular buffer!"); require(idx >= 0, "Index of the item cannot be negative!");
if (self.length < MaxCircularBufferSize) { // Note, that we use !! operator as we know for sure that the value would be there return self.m.get(idx % self.length)!!; }
// Return the value rotating around the circular buffer, also guaranteed to be there return self.m.get((self.start + idx) % MaxCircularBufferSize)!!;}
// Extension function for iterating over all items in the circular buffer and dumping them to the consoleextends fun printAll(self: CircularBuffer) { let i: Int = self.start; repeat (self.length) { dump(self.m.get(i)!!); // !! tells the compiler this can't be null i = (i + 1) % MaxCircularBufferSize; }}
// Extension function for deleting all items in the CircularBufferextends mutates fun deleteAll(self: CircularBuffer) { self.m = emptyMap(); self.length = 0; self.start = 0;}
// Global static function for creating an empty CircularBufferfun emptyCircularBuffer(): CircularBuffer { return CircularBuffer{m: emptyMap(), length: 0, start: 0}; // length and start default to 0}
// This contract records the last 5 timestamps of when "timer" message was receivedcontract MapAsCircularBuffer with Deployable { // Persistent state variables cBuf: CircularBuffer; // our circular buffer, which uses a map
// Constructor (initialization) function of the contract init() { self.cBuf = emptyCircularBuffer(); }
// Internal message receiver, which responds to a String message "timer" // and records the timestamp when it receives such message receive("timer") { let timestamp: Int = now(); self.cBuf.put(timestamp); }
// Internal message receiver, which responds to a String message "get_first" // and replies with the first item of the circular buffer receive("get_first") { self.reply(self.cBuf.getIdx(0).toCoinsString().asComment()); }
// Internal message receiver, which responds to a String message "print_all" receive("print_all") { self.cBuf.printAll(); }
// Internal message receiver, which responds to a String message "delete_all" receive("delete_all") { self.cBuf.deleteAll(); }}