「新・明解Pythonで学ぶアルゴリズムとデータ構造」で勉強日記#13

【出典】[「新・明解Pythonで学ぶアルゴリズムとデータ構造」](https://www.amazon.co.jp/%E6%96%B0%E3%83%BB%E6%98%8E%E8%A7%A3Python%E3%81%A7%E5%AD%A6%E3%81%B6%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0%E3%81%A8%E3%83%87%E3%83%BC%E3%82%BF%E6%A7%8B%E9%80%A0-%E6%96%B0%E3%83%BB%E6%98%8E%E8%A7%A3%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-%E6%9F%B4%E7%94%B0%E6%9C%9B%E6%B4%8B/dp/4815603197/ref=sr_1_2?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=1BG3JS7CKMZA0&keywords=Python%E3%81%A7%E5%AD%A6%E3%81%B6%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0%E3%81%A8%E3%83%87%E3%83%BC%E3%82%BF%E6%A7%8B%E9%80%A0&qid=1644336329&sprefix=python%E3%81%A7%E5%AD%A6%E3%81%B6%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0%E3%81%A8%E3%83%87%E3%83%BC%E3%82%BF%E6%A7%8B%E9%80%A0%2Caps%2C212&sr=8-2)
[前回の記事はこちら](https://qiita.com/kato_squalldeka/items/ff95e1234dadd81b7eec)

#8-4 循環・重連結リスト
循環・重連結リストというものについてです。
これは、前回とは違って、前に戻ったりする機能がある程度に覚えておきました。
また3章に戻って順番が来たら改めて思い出しながら勉強したいと思います。

```python:list8-5,double_list.py
#循環・重連結リスト

from __future__ import annotations
from typing import Any, Type

class Node:
    """循環・重連結リスト用ノードクラス"""
    
    def __init__(self, data: Any = None, prev: Node = None, next: Node = None) -> None:
        """初期化"""
        self.data = data #データ
        self.prev = prev or self #先行ポインタ
        self.next = next or self #後続ポインタ
        
class DoubleLinkedList:
    """循環・重連結リストクラス"""
    
    def __init__(self) -> None:
        """初期化"""
        self.head = self.current = Node() #ダミーノードを作成
        self.no = 0
        
    def __len__(self) -> int:
        """線形リスト上のノード数を返却する"""
        
    def is_empty(self) -> bool:
        """リストは空か"""
        return self.head.next is self.head
    
    
    def search(self, data: Any) -> Any:
        """dataと等価なノードを探索"""
        cnt = 0
        ptr = self.head.next #現在走査中のノード
        while ptr is not self.head:
            if data == ptr.data:
                self.current = ptr
                return cnt #探索成功
            cnt += 1
            ptr = ptr.next #後続ノードに着目
        return -1 #探索失敗
    
    def __contains__(self, data: Any) -> bool:
        """線形リストにデータは含まれているか"""
        return self.search(data) >= 0
    
    def print_current_node(self) -> None:
        """着目ノードを表示"""
        if self.is_empty():
            print('着目ノードはありません')
        else:
            print(self.current.data)
            
    def print(self) -> None:
        """全ノードを表示"""
        ptr = self.head.next #ダミーノードの後続ノード
        while ptr is not self.head:
            print(ptr.data)
            ptr = ptr.next
                
    def print_reverse(self) -> None:
        """全ノードを逆順に表示"""
        ptr = self.head.prev #ダミーノードの先行ノード
        while ptr is not self.head:
            print(ptr.data)
            ptr = ptr.prev
                
    def next(self) -> bool:
        """着目ノードを一つ後方に進める"""
        if self.is_empty() or self.current.next is self.head:
            return False #進めることはできなかった
        self.current = self.current.next
        return True
        
    def prev(self) -> bool:
        """着目ノードを一つ前方に進める"""
        if self.is_empty() or self.current.prev is self.head:
            return False #進めることはできなかった
        self.current = self.current.prev
        return True
    
    
    def add(self, data: Any) -> None:
        """着目ノードの直後にノードを挿入"""
        node = Node(data, self.current, self.current.next)
        self.current.next.prev = node
        self.current.next = node
        self.current = node
        self.no += 1
        
    def add_first(self, data: Any) -> None:
        """先頭にノードを挿入"""
        self.current = self.head #ダミーノードheadの直後に挿入
        self.add(data)
        
    def add_last(self, data: Any) -> None:
        """末尾にノードを挿入"""
        self.current = self.head.prev #末尾ノードhead.prevの直後に挿入
        self.add(data)
        
    def remove_current_node(self) -> None:
        """着目ノードを削除"""
        if not self.is_empty():
            self.current.prev.next = self.current.next
            self.current.next.prev = self.current.prev
            self.current = self.current.prev
            self.no -= 1
            if self.current is self.head:
                self.current = self.head.next
                
    def remove(self, p: Node) -> None:
        """ノードpを削除"""
        ptr = self.head.next
        
        while ptr is not self.head:
            if ptr is p: #pを見つけた
                self.current = p
                self.remove_current_node()
                break
            ptr = ptr.next
            
    def remove_first(self) -> None:
        """先頭ノードを削除"""
        self.current = self.head.next #先頭ノードhead.nextを削除
        self.remove_current_node()
        
    def remove_last(self) -> None:
        """先頭ノードを削除"""
        self.current = self.head.prev #末尾ノードhead.prevを削除
        self.remove_current_node()
        
    def clear(self) -> None:
        """全ノードを削除"""
        while not self.is_empty(): #空になるまで
            self.remove_first() #先頭ノードを削除
        self.no = 0
        
    
    def __iter__(self) -> DoubleLinkedListIterator:
        """イテレータを返却"""
        return DoubleLinkedListIterator(self.head)
    
    def __reversed__(self) -> DoubleLinkedListReverseIterator:
        """降順イテレータを返却"""
        return DoubleLinkedListReverseIterator(self.head)
    

class DoubleLinkedListIterator:
    """DoubleLinkedListのイテレータ用クラス"""
    
    def __init__(self, head: Node):
        self.head = head
        self.current = head.next
        
    def __iter__(self) -> DoubleLinkedListIterator:
        return self
    
    def __next__(self) -> Any:
        if self.current is self.head:
            raise StopIteration
        else:
            data = self.current.data
            self.current = self.current.next
            return data
        
class DoubleLinkedListReverseIterator:
    """DoubleLinkedListの降順イテレータ用クラス"""
    
    def __init__(self, head: Node):
        self.head = head
        self.current = head.prev
        
    def __iter__(self) -> DoubleLinkedListReverseIterator:
        return self
    
    def __next__(self) -> Any:
        if self.current is self.head:
            raise StopIteration
        else:
            data = self.current.data
            self.current = self.current.prev
            return data
 ```

```python:list8-6,double_list_test.py      

#循環・重連結リストクラスDoubleLinkedListの利用例

from enum import Enum
from double_list import DoubleLinkedList

Menu = Enum('Menu', ['先頭に挿入', '末尾に挿入','着目の直後に挿入', '先頭を削除', '末尾を削除', '着目を表示', '着目を進める', '着目を戻す', '着目を削除', '全削除', '探索', '帰属性判定', '全ノードを表示', '全ノードを逆順に表示', '逆走査', '走査', '終了'])

def select_Menu() -> Menu:
    "メニュー選択"
    s = [f'({m.value}){m.name}' for m in Menu]
    while True:
        print(*s, sep='   ', end='')
        n = int(input('：'))
        if 1 <= n <= len(Menu):
            return Menu(n)

lst = DoubleLinkedList()

while True:
   
    menu = select_Menu()
    
    if menu == Menu.先頭に挿入:
        lst.add_first(int(input('値：')))
    
    elif menu == Menu.末尾に挿入:
        lst.add_last(int(input('値：')))
        
    elif menu == Menu.着目の直後に挿入:
        lst.add(int(input('値：')))
        
    elif menu == Menu.先頭を削除:
        lst.remove_first()
    
    elif menu == Menu.末尾を削除:
        lst.remove_last()
        
    elif menu == Menu.着目を表示:
        lst.print_current_node()
        
    elif menu == Menu.着目を進める:
        lst.next()
        
    elif menu == Menu.着目を戻す:
        lst.prev()
        
    elif menu == Menu.着目を削除:
        lst.remove_current_node()
    
    elif menu == Menu.全削除:
        lst.clear()
        
    elif menu == Menu.探索:
        pos = lst.search(int(input('値：')))
        if pos >= 0:
            print(f':そのキーをもつデータは{pos + 1}番目にあります。')
        else:
            print('該当するデータはありません。')
        
    elif menu == Menu.帰属性判定:
        print('その値のデータは含まれま' + ('す。'if int(input('値：'))in lst else 'せん。'))
        
    elif menu == Menu.全ノードを表示: 
        lst.print()
    
    elif menu == Menu.全ノードを逆順に表示: 
        lst.print_reverse()
        
    elif menu == Menu.走査:
        for e in lst:
            print(e)
    elif menu == Menu.逆走査:
        for e in reversed(lst):
            print(e)
    else:
        break
```

|**コラム8-3 Pythonの代入について**|
|-|

pythonの場合
`self.current.next = self.current.next.prev = node`
というようなコードがあった場合
pythonでは正しく動作しません。(CやJavaと比較すると代入順序が逆になるようです)

今回はさらっと流しますが、また改めて学習したいと思います。

以上ですありがとうございました。