paginatorのページ番号リストを作る

paginatorで必要になるページ番号のリストを生成するコードをわざわざ書く必要が有ったのでPython初心者が書いてみた。のをここに書いておきます。

ページ番号は正攻法だととてもめんどくさいのでどこかから引っ張ってくるか、と思って探していたんだけれど、どうも適当なコードが見当たらず、ふと「Pythonならリスト内包表記とかで簡単にできるんじゃないか?」と思いついたのが今回の方法。

window_sizeが奇数だとずれるとかいろいろあるけれど、こういう方法どうでしょうか、と言う事ですのでまあなんとなくぬるく楽しんでいただければ。

これはなに?

yahoo.co.jpの検索でのページングと同じような動きで数字のリストを出力するもの

  • 一度に10ページ分のリンクを表示する(引数で変更可能)
  • 現在のページがリストの中央を超えるとリスト全体がシフトする

現在のページ番号、トータルのページ数、表示するページの幅を渡せばこんな感じの配列が帰ってくる。
※current_pageは「[x]」で強調しているけれど実際はなにもないただの配列

  • current_page = 1, total_pages = 10
    • 1 2 [3] 4 5 6 7 8 9 10
  • current_page = 8, total_pages = 20
    • 3 4 5 6 7 [8] 9 10 11 12
  • current_page = 14, total_pages = 14
    • 5 6 7 8 9 10 11 12 13 [14]


実際はもう少し手を入れてメソッドにしているけれど、こんなコードになった。
最初に書いたのがPythonで、それからPHPrubyで書き直した。perlは知らない。というかperl界隈の人が恐ろしい。

Python

def get_page_list(current_page, total_pages, window_size=10):
    pages = range( current_page - window_size, current_page + window_size )
    pages = [page for page in pages if page > 0 if page <= total_pages]
    if len(pages) > window_size:
        if pages.index(current_page) > window_size/2:
            pages = [page for page in pages if page < current_page+window_size/2]
            pages = pages[-window_size:]
        else:
            pages = pages[0: window_size]
    return pages

最初に書いたのがこれ。配列でなんとかする方法はPythonにすごく合っている様に思う。
コンパクトで割と分かりやすい。んじゃないだろうか。

PHP

<?php
function get_page_list( $current_page, $total_pages, $window_size = 10 ){
  $pages = range( $current_page - $window_size, $current_page + $window_size );
  $pages = array_filter( $pages, create_function( '$page', 'return( $page > 0 && $page <= ' . $total_pages . ' ) ? true: false;' ));
  if( count($pages) > $window_size ){
    if( intval(array_search( $current_page, $pages )) > intval( $window_size / 2 )){
      $pages = array_filter( $pages, create_function( '$page', 'return $page <= ' . ($current_page + intval($window_size / 2 )) . ' ? true : false;' ));
      $pages = array_slice( $pages, -$window_size );
    } else {
      $pages = array_slice( $pages, 0, $window_size );
    }
  }
  return $pages;
}

うわー。なんだかごちゃごちゃ括弧が重なりすぎている。丸々同じ動作をさせる必要はなかったのかも知れないけれど、でも、部分部分を名前を持った関数にするのはいやだったので無理矢理create_functionに押し込んだ。
array_filterとcreate_functionの組み合わせで引数渡せなくて文字列として埋め込んで渡すのがPHPらしいと言えばらしい気がする。
あと、int/intの結果がfloatになるのはなんか違和感ある。int%intはintなのに。。。
もういっこ、array_searchの返りに明示的にintvalを付けなければいけないのってなんでだっけ?

ruby

def get_page_list( current_page, total_pages, window_size=10 )
  pages = (( current_page - window_size ) .. ( current_page + window_size )).to_a
  pages.delete_if{ |page| not ( page > 0 && page <= total_pages ) }
  if pages.length > window_size then
    if pages.index( current_page ) > window_size / 2 then
      pages.delete_if{ |page| not ( page < current_page + window_size/2 ) }
      pages = pages.slice( -window_size, window_size )
    elsif
      pages = pages.slice( 0, window_size )
    end
  end
  return pages
end

delete_ifが他の言語と論理的に逆なのがちょっと意外だった。
ブロックに条件を書ける点はpythonより書きやすいかも。
そして「end」って意外に目につく。

書いてみて

先に大きめの数字の列を作って、両端を必要に応じてカットしている。
がんばって開始、終了のページ番号を計算するよりはだいぶすっきりしたコードになってると思う。
結局やっていることは同じだけれど。


指定した幅の倍の配列を作るのが少し富豪っぽいけれど、ここに1000とか入れることはまず考えられないので実用的にはこれで良いという割り切り。
あとcurrent_page > total_pagesな状態は考えていないけど念のためチェックするならcurrent_page = max( current_page, total_pages )かなあ

書き換えてみての印象

  • Pythonの配列操作は良い感じ。
  • PHPはなんでも関数でやれるけど、array_filterとcreate_functionの辺りがちょっとどうかと思うコードになっている。(5.3以降の無名関数使えばきれいになるのかもしれないけど手元に環境がなかったので。)
    • やりたいことはシンプルなのに全部関数で書かなければいけないから一気に分かりにくくなる。
  • Rubyも良い感じで配列操作できる。ただドキュメントが簡潔すぎて書き慣れてないとちょっと苦労した。


配列操作に関してはPython>Ruby>>>PHPと言う印象。まあPHPでarray_filter+create_functionは結構めんどくさいし見るからに汚いので、こういうアプローチには向いてないんだろうな。

PHPまで同じ処理の流れにおさめようとしたのがよくない、と言えばよくないんだけど、でもだからといってPHPのこれはどうなのよ。。。面白いロジック思いついたからさくっと実装してみようか、ってときにこれじゃあちょっと難しいんじゃないかな。前の仕事もPHPだったけどPHPで大きなことをやるのはなんか違うような気がするな。ほんのちょっと世の中を便利にするような仕組みを半日ぐらいで作るには良い言語だと思うのでそっち方面で使っていきたいな。