diff --git a/docs/types/mapping-types.rst b/docs/types/mapping-types.rst index a7d607786..4ce51faf2 100644 --- a/docs/types/mapping-types.rst +++ b/docs/types/mapping-types.rst @@ -53,7 +53,100 @@ each ``_KeyType``, recursively. For example with a mapping: } } +.. index:: !iterable mappings +.. _iterable-mappings: -.. note:: - Mappings are not iterable, but it is possible to implement a data structure - on top of them. For an example, see `iterable mapping `_. +Iterable Mappings +----------------- + +Mappings are not iterable, but it is possible to implement a data structure on +top of them and iterate over that. For example, the code below implements an +``IterableMapping`` library that the ``User`` contract then adds data too, and +the ``sum`` function iterates over to sum all the values. + +:: + + pragma solidity >=0.4.0 <0.7.0; + + library IterableMapping { + + struct itmap { + mapping(uint => IndexValue) data; + KeyFlag[] keys; + uint size; + } + + struct IndexValue { uint keyIndex; uint value; } + struct KeyFlag { uint key; bool deleted; } + + function insert(itmap storage self, uint key, uint value) internal returns (bool replaced) { + uint keyIndex = self.data[key].keyIndex; + self.data[key].value = value; + if (keyIndex > 0) + return true; + else { + keyIndex = self.keys.length++; + self.data[key].keyIndex = keyIndex + 1; + self.keys[keyIndex].key = key; + self.size++; + return false; + } + } + + function remove(itmap storage self, uint key) internal returns (bool success) { + uint keyIndex = self.data[key].keyIndex; + if (keyIndex == 0) + return false; + delete self.data[key]; + self.keys[keyIndex - 1].deleted = true; + self.size --; + } + + function contains(itmap storage self, uint key) internal view returns (bool) { + return self.data[key].keyIndex > 0; + } + + function iterate_start(itmap storage self) internal view returns (uint keyIndex) { + return iterate_next(self, uint(-1)); + } + + function iterate_valid(itmap storage self, uint keyIndex) internal view returns (bool) { + return keyIndex < self.keys.length; + } + + function iterate_next(itmap storage self, uint keyIndex) internal view returns (uint r_keyIndex) { + keyIndex++; + while (keyIndex < self.keys.length && self.keys[keyIndex].deleted) + keyIndex++; + return keyIndex; + } + + function iterate_get(itmap storage self, uint keyIndex) internal view returns (uint key, uint value) { + key = self.keys[keyIndex].key; + value = self.data[key].value; + } + } + + // How to use it + contract User { + // Just a struct holding our data. + IterableMapping.itmap data; + + // Insert something + function insert(uint k, uint v) public returns (uint size) { + // Actually calls itmap_impl.insert, auto-supplying the first parameter for us. + IterableMapping.insert(data, k, v); + // We can still access members of the struct - but we should take care not to mess with them. + return data.size; + } + + // Computes the sum of all stored data. + function sum() public view returns (uint s) { + for (uint i = IterableMapping.iterate_start(data); + IterableMapping.iterate_valid(data, i); + i = IterableMapping.iterate_next(data, i)) { + (, uint value) = IterableMapping.iterate_get(data, i); + s += value; + } + } + } \ No newline at end of file