2013年7月13日土曜日

[C++]メンバ変数を比較してソート

テンプレートを使う練習をかねて、構造体のメンバ変数同士を比較してソートしてみます。

#include <iostream>
#include <iomanip>
#include <algorithm>
#include <string>

using namespace std;

/* メンバ変数同士を比較するためのクラス */
template <typename T, typename Elem>
class MemberComp {
private:
  Elem T::* e;

public:
  MemberComp(Elem T::* e) {
    this->e = e;
  };

  bool operator()(const T &a, const T&b) {
    return a.*e < b.*e;
  };
};

template <typename T, typename Elem, typename Nest>
class NestedMemberComp {
private:
  Nest T::* nest;
  Elem Nest::* e;

public:
  NestedMemberComp(Nest T::* nest, Elem Nest::* e) {
    this->nest = nest;
    this->e = e;
  };

  bool operator()(const T &a, const T&b) {
    return a.*nest.*e < b.*nest.*e;
  };
};

/* インスタンス作成時に型名を省略するための関数 */
template<typename T, typename Elem>
MemberComp<T, Elem> make_comp(Elem T::* e) {
  return MemberComp<T, Elem>(e);
}

template<typename T, typename Elem, typename Nest>
NestedMemberComp<T, Elem, Nest> make_comp(Nest T::* nest, Elem Nest::* e) {
  return NestedMemberComp<T, Elem, Nest>(nest, e);
}

/* 比較対象の構造体 */
struct Data {
  int a;
  double b;
  struct _c {
    int d;
  } c;
};

void print_data(Data *t, int n) {
  for (int i = 0; i < n; ++i) {
    cout << t[i].a << ", "
         << setprecision(2) << setiosflags(ios::fixed) << t[i].b 
         << ", " << t[i].c.d << endl;
  }
}

int main(void) {
  Data target[3] = {{0, 1.5, {3}},
                    {1, 3.0, {2}},
                    {2, 2.0, {1}}};

  print_data(target, 3);

  cout << "b でソート" << endl;
  std::sort(target, target + 3, make_comp(&Data::b));
  print_data(target, 3);

  cout << "c.d でソート" << endl;
  std::sort(target, target + 3, make_comp(&Data::c, &Data::_c::d));
  print_data(target, 3);

  // lambda式を使える場合
  cout << "lambda式を使って b でソート" << endl;
  std::sort(target, target + 3, [](const Data &a, const Data &b) { return a.b < b.b; });
  print_data(target, 3);

  return 0;
}

実行結果

0, 1.50, 3
1, 3.00, 2
2, 2.00, 1
b でソート
0, 1.50, 3
2, 2.00, 1
1, 3.00, 2
c.d でソート
2, 2.00, 1
1, 3.00, 2
0, 1.50, 3
lambda式を使って b でソート
0, 1.50, 3
2, 2.00, 1
1, 3.00, 2

lambda式を使えると楽で良さそう。C++11を使えればなぁ。

2013年7月7日日曜日

[Common Lisp] FTPでファイル転送

最近、複数のサーバーのいくつかのディレクトリにファイルを転送する、という作業が必要になりました。

expect + scp(ftp)なシェルスクリプトが作成されましたが、1ファイルごとに接続を行っていて遅かったので python + ftplib で書き直したりしました。

久々に Common Lisp を書こうと思ったので、ftpでの転送処理を題材にしてみます。

1 ライブラリを探す

さすがにftpを実装しようと試みたりはせず、ライブラリを探します。
quicklisp の system-apropos 関数を使ってそれっぽい名前のライブラリを探してみます。

 CL-USER> (ql:system-apropos "ftp")
#<SYSTEM ftp / cl-ftp-20101006-http / quicklisp 2013-06-15>

cl-ftp を使うことにします。

ファイルパスの処理も行いたいです。
組み込みの関数や cl-fad だとパスネームからファイル名+拡張子を取ってくる方法が分からなかったので、 ファイルパスの処理用のライブラリも探してみます。

