1
2

More than 5 years have passed since last update.

Ruby で双方向リスト

Last updated at Posted at 2012-12-26

せっかく双方向なんだから list[-1] とかによるアクセスにも対応したほうがいいんだろうけど, 何となく満足したのでここまで.

doubly_linked_list.rb
class DoublyLinkedList
  class Bucket
    attr_accessor :data, :next, :prev

    def initialize(data = nil)
      @data = data
    end
  end

  class BucketIterator
    include Enumerable

    def initialize(head)
      @head = head
    end

    def each
      bucket = @head
      while not bucket.next == @head
        bucket = bucket.next
        yield bucket
      end
      self
    end
  end

  class BucketNotFoundException < Exception; end

  include Enumerable

  attr_reader :head

  def initialize
    @head = Bucket.new
    @head.next = @head.prev = @head
  end

  def each
    buckets.each {|bucket| yield bucket.data }
    self
  end

  def push(data)
    new_bucket = Bucket.new(data)
    tail = find_tail_bucket
    new_bucket.next = head
    new_bucket.prev = tail
    tail.next = new_bucket
    head.prev = new_bucket
    self
  end

  def delete_at(n)
    bucket = find_bucket(n)
    bucket.prev.next = bucket.next
    bucket.next.prev = bucket.prev
    self
  end

  def []=(key, value)
    find_bucket(key).data = value
  end

  def [](key)
    find_bucket(key).data
  end

  private
  def buckets
    BucketIterator.new(head)
  end

  def find_tail_bucket
    buckets.reduce(head) {|last_bucket, next_bucket| next_bucket }
  end

  def find_bucket(n)
    bucket = head
    (n + 1).times do
      raise BucketNotFoundException if bucket.next == head
      bucket = bucket.next
    end
    bucket
  end
end
doubly_linked_list_spec.rb
# coding: utf-8
require './doubly_linked_list'

describe DoublyLinkedList::Bucket do
  let(:bucket) { DoublyLinkedList::Bucket.new }
  subject { bucket }

  describe '#data' do
    subject { bucket.data }

    context '値がセットされていないとき' do
      it { should be_nil }
    end

    context '次の要素をセットしたとき' do
      before { bucket.data = some_data }
      let(:some_data) { :foo }

      it { should == some_data }
    end

    context 'コンストラクタに値をセットしたとき' do
      subject { DoublyLinkedList::Bucket.new(:foo) }
      its(:data) { should == :foo }
    end
  end

  describe '#next' do
    subject { bucket.next }

    context '次の要素が無いとき' do
      it { should be_nil }
    end

    context '次の要素をセットしたとき' do
      before { bucket.next = next_bucket }
      let(:next_bucket) { DoublyLinkedList::Bucket.new }

      it { should === next_bucket }
    end
  end

  describe '#prev' do
    subject { bucket.prev }

    context '前の要素が無いとき' do
      it { should be_nil }
    end

    context '前の要素をセットしたとき' do
      before { bucket.prev = prev_bucket }
      let(:prev_bucket) { DoublyLinkedList::Bucket.new }

      it { should === prev_bucket }
    end
  end
end

describe DoublyLinkedList do
  subject { list }
  let(:list) { DoublyLinkedList.new }

  describe '#to_a' do
    subject { list.to_a }

    context '要素が何も無いとき' do
      it { should == [] }
    end

    context '要素をひとつ (:foo) 追加したとき' do
      before { list.push(:foo) }
      it { should == [:foo] }
    end

    context '要素をふたつ (:foo, :bar) 追加したとき' do
      before { list.push(:foo).push(:bar) }
      it { should == [:foo, :bar] }
    end
  end

  describe '#haed' do
    context 'リストに要素が何も無いとき' do
      it 'next は自分自身を指す' do
        list.head.next.should === list.head
      end

      it 'prev は自分自身を指す' do
        list.head.prev.should === list.head
      end
    end
  end

  describe '#push' do
    context '要素をひとつ追加したとき' do
      before { list.push(:foo) }

      it 'head の next の data にその値が入る' do
        list.head.next.data.should == :foo
      end

      it 'head の next の prev は head を指す' do
        list.head.next.prev.should === list.head
      end

      it 'head の next の next は head を指す' do
        list.head.next.next.should === list.head
      end

      it 'head の prev は head の next を指す' do
        list.head.prev.should === list.head.next
      end
    end

    context '要素をふたつ (:foo, :bar) 追加したとき' do
      before do
        list.push(:foo)
        list.push(:bar)
      end

      it 'head の next の data には :foo が入る' do
        list.head.next.data.should == :foo
      end

      it 'head の next の next の data には :bar が入る' do
        list.head.next.next.data.should == :bar
      end

      it 'head の next の next の next は head を指す' do
        list.head.next.next.next.should === list.head
      end

      it 'head の prev の prev の prev は head を指す' do
        list.head.prev.prev.prev.should === list.head
      end
    end
  end

  describe '#[]' do
    context '指定した要素がセットされていないとき' do
      it 'BucketNotFoundException を投げる' do
        proc { list[0] }.should raise_error(DoublyLinkedList::BucketNotFoundException)
      end
    end

    context '要素がセットされているとき' do
      before { list.push(:foo).push(:bar).push(:baz) }
      it 'セットされた要素を取得する' do
        list[2].should == :baz
      end
    end
  end

  describe '#[]=' do
    context '指定した要素が存在しないとき' do
      it 'BucketNotFoundException を投げる' do
        proc { list[0] = :foo }.should raise_error(DoublyLinkedList::BucketNotFoundException)
      end
    end

    context '指定した要素が存在するとき' do
      before do
        list.push(:foo)
        list[0] = :override
      end

      it 'セットされた要素の値を上書く' do
        list[0].should == :override
      end
    end
  end

  describe '#delete_at' do
    subject { list.entries }

    context '要素が 3 つ (:foo, :bar, :baz) あるとき' do
      before { list.push(:foo).push(:bar).push(:baz) }

      context '0 番目の要素を削除したら' do
        before { list.delete_at(0) }
        it { should == [:bar, :baz] }
      end

      context '1 番目の要素を削除したら' do
        before { list.delete_at(1) }
        it { should == [:foo, :baz] }
      end

      context '2 番目の要素を削除したら' do
        before { list.delete_at(2) }
        it { should == [:foo, :bar] }
      end

      context '存在しない要素を削除しようとしたら' do
        it 'BucketNotFoundException を投げる' do
          proc { list.delete_at(3) }.should raise_error(DoublyLinkedList::BucketNotFoundException)
        end
      end
    end
  end
end
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2