2012年11月29日木曜日

Clojure+Apache POI+Event API

Apache POIで普段使うのは'User API'というAPIのようですが、 このAPIを利用して大きなファイルを扱おうとすると大量のメモリを必要とし、OutOfMemoryが発生します。

この問題を回避するには、

  • ヒープ領域を増やして実行する
  • Event APIを使う
  • Streaming User APIを使う
  • xlsxファイルを解凍して中身のxmlファイルを処理する

といった解決方法があるようです。

ためしに、Clojure+Apache POIでEvent APIを利用したコードを書いてみます。
ぱっと見た限りではxlsxファイルを解凍して処理する手間の一部を省いてくれているだけのようです。

;; project.clj
(defproject poixml "0.1.0-SNAPSHOT"
  :description "dummy"
  :url "dummy"
  :license {:name "dummy"}
  :dependencies [[org.clojure/clojure "1.4.0"]
                 [org.apache.poi/poi "3.8"]
                 [org.apache.poi/poi-ooxml "3.8"]
                 [org.clojure/data.xml "0.0.6"]]
  :main poixml.core)
;; src/poixml/core.clj
(ns poixml.core
  (:gen-class))

;; @see http://poi.apache.org/spreadsheet/how-to.html

(require 'clojure.data.xml)

(import
 '(org.apache.poi.xssf.eventusermodel XSSFReader)
 '(org.apache.poi.xssf.model SharedStringsTable)
 '(org.apache.poi.xssf.usermodel XSSFSheet
                                 XSSFWorkbook
                                 XSSFRow
                                 XSSFCell
                                 XSSFRichTextString))

