Re: Ruby で季節のような循環する概念を表したい

この記事は[Okinawa.rb Advent Calendar 2018](https://qiita.com/advent-calendar/2018/okinawarb)の2日目の記事です。
昨日は @hanachin_ さんの[RailsのviewでPDFを書くSATySFi-rails gemの使い方](https://qiita.com/hanachin_/items/f8424d766d3020d32acf)でした。
明日は @hanachin_ さんの[Re: シクシク素数アドベントカレンダー Ruby 編](https://qiita.com/hanachin_/items/ac842237556d0a1d5a1b)です。

こちらの記事を読んで色々書きたくなったので書きます。
https://qiita.com/QUANON/items/5beef387a4b2941aa239

## 双方向循環リスト

たぶん書けるといいのはこれ（ただし要素数が変更されることはない）

> 双方向循環リスト（doubly-circularly-linked list）では、各ノードは線形の双方向リストと同じように2つのリンクを持つが、先頭ノードの後方リンクは最後尾ノードを指し、最後尾ノードの前方リンクは先頭ノードを指す。双方向リストと同様、挿入も削除もその位置に隣接するノードへの参照が1つあれば、高速に行える。構造的には双方向循環リストには先頭も最後尾もないが、一般に外部のアクセスポインタを用意して、先頭または最後尾のノードを指しておくことが多い。そして、双方向リストでの番兵ノードのように順序を把握するのに使われる[1]。
https://ja.wikipedia.org/wiki/%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88#%E5%8F%8C%E6%96%B9%E5%90%91%E5%BE%AA%E7%92%B0%E3%83%AA%E3%82%B9%E3%83%88

## 片方向循環リスト

コメント欄で書いたこれは実質片方向循環リストっぽい動きで、ノードを辿りながら1つずつ取り出すみたいなのができる(seasonがポインタ的になっていてnextするとseasonが上書きされる)

```rb
season = %w[春 夏 秋 冬].cycle
season.next # => "春"
season.next # => "夏"
season.next # => "秋"
season.next # => "冬"
```

## 双方向循環リスト

Arrayのリスト自体を回す発想 (seasonがポインタでrotateすると状態が変更される)

```rb
season = %w[春 夏 秋 冬].extend Module.new {
  def next
    first
  ensure
    rotate!
  end

  def prev
    rotate!(-1)
    last
  end
}
season.next # => "春"
season.next # => "夏"
season.next # => "秋"
season.next # => "冬"
season.prev # => "秋"
season.prev # => "夏"
season.prev # => "春"
```

## 扱うEnumerableが無限の場合

考えたくないのでこの実装はなし

## 扱うのが春夏秋冬の場合

数が少なかったらベタに書いて動きそう

```rb
class Node < Struct.new(:value, :prev, :next)
  def next
    super.call
  end

  def prev
    super.call
  end

  alias to_s value
end

春, 夏, 秋, 冬 = Node.new("春", -> { 冬 }, -> { 夏 }), Node.new("夏", -> { 春 }, -> { 秋 }), Node.new("秋", -> { 夏 }, -> { 冬 }), Node.new("冬", -> { 秋 }, -> { 春 })

season = 春
7.times do
  puts season.to_s
  season = season.next
end

8.times do
  puts season.to_s
  season = season.prev
end
# 春
# 夏
# 秋
# 冬
# 春
# 夏
# 秋
# 冬
# 秋
# 夏
# 春
# 冬
# 秋
# 夏
# 春
```

## めちゃくちゃでかい配列でどう書けばいいんか

雑にnextとprevを直で定義

```rb
seasons = %w[
  立春 雨水 啓蟄 春分 清明 穀雨
  立夏 小満 芒種 夏至 小暑 大暑
  立秋 処暑 白露 秋分 寒露 霜降
  立冬 小雪 大雪 冬至 小寒 大寒
]

seasons.inject(seasons.last) do |prev_season, season|
  season.define_singleton_method(:prev) do
    prev_season
  end
  season
end

seasons.reverse_each.inject(seasons.last) do |next_season, season|
  season.define_singleton_method(:next) do
    next_season
  end
  season
end

season = seasons.first

seasons.size.times do
  puts season
  season = season.next
end

seasons.size.times do
  season = season.prev
  puts season
end
```

## 経路が定義されていればいい

経路を定義しておいてStringにRefinementsでメソッドを足して感じにした

```rb
seasons = %w[
  立春 雨水 啓蟄 春分 清明 穀雨
  立夏 小満 芒種 夏至 小暑 大暑
  立秋 処暑 白露 秋分 寒露 霜降
  立冬 小雪 大雪 冬至 小寒 大寒
]

using Module.new {
  prev_path = {}
  seasons.inject(seasons.last) do |prev_season, season|
    prev_path[season] = prev_season
    season
  end

  next_path = {}
  seasons.reverse_each.inject(seasons.last) do |next_season, season|
    next_path[season] = next_season
    season
  end

  refine(String) do
    define_method(:prev) do
      prev_path.fetch(self)
    end

    define_method(:next) do
      next_path.fetch(self)
    end
  end
}

season = seasons.first

seasons.size.times do
  puts season
  season = season.next
end

seasons.size.times do
  season = season.prev
  puts season
end
```

## まとめ

Rubyべんり