Mac mini M4 を買った。ただいまの構成はこんな感じ。
- Mac mini M4 (1 TB SSD & 16 GB メモリ)
- Dell U3821DW
- HHKB Professional Hybrid Type-S (US)
- Magic Trackpad (USB-C)
- Pebble V3
- Satechi Mac mini M4 ハブ&スタンド ドッキングステーション
- Buffalo HD-AD4U3 (4TB)
Mac mini M4 を買った。ただいまの構成はこんな感じ。
ターミナルに簡単にグラフを書きたいと思って PyPI を探してみたら termgraph というのを見つけたものの、README.md を見ると何やらコマンドとして使うのが想定されているようで Python スクリプトから呼び出す方法については書かれていなかった。 仕方ないので termgraph の中を見てみて、こんな感じでやれば自分の Python スクリプトから termgraph 内の関数を呼び出してグラフを書けるよ、という内容のメモ。
termgraph に付属のサンプルデータにある data/ex4.txt を例にする。
# Example Data Set 4 with 2 Categories
@ Boys,Girls
2007,183.32,190.52
2008,231.23,5.0
2009,16.43,53.1
2010,50.21,7
2011,508.97,10.45
2012,212.05,20.2
2014,30.0,20.0
termgraph コマンドを使うと、こんなグラフが書ける。
% termgraph ~/src-misc/termgraph/data/ex4.dat
▇ Boys ▇ Girls
2007: ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 183.32
▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 190.52
2008: ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 231.23
▏ 5.00
2009: ▇ 16.43
▇▇▇▇▇ 53.10
2010: ▇▇▇▇ 50.21
▏ 7.00
2011: ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 508.97
▇ 10.45
2012: ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 212.05
▇ 20.20
2014: ▇▇ 30.00
▇ 20.00
%
コマンドを使うのではなく、自分の Python スクリプトから termgraph の中のものを使って同じことをやろう、というのが目標。
データを用意して、以下の termgraph 内の関数を呼び出す。
最終的に termgraph.chart 関数を呼んでグラフを書くことになる。
def chart(colors: List, data: List, args: Dict, labels: List) -> None:
"""Handle the normalization of data and the printing of the graph."""
colors, data, args, labels を用意する必要がある。
上の例では Boys / Girls ともに端末の文字の色でグラフが描かれるが、termgraph コマンドに --color {blue,red} とオプションを付け加えると、Boys は青で Girls は赤でグラフが描かれる。
この colors リストは自分で用意する必要はない。termgraph.check_data 関数の返り値を colors にする。
colors = tg.check_data(labels, data, args)
では、どのように色を指定するのかというと、args['color'] に値を入れておく。端末の文字色でよければ None を入れる。categories の要素ごとに色を変えたい場合リストに色を指定する、例えば Boys は青で Girls は赤にしたいという場合には、args['color'] = ['blue', 'red'] という感じ。このリストの要素数は、categories リストの要素数と等しくなくてはならない。
指定できる色は、termgraph.AVAILABLE_COLORS に定義されている。
# ANSI escape SGR Parameters color codes
AVAILABLE_COLORS = {
"red": 91,
"blue": 94,
"green": 92,
"magenta": 95,
"yellow": 93,
"black": 90,
"cyan": 96,
}
色を追加で定義したいのなら、この AVAILABLE_CHARTS に追加すれば良い。例えば白なら tg.AVAILABLE_COLORS = {'white': 97} と書く。 wikipedia の ANSI escape code のページを見ると、30 から 37 の値も使えそうではあるが試してはいない。
2007 から 2014 までの文字列のリストを作っておけば良い。
labels = ['2007', '2008', '2009', '2010', '2011', '2022', '2014']
[boysの値,girlsの値] のリストを作っておけば良い。こんな感じ。
data = [
# Boys, Girls
[183.32, 190.52],
[231.23, 5.0 ],
[ 16.43, 53.1],
[ 50.21, 7 ],
[508.97, 10.45],
[212.05, 20.2 ],
[ 30.0, 20.0 ],
]
termgraph コマンドのヘルプ (termgraph --help) を参考に dict を作っていけば良いのだが、termgraph.chart 関数や termgraph.check_data 関数を呼ぶだけなら不要だったりするものや、そもそも使われていないものもある。。。
| オプション名 | タイプ | オプションの意味 |
|---|---|---|
| width | int | グラフの幅。デフォルト 50。 |
| format | str | 各棒の後に数値を表示する際の形式。デフォルト {:<5.2f} |
| suffix | str | 各棒の後に表示した数値の後につける文字列。 % とかをつけたいなら。 |
| no_labels | bool | 各棒の先頭にラベルを表示するかどうか。 |
| no_values | bool | 各棒の後に値を表示するかどうか。 |
| color | list | 色名をリストで指定。 ["blue", "red"] とか。指定がなければ None を入れておけば良い。 |
| vertical | bool | 横ではなく縦にグラフを書きたい! |
| stacked | bool | 各カテゴリを合わせたグラフにしたい! |
| histogram | bool | ヒストグラムでお願い! |
| bins | int | ヒストグラムにしたときの bin の数。 デフォルト 5。 |
| different_scale | bool | Categories have different scales らしいが調べてない |
タイトルは自分で書けばいい。
calendar と start-dt は、カレンダーヒートマップを書きたい場合に使う。termgraph.chart 関数ではなく、termgraph.calendar_heatmap 関数を呼ぶ。
custom-tick は、グラフを描くときの文字を変更したい場合に使う。
delim は、データ入力をファイルから行うときに区切りもじを指定するために使う。(termgraph.DELIM の値を変える。)
というわけで、こんな感じで args を用意すればよい。
args = {
'width': 50,
'format': '{:<5.2f}',
'suffix': '',
'no_labels': False,
'no_values': False,
'color': None,
'vertical': False,
'stacked': False,
'histogram': False,
'bins': 5,
'different_scale': False
}
凡例を表示するには、termgraph.print_categories(categories, colors) を呼ぶ。 categories リストは ['Boys', 'Girls'] として用意する。
全部合わせてみると、こんな感じのスクリプトになる。
from termgraph import termgraph as tg
# data/ex4.dat
categories = ['Boys', 'Girls']
labels = ['2007', '2008', '2009', '2010', '2011', '2022', '2014']
data = [
# Boys, Girls
[183.32, 190.52],
[231.23, 5.0 ],
[ 16.43, 53.1],
[ 50.21, 7 ],
[508.97, 10.45],
[212.05, 20.2 ],
[ 30.0, 20.0 ],
]
args = {
'width': 50,
'format': '{:<5.2f}',
'suffix': '',
'no_labels': False,
'no_values': False,
'color': None,
'vertical': False,
'stacked': False,
'histogram': False,
'bins': 5,
'different_scale': False
}
colors = tg.check_data(labels, data, args)
if categories:
tg.print_categories(categories, colors)
tg.chart(colors, data, args, labels)
hoge.py として保存して、実行してみる。
% python hoge.py
▇ Boys ▇ Girls
2007: ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 183.32
▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 190.52
2008: ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 231.23
▏ 5.00
2009: ▇ 16.43
▇▇▇▇▇ 53.10
2010: ▇▇▇▇ 50.21
▏ 7.00
2011: ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 508.97
▇ 10.45
2022: ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 212.05
▇ 20.20
2014: ▇▇ 30.00
▇ 20.00
%
できあがり。
#C言語クイズ
— Yutaka Hirata (@yutakakn) 2023年1月3日
実行結果はどうなるでしょう?#include <stdio.h>#include <stdbool.h>
int main(void)
{
char buf[5] = "2023";
char c1 = buf[0];
char c2 = 2[buf];
bool cmp = (c1 == c2);
printf("%s\n", cmp ? "true" : "false");
return 0;
}
この 2[buf] みたいな書き方もできちゃうというのが記憶の片隅にあり、true と印字されるんだろうなと思ったので最初の選択肢をポチッとした。 言語仕様で決まっているのかどうだったかまでは覚えてないけれども、とりあえずコンパイラがどんな機械語を生成するのか確認してみた。
やはり true が印字される。
[yusuke@albirex foo]$ gcc -g foo.c -o foo [yusuke@albirex foo]$ file ./foo ./foo: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=12cde68dd82759dc3f81ce929093e43480f3c6f7, not stripped [yusuke@albirex foo]$ ./foo true [yusuke@albirex foo]$
やはり 2[buf] は buf[2] のように処理されている。。。
(gdb) disas main Dump of assembler code for function main: 0x000000000040052d <+0>: push %rbp 0x000000000040052e <+1>: mov %rsp,%rbp 0x0000000000400531 <+4>: sub $0x10,%rsp スタックを16バイト伸長する 0x0000000000400535 <+8>: movl $0x33323032,-0x10(%rbp) ベースポインタから -0x10 のところから "2" "0" "2" "3" 0x000000000040053c <+15>: movb $0x0,-0xc(%rbp) nullターミネート "\0" 0x0000000000400540 <+19>: movzbl -0x10(%rbp),%eax buf[0] の値をバイト幅で eax へ 0x0000000000400544 <+23>: mov %al,-0x1(%rbp) eax の下位のバイトを rbp-1 へ 0x0000000000400547 <+26>: movzbl -0xe(%rbp),%eax 2[buf] の値をバイト幅で eax へ 0x000000000040054b <+30>: mov %al,-0x2(%rbp) eax の下位のバイトを rbp -2 へ 0x000000000040054e <+33>: movzbl -0x1(%rbp),%eax rbp-1に取っておいた値をバイト幅で eax へ 0x0000000000400552 <+37>: cmp -0x2(%rbp),%al eax の下位バイトと rbp-2 の値を比較 0x0000000000400555 <+40>: sete %al ...以下省略... 0x0000000000400558 <+43>: mov %al,-0x3(%rbp) 0x000000000040055b <+46>: cmpb $0x0,-0x3(%rbp) 0x000000000040055f <+50>: je 0x400568 <main+59> 0x0000000000400561 <+52>: mov $0x400610,%eax 0x0000000000400566 <+57>: jmp 0x40056d <main+64> 0x0000000000400568 <+59>: mov $0x400615,%eax 0x000000000040056d <+64>: mov %rax,%rdi 0x0000000000400570 <+67>: callq 0x400410 <puts@plt> 0x0000000000400575 <+72>: mov $0x0,%eax 0x000000000040057a <+77>: leaveq 0x000000000040057b <+78>: retq End of assembler dump. (gdb)
分かりにくいけど、こんな感じ。
+------ rbp-0x10。つまり buf[0]
| +---- rbp-0xe。 つまり buf[2] であり 2[buf]
| | +-- rbp-0xc。 つまり buf[4]
| | | +-- rbp
| | | |
v v v v
--------+-+-+-----------+-------
2023n 22 // n は \0。2, 0, 2, 3 は、数値ではなく char。
--------+-------------+++-------
| ^^
+-- rsp ||
|+-- buf[0] からのコピー。つまり char c1
+--- 2[buf] からのコピー。つまり char c2
ちなみに、-g をつけない場合も、main の disas は同じだった。-g はデバッグ情報を持つセクションを用意するだけで生成するマシン語には影響しないと思っておいてよさそう。 -O1 だとこんな感じ。もはや true を印字するのみ。
(gdb) disas main Dump of assembler code for function main: 0x000000000040052d <+0>: sub $0x8,%rsp 0x0000000000400531 <+4>: mov $0x4005e0,%edi 0x0000000000400536 <+9>: callq 0x400410 <puts@plt> 0x000000000040053b <+14>: mov $0x0,%eax 0x0000000000400540 <+19>: add $0x8,%rsp 0x0000000000400544 <+23>: retq End of assembler dump. (gdb) (gdb) x/s $rdi 0x4005e0: "true" (gdb)
そして、gcc がどんなアセンブラを作るのかだけみたいのなら、gdb など使わずとも gcc -S foo.c とやれば良い。数値が 10 進数で表記されている。
[yusuke@albirex foo]$ gcc -S foo.c
[yusuke@albirex foo]$ cat foo.s
.file "foo.c"
.section .rodata
.LC0:
.string "true"
.LC1:
.string "false"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $858927154, -16(%rbp)
movb $0, -12(%rbp)
movzbl -16(%rbp), %eax
movb %al, -1(%rbp)
movzbl -14(%rbp), %eax
movb %al, -2(%rbp)
movzbl -1(%rbp), %eax
cmpb -2(%rbp), %al
sete %al
movb %al, -3(%rbp)
cmpb $0, -3(%rbp)
je .L2
movl $.LC0, %eax
jmp .L3
.L2:
movl $.LC1, %eax
.L3:
movq %rax, %rdi
call puts
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)"
.section .note.GNU-stack,"",@progbits
[yusuke@albirex foo]$
maxsplit という引数があるのを知らなかったので、確認。
>>> 'a-b-c-d-e'.split('-', -5)
['a', 'b', 'c', 'd', 'e']
>>> 'a-b-c-d-e'.split('-', -4)
['a', 'b', 'c', 'd', 'e']
>>> 'a-b-c-d-e'.split('-', -3)
['a', 'b', 'c', 'd', 'e']
>>> 'a-b-c-d-e'.split('-', -2)
['a', 'b', 'c', 'd', 'e']
>>> 'a-b-c-d-e'.split('-', -1)
['a', 'b', 'c', 'd', 'e']
>>> 'a-b-c-d-e'.split('-', 0)
['a-b-c-d-e']
>>> 'a-b-c-d-e'.split('-', 1)
['a', 'b-c-d-e']
>>> 'a-b-c-d-e'.split('-', 2)
['a', 'b', 'c-d-e']
>>> 'a-b-c-d-e'.split('-', 3)
['a', 'b', 'c', 'd-e']
>>> 'a-b-c-d-e'.split('-', 4)
['a', 'b', 'c', 'd', 'e']
>>> 'a-b-c-d-e'.split('-', 5)
['a', 'b', 'c', 'd', 'e']
>>> 'a-b-c-d-e'.split('-', 6)
['a', 'b', 'c', 'd', 'e']
>>>
以下のように複数のコマンドの出力を保存したかった。
例えば、複数のホストに対する ping コマンドの出力とか。
とりあえず、こんな感じ。。。?
import subprocess
def do_pings(ip_addrs, count):
procs = []
for ip_addr in ip_addrs:
p = subprocess.Popen(
f'ping -c {count} {ip_addr}',
shell=True,
text=True, # this "text" argument is valid since 3.7.
# For earlier releases, use
# universal_newlines=True, or
# encoding='utf-8'.
stdout=open(f'ping_{ip_addr}_stdout.txt', 'w'),
stderr=open(f'ping_{ip_addr}_stderr.txt', 'w'),
)
procs.append(p)
[p.wait() for p in procs]
def main():
IP_ADDRS = ('192.168.0.1', '192.168.0.2')
PING_COUNT = 30
do_pings(IP_ADDRS, PING_COUNT)
if __name__ == '__main__':
main()
O'REILY 「入門 Python 3」の 12 章「データの自在な操作」にある復習課題 12.9 は、84 文字の文字列を入力しなければならない。 もう二度と 0 とか f を目で数えたくはないので、ここにメモしておくことにする。
'47494638396101000100800000000000ffffff21f9' '0401000000002c000000000100010000020144003b'