python のソート関数が key を要求してくる件について

今の仕事でメインに使ってる言語は Kotlin なのだけど、Excel のワークブックからデータを読み取ってくるような用途には python が使われてる。
そこで python でソートしたかったのだけど、組み込みの sorted() 関数の引数が key となっていて、「これだな」と思った cmp は deprecated だった。

同じ状況の人へのサクッとした解決策としては key に functools.cmp_to_key(your_cmp_func) のような感じで指定すればおk

 

import functools

def compare_as_see_fit(lhs, rhs):
    if lhs > rhs:
        return 1
    elif lhs < rhs:
        return -1
    return 0

print(sorted([65535, 7, 255, 7, 31], key=functools.cmp_to_key(compare_as_see_fit)))

 

もうこれだけで it works! なのだけど、この functools.cmp_to_key() クンが何をどうしてくれてるのかイメージできなかった。

ほな、ソースみよか?

 

# Copyright (C) 2006-2013 Python Software Foundation.

def cmp_to_key(mycmp):
    """Convert a cmp= function into a key= function"""
    class K(object):
        __slots__ = ['obj']
        def __init__(self, obj):
            self.obj = obj
        def __lt__(self, other):
            return mycmp(self.obj, other.obj) < 0
        def __gt__(self, other):
            return mycmp(self.obj, other.obj) > 0
        def __eq__(self, other):
            return mycmp(self.obj, other.obj) == 0
        def __le__(self, other):
            return mycmp(self.obj, other.obj) <= 0
        def __ge__(self, other):
            return mycmp(self.obj, other.obj) >= 0
        __hash__ = None
    return K

 

あ、あ、そういう事ね!!

自分が勝手に「絶対値に変換してからソート関数に渡せ」って意味に勘違いしてただけで、比較可能ならそれでいいのね!!
そもそも key っていう引数の意味自体を勘違いしてたわ。
要素の絶対値を求める関数だと思ってた!!
型を明示しない言語だとそういう部分を正確に読み取るの難しいよね。

 

膿汁出る

このブログ(もともとは日記)のタイトルを見ればわかるように、私は C++ プラグラマーであった。

 

過去形である。

要するにおじさんは classic C++ プログラマーだったのである。

 

この10年ほど、C++ が激変していた事は知っていたが、もう使ってなかったし、まるで子供時代の情景かのように、今更戻るところでも、戻れるところでもないかのように感じていた。

 

ただ、なんとなく悔しかった。

最も熱狂したプログラミング言語で、最も好きなプログラミング言語だとも言いたかった。

しかし最近あまりにも C++ は変わりすぎていて、それが言いづらい雰囲気になっていた。

 

というわけで、なんとなく modern な C++ に再入門してみようと思う。

はっきり言ってガベコレのある言語に慣れ過ぎたし、今の C++ を見る限り取りこぼしをキャッチアップするという気持ちでは絶対ダメだ。

これは再入門だと捉えている。

 

そして Effective Modern C++ を読み始めた。

最初の数ページで最早何言ってるのか全然わからない。

ただ、膿汁が溢れ出る懐かしい感じが濃厚にした。

多分、世の中の C++ 好きという人種はこういう変態なのだろう。

 

この感覚は論理的ではない。

ただ、論理を軽々と超えてくる本能なのだ。

C++ は間違いなく、今も昔も変態専用言語である。

 

 

ただ当時も思ってたけど今は余計に、これ原著ないと何書いてあるのかさっぱりわかんねえな。

ムーブ演算可能とか書かれても able to move operation なのか、move operable なのか can move operation なのかそれ以外の何かなのか推測不可能だよ。

そもそも俺は仮引数と実引数って言われると parameter なのか argument なのか混乱するんだよw

大和言葉と漢字と英語の三つを併せて覚えないといけないっていう日本人プログラマーは、もうその時点で一定レベル変態なのに、日本人 C++ 使いとか、ど変態じゃないとやってられないと思うw

bash の prompt を変える

変数に指定する時点で一度バックスラッシュが解釈されるからややこしい

メモしておく。


# MSYS2 default
PS1="\\[\\e]0;\\w\\a\\]\\n\\[\\e[32m\\]\\u@\\h \\[\\e[35m\\]$MSYSTEM\\[\\e[0m\\] \\[\\e[33m\\]\\w\[\\e[0m\\]\\n\\$ "

# Ubuntu default
PS1="\\[\\e]0;\\u@\\h: \\w\\a\\]${debian_chroot:+($debian_chroot)}\\[\\033[01;32m\\]\\u@\\h\\[\\033[00m\\]:\\[\\033[01;34m\\]\\w\\[\\033[00m\\]\\$"

# 色を変える
# \\[\\e[1;30m\\]
# 戻す
# \\[\\e[m\\]

