Ruby で JSONCanvas 形式を読み書きできるライブラリを作りました

json_canvasJSONCanvasは形式のファイルを Ruby で簡単に読み書きできるライブラリです。すでにいくつかの言語のライブラリはあったのですが Ruby 用が無かったので作りました。

インストール方法

json_canvas を使い始めるには、まずアプリケーションのGemfileに以下の行を追加してください。

gem 'json_canvas'

その後、ターミナルで以下のコマンドを実行します。

$ bundle install

もしくは、直接以下のコマンドを使ってインストールすることも可能です。

$ gem install json_canvas

基本的な使い方

JsonCanvasを使用するには、Rubyスクリプト内で以下のようにgemを読み込んでください。

require 'json_canvas'

キャンバスの作成

新しいキャンバスを作成するには、次のコードを使用します。

jc = JsonCanvas.create

テキストノードの追加

キャンバスにテキストノードを追加する方法は以下の通りです。

text_node = jc.add_text(text: "Hi")

テキストノードには、id, 位置, サイズ, 内容, 色などのパラメータをカスタマイズして設定することができます。

custom_text = jc.add_text(id: "unique_id", x: 50, y: 100, width: 200, height: 50, text: "Hello World!", color: "2")

このテキストを後述の save メソッドを使ってファイルに保存してから Obsidian などの対応するアプリケーションで開くと以下のように表示されます。

その他のノードタイプ

ファイルノードやリンクノード、グループノードを特定の属性で追加することもできます。

file_node = jc.add_file(file: "path/to/file")
link_node = jc.add_link(url: "https://example.com")
group_node = jc.add_group(label: "Test Group")

エッジの追加

ノード間をエッジで接続することができます。

jc = JsonCanvas.create
start = jc.add_text(id: "START", text: "start")
goal = jc.add_text(id: "GOAL", x: 400, text: "goal")
jc.add_edge(id: "edge1", fromNode: start.id, toNode: goal.id)
jc.add_edge(id: "edge2", fromNode: start.id, fromSide: "top", fromEnd: "arrow", toNode: goal.id, toSide: "bottom", toEnd: "arrow", color: "2", label: "HELLO")

JSON文字列への変換

作成したキャンバスインスタンスJSON文字列に変換する場合は、to_jsonメソッドを使用します。

jc.to_json  #=> '{"nodes":[{"id":"START","x":0,"y":0,"width":250,"height":60,"type":"text","text":"start"},{"id":"GOAL","x":400,"y":0,"width":250,"height":60,"type":"text","text":"goal"}],"edges":[{"id":"edge1","fromNode":"START","toNode":"GOAL","fromSide":"right","toSide":"left"},{"id":"edge2","fromNode":"START","toNode":"GOAL","fromSide":"top","fromEnd":"arrow","toSide":"bottom","toEnd":"arrow","color":"2","label":"HELLO"}]}'

保存と読み込み

キャンバスをファイルに保存することや、ファイルからキャンバスオブジェクトを読み込むことができます。

# "sample.canvas"ファイルに保存。
jc.save("sample.canvas")

# キャンバスオブジェクトを読み込み。
loaded_canvas = JsonCanvas.parse(File.read("sample.canvas"))

まとめ

Ruby コードからクラス構造を出力したり、複雑なルールに沿ったグラフを自動生成したいときなどはスクリプトから作るのが便利かもしれません。もしくは Obsidian で生成したグラフを読み込んで特定のルールに沿ってノードを整形するなどもよさそうです。是非色々な使い方を考えてみてください。

読んだ - R3のコードから見る実践LINQ実装最適化コンカレントプログラミング実例

https://neue.cc/2024/05/01_R3_Presentation.html

合わせてスライドも読む。

TIPS: class と struct をエディタ上で視覚的に区別する

44

ちなみに、私はエディタ上で class と struct を視覚的に区別したいので、struct は緑色にしています(enumは黄色)。かなり捗るのでお勧め

真似したい、Visual Studio だとどこで設定するといいのだろう。

TimeProvider DeepDive

二点の経過時間を求めるなら、Stopwatch.GetTimeStamp() と Stopwatch.GetElapsedTime(timestamp) を使うのがよい。

このlongは、通常は高解像度タイムスタンプ、WindowsではQueryPerformanceCounterが使われています。TimeSpanでよく使うTicksではないことに注意してください。

BenchmarkDotNet

マイクロベンチマークを取るときは必ずBenchmarkDotNetを使ってください。

mutable Struct

最適化に便利。でも readonly を付けるとコンパイルエラー無しで書き換え不可になるので注意。

Rx の Operator は ThreadSafe ではない

32p

並列で OnNext を叩くと壊れる。これは R3 も同様。

35p