(defn call-with-cell-value [path f]
  (let [reader (XSSFReader. (org.apache.poi.openxml4j.opc.Package/open path))
        sst (.getSharedStringsTable reader)]
    (with-open [istream (.next (.getSheetsData reader))]
      (doseq [r (:content (first
                           (filter #(= :sheetData (:tag %))
                                   (:content (clojure.data.xml/parse istream)))))]
        (when (= :row (:tag r))
          (doseq [[col cell] (map vector (iterate inc 1) (:content r))]
                 (let [val (first (filter #(= :v (:tag %)) (:content cell)))]
                   (f (Integer/parseInt (:r (:attrs r))) ; 行番号
                      col ; 列番号
                      (if (= "s" (:t (:attrs cell)))
                          ;; 文字列はSharedStringsTableにある
                          (-> (.getEntryAt sst (bigint (first (:content val))))
                              XSSFRichTextString.
                              .toString)
                          (first (:content val)))))))))))

;; --------------------------------------------------
;; 適当にxlsxファイルを作成
(defn create-3x3 [path]
  (let [wb (XSSFWorkbook.)
        sh (.createSheet wb)]
    (dorun
     (for [y (range 3)]
       (.createRow sh y)))
    (dorun
     (for [x (range 3) y (range 3)]
       (-> (.createCell (.getRow sh y) x)
           (.setCellValue (str (* (inc x) (inc y)))))))
    (with-open [out (java.io.FileOutputStream. path)]
      (.write wb out))))

;; xlsxファイルの各セルを表示
(defn print-cells [path]
  (call-with-cell-value path
    (fn [row col val]
        (printf "[%d,%d] %s\n" row col val)
        (flush))))

;; 引数に作成するファイル名を指定
(defn -main
  [& args]
  (when (= (count args) 1)
    (let [path (first args)]
      (create-3x3 path)
      (print-cells path))))
> lein run test.xlsx 
Compiling poixml.core
[1,1] 1
[1,2] 2
[1,3] 3
[2,1] 2
[2,2] 4
[2,3] 6
[3,1] 3
[3,2] 6
[3,3] 9

2012年11月15日木曜日

Clojure+seesawでMigLayoutを利用する

ClojureのGUIライブラリseesawでMigLayoutを使ってみます。
簡単なツールを作ったときにGUIをでっちあげるのに使えそうです。

(ns seesaw-test.core
  (:use [seesaw core chooser mig])
  (:gen-class))

(defn set-filename [root target]
  (choose-file
   :success-fn #(text! (select root [target])
                       (.getAbsolutePath %2))))

(defn run []
  (let [root (frame :title "mig-layout test"
                    :on-close :exit
                    :size [400 :by 200])]
    (config!
     root
     :content (mig-panel
               ;; [Layout Row Column]
               :constraints ["fillx" "[][grow,fill][]" ""]
               :items  ; [ウィジェット 設定(省略可)]
               [["名前:"]
                ;; wrap で次の行へ進む
                [(text :id :name :text "name") "wrap"]

                ["ファイル:"]
                [(text :id :file :text "file")]
                [(action :name "選択"
                         :handler (fn [_] (set-filename root :#file)))
                 "wrap"]

                [(action :name "OK"
                         :handler (fn [_]
                                    (alert
                                     (str
                                      "Name:" (text (select root [:#name]))
                                      "\n"
                                      "File:" (text (select root [:#file]))))))
                 "span 3, center"]]))
    root))

(defn -main
  [& args]
  (native!)
  (invoke-later (show! (pack! (run)))))

2012年11月13日火曜日

お酒に関する用語:単位・量

酒に関する用語のうち、単位や量に関するもののメモ。

  • 適量
    飲酒するときの適量はアルコール量でだいたい20mlくらいらしい。
    一般的な度数で考えると、飲酒量換算は以下のようになると思われる。
    ビール (5%)20ml * 100/5 = 400ml
    ワイン (13%)20ml * 100/13 = 153ml
    日本酒 (15%)20ml * 100/15 = 133ml = 1合(180ml)程度
    焼酎(25%)20ml * 100/25 = 80ml
    ウィスキー (40%)20ml * 100/40 = 50ml = シングル(30ml) ~ ダブル(60ml)
  • アルコール度数
    酒の容量のうちどの程度がアルコール(エタノール)かを表す割合。
  • プルーフ proof
    アルコール容量の単位。アルコール度数1度 = 2 proof。
  • パイント(pint)
    体積の単位。イギリスでは570ml(20英液量オンス)、アメリカでは470ml(16米液量オンス)。
  • シングル
    主に洋酒を飲むときのお酒1杯あたりの容量。だいたいの場合30ml。
  • ダブル
    シングルの倍の容量。60ml。
  • 合(ごう)
    尺貫法の体積の単位で1/10升。主に日本酒で使う。1合=180mlくらい。
    日本酒の瓶の小さめのものは4合瓶なので720ml入り。
  • 升(しょう)
    尺貫法の体積の単位で10合。1升=10合=1800mlくらい。
    日本酒の瓶の大きいものは1升瓶なので1800ml入り。
  • ドロップ(drop)
    カクテルのレシピに書いてある量。1滴。
  • ダッシュ(dash)
    カクテルのレシピに書いてある量。1ダッシュは5,6滴(1ml)くらい。
  • ティースプーン(tsp)
    カクテルのレシピに書いてある量。1tspは小さじ1杯(5ml)くらい。
  • 日本酒度
    日本酒の甘口・辛口の目安。+になるほど糖分が少なく(辛口)、-になるほど糖分が多い(甘口)。
    日本酒を15℃にしたときに4℃の蒸留水と同じ重さなら日本酒度0で、以下の用な関係式になる(Wikipedia情報)
    日本酒度 = ((1/比重) - 1) x 1,443
  • 甘辛度
    日本酒の甘口・辛口の目安。+になるほど甘い。 計算式は以下のとおり(Wikipedia情報)
    0.86 x ブドウ糖濃度 - 1.16 x 酸度 - 1.31 または
    (193,593 / (1,443 + 日本酒度)) - 1.16 x 酸度 - 132.57
  • 国際苦味単位 (IBU: International Bitterness Units)
    ビールの苦味の単位。

2012年11月10日土曜日

オイラーの贈物(1.2)とLispと順列

二項展開(binomial expansion)の展開後の各項の係数(二項係数(binomial coefficient))は 階乗(factorial)を利用して以下の用に書けます。

${}_n C _r \equiv \frac{n!}{r!(n - r)!}$
latexの数式
${}_n C _r \equiv \frac{n!}{r!(n - r)!}$
;; Common Lisp

(defun recursive-factorial (n)
  (check-type n (integer 0 *))
  (if (zerop n)
      1
      (* n (recursive-factorial (1- n)))))

(defun factorial (n)
  (check-type n (integer 0 *))
  (if (zerop n)
      1
      (loop
         :named loop
         :for i from 1 to n
         :for result = i then (* i result)
         :finally (return-from loop result))))

(defvar *memoized-factorial* (make-hash-table))
(setf (gethash 0 *memoized-factorial*) 1)
(setf (gethash 1 *memoized-factorial*) 1)
(defun memoized-factorial (n)
  (check-type n (integer 0 *))
  (let ((x (gethash n *memoized-factorial*)))
    (if x
        x
        (let ((y (* n (memoized-factorial (1- n)))))
          (setf (gethash n *memoized-factorial*) y)
          y))))

(defun binomial-coefficient (n r)
  (check-type n (integer 0 *))
  (check-type r (integer 0 *))
  (assert (<= r n))
  ;; n! / r!(n - r)!
  (/ (memoized-factorial n)
     (* (memoized-factorial r)
        (memoized-factorial (- n r)))))

この式はn個の中からr個を取る組み合わせ(combination)の数を求める式でもあります。

要素の順番も考慮する順列(permutation)の数の場合は、以下のようになります。

${}_n P _r \equiv \frac{n!}{(n - r)!}$
latexの数式
$ {}_n P _r \equiv \frac{n!}{(n - r)!}$
(defun permutation (n r)
  (check-type n (integer 0 *))
  (check-type r (integer 0 *))
  (assert (<= r n))
  ;; n! / (n - r)!
  (/ (memoized-factorial n)
     (memoized-factorial (- n r))))

多くのプログラミング言語では順列を生成するための機能が用意されているようです。

RubyのArrayクラス(array.cで定義)とC++のstd::next_permutationを使ってみます。

> p [1, 2, 3].permutation(2).to_a
[[1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]]

## nPr = 3P2 = 3! / (3-2)! = 3! = 6
> p [1, 2, 3].permutation(2).to_a.size
6

> p [1, 2, 3].combination(2).to_a
[[1, 2], [1, 3], [2, 3]]

## nCr = 3C2 = 3! / 2!(3-2)! = 3! / 2 = 3
> p [1, 2, 3].combination(2).to_a.size
3
#include <algorithm>
#include <cstdio>

void print_array(int arr[3]){
        printf("%d, %d, %d\n", arr[0], arr[1], arr[2]);
}

int main(void){
        int arr[3] = {1, 2, 3};
        std::sort(arr, arr+3);
        int count = 0;
        do {
                print_array(arr);
                count++;
        } while(std::next_permutation(arr, arr+3));

        printf("count = %d\n", count);
        return 0;
}

// 1, 2, 3
// 1, 3, 2
// 2, 1, 3
// 2, 3, 1
// 3, 1, 2
// 3, 2, 1
// count = 6

これらのアルゴリズムをCommon Lispで書いてみます。

なお、Common Lispには順列を操作するためのライブラリとして cl-permutation があります。 プログラム中で順列を生成したりしたい場合はわざわざ自作せずありがたく利用させて頂きましょう。

;; from Ruby (array.c)
(defun permute0 (n r p index used vals result)
  (dotimes (i n)
    (when (zerop (bit used i))
      ;; 順列のindex番目の要素として元の配列のi番の要素を選択
      (setf (svref p index) i)
      (if (< index (1- r))
          (progn
            ;; 選択済みの要素に対応するフラグをONにする
            (setf (bit used i) 1)
            (permute0 n r p (1+ index) used vals result)
            ;; 選択済みフラグをOFFにする
            (setf (bit used i) 0))
          (progn
            ;; 添字の配列から値の配列を作成して結果配列に追加
            (vector-push 
             (loop :for j :across p :collect (elt vals j))
             result))))))

(defun permute (seq r)
  (let* ((n (length seq))
         (result (make-array (permutation n r) :fill-pointer 0 :adjustable t))
         (used (make-array n :element-type 'bit :initial-element 0))
         (p (make-array r :element-type '(integer 0 *))))
    (permute0 n r p 0 used seq result)
    result))
> (permute '(1 2 3) 2)
#((1 2) (1 3) (2 1) (2 3) (3 1) (3 2))
;; from C++ (std::next_permutation)
(defun next-permutation (arr)
  (let ((len (length arr)))
    (unless (or (= len 0) (= len 1))
      (loop
         :for pos :from (1- len) :downto 1
         :when (< (aref arr (1- pos)) (aref arr pos))
         :do (progn
               (rotatef (aref arr (1- pos))
                        (aref arr
                              (position-if
                               #'(lambda (x) (< (aref arr (1- pos)) x))
                               arr
                               :from-end t)))
               (setf (subseq arr pos)
                     (nreverse
                      (make-array (- len pos)
                                  :displaced-to arr
                                  :displaced-index-offset pos)))
               (return-from next-permutation t))))))
> (defmacro do-while (test &body body)
          `(loop
              :do (progn ,@body)
              :while ,test))
> (let ((tmp (vector 0 1 2)))
          (do-while (next-permutation tmp)
            (print tmp)))
#(0 1 2) 
#(0 2 1) 
#(1 0 2) 
#(1 2 0) 
#(2 0 1) 
#(2 1 0)

他にも色々なアルゴリズムが存在するようです。 その中のひとつとして、階乗進数 を利用したプログラムを書いてみます。

(defun factoradic-permutation (n width)
  (let ((fact (make-array width :initial-element 1))
        (mantissa (make-array width :initial-element 0)))
    ;; 階乗を計算して配列に設定
    (loop
       :for i :from 1 :below width
       :for acc := i :then (* acc i)
       :do (setf (aref fact i) acc))
    ;; 階乗進数の仮数を計算して配列に設定
    (loop
       :for i :from (1- width) :downto 1
       :for acc := n :then (mod acc (aref fact (1+ i)))
       :do (setf (aref mantissa i)
                 (floor acc (aref fact i))))
    ;; 階乗進数を利用して順列を作成 (配列mantissaを使いまわす)
    (let ((tmp (loop :for i :from 0 :below width :collect i)))
      (loop
       :for i :from (1- width) :downto 0
       :for x := (aref mantissa i)
       :do (setf (aref mantissa i) (nth x tmp)
                 tmp (delete (nth x tmp) tmp))))
    (nreverse mantissa)))
> (dotimes (n 6)
>  (print (factoradic-permutation n 3)))
#(0 1 2) 
#(0 2 1) 
#(1 0 2) 
#(1 2 0) 
#(2 0 1) 
#(2 1 0)