# my MSYS
export PS1="\\[\\e[1;32m\\]\\u@\\h\\[\\e[m\\] \\[\\e[1;35m\\]MSYS \\[\\e[1;37m\\]\\t \\[\\e[1;33m\\]\\w \\n\\[\\e[1;30m\\]\\$ \\[\\e[m\\]"

# my WSL2
export PS1="\\[\\e[1;32m\\]\\u@\\h\\[\\e[m\\] \\[\\e[1;35m\\]WSL2 \\[\\e[1;37m\\]\\t \\[\\e[1;34m\\]\\w \\n\\[\\e[1;30m\\]\\$ \\[\\e[m\\]"

 

Windows の Rust で go の関数を呼ぶ

GoでRustを呼ぶ。そしてRustでGoを呼ぶ。https://qiita.com/momotaro98/items/92e39e214b0e92f454a7

こちらを参考にさせて頂いたが Windows の場合 go の -buildmode=c-archive で作られる lib が mingw 用なり Windows の Rust が使う MSVC 用と合わないので DLL にして回避した。(具体的には crt の関数がないエラーとセクションの形式が合っていないような警告でリンクできない)

細かい事は省くが x64 の msys2 環境で go も Rust もインストール済みの場合の例です。

main.go

package main

import "C"

//export GoFunc1
func GoFunc1(name string) *C.char {
	str := name + " ʕ ◔ϖ◔ʔ"
	return C.CString(str)
}

func main() {}

go のソースコードから "gofuncs.dll" という名前の dll を作る

go build -buildmode=c-shared -o gofuncs.dll ./main.go

dll で公開している関数を記述した def ファイルを作る (TODO: 自動化, dumpbin.exe からできるはず)

gofuncs.def

LIBRARY   gofuncs
EXPORTS
   GoFunc1 @1

lib.exe で DLL からインポートライブラリを作成する

LIBEXE=`cygpath --unix "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC/14.32.31326\bin\Hostx64\x64\lib.exe"`
"$LIBEXE" /def:gofuncs.def /out:gofuncs.lib /machine:x64

これでインポートライブラリ gofuncs.lib (MSVC 形式) が作成される

Rust のソースファイル

use std::ffi::{CStr, CString};
use std::os::raw::c_char;

extern "C" {
	fn GoFunc1(name: GoString) -> *const c_char;
}

#[repr(C)]
struct GoString {
	ptr: *const c_char,
	len: i64,
}

fn main() {
	let src_str = CString::new("I'm a Rustacean").expect("CString::new failed");
	let ptr = src_str.as_ptr();
	let param = GoString {
		ptr: ptr,
		len: src_str.as_bytes().len() as i64,
	};
	
	let go_func_result = unsafe { GoFunc1(param) };
	let c_str = unsafe { CStr::from_ptr(go_func_result) };
	let result = c_str.to_str().expect("to_str failed");
	println!("{}", result);
}

cargo でリンクするライブラリを指定

build.rs
fn main() {
	let dir = std::env::var("PWD").unwrap();
	println!("cargo:rustc-link-search={}", std::path::Path::new(&dir).display());
	println!("cargo:rustc-link-lib=gofuncs");
}

これで cargo run すると "I'm a Rustacean ʕ ◔ϖ◔ʔ" と表示される。

Linux Mint Cinnamon に Windows から Chrome Remote Desktop する

