Explorer-likeなPhotoViewer作った

名前は Potov。「ポトフ」と発音してほしい。

Potov-20160207

 cf. https://github.com/takatoh/Potov

Explorer-like とはつまり、左側にディレクトリツリーが、右側に選択したディレクトリの中の画像(サムネイル)が表示されるってこと。ディレクトリツリーの表示には jsTree を、画像の表示には Colorbox を使っている。表示専用で、画像の移動なんかは出来ない(するつもりもない)。
メインの実装言語は Ruby。フレームワークには Sinatra を使っている。一応シングルページアプリケーション。

画像を追加したり移動したりはファイルシステムで物理的に行う。今のところ、サムネイルを手動で作成する必要があるので、このへんは今後改修したいところだ。

FlaskでJSONを受け取る

Flask で POST された JSON を受け取るには request.json を使えばいい。
サーバー側(Flask)のコードはこんな感じ:

from flask import request, redirect, url_for, render_template, flash, json

(中略)

@app.route('/api/book/add/', methods=['POST'])
def api_book_add():
    categ = Category.query.filter_by(name=request.json['category']).first()
    fmt = Format.query.filter_by(name=request.json['format']).first()
    book = Book(
        title = request.json['title'],
        volume = request.json['volume'],
        series = request.json['series'],
        series_volume = request.json['series_volume'],
        author = request.json['author'],
        translator = request.json['translator'],
        publisher = request.json['publisher'],
        category_id = categ.id,
        format_id = fmt.id,
        isbn = request.json['isbn'],
        published_on = request.json['published_on'],
        original_title = request.json['original_title'],
        note = request.json['note'],
        keyword = request.json['keyword'],
        disk = request.json['disk']
    )
    if request.json['disposed'] == '1':
        book.disposed = True
    db.session.add(book)
    db.session.commit()
    return json.dumps({ "status": "OK", "books": [book.to_dictionary()]})

POST するクライアント側(こっちは Ruby)はこう:

#!/usr/bin/env ruby
# encoding: utf-8

require 'httpclient'
require 'yaml'
require 'csv'
require 'json'
require 'optparse'

def post_book(book)
  post_url = "http://localhost:5000/api/book/add/"
  post_data = {
    "title"          => book["title"],
    "volume"         => book["volume"]         || "",
    "series"         => book["series"]         || "",
    "series_volume"  => book["series_volume"]  || "",
    "author"         => book["author"]         || "",
    "translator"     => book["translator"]     || "",
    "publisher"      => book["publisher"]      || "",
    "category"       => book["category"]       || "その他",
    "format"         => book["format"]         || "その他",
    "isbn"           => book["isbn"]           || "",
    "published_on"   => book["published_on"]   || "",
    "original_title" => book["original_title"] || "",
    "note"           => book["note"]           || "",
    "keyword"        => book["keyword"]        || "",
    "disk"           => book["disk"]           || "",
    "disposed"       => book["disposed"]       || "0"
  }
  json_data = post_data.to_json

  res = @client.post_content(post_url, json_data, "Content-Type" => "application/json")
  result = JSON.parse(res)
  puts title_with_vol(result["books"].first)
end

def title_with_vol(book)
  if book["volume"].nil? || book["volume"].empty?
    book["title"]
  else
    book["title"] + " [" + book["volume"] + "]"
  end
end

options = {}
opts = OptionParser.new
opts.banner = <<EOB

Options:
EOB
opts.on("--csv", "Input from CSV."){|v| options[:csv] = true }
opts.on_tail("--help", "-h", "Show this message"){|v|
  puts opts.help
  exit(0)
}
opts.parse!

@client = HTTPClient.new

inputfile = ARGV.shift
books = if options[:csv]
  csvfile = File.open(inputfile, "r")
  CSV.new(csvfile, headers: true)
else
  YAML.load_file(inputfile)["books"]
end
books.each do |book|
  post_book(book)
end

HTTPClient#post_content に JSON と "Content-Type" => "application/json" を渡してるのがミソ。Content-Typeapplication/json になってることで、Flask.request.json でデコードされたデータが得られる。

filestorageとfilestorage-s3の0.1.0をリリースした

以前作って rubygems.org に登録した gem、filestorage を filestorage と filestorage-s3 に分割してリリースした。どっちもバージョン0.1.0。

