LoginSignup
3
2

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-12-02

この記事は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べんり

3
2
1

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
3
2