Rust で git cat-file -p コマンドを作る

晴れて無職となったので、今後はもうちょい積極的に更新していこうと思う。

これは何

https://engineering.mercari.com/blog/entry/2015-09-14-175300/
こちらの記事で解説されていることをRustとGitの仕組みの勉強を兼ねて試してみた。

方法

zlib圧縮されたファイルを解凍する。

解説

今回したこと

リポジトリこちら

$ cargo run .git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad 
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/git_cat_file_p .git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad`
hello world

$ cargo run .git/objects/a4/940ec3db4cd24542203a9447c4259c96294c09 | nkf -w
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/git_cat_file_p .git/objects/a4/940ec3db4cd24542203a9447c4259c96294c09`
坊っちゃん
夏目漱石

-------------------------------------------------------
【テキスト中に現れる記号について】
~~~~~
省略
~~~~~

こんな感じで.git/objects/ 配下のファイルをzlib解凍して、git cat-file -p <hash 値> と同じ結果になるようにした。

コード

コードはDQNEOさんの記事で使われてるC言語のものをRustで書き直したものです。 DONEQさん、奥村先生、ありがとうございました!

use libflate::zlib::Decoder;
use std::{
    fs::File,
    io::{self, Cursor, Read},
    str,
};

fn usage() {
    println!("Usage: ");
    println!("git_cat_file_p blob_file");
}

fn decompress(mut file: File) -> Result<String, io::Error> {
    let mut buf = Vec::new();
    file.read_to_end(&mut buf).unwrap();

    let mut decoder = Decoder::new(Cursor::new(&buf)).unwrap();
    let mut decoded_data = Vec::new();
    decoder.read_to_end(&mut decoded_data).unwrap();

    unsafe {
        let str_data = String::from_utf8_unchecked(decoded_data);
        Ok(str_data)
    }
}

fn format(decoded: &str) -> &str {
    decoded.trim_end().split('\u{0}').collect::<Vec<_>>()[1]
}

fn main() {
    let args: Vec<String> = std::env::args().collect();
    if args.len() == 1 {
        usage();
        std::process::exit(1)
    }

    let f = File::open(&args[1]);

    let file = match f {
        Ok(file) => file,
        Err(error) => panic!("{}: Can't open: {}", error, args[1]),
    };

    let decoded = decompress(file).unwrap();
    let formatted = format(&decoded);
    println!("{}", formatted);
}

ただファイルをオープンして、それをzlib解凍して、コンテンツ本体部分を取り出して出力するだけというシンプルなものです。 String::from_utf8でなく、String::from_utf8_uncheckedを使っているのは、Shift_JISのときにエラーになるため。(坊っちゃんのファイルがShift_JIS)

改めて見ると、コンテンツ本体を取り出す方法format 関数のところをもっとスマートにできたらいいなあって感じですね。

参考記事

大変参考になりました。ありがとうございました。

Firefoxで「DRM 制御のコンテンツを再生」にチェックを入れてもコンテンツが再生できないとき

これは何

自分用メモ。 Ubuntu20.04 + FirefoxSpotify やら Netflix が再生できなくてOS入れるたびに困った。

対応

コーデックが足りてないようなので入れる。

$ sudo apt install libavcodec-extra

以上。

Clojureで中置記法を計算する

これは何

Clojure for the Brave and Trueのchapter7のExercises 2より。
年末年始の休みを利用して取り組んでいた問題。
あまり英語が得意ではないので、雰囲気で読み取り、 (1 + 2 * 3)のようなリストを引数として取り、演算子の優先順位規則に従って計算をする関数を作れ、という問題として受け取ってたんだけど、 今見ると、正しく評価できるリストに変換する関数を作れとも受け取れる。
今回は計算を行う関数を作ったので、そのことについて書く。

方法

「操車場アルゴリズム」を使う。
wikipediaによると、ダイクストラ先生が考案したらしい。
が、よくわからないので色々調べた結果、こちらの記事が大変参考になった。

解説

今回したこと