Observable の呼び出し側が単一ストリームはシングルスレッドを保証してあげる必要がある。

この辺りそうだろうな思っていたけどちゃんと言及されている文章を見つけられてよかった。

C# の null 条件演算子と null 合体演算子の組み合わせを理解する

C# における if (obj?.Foo?.Bar ?? false) のような null 条件演算子null 合体演算子を組み合わせた式が読みにくくて苦手だった。

if (obj?.Foo?.Bar ?? false) {
    // obj.Foo.Bar の途中に null が存在しない、
    // かつ obj.Foo.Bar == true の場合に実行される。
}

特に if (!obj?.Foo?.Bar ?? true) のような真偽が反転するような書き方だと頭がこんがらがってしまう。

if (!obj?.Foo?.Bar ?? true) {
    // obj.Foo.Bar の途中に null が存在する、
    // または obj.Foo.Bar == false のときに実行される。(ややこしい・・)
}

そもそもなぜ if (obj?.Foo?.Bar) では駄目なのか?

if (obj?.Foo?.Bar) {
    // コンパイルエラー
}

途中に null 条件演算子があるため式全体の戻り値の型が bool? として評価されてしまうのが原因だ。C# では Ruby などの言語と違って null が if 文の偽として振る舞うことが禁止されている。そのため Bar が bool を返すプロパティであってもコンパイルエラーとなってしまう。

