この記事はOkinawa.rb Advent Calendar 2018の2日目の記事です。
昨日は @hanachin_ さんのRailsのviewでPDFを書くSATySFi-rails gemの使い方でした。
明日は @hanachin_ さんのRe: シクシク素数アドベントカレンダー Ruby 編です。
こちらの記事を読んで色々書きたくなったので書きます。
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が上書きされる)
season = %w[春 夏 秋 冬].cycle
season.next # => "春"
season.next # => "夏"
season.next # => "秋"
season.next # => "冬"
双方向循環リスト
Arrayのリスト自体を回す発想 (seasonがポインタでrotateすると状態が変更される)
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が無限の場合
考えたくないのでこの実装はなし
扱うのが春夏秋冬の場合
数が少なかったらベタに書いて動きそう
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を直で定義
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でメソッドを足して感じにした
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べんり