下記の演算子と数値を含むリストを正しい計算順序で計算できるようにした。

  • * (乗算)
  • / (除算)
  • + (加算)
  • - (減算)

また式内に()(括弧)が含まれている場合は、そちらを優先する。
つまりこんなリストを計算できるようにした。
'(1 + 4 - (3 * (4 - 5)))
この場合は8になる。グーグル先生の電卓でも8と計算されているので正しいはず。
https://www.google.com/search?q=(1+%2B+4+-+(3+*+(4+-+5)))&ie=utf-8&oe=utf-8

操車場アルゴリズムについて

詳しい説明については、検索してもらうのが一番良いと思う。
今、wikipediaを見ると今回の実装が果たして操車場アルゴリズムと呼べるのかもちょっと怪しい気がしてきた。。。
ここでは今回実装したアルゴリズムについて簡単に説明する。
例として、下記のような数式で考える。

1 + 2 * 3

この数式の場合、単純に左から計算していくとだめで、先に2 * 3を計算しないといけない。 つまりこのように計算したい。

1 + (2 * 3)

そのために、数式を下記のように見ていく。 トークンとは数式の先頭の値のこと。

1. 
式: 1 + 2 * 3
トークン: 1
演算子スタック: []
被演算子スタック: []
動作: 被演算子スタックにトークンを追加する。

2. 
式: + 2 * 3
トークン: +
演算子スタック: []
被演算子スタック: [1]
動作: 演算子スタックにトークンを追加する。

3. 
式: 2 * 3
トークン: 2
演算子スタック: [+]
被演算子スタック: [1]
動作: 被演算子スタックにトークンを追加する。

4. 
式: * 3
トークン: *
演算子スタック: [+]
被演算子スタック: [2, 1]
動作:
    * トークンが演算子スタックの先頭の演算子より優先度が高い
    * トークンを用いて、被演算子スタックの先頭の値とトークンの右の被演算子を計算し、その結果を被演算子スタックに追加する:  [(2 * 3), 1] => [6, 1]
    * 次のループはトークンより2つ右の値からとなる。この場合は3までなので、次のループで終了となる

5. 
式: なし
トークン: なし
演算子スタック: [+]
被演算子スタック: [6, 1]
動作: 
    * 式の終了まで来たので、演算子スタックと被演算子スタックの値を計算する
    * 演算子スタック [+] ,被演算子スタック: [6,1] なので、古い順から 1 + 6 と計算する。答えは7となる。

コード

リポジトリこちら
詳細についてはコードを読んでいただきたい。
肝になるのはsrc/infix_calculator/calc.clj
特にparse関数。
パースした結果が演算子である場合の条件分岐が結構大変だった気がする。
コードにコメントを追加したので、それを見て何をしているのか理解していただきたい。

(ns infix-calculator.calc)

