2023 パズル in F#

他の人の回答などを見ずに愚直に解いてみたが全部を算出しようとすると遅すぎた.

type Opcode =
    | Add
    | Sub
    | Mul
    | Div

type Operand =
    | Opcode of Opcode
    | Number of int

let calculateRpnExpression (exp: Operand list) : Option<int> =
    let rec calculateRpnExpression' exp stack =
        match exp with
        | [] -> Some(List.head stack)
        | Number (n) :: t -> calculateRpnExpression' t (n :: stack)
        | Opcode (op) :: t ->
            match stack with
            | n2 :: n1 :: rest ->
                match op with
                | Add -> calculateRpnExpression' t ((n1 + n2) :: rest)
                | Sub -> calculateRpnExpression' t ((n1 - n2) :: rest)
                | Mul -> calculateRpnExpression' t ((n1 * n2) :: rest)
                | Div ->
                    if n2 = 0 then
                        None
                    else
                        calculateRpnExpression' t ((n1 / n2) :: rest)
            | _ ->
                printfn $"stack=${stack} exp=${exp}"
                failwith "never reach here"

    calculateRpnExpression' exp []

let normalizeRpnExpression (exp: Operand list) : string =
    let rec normalizeRpnExpression' exp stack =
        match exp with
        | [] -> List.head stack
        | Number (n) :: t -> normalizeRpnExpression' t ((string n) :: stack)
        | Opcode (op) :: t ->
            match stack with
            | v2 :: v1 :: rest ->
                match op with
                | Add -> normalizeRpnExpression' t ((sprintf "(%s+%s)" v1 v2) :: rest)
                | Sub -> normalizeRpnExpression' t ((sprintf "(%s-%s)" v1 v2) :: rest)
                | Mul -> normalizeRpnExpression' t ((sprintf "(%s*%s)" v1 v2) :: rest)
                | Div -> normalizeRpnExpression' t ((sprintf "(%s/%s)" v1 v2) :: rest)
            | _ -> failwith "never reach here"

    normalizeRpnExpression' exp []

