あっぽログ
← 記事一覧に戻る

PHPのSplDoublyLinkedListを使いこなす:双方向リストで柔軟なデータ管理を実現する

SplDoublyLinkedListとは

PHPには標準ライブラリ(SPL)として、様々なデータ構造が用意されています。その中の一つ SplDoublyLinkedList は、**双方向リスト(Doubly Linked List)**を実装したクラスです。

双方向リストとは、各要素が「前の要素」と「次の要素」への参照を持つデータ構造です。通常の配列と異なり、先頭・末尾への追加・削除が**O(1)(定数時間)**で行える点が特徴です。

既存記事でも紹介している SplStackSplQueue は、実はこの SplDoublyLinkedList を継承して作られています。今回はその土台となる SplDoublyLinkedList 自体の使い方を理解しましょう。


基本的な使い方

要素の追加

要素の追加は先頭・末尾どちらにも行えます。

<?php

$list = new SplDoublyLinkedList();

// 末尾に追加
$list->push('apple');
$list->push('banana');
$list->push('cherry');

// 先頭に追加
$list->unshift('mango');

echo $list->count(); // 4
メソッド説明
push($value)末尾に要素を追加
unshift($value)先頭に要素を追加

要素の取得・削除

<?php

$list = new SplDoublyLinkedList();
$list->push('first');
$list->push('second');
$list->push('third');

// 先頭の要素を取得(削除しない)
echo $list->bottom(); // first

// 末尾の要素を取得(削除しない)
echo $list->top(); // third

// 末尾から取り出して削除
echo $list->pop(); // third

// 先頭から取り出して削除
echo $list->shift(); // first

echo $list->count(); // 1

インデックスを使ったアクセス

SplDoublyLinkedList は配列のようにインデックスでもアクセスできます。

<?php

$list = new SplDoublyLinkedList();
$list->push('a');
$list->push('b');
$list->push('c');

// インデックスで取得
echo $list->offsetGet(0); // a
echo $list->offsetGet(2); // c

// インデックスで上書き
$list->offsetSet(1, 'B');
echo $list->offsetGet(1); // B

// インデックスで削除
$list->offsetUnset(0);
echo $list->bottom(); // B

イテレーション(繰り返し処理)

SplDoublyLinkedListIterator インターフェースを実装しているため、foreach で繰り返し処理できます。

また、イテレーションモードを切り替えることで、前から辿るか後ろから辿るかを制御できます。

<?php

$list = new SplDoublyLinkedList();
$list->push('one');
$list->push('two');
$list->push('three');

// デフォルト(先頭から末尾)
$list->setIteratorMode(SplDoublyLinkedList::IT_MODE_FIFO);

foreach ($list as $value) {
    echo $value . PHP_EOL;
}
// one
// two
// three

// 逆順(末尾から先頭)
$list->setIteratorMode(SplDoublyLinkedList::IT_MODE_LIFO);

foreach ($list as $value) {
    echo $value . PHP_EOL;
}
// three
// two
// one

イテレーションモードの組み合わせ

モードは | で組み合わせられます。

// 末尾から辿り、辿った要素を削除する
$list->setIteratorMode(
    SplDoublyLinkedList::IT_MODE_LIFO | SplDoublyLinkedList::IT_MODE_DELETE
);

foreach ($list as $value) {
    echo $value . PHP_EOL;
}

echo $list->count(); // 0(全削除された)
モード定数説明
IT_MODE_FIFO先頭から末尾へ(デフォルト)
IT_MODE_LIFO末尾から先頭へ
IT_MODE_KEEP要素を残す(デフォルト)
IT_MODE_DELETE辿った要素を削除する

実践的な活用例:履歴管理

双方向リストは「直近の操作履歴を管理する」ユースケースに向いています。以下はシンプルなコマンド履歴クラスの例です。

<?php

class CommandHistory
{
    private SplDoublyLinkedList $history;
    private int $maxSize;

    public function __construct(int $maxSize = 5)
    {
        $this->history = new SplDoublyLinkedList();
        $this->maxSize = $maxSize;
    }

    public function record(string $command): void
    {
        // 上限を超えたら先頭(最古)を削除
        if ($this->history->count() >= $this->maxSize) {
            $this->history->shift();
        }
        $this->history->push($command);
    }

    public function showLatest(): void
    {
        // 末尾(最新)から表示
        $this->history->setIteratorMode(SplDoublyLinkedList::IT_MODE_LIFO);
        foreach ($this->history as $command) {
            echo $command . PHP_EOL;
        }
    }
}

$history = new CommandHistory(maxSize: 3);
$history->record('git init');
$history->record('git add .');
$history->record('git commit -m "first"');
$history->record('git push'); // 上限超過 → "git init"が削除される

echo "=== 最新の履歴 ===" . PHP_EOL;
$history->showLatest();
// git push
// git commit -m "first"
// git add .

配列との使い分け

観点配列SplDoublyLinkedList
先頭への追加O(n)(遅い)O(1)(速い)
末尾への追加O(1)O(1)
インデックスアクセスO(1)O(n)
メモリ効率大量要素で有利参照オーバーヘッドあり

先頭や末尾への頻繁な追加・削除が必要な場合SplDoublyLinkedList が有利です。一方、インデックスによるランダムアクセスが多い場合は素直に配列を使いましょう。


まとめ

  • SplDoublyLinkedList は双方向リストを実装したSPLクラス
  • push / pop / unshift / shift で先頭・末尾の操作が可能
  • イテレーションモードで繰り返しの方向や削除挙動を制御できる
  • 先頭・末尾への追加・削除が多いシナリオで配列より効率的

SplStackSplQueue を使う前にその親クラスである SplDoublyLinkedList を理解しておくと、SPL全体への理解が深まります。ぜひ活用してみてください!

← 記事一覧に戻る