ページ繰り

現在のページの近傍ページへのリンクを並べたものを「ページ繰り」と称することがあります。 例えば全部で 8 ページあるとき、 4 ページの近傍ページへのリンクを並べることにします。 できるだけ現在のページを中央に置き、 前後のどちらへも飛べるようにします。 有名な Google のページ繰りは最大 10 ページ分を表示し、 現在のページに対して左右の個数が非対称になる場合がありますが、 ここでは計算式を簡単にするため、 奇数個のページを表示することにします。

     ページ 1 のページ繰り   *1  2  3  4  5
     ページ 2 のページ繰り    1 *2  3  4  5
     ページ 3 のページ繰り    1  2 *3  4  5
     ページ 4 のページ繰り    2  3 *4  5  6
     ページ 5 のページ繰り    3  4 *5  6  7
     ページ 6 のページ繰り    4  5 *6  7  8
     ページ 7 のページ繰り    4  5  6 *7  8
     ページ 8 のページ繰り    4  5  6  7 *8

ページの配列 pages があるものとして、 それの添字 i のページ用にページ繰りを作るために配列を切り出す手続きを書くと、 次のようになります。 引数の halfspan に現在のページの左右に置くページの個数を指定します。 上の例のとき、 この値は 2 です。

def nearslice(pages, i, halfspan)
  return [] if pages.empty?
  width = halfspan * 2 + 1
  first = [[i - halfspan, 0].max, [pages.size - width, 0].max].min
  second = first + [pages.size, width].min
  pages[first ... second]
end

今度は逆に、 ページ i をページ繰りに持つページの範囲を求めてみます。

    ページ繰りに 1 を含むページ     1  2  3
    ページ繰りに 2 を含むページ     1  2  3  4
    ページ繰りに 3 を含むページ     1  2  3  4  5
    ページ繰りに 4 を含むページ     1  2  3  4  5  6  7  8
    ページ繰りに 5 を含むページ     1  2  3  4  5  6  7  8
    ページ繰りに 6 を含むページ              4  5  6  7  8
    ページ繰りに 7 を含むページ                 5  6  7  8
    ページ繰りに 8 を含むページ                    6  7  8

これを使って、 ページを書き直したとき、 更新するべき出力ページ範囲を求めることができます。 この手続きはページの書き直しだけでなく、 ページ挿入後に更新するべき出力ページ範囲の決定にも利用可能です。

def depends_nearslice_change(pages, j, halfspan)
  return (0 ... 1) if pages.empty?
  width = halfspan * 2 + 1
  first = j < width ? 0 : j - halfspan
  second = pages.size - j - 1 < width ? pages.size : j + halfspan + 1
  first ... second
end

一方、 ページ削除時は、 削除前用の上の手続きに類似のやりかたで更新を受けるページを求めることができます。

def depends_nearslice_remove(pages, j, halfspan)
  return (0 ... 1) if pages.empty?
  width = halfspan * 2 + 1
  oldsize = pages.size + 1
  first = j < width ? 0 : j - halfspan
  second = oldsize - j - 1 < width ? oldsize : j + halfspan + 1
  first ... second - 1
end

以下は、 上の 3 つをどのように使うかの備忘録です。

ページ繰りを生成するには nearslice を使います。 動的にページを生成する場合は、 その都度、 個々のページごとにページ繰りを生成していきます。 静的にページを生成する場合は、 全ページの出力を一気におこなうときに利用します。 ページを追加するときは、 後のページ挿入で処理します。

pages = [:p1, :p2, :p3, :p4, :p5, :p6, :p7, :p8]
halfspan = 2
output = pages.each_index.map {|i| nearslice(pages, i, halfspan) }
p output
#=> [[:p1, :p2, :p3, :p4, :p5],
#    [:p1, :p2, :p3, :p4, :p5],
#    [:p1, :p2, :p3, :p4, :p5],
#    [:p2, :p3, :p4, :p5, :p6],
#    [:p3, :p4, :p5, :p6, :p7],
#    [:p4, :p5, :p6, :p7, :p8],
#    [:p4, :p5, :p6, :p7, :p8],
#    [:p4, :p5, :p6, :p7, :p8]]

