有时Solidity语言本身的数据结构无法很好地满足开发需求,此时我们需要实现相关库。本文介绍一个双向链表的库合约,在其它合约中引入即可使用。
有时Solidity语言本身的数据结构无法很好地满足开发需求,此时我们需要实现相关库。下面是一个双向链表的库合约,在其它合约中引入即可使用。
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.8.11;
/**
* @title Maintains a doubly linked list keyed by bytes32.
* @dev Following the `next` pointers will lead you to the head, rather than the tail.
*/
library LinkedList {
// 链表节点
struct Element {
bytes32 previousKey;
bytes32 nextKey;
bool exists;
}
// 整条链的信息
struct List {
bytes32 head;
bytes32 tail;
uint256 numElements;
mapping(bytes32 => Element) elements;
}
/**
* @notice Inserts an element into a doubly linked list.
* @param list A storage pointer to the underlying list.
* @param key The key of the element to insert.
* @param previousKey 在previousKey键所对应的节点之前插入
* @param nextKey 在nextKey键所对应的节点之后插入
*/
function insert(List storage list, bytes32 key, bytes32 previousKey, bytes32 nextKey) internal {
require(key != bytes32(0), "Key must be defined");
require(!contains(list, key), "Can't insert an existing element");
require(
previousKey != key && nextKey != key,
"Key cannot be the same as previousKey or nextKey"
);
Element storage element = list.elements[key];
element.exists = true;
if (list.numElements == 0) { // 首个节点
list.tail = key;
list.head = key;
} else {
require( // 不能全为0 (因为只有插入首个节点时,才是全为0)
previousKey != bytes32(0) || nextKey != bytes32(0),
"Either previousKey or nextKey must be defined"
);
element.previousKey = previousKey;
element.nextKey = nextKey;
if (previousKey != bytes32(0)) { // 等于0时表示插入末尾
require(
contains(list, previousKey),
"If previousKey is defined, it must exist in the list"
);
Element storage previousElement = list.elements[previousKey];
// 检查previousKey和nextKey一开始是不是相邻的
require(previousElement.nextKey == nextKey, "previousKey must be adjacent to nextKey");
previousElement.nextKey = key;
} else {
list.tail = key;
}
if (nextKey != bytes32(0)) {
require(contains(list, nextKey), "If nextKey is defined, it must exist in the list");
Element storage nextElement = list.elements[nextKey];
require(nextElement.previousKey == previousKey, "previousKey must be adjacent to nextKey");
nextElement.previousKey = key;
} else {
list.head = key;
}
}
list.numElements = list.numElements + 1;
}
/**
* @notice 在双向链表的节点插入一个元素
* @param list A storage pointer to the underlying list.
* @param key The key of the element to insert.
*/
function push(List storage list, bytes32 key) internal {
insert(list, key, bytes32(0), list.tail);
}
/**
* @notice Removes an element from the doubly linked list.
* @param list A storage pointer to the underlying list.
* @param key The key of the element to remove.
*/
function remove(List storage list, bytes32 key) internal {
Element storage element = list.elements[key];
require(key != bytes32(0) && contains(list, key), "key not in list");
if (element.previousKey != bytes32(0)) {
Element storage previousElement = list.elements[element.previousKey];
previousElement.nextKey = element.nextKey;
} else {
list.tail = element.nextKey;
}
if (element.nextKey != bytes32(0)) {
Element storage nextElement = list.elements[element.nextKey];
nextElement.previousKey = element.previousKey;
} else {
list.head = element.previousKey;
}
delete list.elements[key];
list.numElements = list.numElements - 1;
}
/**
* @notice Updates an element in the list.
* @param list A storage pointer to the underlying list.
* @param key The element key.
* @param previousKey The key of the element that comes before the updated element.
* @param nextKey The key of the element that comes after the updated element.
*/
function update(List storage list, bytes32 key, bytes32 previousKey, bytes32 nextKey) internal {
require(
key != bytes32(0) && key != previousKey && key != nextKey && contains(list, key),
"key on in list"
);
remove(list, key);
insert(list, key, previousKey, nextKey);
}
/**
* @notice Returns whether or not a particular key is present in the sorted list.
* @param list A storage pointer to the underlying list.
* @param key The element key.
* @return Whether or not the key is in the sorted list.
*/
function contains(List storage list, bytes32 key) internal view returns (bool) {
return list.elements[key].exists;
}
/**
* @notice Returns Element based on key.
* @param list A storage pointer to the underlying list.
* @param key The element key.
* @return Whether or not the key is in the sorted list.
*/
function get(List storage list, bytes32 key) internal view returns (Element memory) {
return list.elements[key];
}
/**
* @notice Returns the keys of the N elements at the head of the list.
* @param list A storage pointer to the underlying list.
* @param n The number of elements to return.
* @return The keys of the N elements at the head of the list.
* @dev Reverts if n is greater than the number of elements in the list.
*/
function headN(List storage list, uint256 n) internal view returns (bytes32[] memory) {
require(n <= list.numElements, "not enough elements");
bytes32[] memory keys = new bytes32[](n);
bytes32 key = list.head;
for (uint256 i = 0; i < n; i = i + 1) {
keys[i] = key;
key = list.elements[key].previousKey;
}
return keys;
}
/**
* @notice Gets all element keys from the doubly linked list.
* @param list A storage pointer to the underlying list.
* @return All element keys from head to tail.
*/
function getKeys(List storage list) internal view returns (bytes32[] memory) {
return headN(list, list.numElements);
}
}
上面库合约中只是进行了节点的链接,并不能存放值。我们应该这样使用:
import "./LinkedList.sol"; // 引入
contract Test {
using LinkedList for LinkedList.List;
struct List {
LinkedList.List list;
mapping(bytes32 => uint256) values;
}
function insert(
List storage list,
bytes32 key,
uint256 value,
bytes32 previousKey,
bytes32 nextKey
) {
// 关键部分
list.list.insert(key, previousKey, nextKey);
list.values[key] = value;
}
}
<br>
区块链\&web3开发技术交流群(纯净版)欢迎加入交流:<https://t.me/+PGwDonY3f2o3NDg1>
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!