(ql:system-apropos "path")
#<SYSTEM cl-paths / cl-vectors-20130312-git / quicklisp 2013-06-15>
#<SYSTEM cl-paths-ttf / cl-vectors-20130312-git / quicklisp 2013-06-15>
#<SYSTEM com.gigamonkeys.pathnames / monkeylib-pathnames-20120208-git / quicklisp 2013-06-15>
#<SYSTEM iolib.pathnames / iolib-0.7.3 / quicklisp 2013-06-15>
#<SYSTEM xpath / plexippus-xpath-20120909-darcs / quicklisp 2013-06-15>

iolib.pathnames を使ってみることにします。

ハッシュテーブルのキーと値に対するループ処理を行いたいです。
loopマクロでハッシュテーブルを使おうとすると記述が面倒なので、ユーティリティマクロを定義します。 が、自分で書くのは面倒なのでライブラリを使います。

Common Lispにはユーティリティライブラリがたくさん存在しますが、 今回は最近登場した Quickutil というユーティリティライブラリを使って、 使いたいユーティリティだけを読み込むことにします。

2 コードを書く

;; ** ライブラリ読み込み
;; quicklispで cl-ftp と iolib.pathnames を取得して読み込む。
(ql:quickload :ftp)
(ql:quickload :iolib.pathnames)

;; quickutilを読み込む。
;; 現時点(2013年7月6日)では、まだ quicklisp に登録されていないので、
;; git cloneしたりアーカイブをダウンロードしたりして
;; quicklisp/local-protects の中に保存しておく。
(ql:quickload :quickutil)

