Solidity는 기본적으로 배열(array)과 mapping(map)은 제공하지만,
✅ linked list 자료구조는 제공하지 않습니다.
→ 따라서 직접 구현해야 합니다.

🧩 왜 Solidity는 Linked List를 제공하지 않을까?
• EVM의 storage 구조가 랜덤 접근 기반이기 때문에
→ 연속된 메모리보다 keccak256(slot) 방식의 mapping 기반 인덱싱이 더 유리합니다.
• Solidity의 gas 비용 구조상, linked list는 효율적이지 않은 경우가 많습니다.
• 그래도 👇 순차적 삽입/삭제가 필요한 경우엔 여전히 유용합니다.

✅ 예제1: 단일 연결 리스트 (Singly Linked List)

아래는 Solidity로 구현한 간단한 Singly Linked List 예제입니다.

🔧 Solidity 코드 (0.8.x 이상)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract LinkedList {
    struct Node {
        uint data;
        uint next;
        bool exists;
    }

    mapping(uint => Node) public nodes;
    uint public head;
    uint public length;
    uint private idCounter;

    constructor() {
        head = 0; // 초기에는 0이 head (0은 dummy head 역할)
        nodes[head] = Node({ data: 0, next: 0, exists: true });
        idCounter = 1;
    }

    /// @notice 리스트 끝에 추가
    function append(uint _data) external {
        uint current = head;
        while (nodes[current].next != 0) {
            current = nodes[current].next;
        }

        uint newId = idCounter++;
        nodes[newId] = Node({ data: _data, next: 0, exists: true });
        nodes[current].next = newId;
        length++;
    }

    /// @notice 특정 노드 ID 기준으로 삭제 (head는 삭제 불가)
    function remove(uint id) external {
        require(id != head, "cannot remove dummy head");
        require(nodes[id].exists, "node not exists");

        uint prev = head;
        while (nodes[prev].next != id && nodes[prev].next != 0) {
            prev = nodes[prev].next;
        }
        require(nodes[prev].next == id, "node not found");

        nodes[prev].next = nodes[id].next;
        delete nodes[id];
        length--;
    }

    /// @notice 전체 리스트 조회
    function getAll() external view returns (uint[] memory) {
        uint[] memory result = new uint[](length);
        uint current = nodes[head].next;
        uint i = 0;
        while (current != 0) {
            result[i++] = nodes[current].data;
            current = nodes[current].next;
        }
        return result;
    }
}

📌 핵심 설명

요소 설명
mapping(uint => Node) 노드를 ID로 저장 (linked list의 key 역할)
Node 구조체 data, next, exists 필드 포함
head 항상 0번 노드 (dummy head)
idCounter 노드마다 고유한 ID 부여

✅ 사용 예시
1. append(100) → 노드 추가
2. getAll() → [100]
3. append(200) → [100, 200]
4. remove(1) → [200] (ID 1번 노드 삭제)

🧠 추가 팁
• 양방향 리스트도 구현 가능: prev 필드를 추가하면 됩니다
• LinkedListLib로 만들면 재사용 가능
• 메모리 구조와 storage 구조를 구분해서 설계하세요

✅ 예제2: 단일/양방향/Enumable(반복가능한) 연결 리스트

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
 * ✅ Singly Linked List
 */
contract SinglyLinkedList {
    struct Node {
        uint data;
        uint next;
        bool exists;
    }

    mapping(uint => Node) public nodes;
    uint public head;
    uint public length;
    uint private idCounter;

    constructor() {
        head = 0; // dummy head
        nodes[head] = Node({ data: 0, next: 0, exists: true });
        idCounter = 1;
    }

    function append(uint _data) external {
        uint current = head;
        while (nodes[current].next != 0) {
            current = nodes[current].next;
        }

        uint newId = idCounter++;
        nodes[newId] = Node({ data: _data, next: 0, exists: true });
        nodes[current].next = newId;
        length++;
    }

    function remove(uint id) external {
        require(id != head, "cannot remove head");
        require(nodes[id].exists, "not found");

        uint prev = head;
        while (nodes[prev].next != id && nodes[prev].next != 0) {
            prev = nodes[prev].next;
        }
        require(nodes[prev].next == id, "not found");

        nodes[prev].next = nodes[id].next;
        delete nodes[id];
        length--;
    }

    function getAll() external view returns (uint[] memory) {
        uint[] memory result = new uint[](length);
        uint current = nodes[head].next;
        uint i = 0;
        while (current != 0) {
            result[i++] = nodes[current].data;
            current = nodes[current].next;
        }
        return result;
    }
}

/**
 * ✅ Doubly Linked List
 */
contract DoublyLinkedList {
    struct Node {
        uint data;
        uint next;
        uint prev;
        bool exists;
    }

    mapping(uint => Node) public nodes;
    uint public head;
    uint public tail;
    uint public length;
    uint private idCounter;

    constructor() {
        head = 0;
        tail = 0;
        idCounter = 1;
    }

    function append(uint _data) external {
        uint newId = idCounter++;
        nodes[newId] = Node({ data: _data, next: 0, prev: tail, exists: true });

        if (length == 0) {
            head = newId;
        } else {
            nodes[tail].next = newId;
        }

        tail = newId;
        length++;
    }

    function remove(uint id) external {
        require(nodes[id].exists, "not found");

        if (nodes[id].prev != 0) {
            nodes[nodes[id].prev].next = nodes[id].next;
        } else {
            head = nodes[id].next;
        }

        if (nodes[id].next != 0) {
            nodes[nodes[id].next].prev = nodes[id].prev;
        } else {
            tail = nodes[id].prev;
        }

        delete nodes[id];
        length--;
    }

    function getAllForward() external view returns (uint[] memory) {
        uint[] memory result = new uint[](length);
        uint current = head;
        uint i = 0;
        while (current != 0) {
            result[i++] = nodes[current].data;
            current = nodes[current].next;
        }
        return result;
    }

    function getAllBackward() external view returns (uint[] memory) {
        uint[] memory result = new uint[](length);
        uint current = tail;
        uint i = 0;
        while (current != 0) {
            result[i++] = nodes[current].data;
            current = nodes[current].prev;
        }
        return result;
    }
}

/**
 * ✅ Enumerable Iterable Linked List (Mapping + Array)
 */
contract EnumerableList {
    struct Item {
        uint data;
        bool exists;
    }

    mapping(uint => Item) public items;
    uint[] public keys;

    function add(uint key, uint value) external {
        require(!items[key].exists, "already exists");
        items[key] = Item({ data: value, exists: true });
        keys.push(key);
    }

    function remove(uint key) external {
        require(items[key].exists, "not exists");
        delete items[key];
        for (uint i = 0; i < keys.length; i++) {
            if (keys[i] == key) {
                keys[i] = keys[keys.length - 1];
                keys.pop();
                break;
            }
        }
    }

    function getAll() external view returns (uint[] memory values) {
        uint count = 0;
        for (uint i = 0; i < keys.length; i++) {
            if (items[keys[i]].exists) count++;
        }

        values = new uint[](count);
        uint j = 0;
        for (uint i = 0; i < keys.length; i++) {
            if (items[keys[i]].exists) {
                values[j++] = items[keys[i]].data;
            }
        }
    }
}

'Solidity' 카테고리의 다른 글

Solidity : 최대값 검색을 위한 Heap Tree 구현  (0) 2025.03.26
연결 리스트 구현 개선  (0) 2025.03.26
인터페이스 + 상속 조합  (0) 2025.03.25
Solidity 에서의 상속  (0) 2025.03.25
solidity 의 hash table  (0) 2025.03.25

+ Recent posts