機能的には直前のバージョン0.0.6と変わったところはないけど、ローカルファイルシステム用の filestorage と Amazon S3 用の filestorage-s3 に分けた。これでローカルファイルシステムしか使わないのに、aws-sdk とかがインストールされることもなくなった。
良かったら使ってください。

ソースコードはこちら:

フィボナッチ数列を100項まで計算しようとするとunsigned long longでもたりないらしい

タイトルのとおり。

#include
#include

int main(int argc, char *argv[])
{
    unsigned long long int a, b, tmp;
    int n, i;

    n = atoi(argv[1]);
    a = 1;
    b = 1;
    for (i = 1; i <= n; i++) {
        printf("%lld\n", a);
        tmp = a + b;
        a = b;
        b = tmp;
    }

    return 0;
 }

実行結果:

takatoh@nightschool $ ./fib 100
1
1
2
3
5
8
13
21
34

(中略)

4660046610375530309
7540113804746346429
12200160415121876738
1293530146158671551
13493690561280548289
14787220707439219840
9834167195010216513
6174643828739884737
16008811023750101250
3736710778780434371

最後のほうで桁が少なくなったり、明らかにおかしな数値になってしまう。

試しに Ruby でやってみた。

def fib(n)
  a = b = 1
  1.upto(n) do |i|
    puts a
    a, b = b, a + b
  end
end

fib(ARGV[0].to_i)
takatoh@nightschool $ ruby fib.rb 100
1
1
2
3
5
8
13
21
34

(中略)

4660046610375530309
7540113804746346429
12200160415121876738
19740274219868223167
31940434634990099905
51680708854858323072
83621143489848422977
135301852344706746049
218922995834555169026
354224848179261915075

ちゃんと計算できてる! Ruby すげー!

二分木を使ったソート

ヒープじゃなくて単純な二分木を使ったソート。すなわち、

  • 左の子は親よりも小さい
  • 右の子は親よりも大きい

という構造。で、これを通りがけ順(左の枝、根、右の枝の順に調べる)に出力すると昇順にソートされる。

class BinaryTree

  def initialize(x)
    @val = x
    @left = nil
    @right = nil
  end

  def insert(x)
    if x < @val
      @left.nil? ? @left = BinaryTree.new(x) : @left.insert(x)
    else
      @right.nil? ? @right = BinaryTree.new(x) : @right.insert(x)
    end
  end

  def traverse(&block)
    @left.traverse(&block) unless @left.nil?
    block.call(@val)
    @right.traverse(&block) unless @right.nil?
  end

end

def sort(ary)
  btree = BinaryTree.new(ary.shift)
  ary.each{|x| btree.insert(x) }
  [].tap{|a| btree.traverse{|v| a << v } }
end

if $0 == __FILE__
  sample = (1..100).to_a.sample(10)
  puts "unsorted: " + sample.inspect
  result = sort(sample)
  puts "sorted:   " + result.inspect
end
takatoh@nightschool $ ruby btree_sort.rb
unsorted: [47, 97, 34, 39, 74, 44, 38, 5, 10, 53]
sorted:   [5, 10, 34, 38, 39, 44, 47, 53, 74, 97]

配列を使ったヒープソート

一昨日は二分木を使ったヒープソートを作った。配列を使ったヒープの構造についてはここのページのヒープの項が分かりやすい。

 cf. 二分木 (binary tree) とヒープ (heap) – Algorithms with Python

二分木のルートを番号 0 として、各ノードに幅優先で左から番号をふっていく。ふった番号を配列の添字だと考えれば、次の関係が成り立つ。
すなわち、親の添字を k とすると:

  • 左側の子の添字は 2 * k + 1
  • 右側の子の添字は 2 * k + 2

子の添字を k とすると:

  • 親の添字は floor((k – 1) / 2)

となる。
これで二分木を配列に置き換えられる。後はやることは基本的に一緒だ。