そこで null 合体演算子の出番となる。if (obj?.Foo?.Bar ?? false) のように null の場合に代わりの値を明示することで式全体の戻り値は `bool`` となり無事コンパイルを通過することができる。

if (obj?.Foo?.Bar ?? false) {
    // obj.Foo.Bar の途中に null が存在しなければその結果を、そうでなければ false
}

少しずつ理解を進めていったあるとき、同僚が int i = obj?.Foo?.Bar ?? 0 のような書き方しているのを見た。

// obj.Foo.Bar の途中に null が存在しなければその結果を、そうでなければ 0
int i = obj?.Foo?.Bar ?? 0; 

なるほどつまり null 合体演算子swtch 文の default や switch 式の _ などデフォルト値のように振る舞っていると考えればいいのだと気がついた。

ここで2つ目の式に戻る。if (!obj?.Foo?.Bar ?? true) の null 合体演算子が null 条件演算子 のデフォルト値として振る舞っていると考えると、この式はすでに一度別の法則で展開されていることに気がつく。そう ド・モルガンの法則 だ。

if (!(obj?.Foo?.Bar ?? false)) {
    // 「obj.Foo.Bar の途中に null が存在しなければその結果を、そうでなければ false」の結果を反転する
}

つまり if (!obj?.Foo?.Bar ?? true)if (!(obj?.Foo?.Bar ?? false)) の括弧を一度展開した状態だったのだ、やっと理解できた!

はてなブログを git レポジトリで運用する

過去のすべてのブログ記事を git レポジトリで管理することにした。

記事の管理や投稿には blogsync という Go 製のツールを使っている。

github.com

blogsync をインストールしたら、以下のコマンドを叩くだけで記事の取得や投稿、更新ができる。

# レポジトリに移動
$ cd ~/Document/blog/

# 更新
$ blogsync pull ongaeshi.hatenablog.com

# 投稿&更新
$ blogsync push ongaeshi.hatenablog.com\entry\2023\04\16\152439.md

原稿がローカルにあるので、一度投稿した記事を微調整したり、検索して過去記事にリンクを貼ったり、複数記事のカテゴリをまとめて編集するなどがとてもやりやすくなった。

コマンドラインで記事の取得や投稿ができるので他のコマンドとの連携もやりやすい。例えば現在編集中の記事だけを再投稿したい場合は以下のようなコマンドを叩けばOK。

$ git diff --name-only | ForEach-Object { blogsync push $_ }

お気に入りのエディタを使ってドキュメントを書くのが好きな人など、 blogsync + git レポジトリの組み合わせによるブログ編集は大変おすすめです。

blogsync で Windows からも直接新規記事を投稿したい

github.com

Windows からもやりたい。

PowerShell で標準入力を終了するにはCtrl+Z Enter なので・・

PS> ~/Documents/blog
PS> blogsync post --draft --title=blogsync ongaeshi.hatenablog.com
さてかきはじめるか...
(Ctrl+Z Enter)

これで OK。

ChatGPT に Ruby など他のプログラミング言語も実行させたい

ChatGPT はたくさんの種類のプログラミング言語のコードを出力できますが、直接実行できるのは現状 Python のみとなっています。Ruby など他の言語も実行することはできないかと考えていました。

1つのアイデアとして思いついたのは ChatGPT に生成したコードを Ruby on Browser に渡す URL を生成してもらい、それをクリックするだけで実行できるようにするという方法です。これなら他の言語でも URL 経由で実行できる処理系なら応用できそうです。(現状は ChatGPT 4 でのみ実行可能な模様)

下準備

まず Ruby on Browser に URL 経由でコードを渡す方法を用意する必要があります。もともとクエリーパラメータ q= で渡せるのですが、文字数を短くするために lz-string という Javascript のライブラリを使っておりそのエンコーディングPython 標準では提供されていないためパラメータ文字列が生成できないためです。そのためにシンプルな URL エンコーディンでコードを渡すことができる q2= を新規に用意することにしました。

github.com

これで準備は完了です。

プロンプト

色々調整して ChatGPT に渡すプロンプトはこんな感じになりました。◯◯◯の箇所を生成して欲しいスクリプト種類に変更して使います。

◯◯◯ Rubyスクリプトを作成してください。
このRubyスクリプトをrubyonbrowser上で実行させたいです。

- URL は https://rubyonbrowser.ongaeshi.me/
- 後ろにクエリ文字列 ?q2=
- コードテキストを URLエンコーディングした文字列を q2 のパラメータに設定

コードと生成したURLへのリンクの順に一回で出力してください。

実行例1: 文字列を大文字に変換する

あなた

文字列を大文字に変換するRubyスクリプトを作成してください。 このRubyスクリプトをrubyonbrowser上で実行させたいです。

コードと生成したURLへのリンクの順に一回で出力してください。

ChatGPT

以下に、文字列を大文字に変換するRubyスクリプトと、それを実行するためのrubyonbrowserへのリンクを提供します。

Rubyスクリプト:

puts gets.chomp.upcase

生成したURL: https://rubyonbrowser.ongaeshi.me/?q2=puts+gets.chomp.upcase

感想

実際の実行はモバイル上から行っており、リンクを生成することで特にモバイルからだと確認しやすくなります。Ruby on Browser で実行することで、コードをちょっと修正できるのも嬉しいですね。


www.youtube.com

実行例2: Hash#dig のサンプルコードを出力させる

あなた

Hash#dig の使い方を初心者プログラマに説明するRubyスクリプトを作成してください。 このRubyスクリプトをrubyonbrowser上で実行させたいです。

コードと生成したURLへのリンクの順に一回で出力してください。

ChatGPT

RubyHash#digメソッドは、ネストされたハッシュから安全に値を取得するために使用されます。例えば、あるハッシュが複数の階層を持っている場合、digメソッドを使って特定のキーの経路に沿って値を取得できます。もし指定したキーのいずれかが存在しない場合、nilが返されます。

初心者の方に向けた説明用のRubyスクリプトを以下に示します。このスクリプトでは、簡単なハッシュを作成し、digメソッドを使用して値を取得します。

# ネストされたハッシュの例
user_data = {
  name: "Alice",
  contact: {
    email: "alice@example.com",
    phone: {
      home: "123-456-7890",
      mobile: "987-654-3210"
    }
  }
}

# 'dig' メソッドを使用して、モバイル電話番号を取得
mobile_phone = user_data.dig(:contact, :phone, :mobile)

# 結果を出力
puts "Mobile phone number: #{mobile_phone}"

このスクリプトをRubyOnBrowserで実行するためのURLは以下の通りです。

実行用URL

感想

実行すると "987-654-3210" が返ってきました。引数を (:contact, :phone, :home) に変更すると "123-456-7890" になりました。途中のパラメータ名が間違っていても例外にならず nil で返ってくるのが便利なのだと理解できました。サンプルデータも含めてコードを作成してくれるのがありがたいです。

実行例3: 未知のアルゴリズムを教えてもらう

あなた

レーベンシュタイン距離を求めるRubyスクリプトを作成してください。 このRubyスクリプトをrubyonbrowser上で実行させたいです。

コードと生成したURLへのリンクの順に一回で出力してください。

ChatGPT

レーベンシュタイン距離を計算するためのRubyスクリプトと、そのスクリプトRuby on Browserで実行するためのURLを生成しました。

Rubyスクリプトは以下の通りです。

def levenshtein_distance(s, t)
  m = s.length
  n = t.length
  return m if n == 0
  return n if m == 0
  d = Array.new(m+1) {Array.new(n+1)}

  (0..m).each {|i| d[i][0] = i}
  (0..n).each {|j| d[0][j] = j}

  (1..n).each do |j|
    (1..m).each do |i|
      cost = (s[i-1] == t[j-1]) ? 0 : 1
      d[i][j] = [d[i-1][j]+1, d[i][j-1]+1, d[i-1][j-1] + cost].min
    end
  end
  d[m][n]
end

# 使用例
puts levenshtein_distance("kitten", "sitting")

このスクリプトRuby on Browserで実行するためのURLは以下のリンクからアクセスできます:Ruby on Browserでスクリプトを実行する.

感想

git のコマンドのスペルミスがあったときに近いコマンドを提示してくれるアルゴリズムが確かこんな名前だったという記憶だけで聞いてみました。ハルシネーションが怖いですが試した感じ正しく動いていそうな感じでした。実際に使いたいときはこれをベースに挙動を理解するとこから始めるもよさそうです。(そして調べたら git ではそれよりも少し複雑なダメラウ・レーベンシュタイン距離 を使っているそう)

実行例4: ゲームを作ってもらう

あなた

シンプルなゲームができるRubyスクリプトを作成してください。 このRubyスクリプトをrubyonbrowser上で実行させたいです。

ChatGPT

わかりました。シンプルなRubyスクリプトを作成して、それをrubyonbrowser上で実行するためのURLを生成します。ゲームの内容について、どのようなものを希望されますか?例えば、「数当てゲーム」や「じゃんけんゲーム」など、基本的なものであれば実装可能です。

あなた

数当てゲーム

ChatGPT

数当てゲームのRubyスクリプトを作成します。このゲームでは、コンピュータが1から100の間でランダムに数を選び、ユーザーがその数を当てるまで推測を続けます。ユーザーが数を入力すると、コンピュータはその数が正解より高いか低いかを教えます。正解を当てるとゲームが終了します。

数当てゲームのRubyスクリプトをURLエンコーディングしました。以下のURLを使用してrubyonbrowserでゲームを実行できます。

https://rubyonbrowser.ongaeshi.me/?q2=

リンクをクリックすると、ゲームが開始されます。楽しんでください!

あなた

gets が使えないのでコンピュータが自動で入力するような方式にできますか?

ChatGPT

了解しました。getsを使用しないで、コンピュータが自動で数を推測する方式に変更します。この場合、コンピュータは一定のルールに基づいて数を推測し、それに応じて次の推測を行います。

コンピュータが自動で数を推測する方式の数当てゲームのRubyスクリプトをURLエンコーディングしました。以下のURLを使用してrubyonbrowserでゲームを実行できます。

https://rubyonbrowser.ongaeshi.me/?q2=

リンクをクリックすると、ゲームが開始されます。楽しんでください!

感想

最後は簡単なゲームを作ってもらいました。答える側の AI 部分もこちらが要求すれば作ってくれるのはすごいですね。

まとめ

プロンプト側に URL も含めて生成させておくと後からチャットログを再確認するときや、今回のようにブログに貼り付けるときにすぐに確認できるので結構便利です。その場でコードを変更して確認できるのもよいです。是非みなさんも色々な Ruby コードを生成させて実行してみてください。

Obsidian で情報カードを作るための 5 ステップ

Obsidian では普段デイリーノートを中心にメモを取っていますが、それらの情報を元に、より密度の高い情報カードを作りたくなることがあります。作成したカードを元にブログや SNS に記事を投稿します。

今日は私がどのように情報を整理しているかをシェアしたいと思います。これらの小さなテクニックを使えば、あなたの情報管理も簡単で効果的になるはずです。

1. 置き場所は1箇所にまとめる

情報カードはObsidian内の05_Cardフォルダに保存します(私はルートフォルダに数字を振っているためこうなっていますが具体的な名前はお好みで)。ここにカードを保管することで、全てが一箇所に整理されて、簡単にアクセスできます。パスが分かっていれば後から検索できるのでタグは付けません。

2. ファイル作成方法の統一

Obsidian のファイルエクスプローラーを使用して05_Cardフォルダ内で直接ファイルを作成します。最初は「無題のファイル」として作成し、内容が完成したらタイトルを付けます。大抵、文書の最初の行がそのまま適切なタイトルになります。

3. カードの書き方

各カードは、それ自体で理解可能な形式で書かれるべきです。これにより、カードは独立したメモとして、そのまま他の文脈にも使用できます。

4. 効果的な検索方法

次のクエリを使用して、05_Cardフォルダ内のカードを簡単に検索できます。

path:05_Card/ 検索ワード

5. 画像の管理

画像はGyazoにアップロードしてリンクを使うことをお勧めします。これにより、Obsidian レポジトリの肥大化とカードを他の場所に使用した際にリンクが切れることはありません。thumb/解像度.jpgを付けると画像サイズも簡単に変更できます。

![](https://gyazo.com/19240602e2eb34a3989f8040b26f7d9a/thumb/640.jpg)

これらの簡単なステップを実践することで、Obsidianを使った情報管理がぐっと楽になります。整理された情報は、より良いアイデアと生産性の向上につながります。ぜひ試してみてください!