ページ j を書き換えたいとき、 近傍の出力ページのページ繰りにページ j も含まれているため、 ペそれらの近傍の出力ページを更新します。

pages = [:p1, :p2, :p3, :p4, :p5, :p6, :p7, :p8]
halfspan = 2
output = pages.each_index.map {|i| nearslice(pages, i, halfspan) }
#       [:p1, :p2, :p3, :p4, :p5, :Q6, :p7, :p8]  pages[5]==:p6 を :Q6 に変更
j = 5
pages[j] = :Q6
depends_nearslice_change(pages, j, halfspan).each do |i|
  output[i] = nearslice(pages, i, halfspan)
end
p output
#=>[[:p1, :p2, :p3, :p4, :p5],
#   [:p1, :p2, :p3, :p4, :p5],
#   [:p1, :p2, :p3, :p4, :p5],
#   [:p2, :p3, :p4, :p5, :Q6],
#   [:p3, :p4, :p5, :Q6, :p7],
#   [:p4, :p5, :Q6, :p7, :p8],
#   [:p4, :p5, :Q6, :p7, :p8],
#   [:p4, :p5, :Q6, :p7, :p8]]

ページ j を挿入するときは、 近傍の出力ページのページ繰りにページ j を追加するために、 近傍の出力ページの更新も一緒におこないます。 そのとき、pages に追加するだけでなく、 出力ページ配列の output の同じ位置にもダミーを挿入しておきます。

pages = [:p1, :p2, :p3, :p4, :p5, :p6, :p7, :p8]
halfspan = 2
output = pages.each_index.map {|i| nearslice(pages, i, halfspan) }
#       [:p1, :p2, :p3, :p4, :p5, :Q5, :p6, :p7, :p8]  pages[5]==:Q5 を挿入
j = 5
pages[j, 0] = [:Q5]
output[j, 0] = [nil]  # ダミー
depends_nearslice_change(pages, j, halfspan).each do |i|
  output[i] = nearslice(pages, i, halfspan)
end
p output
#=> [[:p1, :p2, :p3, :p4, :p5],
#    [:p1, :p2, :p3, :p4, :p5],
#    [:p1, :p2, :p3, :p4, :p5],
#    [:p2, :p3, :p4, :p5, :Q5],
#    [:p3, :p4, :p5, :Q5, :p6],
#    [:p4, :p5, :Q5, :p6, :p7],
#    [:p5, :Q5, :p6, :p7, :p8],
#    [:p5, :Q5, :p6, :p7, :p8],
#    [:p5, :Q5, :p6, :p7, :p8]]

ページ削除時も近傍ページの出力ページを更新しなければいけません。

pages = [:p1, :p2, :p3, :p4, :p5, :p6, :p7, :p8]
halfspan = 2
output = pages.each_index.map {|i| nearslice(pages, i, halfspan) }
#       [:p1, :p2, :p3, :p4, :p5, :p7, :p8]  pages[5]==:p6 を削除
j = 5
pages[j, 1] = []
output[j, 1] = []
depends_nearslice_remove(pages, j, halfspan).each do |i|
  output[i] = nearslice(pages, i, halfspan)
end
p output
#=> [[:p1, :p2, :p3, :p4, :p5],
#    [:p1, :p2, :p3, :p4, :p5],
#    [:p1, :p2, :p3, :p4, :p5],
#    [:p2, :p3, :p4, :p5, :p7],
#    [:p3, :p4, :p5, :p7, :p8],
#    [:p3, :p4, :p5, :p7, :p8],
#    [:p3, :p4, :p5, :p7, :p8]]