# HashedMap - Chinese

Map是一种非常常用的数据结构。sCrypt 的标准库提供了一个实现了 `Map` 数据结构的库合约 `HashedMap`。概念上与java中的map，python中的字典类型类似，但在使用上有一些区别。

## HashedMap定义 <a href="#hashedmap_1" id="hashedmap_1"></a>

在 `HashedMap` 中， `key` 和 `value` 的类型没有限制，可以是整型、字节、布尔等基本数据类型，也可以是数组、结构体等复杂数据结构。定义 `HashedMap` 的语法如下：

1. 完整定义

```javascript
HashedMap<int, int> map = new HashedMap<int, int>(b'')
```

1. 右边简写定义

```javascript
HashedMap<int, int> map = new HashedMap(b'')
```

1. 使用 `auto` 关键字定义

```javascript
auto map = new HashedMap<int, int>(b'')
```

## 序列化 <a href="#id-1" id="id-1"></a>

`data()` 方法可以将 `HashedMap` 序列化成 `bytes` 字节。scryptlib 也提供了对应的 `toData(map)` 函数。用来在链外序列化 map。

下面是一个将 `HashedMap` 作为状态的有状态合约。

```javascript
contract StateMapTest {

    @state
    bytes _mpData;  //Save the serialized data of the map

    // Add key-value pairs to the map
    public function insert(MapEntry entry, SigHashPreimage preimage) {
        require(Tx.checkPreimage(preimage));
        HashedMap<int, int> map = new HashedMap(this._mpData);
        require(map.set(entry.key, entry.val, entry.keyIndex));
        require(this.passMap(map.data(), preimage));
    }

    // update key-value pairs in the map
    public function update(MapEntry entry, SigHashPreimage preimage) {
        require(Tx.checkPreimage(preimage));
        HashedMap<int, int> map = new HashedMap(this._mpData);
        require(map.set(entry.key, entry.val, entry.keyIndex));
        require(this.passMap(map.data(), preimage));
    }

    // delete key-value pairs in the map
    public function delete(int key, int keyIndex, SigHashPreimage preimage) {
        require(Tx.checkPreimage(preimage));
        HashedMap<int, int> map = new HashedMap(this._mpData);
        require(map.delete(key, keyIndex));
        // Serialize map, update state
        require(this.passMap(map.data(), preimage));
    }

    // update state _mpData, and build a output contains new state
    function passMap(bytes newData, SigHashPreimage preimage) : bool {
        this._mpData = newData;
        bytes outputScript = this.getStateScript();
        bytes output = Util.buildOutput(outputScript, Util.value(preimage));
        return (hash256(output) == Util.hashOutputs(preimage));
    }
}
```

## 添加元素 <a href="#id-2" id="id-2"></a>

`HashedMap` 合约提供了很多有用的方法，添加键值对(key-value)可以使用 `HashedMap` 合约的 `set(K key, V val, int keyIndex)` 方法。与其它语言的 `map` 不同的是添加键值需要传递 `keyIndex` 参数。该参数可以通过 `scryptlib` 提供的 `findKeyIndex` 函数在链下计算。首先我们需要在链外维护一个和链上保存同步的 `map`，并把要添加的元素先添加到该 `map` 中，然后使用 `findKeyIndex` 计算出 `keyIndex`。

### **scrypt**:

```typescript
public function insert(MapEntry entry, SigHashPreimage preimage) {
    require(Tx.checkPreimage(preimage));
    HashedMap<int, int> map = new HashedMap(this._mpData);
    require(map.set(entry.key, entry.val, entry.keyIndex));
    require(this.passMap(map.data(), preimage));
}
```

### **typescript**:

```javascript
let map = new Map<number, number>();
map.set(key, val);  //先把键值对添加到链外的 map

const tx = buildTx(map);
const preimage = getPreimage(tx, mapTest.lockingScript, inputSatoshis)
const result = mapTest.insert(new MapEntry({
    key: key,
    val: val,
    keyIndex: findKeyIndex(map, key) // 获取 `keyIndex` 参数
}), preimage).verify()
expect(result.success, result.error).to.be.true;

mapTest._mpData = toData(map)
```

如果把所有元素添加到添加到 `map` 之后，再开始计算 `keyIndex`，那需要先将 `map` 转换成有序的数组。 使用以下代码可以直接转换：

```typescript
const mapEntrys = Array.from(sortmap(map), ([key, val]) => ({ key, val }))
    .map((entry, index) => {
        ...entry,
        keyIndex: index
    })
```

## 更新元素 <a href="#id-3" id="id-3"></a>

更新元素和添加元素一样，都使用 `HashedMap` 合约的 `set(K key, V val, int keyIndex)` 方法。

## 删除元素 <a href="#id-4" id="id-4"></a>

使用 `HashedMap` 合约的 `delete(K key, int keyIndex)` 方法来删除元素。如果删除的元素不存在会返回失败。同样需要在链外使用 `findKeyIndex(map, key)` 函数来计算 `keyIndex`。

### **scrypt**:

```javascript
public function delete(int key, int keyIndex, SigHashPreimage preimage) {
    require(Tx.checkPreimage(preimage));
    HashedMap<int, int> map = new HashedMap(this._mpData);
    require(map.delete(key, keyIndex));
    require(this.passMap(map.data(), preimage));
}
```

### **typescript**:

```javascript
const keyIndex = findKeyIndex(map, key);  //从链外map删除之前，先计算出keyIndex，并保存
map.delete(key);

const tx = buildTx(map);
const preimage = getPreimage(tx, mapTest.lockingScript, inputSatoshis)

const result = mapTest.delete(key, keyIndex, preimage).verify()  // 调用合约的删除方法需要提供key, keyIndex
expect(result.success, result.error).to.be.true;

mapTest._mpData = toData(map)
```

## 存储模型 <a href="#id-5" id="id-5"></a>

`HashedMap` 只存储 `key` 和 `value` 的哈希。无法通过 `HashedMap` 的序列化 `data` 反序列出原始的键值对。这对于一些注重隐私性的应用场景很有帮助。

可以通过从外部传入原始键值对，并调用 `canGet(key, val, keyIndex)` 接口来检查是否包含某个键值对。

```javascript
require(map.canGet(key, val, keyIndex));
```

或者通过 `has(key, keyIndex)` 接口检查是否包含某个键。

```javascript
require(map.has(key, keyIndex));
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://radiantblockchain.gitbook.io/wiki/programming/scryptlib/hashedmap/hashedmap-chinese.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