let createRpnExpression (nums: int list) (target: int) =
    let len = nums |> List.length
    let limit = len * 2 - 1

    let rec createRpnExpression' nums prevNum numCount opCount exp acc =
        if numCount + opCount >= limit then
            let exp' = List.rev exp

            match calculateRpnExpression exp' with
            | None -> acc
            | Some (v) ->
                if v = target then
                    printfn "%A" (normalizeRpnExpression exp')
                    exp' :: acc
                else
                    acc
        else
            let acc' =
                if numCount - opCount >= 2 then
                    let ops =
                        if prevNum = 8 then
                            [ Div ]
                        else
                            [ Add; Sub; Mul; Div ]

                    ops
                    |> List.fold
                        (fun acc' op ->
                            createRpnExpression' nums -1 numCount (opCount + 1) (Opcode(op) :: exp) acc')
                        acc
                else
                    acc

            if prevNum <> 8 && numCount < len then
                let n = List.head nums
                createRpnExpression' (List.tail nums) n (numCount + 1) opCount (Number(n) :: exp) acc'
            else
                acc'

    createRpnExpression' nums -1 0 0 [] []


let quiz2023 (nums: int list) (target: int) : string list =
    let rpnExpressions = createRpnExpression nums target
    rpnExpressions |> List.map normalizeRpnExpression

この本に単純な場合のものは載っているので全くわからない人は見ても良いかもしれません。

Ubuntu 22.10に handbrakeをflatpakで インストールする

大昔の DVD整理を今更やろうとして bandbrakeをインストールしようとしたが, apt版だと SEGVが出てしまい使えなかったので flatpakで最新版を入れる必要があった. flatpakを使ったことがなかったのでそのメモ

flatpakのインストール

sudo apt install flatpak

最新版の handbrakeを flatpakでインストール

flatpak install https://dl.flathub.org/repo/appstream/fr.handbrake.ghb.flatpakref 

handbrakeを起動する

flatpak run fr.handbrake.ghb

clangdを使う際のメモ

最近ささっと C/C++書くとき, Emacsを使っているのだが, LSP serverとして clangdを使う際のメモ

Emacsの設定

Emacs開発版を使っているので, eglotをそのまま使う(C/C++ではデフォルトではサーバとして clangdが使われる)

(add-hook 'c-mode-hook #'eglot-ensure)
(add-hook 'c++-mode-hook #'eglot-ensure)

1ファイルの場合

特に対応が不要. よしなし補完なりエラーを表示してくれる

ビルドファイルがあるような本格的なプロジェクトの場合

各種フラグをセットしていたり, include pathを指定しておく必要があるので compile_commands.json を生成しておく必要がある. compile_commands.json はファイルの親ディレクトリかそのいずれかのディレクトリにある build/ サブディレクトリ以下にある必要がある. なのでビルドディレクトリ名には注意が必要となる.

CMakeの場合

CMAKE_EXPORT_COMPILE_COMMANDS というフラグがあるのでこれを onにしてビルドファイルを生成する

mkdir build/
cd build/
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=YES ..

Makefileの場合

compiledbbearMakefileから compile_commands.json を生成することができるので利用する. 一応ビルドせずファイルを生成する機能があるが, 1度ビルドした方が無難なようである.

# compiledbの場合
compiledb make

# bearの場合
bear -- make

これで補完やら定義元ジャンプやらが使えるようになる.

WebHIDで PatliteのUSB機器を動かすコードを書いた

リポジトリ

github.com

デモサイト

syohex.github.io

仕事でモバイルデバイスで USB機器の対応をする作業があって, Windowsとか macOSとか Linuxで対応する場合どんなコードになるのだろうかと調べていたんだけど, libusbを使ったりしてもポータブルに書くのが難しくて挫折していたところ, WebHIDならどうなのだろうかと試したところ, Chrome系統だけではあるが, Windows, macOS, Linuxでさくっと動かせて驚いた. WebHIDとか USB機器を扱う仕様は Google以外は乗り気でなく, 将来的にどうなるかはわからないけどお手軽に USB機器を扱う際はとても良いプラットフォームなのかもなと思った. まあセキュリティが重要な昨今厳しいとは思うのですが何らかの形では存続してほしいですね.

emacs --no-window-systemで truecolorを効かせずに起動する

GUIエディタは VScodeでいいかということで, Emacsは常に -nw で起動するようにしてみたのだが, 昨今の terminal emulatorは truecolor対応になっており, リモートの環境で使っていた Emacsの色設定がかなり変に見えてしまう問題があった. 対応も面倒くさいということで, 256color対応で立ち上げる方法を模索したところ環境変数 COLORTERMtruecolor から適当の値に変えてやればよいということがわかった. なので下記のような aliasを作成した.

alias emacs='env COLORTERM=1 emacs'

Emacs 29の eglotを使った Rust環境の設定

Emacs 29から eglotが標準パッケージに入るということで, 最近はちょっとした C++や Goを書くときなどは Emacsを使うことが少し増えてきた. 仕事だと IDEVScodeだけど. そこで Rustも設定しておくかということでやってみた. VSCodeだと基本的に全部自動だが, Emacsでの作業が幾分必要であるのはやはり面倒だと思った.

必要なツールのインストール

% rustup component add rust-analyzer
% rustup component add rust-src 

PATHの設定

rust-analyzer を PATHに設定されている場所に置く. 直接 PATH設定するなり好きにすればよい

% ln -s ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/rust-analyzer ~/.cargo/bin/ 

Emacsの設定

;; rust-modeのインストール. 使っているパッケージに合わせてください. 下記は el-get
(el-get-bundle rust-lang/rust-mode)

;; 補完のために company-modeも入れる
(el-get-bundle company-mode/company-mode :name company-mode)

;; elgotの有効化
(add-hook 'rust-mode-hook 'elgot-ensure)

単純な Rustプロジェクトであればこれだけで動くが, 複数プロジェクト構成だったり, git rootに Cargo.tomlがないケースではうまく行かない問題があった. rust-analyzerは性質上 Rust project rootで起動する必要があるようだが, eglotは project.elの機能を使って project rootを探しており, それはデフォルトでは .gitがあるディレクトリになってしまう. このため .gitがあるディレクトリと Cargo.tomlがあるディレクトリが一致しない場合, LSP用のサーバが起動できずエラーになってしまう. そこで project rootを探す処理に手を入れ対応する必要がある. 具体的には project.elは project-find-functions に登録されている関数を順番に実行し, non-nilが返った場合 それを project rootとして認識するので下記のような対応を行った.

(defun my/find-rust-project-root (dir)                                                                           
   (when-let ((root (locate-dominating-file dir "Cargo.toml")))                                                         
     (list 'vc 'Git root)))

(defun my/rust-mode-hook ()
  (setq-local project-find-functions (list #'my/find-rust-project-root)))

(add-hook 'rust-mode-hook #'my/rust-mode-hook)

これで .git rootに Cargo.tomlがないようなケースでも動くようになった.

おわりに

VSCodeとか IDE使っていたら自動ですべてやってくれるから LSPの恩恵というのをイマイチ実感していなかったけど, LSPはかなりライフチェンジングなものなんだなと Emacsの設定を通じて今更感じた。VSCodeとかに比べればまだまだ手間なんだけど、Emacs29からは上記のような特殊なケースは除くとしてもサーバプログラムさえあれば LSPの恩恵を数行の設定で受けられるってのはとても良い体験だと感じた。

tmux + 256colorで Emacsの見た目の調整

Ubuntu 22.10で環境変数 TERM=xterm-256colorEmacsを起動するとこんな感じなのだが

tmuxを起動すると以下のようになりめちゃ見づらい

これが嫌で TERM=xterm でここ 10年ぐらいやってきたのだが, さすがに 256colorの方が良いんじゃないって思って対応方法を調べていろいろ試したんだけど全部うまくいかなくて, 最終的に tmuxでシェルを起動したときだけ TERM=tmux-256color としてやると期待通りになることはわかった.

if [[ "$TERM_PROGRAM" == "tmux" ]]; then
  export TERM=tmux-256color
else
  export TERM=xterm-256color
fi

tmuxのなしの場合と同じような見た目にすることはできた. 原因とかはよくわかっていないがひとまずヨシということで。