class Heap

  def initialize
    @heap = []
  end

  def insert(x)
    @heap << x
    upheap
    self
  end

  def shift
    v = @heap.shift
    @heap.unshift(@heap.pop)
    downheap
    v
  end

  def upheap
    k = @heap.size - 1
    j = (k - 1) / 2    # parent of k
    until k == 0 || @heap[j] < @heap[k]
      @heap[j], @heap[k] = @heap[k], @heap[j]
      k = j
      j = (k - 1) / 2
    end
  end

  def downheap
    k = 0
    i = 2 * k + 1    # left child of k
    j = 2 * k + 2    # right child of k
    until @heap[i].nil?    # no children
      if @heap[j].nil? || @heap[i] < @heap[j]
        if @heap[k] < @heap[i]
          break
        else
          @heap[i], @heap[k] = @heap[k], @heap[i]
          k = i
          i = 2 * k + 1
          j = 2 * k + 2
        end
      else
        if @heap[k] < @heap[j]
          break
        else
          @heap[j], @heap[k] = @heap[k], @heap[j]
          k = j
          i = 2 * k + 1
          j = 2 * k + 2
        end
      end
    end
  end

end   # of class Heap

def heap_sort(ary)
  heap = Heap.new
  ary.each{|x| heap.insert(x) }
  result = []
  while y = heap.shift
    result << y
  end
  result
end

if $0 == __FILE__
  ary = (1..100).to_a.sample(10)
  puts "unsorted: " + ary.inspect
  result = heap_sort(ary)
  puts "sorted: " + result.inspect
end

実行例:

unsorted: [15, 70, 93, 27, 5, 8, 26, 40, 74, 67]
sorted:   [5, 8, 15, 26, 27, 40, 67, 70, 74, 93]

[おまけ]

二分木を使ったヒープソート(heap_sort.rb)と今日の配列を使ったヒープソート(heap_sort2.rb)の実行時間の比較をしてみた。

takatoh@nightschool $ time ruby heap_sort.rb
unsorted: [85, 61, 68, 9, 73, 79, 26, 84, 21, 13]
sorted:   [9, 13, 21, 26, 61, 68, 73, 79, 84, 85]

real	0m0.102s
user	0m0.019s
sys	0m0.008s
takatoh@nightschool $ time ruby heap_sort2.rb
unsorted: [65, 26, 92, 51, 33, 31, 61, 97, 34, 57]
sorted:   [26, 31, 33, 34, 51, 57, 61, 65, 92, 97]

real	0m0.034s
user	0m0.017s
sys	0m0.017s

配列を使ったヒープソートのほうが3倍位速い。ま、そうだろうな…と思ったら、user の時間は大して変わらないな。

RubyからQtを使ってみる

Web を眺めてたら、こんなページを見つけたので試してみることにした。

 cf. QtをRubyで扱おう(qtbindings) – Laboratory of Scarlet

Qt はクロスプラットフォームの GUI フレームワーク。Ruby からは qtbindings (Windows では qtruby4)を経由して使える。
早速インストール。まずは関連ライブラリから。

takatoh@nightschool $ sudo apt-get install libqt4-gui libqt4-dev cmake

そして qtibindings のインストール。

takatoh@nightschool $ gem install qtbindings

さあ、これで使えるようになったはずだ。
上のリンク先に載っているサンプルプログラムを作ってみよう。

require 'Qt'

app = Qt::Application.new(ARGV)
hello = Qt::PushButton.new("Hello world!", nil)
hello.resize(300, 50)
hello.show

app.exec

実行結果

qt_hello

出来たみたい。

二分ヒープ(ヒープ木)を使ったヒープソート

ヒープソートって配列を使ってやるのがフツウみたいだけど、アルゴリズムのアイデアに忠実に二分ヒープ(木)を使ってやってみた。
参考にしたページはココらへん。

 cf. 二分木 (binary tree) とヒープ (heap) – Algorithms with Python
 cf. 二分ヒープ – ウィキペディア

class Node

  attr_accessor :val, :left, :right
  attr_reader :parent

  def initialize(val, parent = nil)
    @val = val
    @parent = parent
    @left = nil
    @right = nil
  end

end   # of class Node