;; 演算子とその優先度
(def operators-order [{:operator '+ :order 1}
                      {:operator '- :order 1}
                      {:operator '* :order 2}
                      {:operator '/ :order 2}])

;; 優先度の高い演算子
(def priorities (map :operator (filter #(= (:order %) 2) operators-order)))

;; 優先度の低い演算子
(def normals (map :operator (filter #(= (:order %) 1) operators-order)))

;; 演算子スタックと被演算子スタックが計算可能かどうか
(defn calc?
  [ops values]
  (and (not (nil? (first ops)))
       (not (nil? (first values)))))

;; 計算を行なう
(defn calc
  [operator operand1 operand2]
  (eval (list operator
              operand1
              operand2)))

;; 引数で渡された演算子の優先度を求める
(defn calc-order
  [operator]
  (:order (first (filter #(= (:operator %) operator) operators-order))))

;; 演算子スタックと被演算子スタックから値を計算する
(defn calc-stack
  [ops numbers]
  (cond
    (empty? ops) (first numbers)
    :else
    (calc-stack (rest ops)
                (cons (eval (list (first ops)
                                  (second numbers)
                                  (first numbers)))
                      (rest (rest numbers))))))

(defn parse
  [infixed]
  (loop [tokens infixed ;; 数式
         ops '() ;; 演算子スタック
         numbers '() ;; 被演算子スタック
         ]
    ;; 数式を先頭と残りに分ける
    ;; 先頭をトークンと呼ぶ
    (let [token (first tokens)
          remains (rest tokens)]
      (cond
        ;; トークンがnilであるか
        ;; nilであれば演算子と被演算子のスタックを計算する
        (nil? token) (calc-stack ops numbers)
        
        ;; トークンがリストであるか
        ;; リストであれば、そのリストをparse関数に渡し、その結果を被演算子スタックに追加する
        (list? token) (recur remains
                             ops
                             (cons (parse token) numbers))
                             
        ;; トークンが数値であるか
        ;; 数値であれば、被演算子スタックに追加する
        (number? token) (recur remains
                               ops
                               (cons token numbers))
                               
        ;; トークンが演算子であるか
        (ifn? token) (cond
                       ;; 演算子スタックが空であれば問答無用で追加する
                       (empty? ops) (recur remains
                                           (cons token ops)
                                           numbers)
                                           
                       ;; 演算子スタックと被演算子スタックが計算可能であり、
                       ;; トークンが優先すべき演算子であり、
                       ;; トークンが演算子スタックの先頭の演算子より優先度が高いか
                       (and (calc? ops numbers)
                            (some #(= token %) priorities)
                            (> (calc-order token)
                               (calc-order (first ops)))
                            (number? (first remains)))
                       ;; トークンを用いて被演算子スタックの先頭の値と、トークンの右の被演算子を計算し、
                       ;; その結果を被演算子スタックに追加する。
                       (recur (rest remains)
                              ops
                              (cons (calc token
                                          (first numbers)
                                          (first remains))
                                    (rest numbers)))

                       ;; 演算子スタックと被演算子スタックが計算可能であり、
                       ;; トークンと演算子スタックの先頭の演算子スタックの優先度が同じか
                       (and (calc? ops numbers)
                            (= (calc-order token)
                               (calc-order (first ops)))
                            (number? (first remains)))
                       ;; 演算子スタックの先頭の演算子を用いて被演算子スタックの値を計算し
                       ;; その結果を被演算子スタックに追加する
                       (recur remains
                              (cons token (rest ops))
                              (cons (calc (first ops)
                                          (second numbers)
                                          (first numbers))
                                    (rest (rest numbers))))
                       
                       ;; それ以外の場合
                       :else
                       ;; トークンを演算子スタックにスタックに追加する
                       (recur remains
                              (cons token ops)
                              numbers))))))

結構泥臭いことをやっていて、loopで愚直に調べている。Lisp的に正しいのかも分からない。
今見ると、calcと付く関数ばかりで自分でもどうなんだろうと思う。
最初は、reduceとかでできないものかと、色々調べたんだけど、何か無理っぽいのでおとなしくloopを使うことにした。
何年か前、Common Lisploopがよくわからなかったので、loopはあまり使いたくなかったのだけど、 これを作ったおかげで、Clojureloopについては何とか使えるようになった。

参考記事

大変参考になりました。本当にありがとうございました。
https://blog.shibayu36.org/entry/2017/03/05/170000

Ubuntuで辞書を引く

普段はUbuntuを使っているのですが、英語のドキュメントなんかを読んでいると、Macみたいに単語をクリックして辞書を引けたらなぁ、と思うことが多々あります。 Linuxでもそんなことできないのかなぁと思って調べたらありました。

GoldenDict

Ubuntuならaptで入るようです。 最新版の入れ方はGitHubを参照。

github.com

導入

とりあえずバージョン。

$ cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic

ひとまずaptで入れます。

$ sudo apt install goldendict

このままでは辞書がないので、辞書を入れる。 Babylon English-Japaneseというのが使えるらしい。

www.babylon-software.com

aptでインストールすると、~/.goldendictというディレクトリが作られるので、ここに辞書ファイルを置くディレクトリを作る。

$ mkdir ~/.goldendict/dict

ダウンロードしたBabylon English-Japaneseの辞書ファイルを移動する。

$ mv /path/to/Babylon_English_Japanese.BGL ~/.goldendict/dict/

ついでにもう一つ辞書を入れておく。

辞書・翻訳・学習・グラフ表示 (パソコン便利ツール集) フリーソフト

ここから「StarDict版 ejdic-hand (無料)」というのをダウンロードして、解凍する。

$ wget http://makoto-watanabe.main.jp/StarDictEjdic-hand.zip
$ unzip StarDictEjdic-hand.zip -d ~/.goldendict/dict/

あとは、Goldendictを開いて、辞書ファイルの設定をする。

$ goldendict

F3を押して、辞書の設定ダイアログを開き、ソース -> ファイル タブの「辞書ファイルを検索するパス」に、 ~/.goldendict/dictパスを追加し、今すぐスキャン -> 適用 -> OK

これで単語をダブルクリックして辞書を引けるようになるはず。

いたずら黒うさぎの思い出

関西では土曜日の25時30分、関東では24時が、
一週間の中で一番楽しみだった時期があるだけに、
今回の発表は結構胸に来るものがあった。
いたずら黒うさぎを聴きながら、お酒飲んでツイッターで実況するのが、
大学生の頃の楽しみだった。
聴くようになったきっかけは何だったのだろう。よく覚えてない。

 

今日、久々にリアルタイムで聴いてみたけど、
内容も、あの声も、タイトルコールも、
メールアドレスの読み方も、何も変わってなくて、
何かいろいろと昔のことを思い出したりもした。
甥っ子がもう5歳になってるみたいで、弟の結婚式行ってきたって話が、
ついこの前のように思えるけど、少なくとももう5年は経ってることにも驚いた。

 

ゆかりんツイッター始めたら、僕もツイッターを始めたし、
マカーだと知ったら、Macも買ったし、
iPhoneを買ったと聞けば、僕も買ったし、
本当にもう影響受けまくってた。
大学入って東京に来て、浜松町の文化放送のビルを初めて見たときは、
ここで黒うさぎが作られているのか、と本当に感動した。
1階のローソンも、ここが罰ゲームでエロ本を買わされてたところか、
とか思いながら、店内をまじまじと見回した。

 

学校サボって、パシフィコ横浜や武道館にも行ったし、
花粉で眼を真っ赤にしながら物販にも並んでピンクの法被も買った。
もちろんファンクラブにも入っていた。多分前の携帯には、
ファンクラブ会員限定のメール(伝書鳩だっけ?)とかも残ってるはず。

 

ゆかりんのことが好きだということで、そのおかげで、たくさんの人にも出会えた。
今の仕事ができているのも、そのとき知り合った人のおかげなわけで、
それはつまり彼女のおかげだと思う。

 

2011年頃から聴かなくなって、
2012年頃からハロプロBerryz工房に流れてしまったけど、
ももちの名前を覚えたのも、ゆかりんがももちのラジオにゲストで出演してたからだ。

 

本当にゆかりんのことが大好きだった。
先日の「重要なお知らせ」がホットエントリ入りしてたので、
今回の件を知ったけど、その後の公式ブログのファンへのメッセージを見て、
やっぱり好きになって良かったと思った。
こんな人だから、好きになったんだと思う。
彼女は僕のことを知らないし、こんな文章を見ることなんてないだろうけど、
これだけは書いておきたい。
本当にありがとう。本当に感謝しています。
節目の年、今の段階では今後どうなるのか分かりませんが、
それでも、これからのあなたの人生に幸多からんことを祈っております。

本買った

「たのしいRuby」という本を買いました。

仕事でRubyを使うことは、

今の職場にいる限りはないと思いますが、

転職したいし、コンパイル系の言語しかほぼ触れたことがないので、

趣味と実益を兼ねて勉強しようと思います。

 

勉強の記録も、ここに記事として残せたらいいなと考えております。

 

ちなみに書ける言語は、CとC#ぐらいです。あとPerlはちょっとだけ書けます。

C++は書けません。

会社ではC#を主に使ってます。

 

たのしいRuby 第4版