次ログ

次ログ

ゆるりと働いているSREの技術ブログのような何か。趣味の話も書く

自分が今まで書いたイラストの書いた時期と枚数を集計する

概要

集計の勉強がてらにbashawkで自分が今まで書いたイラストの枚数と傾向を集計して分析します。

目的

自分の絵師としてのモチベーションの傾向を分析し、未来の活動指針を決定するため。 というのは大嘘でbashawkで集計の練習がしたかったからです。

成果物は月単位、年単位で集計したグラフです。 本目的の達成は、上記のグラフを作成し可視化することをもって完了とします。

コード

成果物

csv形式で出力してLibreOfficeでグラフ化しました。 (コードの方ではcsv形式で出力するようにはしていないので、 スクリプト呼び出し時に| tr '[:blank:]' ','しました。

年単位での集計結果

月単位での集計結果

考察

2016年のころ、僕はまだ大学4年生でした。 大学4年は卒論の提出と就活が終わってしまえばあとはひたすら暇になるので その時間をイラストとプログラミングに最大に使っていたことがよく統計に現れているように思います。

2016年1月〜3月は冬休みでしたし、卒業研究も就活もまだ本腰でなかった時期なので その時間はひたすら絵を書いていたみたいですね。

しかし僕って2011年から絵を書いていたんですね...。 意外と結構期間が長いものです。

最近はめっきり絵を書く熱は冷めてしまって、プログラミングの熱ばかりあがっていますが、 それでもこれからも、年に1,2枚くらいは何かしら書くと思います。

Clojureでアクセスログの処理時間を集計

概要

最近Bashで集計をすることが多いです。集計作業楽しいです。 と同時に、Clojureという言語にも興味ができたので、せっかくなので 簡単な集計作業を両方の言語でやってみたというだけの話です。

目的

access_log というログファイルが存在します。 ログのデータはすべて行単位ですが、共通することは行の一番最後の数値は アクセス時の処理時間です。

このアクセスログの時間を集計して、合計秒と行数を出力したいです。

作成したコード

下記を参照。 ClojureBashのコードです。

まとめ

集計作業のコード量の短さや読みやすさ、手軽さはやっぱり BashAwkのほうが断然上だなぁ...と。 Awkとかはもろに集計のための言語なんでかなり便利。 Clojureに集計やらせるもんじゃないかな...。 でも大量の便利関数群をまだまだ把握できていないので、 それらを駆使したらもっと短くシンプルになるのかも?

個人的にはBashClojureってなんか似ているような気がします。 BashのパイプとClojure関数型言語の性質が似てて、 コードを書いていてパズルを解いているような感じがしてコーディングがすごく楽しい。 逆にGoだとClojureほどコーディングが楽しくなかったり... 実装自体は複雑な機能がないので、つまづくこともあまりなく高速で実装できますが 味気ない感じがどうもあります(気にするべきじゃないのかもですが)。

余談

集計作業を行うとき、今まではなんとなくPythonとかでやっていたのですが、 そこまで複雑ではない処理だったらむしろPythonよりもBashAwkのほうが 短く素早く実装できるなぁと実感しています。

シェルのコードをPythonに置き換えるような話をちらほら聞く中 時代を逆走するようにBashAwkの勉強を最近はしています。 でも使ってみるとかなり便利なので、これからも勉強しようと思います。

Windows環境でgoreleaserを使用してGitHubにデプロイしてみた

前書き

どうもです。

以前Ubuntu環境でgoreleaserを使用してGitHubにデプロイする作業について記事を作成しましたが、今回はそれのWindows環境版です。

前の記事については下記を参照してください。 http://usernameon666.hatenablog.com/entry/2017/10/08/125842

やってることはほとんど同じですが、デプロイのスクリプトbashでやってるかPowerShellでやってるかという違いだけですね。

開発環境

項目
OS Windows 10 Home
Go version 1.9.2
使用スクリプト PowerShell

成果物

game-manager
ゲームのプレイ時間を記録してCSVファイルとして保存するTUIアプリ

わざわざ作るほどのものでもないのですが、まぁ勉強用ということで...
同様のゲームのプレイ時間が把握できるもので僕が使用したことがあるのは SteamやMoeL2などがあったのですが、あれと同じようなもので ターミナル上で動作するものを作成してみたかったんです。

「みんなのGo言語」に登場したtermbox-goとHOME配下のconfigフォルダにデータを格納する手順を勉強するために作成しました。

リリース手順

GitHubリポジトリの作成

今回は下記の通り game-manager というリポジトリを作成しました。 https://github.com/jiro4989/game-manager

GoReleaserの設定ファイルの作成

.goreleaserという設定ファイルを$env:GOPATH/src/game-manager配下に作成します。

PS C:\Users\username\go\src\game-manager> pwd

Path
----
C:\Users\username\go\src\game-manager


PS C:\Users\username\go\src\game-manager> ls


    ディレクトリ: C:\Users\username\go\src\game-manager


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       2017/12/24     20:05                data
d-----       2017/12/24     20:55                dist
d-----       2017/12/24     19:59                game
d-----       2017/12/24     20:51                res
d-----       2017/12/24     20:54                script
-a----       2017/12/24     20:48             19 .gitignore
-a----       2017/12/24     20:22            322 .goreleaser.yml
-a----       2017/12/24     20:04           5082 game-manager.go
-a----       2017/12/23      5:53            622 README.md

.goreleaserに設定を記入

.goreleaser.ymlの中身を下記の通りにする。

builds:
  - binary: game-manager
    goos:
      - windows
      - darwin
      - linux
    goarch:
      - amd64
      - 386
archive:
  format: tar.gz
  replacements:
    amd64: 64-bit
    darwin: mac
    linux: linux
  name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
  files:
    - README.md

デプロイスクリプトを作成

$env:GOPATH\src\game-manager\scriptに下記の内容のデプロイ用のPowerShellスクリプト"deploy.ps1"を作成します。

※別にスクリプトを作成しないといけないわけではないです。

git tag $args[0]
$env:GITHUB_TOKEN = cat ".\res\token.txt"
goreleaser --rm-dist
go install

GitHubのアクセストークンを配置

$env:GOPATH\src\game-manager\res\token.txtというテキストファイルを作成して、GitHubのアクセストークンを貼り付けます。 あんまりこういう運用方法って良くないと思いますけれど、まぁ個人で行う分には気にしないことにしています。

.gitignoreを作成する

デプロイするバイナリとtoken.txtがリポジトリ上に見えてしまうとまずいので それらを除外するように設定します。

.gitignoreの中身は下記のとおりです。 配置するパスは$env:GOPATH\src\game-managerです。

token.txt
/dist/

スクリプトを実行する

下記のパスがカレントディレクトリであることを確認します。

PS C:\Users\username\go\src\game-manager> pwd

Path
----
C:\Users\username\go\src\game-manager

下記のコマンドを実行します。
.\script\deploy.ps1 v1.0.0
※v1.0.0はタグ情報になるので、アップデートするたびに番号を更新する必要があります。

あとはひたすら待機...

PowerShell環境だと、制御コードがむき出しで表示されてパッと見バグったか失敗したように見えることがありますが、正常に動いているので、信じて待ちます。

リリースの確認

GitHubのリリースページを確認しにいきます。 https://github.com/jiro4989/game-manager/releases

Goの正規表現を少し使う

前書き

Go言語の正規表現はあまり早くないらしいので、 なるべくstringsパッケージの関数を使って文字列を扱うほうが パフォーマンスが良いという話をよく聞きます。

とはいえ、正規表現をどうしても使いたくなるときはありますし、 そこまで速度が重要でないときや、使用頻度が低いときは パパッと使って問題解決したほうが良いと考えているので regexpでよく使いそうなものを調べてテストしました。

そもそも速度が求められない状況ならPythonとかLLでよくない? とか言われそうですが...

よく使いそう、の判断基準は、 僕が普段Pythonでテキストを処理するときに よく使ったもの、というだけで、完全に主観です。

目次

コード

以下はPythonとGoの正規表現のコードです。 使用したものは下記の5つの機能です。

  1. 正規表現にマッチするかどうかの判定
  2. 正規表現にマッチしたものをすべて取得する
  3. 正規表現にマッチしたものの数を取得する
  4. 正規表現にマッチしたものを置換する
  5. 後方参照で置換を行う

所感

基本的には一緒でしたが、後方参照の部分での指定で結果が変わったのが気がかりです。
全角空白を含む、名前の間の空白文字をGo言語では\sで認識できない? まだ詳細を調べてはいませんが、気をつけないといけなさそうです。

漫画を読むためのHTMLを生成するツール

どうもです。

最近自宅でも会社でもコードを書かない状態が続いていたので、 簡単なツールを作成しました。

内容は漫画を読むためのHTMLを生成するツールです。
Releaseページに実行ファイルと必要なファイルを配置しています。
GitHub - jiro4989/genidx: 漫画を読むためのHTMLを生成するツール

探さなくても誰かがきっとやっているような内容ですが、リハビリということで...

目次

見た目

見た目は以下のような感じです。

※漫画は「うぶんちゅ!」という 瀬尾浩史 さんの漫画です。
http://www.aerialline.com/comics/ubunchu/

使い方

漫画の存在するディレクトリにgenidex.exeとtemplate.htmlを配置してgenidx.exeを実行 するだけです。

技術要素

Go言語では、html/templateパッケージを利用して、 カレントディレクトリ配下の漫画を読めるリンクをHTMLの中に埋め込むだけです。 そんなに難しいことはしてません。

JSは生のJSをガリガリと。 画像の切り替えやショートカットキーの追加程度ですが。

CSSは簡単なアニメーションと、target要素を利用した縦タブを実装。 ちょろっとだけ影も装飾して、すこしだけ雰囲気づけもしています。

所感

HTMLテンプレートにGo言語で値を埋め込む方法の勉強になりました。

ただ、やっぱり書いていて思うことは、 書き捨てるようなコードでパパっとかくならPythonのほうが絶対いいなぁと感じました。

LL言語なみに短いコードになる、という話を聞いていましたが、個人的にはPythonのほう がはるかに楽だと感じました。

やはり適材適所と言いますが、実装を早くするか、実行速度を早くするかで、 使い分けないといけなさそうですね。

Go言語でクロスコンパイルした実行ファイルをGitHubにリリースしてみた

前書き

せっかく作ったツールなので、いろんな環境用に実行ファイルを生成して、 GitHubからダウンロードできるようにしたいと考える人は多いと思います。

幸いにもGo言語は標準でクロスコンパイルをサポートしてくれています。 ちょろっとシェルスクリプトを書くだけで、一発で異なる環境用の実行ファイルを作成で きます。 問題は、それを公開する方法です。

僕は今までDropboxで配布していましたが、 なんというか、すごく古臭いやりかたなイメージ。 それとGitHubのリリース機能を今まで使ったことがなかったので、 これを機会に少しだけリリース機能を使ってみよう、という趣旨です。

今回はGo言語で書かれたソースからリリースするためのツールの GoReleaseというサードパーティ製のツールを利用して、リ リースしてみます。

今回の成果物

作成したリポジトリ GitHub - jiro4989/md4pt: マークダウンのテキストをプレーンテキストで表示したときに読みやすく整形

リリースした実行ファイル Releases · jiro4989/md4pt · GitHub

環境

項目 内容
OS Ubuntu Gnome 17.04
Go言語 1.9
プロセッサ Intel(R) Core(TM) i7-3667U CPU @ 2.00GHz
メモリ 8GB RAM

僕の今までのやり方

今回Go言語で書いたツールを配布するまでは、 Javaでツールを書いてDropboxにアップしていました。

今回はビルドまでの手順はやりましたが、 実際にはGo言語の手動リリースはやっていません。

今回リリースを初めて行ったのですが、 JARのときと同じ手順でやっていたこうなっていただろう、という想定で書いています。

お手製ビルド

まずベタな方法としては、下記のようなシェルスクリプトを書いて複数の実行ファイルを 生成することだと思います。 最初、僕は下記の方法でやろうとしていました。

#!/bin/bash
# -*- coding: utf-8 -*-

set -eux

mainsrc=./md4pt.go
dirname=./dist

if [ -e $dirname ]; then
  : $dirname is exist
else
  mkdir $dirname
fi

GOOS=linux GOARCH=amd64 go build -o $dirname/md4pt_linux $mainsrc
GOOS=darwin GOARCH=amd64 go build -o $dirname/md4pt_mac $mainsrc
GOOS=windows GOARCH=amd64 go build -o $dirname/md4pt_win.exe $mainsrc

ls $dirname
: build completed

Dropboxにリリース

手動ビルドした後は、実行ファイルをzipかtar.gzで圧縮して公開します。 今回のようなクロスコンパイルの場合でしたら、下記のようなシェルスクリプトになると 思います。

前提として、環境にDropboxディレクトリを同期して置く必要が有ります。 すると、Dropboxディレクトリに配置したものが、Web上のDropboxにも適用されるように なります。

#!/bin/bash
# -*- coding: utf-8 -*-

set -eux

bins=`ls -d dist/*`
for b in $bins; do
  gzfile="${b}.tar.gz"
  tar czf $gzfile $b README.md
  mv $gzfile ~/Dropbox/tools/
done

全体の自動化

手動ビルドの節で使用したスクリプトをbuild.sh 手動リリースの節で使用したスクリプトをrelease.sh とすると、ソースの編集がおわって一気にビルドしてリリースするなら下記のようなスク リプトを実行します。

#!/bin/bash
# -*- coding: utf-8 -*-

set -eux

./build.sh
./release.sh

一応、リリース作業自体は自動化できました。 でもバージョン管理などの観点からあまり良くないと思います。

よって、GitHubにリリースする方法に移行します。

GoReleaseを使ってGitHubにリリース

Gitにリポジトリを作成

中身はすっからかんでいいのでリポジトリを作成。

今回はmd4pt というリポジトリを作成して、実際にリリースしました。

GoReleaseのインストール

go get github.com/goreleaser/goreleaser

GoReleaseの設定ファイルの作成(.goreleaser.yml)

Goのリポジトリのルートディレクトリに.goreleaser.ymlを作成します。 内容は下記。

builds:
  - binary: md4pt
    goos:
      - windows
      - darwin
      - linux
    goarch:
      - amd64
      - 386
archive:
  format: tar.gz
  replacements:
    amd64: 64-bit
    darwin: mac
    linux: linux
  name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
  files:
    - README.md

リリース用のスクリプトの作成

.gitignoreに下記の内容を記述

token.txt
/dist/

名前は何でも良いですが、release.shという名前で 下記の内容のシェルスクリプトを作成。

#!/bin/bash
# -*- coding: utf-8 -*-

set -eux
: $1

git tag $1
GITHUB_TOKEN=`cat token.txt` goreleaser --rm-dist

token.txtというのは、 GitHubのSettings -> Personal access tokensの項目で作成するキーのみを書いたテキ ストファイルです。

https://github.com/settings/tokens

tokenは他の人に知られてはいけないので、シェルスクリプト本体に含めないようにする 必要がありました。

しかし、毎回コマンドラインから入力するのも面倒だし、履歴からたどるのも面倒だったので、 .gitignoreにtoken.txtを書いておいて、catして埋め込むようにしました。

git push

gitに最新のデータをpushします。

git remote add origin $URL
git add .
git commit -m "init commit"
git push origin master

リリースの実行

コマンドラインから以下のコマンドを実行します。
./release.sh v0.0.1

これで、GitHubの指定のリポジトリのリリースにv0.0.1タグでリリースされています。

まとめ

Go言語のクロスコンパイルとリリース作業の自動化を行いました。

Go言語は、Pythonでのpip, Rubyでのgemと同様の、サードパーティ製のライブラリやツー ルを容易に導入できる機能を標準でサポートしています。

このことにより、容易に成果物をWebに公開できました。 今後はこの方法でリリース作業を行っていこうと思います。

Go言語 学習メモ03 - 総当り組み合わせ二次元配列の作成

前書き

どうもです、次郎です。

有給とってGo言語の学習してました。

以前Pythonの記事で紹介したツールの移植がほぼ完了して、今はドキュメントの整備と設 定ファイル編集ツールの作成をしています。

実際ツールを作成するとなったら、いろんな仕様を駆使しないといけないので、 ツール作成を学習に利用するという試みは結構成功だったなぁと。 ある程度使えるようになったらEffective GOとかも買わないとか考えてます。

ということで、今回はそんなツールの中で実装した、総当り組み合わせの実装につ いてメモします。

注意
二次元配列、と書いてますけれど実際はスライスです。
二次元スライスという読みが奇妙なので、ここでは二次元配列と呼びます。

目次

目的

ゲーム用のキャライラストを作成するときに、イラストの差分を用意することがあると思います。

差分の用意は人によってまちまちで、いろんなタイプがあると思います。 表情のみ変える方もいれば、ポーズごと変える方もいると思います。

主な差分は目、眉、口とかでしょうか。
あるいは、それらを組み合わせた顔をまるごと置き換える感じでしょうか。

たとえば以下のような感じの二次元配列です。

[
   ["eye1", "mosue1", "eyebrows1"]
  ,["eye1", "mosue1", "eyebrows2"]
  ,["eye1", "mosue1", "eyebrows3"]
  ,["eye1", "mosue2", "eyebrows1"]
  ,["eye1", "mosue2", "eyebrows2"]
  ,["eye1", "mosue2", "eyebrows3"]
  ,["eye1", "mosue3", "eyebrows1"]
  ,["eye1", "mosue3", "eyebrows2"]
  ,["eye1", "mosue3", "eyebrows3"]
]

これらのうち、あとから口の差分が増えたり、オプションとしてメガネとか猫耳とか尻尾とか
組み合わせの要素が増えたときに、差分画像が一気に増えます。

また、差分の修正が発生したときに組み合わせ直す作業が面倒です。
配布するためにZip圧縮し直す必要もあります。

僕の場合は表情のみ変えるタイプの人なのですが、その表情の差分用意が非常にめんどくさかったので、 それの自動化を行う上で、総当り組み合わせを適用した二次元配列を作成する必要がありました。

今回のコードではそれを実装したものになります。

コード

// 画像組み合わせを保持するための二次元配列patternListに対して、
// 総当り組み合わせの配列を追加する。
//     usage:
//     SetImagePatten(&patternList, nil, images, 0)
func SetImagePatten(patternList *[][]image.Image, pattern []image.Image, imgArr [][]image.Image, index int) {
    if len(imgArr) <= index {
        p := make([]image.Image, len(pattern))
        // リターン先で値が更新されてしまうので、
        // コピーをスライスに追加する
        copy(p, pattern)
        *patternList = append(*patternList, p)
        return
    }

    if index == 0 {
        pattern = make([]image.Image, 0)
    }

    for _, v := range imgArr[index] {
        pattern = append(pattern, v)
        SetImagePatten(patternList, pattern, imgArr, index+1)
        pattern = pattern[:len(pattern)-1]
    }
}

この再起関数を利用しているCLIツールのコード全体は下記。
https://github.com/jiro4989/igen

実装面での工夫点

今回の総当り組み合わせで使ったテクニックは下記のとおりです。

  • 再起関数
  • スライス
  • ポインタ
  • rangeループ

再起関数

今回、画像の総当り組み合わせを実装する上で、forループによる愚直な実装は絶対に嫌 でした。

というのも、柔軟性が失われる上に要素が増えたときに対応できないからです。

下記はPythonで実装したときに、画像の総当り組み合わせを実装したコードです。

others = pattern['others']
if others != None:
    i = 8
    for eyes in others['eye']:
        eye = eyes['name']
        for eyebrows in eyes['eyebrows']:
            for mouse in others['mouse']:
                i += 1
                baseimg  = Image.open(f'{imgdir}/{base}.{ext}').convert(colormodel)
                processing_image(i, out_formatter, outdir, imgdir, ext, opt, baseimg, eyebrows, eye, mouse)

上記のコードでは、組み合わせのリストを生成するのではなくて、もう直接画像ファイル を開いて操作しているので、見てくれは全然違いますが、やっていることは同じです。

こういうのの実装には多分再起関数が有用なのだろと考えたので、苦手な再起関数に挑戦 しました。
ちなみに、最初にPythonで再起関数を作成してからGoで書き直すって感じで実装しました 。
コードのテストをするのにもやっぱりPython楽ですからねー。

Pythonで実装したコードを貼り付けようと思ったらどうも間違えて消してしまったみたい です。もったいない... orz

スライス

JavaPythonでいうところのリストです。 下記のような感じで宣言します。

sl := make([]int, 0)
// または
var sl = []int

配列の宣言の要素数を省略するとスライスとして扱われるようです。
というか、スライス自体は配列のポインタなので、見ているものは配列と同じだそうです 。
http://golang.jp/go_spec#Slice_types

ハマったところ

makeで要素数を最初に指定すると、ゼロ初期化された値が格納された状態で初期化される 。

コード

   sl := make([]int, 5)
    fmt.Println(sl)
 
    sl = append(sl, 1)
    fmt.Println(sl)

結果

[0 0 0 0 0]
[0 0 0 0 0 1]

JavaだとListを宣言するときに、事前に要素数がわかっているときは

List<Integer> list = new ArrayList<>(5);

って宣言してからaddするほうが高速に動作するので、その癖をGoでやったらはまった...
どうようのことをGoでやる方法がわからないので、とりあえず空スライスとして宣言する ように実装しています。

それはさておいて、
Javaだとlist.add(elem)
Pythonだとlist.append(elem)
Goだとsl = append(sl, elem)で、
とごちゃごちゃになってきますね...

ポインタ

func SetImagePatten(patternList *[][]image.Image, pattern []image.Image, imgArr [][]image.Image, index int) {

patternList *[][]image.Imageがそれですね。
この再起関数自体は戻り値を持たずに、引数で渡されたポインタの参照先の要素を変更す ることで、値を変更しています。

ポインタを使うときは、関数の引数で型の前に*をつけて、呼び出し側で&を使うみたい です。

// 関数定義
func f(x *int) {
  *x = 1
}

// 関数呼び出し
x := 0
f(&x)
fmt.Println(x)

大学のC言語の勉強のときにやったような記憶があるくらいでまともに使うのは今回が初 めてです。
Javaの参照渡しと似てるけれど、具体的に何が違うんだろう...

rangeループ

for _, v := range imgArr[index] {
    pattern = append(pattern, v)
    SetImagePatten(patternList, pattern, imgArr, index+1)
    pattern = pattern[:len(pattern)-1]
}

Javaの拡張for文、Pythonのinループと同じですね。 基本的にループをintカウンタで回してインデックスで要素を取得する方法は嫌いなので 、forループはほとんどこちらを利用してます。 もとの要素を変更する必要があるときだけカウンタを使います。

今回はインデックスが不要なので一つ目の要素は_で握り潰しています。
地味にこのインデックスと一緒にループって便利なんですよね。
Javaだと拡張for文でインデックスが欲しくなったらわざわざループの外で変数を宣言し ないといけないので。

まとめ

ということで、今回再起関数を実装するに当たって、いろんなGo言語の機能を利用しまし た。

少し複雑な処理を実装するとなると、一気に勉強量が増えて大変ですが、多分自分にはこ の方法が一番身につきやすいです。モチベーション維持にも効果的ですし。