class HeapTree

  def initialize
    @root = nil
  end

  def insert(val)
    if @root.nil?
      @root = Node.new(val)
    else
      v = vacant
      n = Node.new(val, v)
      if v.left.nil?
        v.left = n
      else
        v.right = n
      end
      upheap(n)
    end
  end

  def shift
    if @root.nil?
      nil
    else
      val = @root.val
      @root.val = delete_last
      downheap(@root)
      val
    end
  end

  def upheap(node)
    until node.parent.nil? || node.val > node.parent.val
      v = node.val
      node.val = node.parent.val
      node.parent.val = v
      node = node.parent
    end
  end

  def downheap(node)
    return nil if node.nil?
    until node.left.nil?
      if node.right.nil? || node.left.val < node.right.val
        if node.val > node.left.val
          v = node.val
          node.val = node.left.val
          node.left.val = v
          node = node.left
        else
          break
        end
      else
        if node.val > node.right.val
          v = node.val
          node.val = node.right.val
          node.right.val = v
          node = node.right
        else
          break
        end
      end
    end
  end

  def vacant
    q = [@root]
    while n = q.shift
      if n.left.nil? || n.right.nil?
        return n
      else
        q << n.left
        q << n.right
      end
    end
  end

  def last
    q = [@root]
    begin
      n = q.shift
      q << n.left if n.left
      q << n.right if n.right
    end until q.empty?
    n
  end

  def delete_last
    node = last
    if node.parent.nil?
      @root = nil
    else
      if node.parent.right == node
        node.parent.right = nil
      else
        node.parent.left = nil
      end
    end
    node.val
  end

end   # of HeapTree


if $0 == __FILE__
  ary = (1..100).to_a.sample(10)
  puts "unsorted: " + ary.inspect
  heap = HeapTree.new
  ary.each do |x|
    heap.insert(x)
  end
  sorted = []
  while v = heap.shift do
    sorted << v
  end
  puts "sorted: " + sorted.inspect
end

なんだかあんまりきれいじゃないけど。特に downheap のあたりが。 とりあえず出来たって感じ。 最初は upheap や downheap でノード自体を入れ替えようとしてたのでもっとひどかった。ノードはそのままにしておいて持っている値だけ入れ替えればいいことに気づいていくらかマシになった。 実行結果:

takatoh@nightschool $ ruby heap_sort.rb
unsorted: [75, 63, 96, 9, 21, 92, 48, 83, 51, 31]
sorted:   [9, 21, 31, 48, 51, 63, 75, 83, 92, 96]

Rubyでクイックソート

cf. RubyでクイックソートとRSpecでそのテストを書いてみる – 凸ろぐ

なんだかまどろっこしい書き方をしてるなあ。これでいいんじゃないか?

require 'benchmark'

class Array
  def qsort
    return self if self.size <= 1
    pivot = self.shift
    left = self.select{|x| x < pivot }
    right = self.select{|x| pivot <= x }
    return left.qsort + [pivot] + right.qsort
  end
end

ary = [1,2,3,4,5,6,7,8,9,10].shuffle
ary2 = ary.dup

Benchmark.bm(13) do |x|
  x.report("Array#sort: ") { ary.sort }
  x.report("Array#qsort:") { ary2.qsort }
end
takatoh@nightschool $ ruby qsort.rb
                    user     system      total        real
Array#sort:     0.000000   0.000000   0.000000 (  0.000006)
Array#qsort:    0.000000   0.000000   0.000000 (  0.000013)

Array#soft 速いな。やっぱり自前で作るよりあるものを使ったほうがいいってことか。
いや、リンク先の実装だと速いのか?

require 'benchmark'

class Array
  def qsort
    return self if self.size <= 1
    mark = self[0]
    right = Array.new
    left = Array.new
    (1..self.size-1).each do |i|
      if self[i] <= mark
        left.push(self[i])
      else
        right.push(self[i])
      end
    end
    left = left.qsort
    right = right.qsort
    return left + [mark] + right
  end
end

ary = [1,2,3,4,5,6,7,8,9,10].shuffle
ary2 = ary.dup

Benchmark.bm(10) do |x|
  x.report("Array#sort: ") { ary.sort }
  x.report("Array#qsort:") { ary2.qsort }
end
takatoh@nightschool $ ruby qsort2.rb
                 user     system      total        real
Array#sort:   0.000000   0.000000   0.000000 (  0.000006)
Array#qsort:  0.000000   0.000000   0.000000 (  0.000019)

そんなことはなかった。

RubyのArray#shiftには引数に整数を指定できる

やばい。今日書かないと2月は1日も書いてないことになってしまう。
というわけで今日知った小ネタ。

なんか 1.8.7 の頃からできるようなんだけど今頃知った。

2.1.1 :001 > a = (0..10).to_a
 => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 
2.1.1 :002 > a.shift(3)
 => [0, 1, 2] 
2.1.1 :003 > a
 => [3, 4, 5, 6, 7, 8, 9, 10]