;; quickutil を使って dohash マクロと alist-hash-table 関数を読み込む
(qtlc:utilize-utilities '(:dohash :alist-hash-table))


;; ** 実装
(defun make-target-file-name (source-file target-dir)
  (let* ((fname (iolib.pathnames:file-path-file
                 (iolib.pathnames:file-path source-file)))
         (target-fname
           (iolib.pathnames:merge-file-paths fname target-dir)))
    target-fname))

(defun ftp-copy-file (conn file target-dir)
  (let ((target-fname (make-target-file-name file target-dir)))
    (format t " - copy to: ~A~%" target-fname)
    (ftp:store-file conn
                    file
                    (iolib.pathnames:file-path-namestring target-fname))))

;; Emacs: (put 'qtl:dohash 'common-lisp-indent-function 1)
(defun ftp-copy (remote-host copy-info-map &key port username password)
  (ftp:with-ftp-connection (conn :hostname remote-host
                                 :port port
                                 :passive-ftp-p t
                                 :username username
                                 :password password) 
    (format t "connect to [~A]~%" remote-host)
    (qtl:dohash (dir files copy-info-map)
      (dolist (file files)
        (ftp-copy-file conn file dir)))))

;; (転送先ディレクトリ . (転送するファイル ...))
;; リモートの tmp ディレクトリに
;; ローカルのカレントディレクトリにある 1.txt, 2.txt を転送する設定
(defparameter *copy-info-map*
  (qtl:alist-hash-table
   '(("tmp" "1.txt" "2.txt"))
  :test #'equal))

(defparameter *remote-hosts*
  '("localhost"))

(defun run ()
  (dolist (remote-host *remote-hosts*)
    (ftp-copy remote-host *copy-info-map*
              :port 10000
              :username "test"
              :password "test")))

;;  (run)

なお、作業マシンにはftpサーバーを入れてなかったので、 python の pyftpdlib で適当にでっちあげて実行しました。

2013年6月16日日曜日

NetBSDでEmacsを起動するまで

ベアボーンPCにNetBSDをインストールし、そのまま放置して1ヶ月以上が経過していました。

パスワードすら忘れてしまったNetBSDにEmacsを入れるまでの道筋を忘れないようにメモ。

1 パスワード再設定

とくに使うことなく放置していたせいでパスワードを忘たので、再設定を行いました。

  • シングルユーザーモードで起動する
  • 以下のコマンドでパスワード再設定
# マウント済みファイルシステムの状態を変更することを指示する
> mount -u

# fstabに書かれたとおりにマウントする
> mount -a

# rootのパスワードを再設定する
> passwd

2 DHCP有効化

インストール時にネットワークの設定をやっていなかったようなので、DHCPを有効にしました。

> /etc/rc.d/dhclient start

起動時に有効になるよう、rc.confを書き換えました。

# /etc/rc.confに以下の行を追加
dhclient=YES

3 パッケージ管理システムを入れる

NetBSD では pkgsrc というパッケージ管理システムでソースからビルドするのが正義っぽいので、入れてみました。

バイナリのインストールだけならば、 quickstart に書かれているように PKG_PATH 環境変数にURLを設定して、 pkg_addコマンドを実行すれば良いようです。

ソースからビルドする場合、 pkgssrc を落としてくる必要があるようです。

# cvsコマンドのバイナリを入れる
## uname -s => NetBSD
## uname -m => amd64
## uname -r => 6.0.1
## cut -f '1 2' -d. => デリミタ"." で区切ったフィールドの1要素め+2要素め
## uname -r | cut -f '1 2' -d. => 6.0
> PKG_PATH="http://ftp.NetBSD.org/pub/pkgsrc/packages/$(uname -s)/$(uname -m)/$(uname -r|cut -f '1 2' -d.)/All"
> export PKG_PATH
> pkg_add cvs

# ビルドに必要なコマンドを取得( comp.tgz, text.tgz )
> ftp ftp://ftp.netbsd.org/pub/NetBSD/NetBSD-6.0.1/amd64/binary/sets/
ftp> get comp.tgz
ftp> get text.tgz
ftp> quit
> tar zxvfp comp.tgz
> tar zxvfp text.tgz

# csv を使って pkgsrc を落とす
> cvs -danoncvs@anoncvs.netbsd.org:/cvsroot checkout pkgsrc

# bootstap (必要ない?)
> cd pkgsrc/bootstrap
> ./bootstrap

# make install
> cd pkgsrc/emacs24-nox11
> make install clean

2013年3月10日日曜日

[Clojure]PegDownとJavaFXでMarkdownを表示する

JavaFXにはHTMLをレンダリングしてくれる WebView というコンポーネントがあるようです。

JavaのMarkdownプロセッサ PegDown と WebView を利用して Markdownファイルを画面に表示してみます。

;; leiningenでAOTコンパイルするファイルを指定
:aot [markdown-viewer.App]
;; App.clj
(ns markdown-viewer.App
  (:import javafx.application.Application
           javafx.scene.Scene
           javafx.scene.control.Label
           javafx.scene.web.WebView
           javafx.stage.Stage
           javafx.stage.FileChooser
           javafx.stage.FileChooser$ExtensionFilter
           [org.pegdown PegDownProcessor Extensions])
  (:gen-class
   :extends javafx.application.Application))

(defn md->html [^String source]
  (let [parser (PegDownProcessor. Extensions/ALL)]
    (.markdownToHtml parser source)))

(defn make-md-file-chooser ^FileChooser []
  (let [fc (FileChooser.)]
    (.setTitle fc "select Markdown file")
    (-> fc .getExtensionFilters
        (.add (FileChooser$ExtensionFilter. "Markdown" ["*.md" "*.markdown"])))
    fc))

(defn load-html [^WebView wview ^String html]
  (.loadContent (.getEngine wview) html))

(defn load-md [wview md]
  (load-html wview (md->html md)))

(defn -start [this ^Stage stage]
  (let [wview (WebView.)
        fc (make-md-file-chooser)
        ;; ファイル選択
        file (.showOpenDialog fc stage)]
    (when file
      (.setScene stage (Scene. wview 750 500))
      (load-md wview (slurp file))
      (.show stage))))
;; core.clj
(ns markdown-viewer.core
  (require markdown-viewer.App)
  (:gen-class))

(defn -main [& args]
  (javafx.application.Application/launch markdown_viewer.App args))

2013年3月6日水曜日

[Clojure]動的にライブラリを追加する

nREPL経由でClojureを使っていると、再起動させずにライブラリを読み込みたいと思うことがあります。 pomegranate というライブラリを使うと、動的にライブラリを取得してクラスパスに追加してくれます。

leiningenのprofile.cljやproject.cljにライブラリを追加します。

;; :dependenciesに追加
[com.cemerick/pomegranate "0.0.13"]

READMEにある例は以下のようになっています。

(use '[cemerick.pomegranate :only (add-dependencies)])

;; Maven CentralとClojarsをレポジトリとして指定し、incanterを取得
(add-dependencies
 :coordinates '[[incanter "1.5.0-SNAPSHOT"]]
 ;; proxyも指定可能
 ;; :proxy {:host "x.x.x.x" :port 8080}
 :repositories (merge cemerick.pomegranate.aether/maven-central
                      {"clojars" "http://clojars.org/repo"}))

;; Incanterを使ってみる
(use '[incanter core charts])
(doto (function-plot sin -4 4 :y-label "y" :series-label "sin" :legend true)
  (add-function cos -4 4 :series-label "cos")
  view)

[Clojure]Cloverageでカバレッジ計測

Clojureのカバレッジ計測用ライブラリ Cloverage を使ってみます。

leiningenのプラグインとして利用できるので、profile.cljの:pluginsにプラグインを追加します。

;; ~/.lein/profile.clj の :plugins に追加
[lein-cloverage "1.0.2"]

カバレッジの計測対象となるプロジェクトを作成します。

> lein new cloverage-test

プログラムを作成します。

;; src/cloverage_test/core.clj
(ns cloverage-test.core)

(defn fizzbuzz [n]
  (case (mod n 15)
    0 "fizzbuzz"
    (3 6 9 12) "fizz"
    (5 10) "buzz"
    (str n)))

(defn run [end]
  (mapv fizzbuzz (range 1 (inc end))))

テストコードを作成します。

;; test/cloverage_test/core_test.clj
(ns cloverage-test.core-test
  (:use clojure.test
        cloverage-test.core))

(deftest fizzbuzz-test
  (testing "run"
    (is (= ["1" "2" "fizz"] (run 3)))))

テスト+カバレッジ計測を実行します。(lein cloverage)

 > lein cloverage
Test namespaces:  (cloverage-test.core-test)
Performance warning, cloverage_test/core.clj:3 - case has int tests, but tested expression is not primitive.
Loaded  cloverage-test.core  .
Instrumented namespaces.

Testing cloverage-test.core-test

Ran 1 tests containing 1 assertions.
0 failures, 0 errors.
Ran tests.
Produced output in /home/kurohuku/project/cloverage-test/target/coverage .
HTML: file:///home/kurohuku/project/cloverage-test/target/coverage/index.html

以下のようなHTMLが出力されます。

テストコードを追加してすべての行が実行するようにしてみます。

;; test/cloverage_test/core_test.clj
(ns cloverage-test.core-test
  (:use clojure.test
        cloverage-test.core))

(deftest fizzbuzz-test
  (testing "run"
    (is (= ["1" "2" "fizz" "4" "buzz"
            "fizz" "7" "8" "fizz" "buzz"
            "11" "fizz" "13" "14" "fizzbuzz"]
           (run 15)))))

2013年3月5日火曜日

[Clojure]LWJGLをでOpenGL

LWJGLでOpenGLの画面を表示してみます。 Javaで書くのと大差無いです。多分。

;; dependenciesに追加
[org.lwjgl/lwjgl "2.7.1"]
[org.lwjgl/lwjgl-util "2.7.1"]
[org.lwjgl/lwjgl-native-platform "2.7.1"]
(import '[org.lwjgl.opengl DisplayMode Display GL11])

(def width 640)
(def height 480)
(def display-mode (DisplayMode. width height))

(defn draw []
  (GL11/glClear GL11/GL_COLOR_BUFFER_BIT)
  (GL11/glBegin GL11/GL_LINES)
  (GL11/glColor3d 1.0 0.0 0.0)
  (GL11/glVertex3d 1.0 1.0 0.0)
  (GL11/glVertex3d (- width 1) (- height 1) 0.0)
  (GL11/glEnd)
  (GL11/glFlush))

(defn start []
  (Display/setDisplayMode display-mode)
  (Display/setTitle "Hello")
  (Display/create)
  (GL11/glEnable GL11/GL_CULL_FACE)
  (GL11/glCullFace GL11/GL_BACK)
  (GL11/glMatrixMode GL11/GL_PROJECTION)
  (GL11/glLoadIdentity)
  (GL11/glOrtho 0 width 0 height 0 1)
  (GL11/glMatrixMode GL11/GL_MODELVIEW)
  (while (not (Display/isCloseRequested))
    (draw)
    (Display/update))
  (Display/destroy))

; (start)

CやC++でOpenGLを書く必要に迫られたら、プロトタイプをClojureで書いてみることもできそうです。