がっつりハマった。
やたら解像度が高くて文字がつぶれて見えないんですよ。
あ、最初の画面は Software rendering を選ぶといいと思います。これを選ばないと右上に「ハードウェアレンダリングできないンゴ」みたいな警告も出ます。
この接続直後に出る設定は /usr/share/xsessions/*.desktop みたいなファイルになってるんですが、これらのファイルは触る必要なかった。
次に /opt/google/chrome-remote-desktop/chrome-remote-desktop というファイルなんですが、これが pythonスクリプトChrome Remote Desktop を待ち受けるサービスを起動/停止してくれます。これも中身は弄る必要なかった(けど何が設定できるのかはわかるので読んでみてもいい)。
この起動スクリプトを読めば、.profile に CHROME_REMOTE_DESKTOP_DEFAULT_DESKTOP_SIZES を設定すればいい事がわかります。
なので .bash_profile で設定しました。
(Nemo で隠しファイルが見えないのは Ctrl+H でトグルできる)

# my .bash_profile

# include .profile if it exists (WARNING: recursive source)
if [ -f "$HOME/.profile" ]; then
    . "$HOME/.profile"
fi

export CHROME_REMOTE_DESKTOP_DEFAULT_DESKTOP_SIZES="1024x768,1280x1024,1680x1050"

これで端末から

sudo login YOUR_NAME

した後に

env | grep CHROME

で書いた設定がでる状態になれば OK (最初横着して起動スクリプトを叩く端末で export してたけどこれじゃ駄目だった。ちゃんと .profile 辺りに書く必要がある。

と思ったけど再起動したら端末立ち上がらなくなったわ……。多分 .profile を読もうとするのが本当にダメなんだと思う。諦めて .profile に export 追記したら直った。

OS を再起動してもいけるけど、さらっと確認したかったら

/opt/google/chrome-remote-desktop/chrome-remote-desktop --stop
/opt/google/chrome-remote-desktop/chrome-remote-desktop --start

したら想定した解像度でつながるようになった。めでたしめでたし。ではなくてもう一つはまったのがキーボードの設定。Windows が日本語キーボードだから記号が打てなかった。
左下の Linux Mint ボタン(っていう名前だよね? ホバーで Menu って出るけど)から Preferences -> Keyboard -> Layouts の左側の一覧に Japanese を追加, English の優先順位を下げても思った通りにならなかったので English は削除したら意図したとおりになった。
これでだいたい大丈夫か? (なおクリップボード共有も効いてるのでいい感じ)

 

Time Capsule 用SSDの載せ替えメモ

ほぼ Time Capsule として使用している Mac miniSSD が満杯になったと警告がきたので大きな SSD にデータを移行しつつ載せ替える作業のメモのつもりだった。

そんなに簡単に事は運ばなかったので後日に続きます。

前提としてこの Mac mini (Mid 2011)は元々は1ドライヴしか付いてなくて内蔵ドライヴ増設キット的なマウンターで2ドライヴ目を増設していたはず(薄い記憶)。

まず新しい 2.5 inch SSD と USB 接続の外付けドライヴ化ケースを用意しました。

こちらで旧データを移行してから、ドライヴを載せ替える想定です。

新しいドライヴを USB 接続すると「このドライヴはこの Mac では読めない。イジェクトする?」という警告が表示されるのでキャンセルして Disk Utility の画面を開きます。

Disk Utility に外付けのドライヴが認識されているはずなので、Erase ボタンを押して、名前とフォーマットの類いを選びます。

今回はフォーマットには Mac OS Extended (Journaled) を、パーティションタイプには GUID パーティションを選択しました。

ドライヴ名には空白を含めないのがおススメです(今回自分は TimeCapsuleSSD という名前にしました。/Volume 以下にこの名前で入るので、絶対空白なんか入れないほうがいいです。旧ドライヴは謎のノリで "Macintosh SSD" という名前にしちゃったのですが、シェル上から扱う時に何かと気持ち悪いですよね。全体をクォーテーションしてたのにパス補完するだけでエスケープに書き換えられたりする辺りとかが)

割とすぐにフォーマットは終ります。今回はパーティションを区切るつもりがないので1個の大きなパーティションのままでいきます。

ここまででドライヴとしての利用は可能になったので、次にデータ移行に移ろうと考えました。

まず現状のサービスを一旦止めようと思いました。

自分が Mac mini を Time Capsule 相当に設定した当時はこの機能は Server アプリの機能だったのですが、High Sierra 辺りで Server アプリを購入しなくても利用できるようになったようで、数日前にこの老骨がサポートしてる限界でもあった High Sierra にアップグレードした結果、設定場所が全然わかりません(Server アプリからは設定個所が消えています)。

どうやら新規設定する場合、共有の設定のファイル共有から Time Machine バックアップ先として設定できるようなのですが、現時点ではそういう設定がされていない模様。

そして新しい設定方法だと SMB 共有からでも TimeCapsule 用のドライヴが無駄に表示されるのなんか嫌だなぁと思って、旧設定みたいに共有ドライヴとしては見えないようにできないかなと思ったのですが、全然わからなかったので諦めて、共有として見える設定でいく事にします(ドライヴ名をそれっぽくして本当によかった)。

じゃあデータ移行だなーと思って、ちょっとググった結果

cp -a ならシンボリックリンク辿らずにコピーできる的な記述をみたので cp -a して1.5日~2日程度放置、「やっぱり USB 2 だから遅いのかなー」とか思いながら待っていたのですが

嘘じゃん

がっつりシンボリックリンク辿った結果、2TB まるまる埋め尽くしてコピーが終わりました…… orz

※後日追記 嘘じゃないかも。単純に新しい SSD もバックアップ対象に含まれてただけかも?

(そもそも更に新しいバージョンの macOS だとこの cp するまでになんか権限設定要るらしい。High Sierra ではそこは問題にならなかった)

わかったよ、わかったパトラッシュ、素直に dd して、後からパーティションを広げればいいんだね。そうするよ。と思ったあたりが今。

Draft として放置してたこの記事を一旦 publish してしまおうと決断した辺りになります。

あと最初はデータ移行が終わったら内蔵のドライヴと入れ替えるつもりだったけど、ここでこんなに手間取ってるのと

「別にバックアップ用のドライヴは USB 2 経由でも問題ないのでは?」

と思い始めたので、外付けのまま運用でいいような気がしてきた。

本体と密着させた状態で放置だと無駄に熱がこもると思うので、それだけなんとかすればむしろ外付けの状態の方がメンテが楽じゃんね?