tag:crieit.net,2005:https://crieit.net/tags/Ruby/feed 「Ruby」の記事 - Crieit Crieitでタグ「Ruby」に投稿された最近の記事 2023-12-06T18:36:02+09:00 https://crieit.net/tags/Ruby/feed tag:crieit.net,2005:PublicArticle/18672 2023-12-06T18:31:09+09:00 2023-12-06T18:36:02+09:00 https://crieit.net/posts/DXOpal DXOpalでスイカゲームみたいなのを作った <p>これは <a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2023/ruby">Ruby Advent Calendar 2023</a> の6日目の記事です。一つ前の記事は<a target="_blank" rel="nofollow noopener" href="https://zenn.dev/ryutaromizokami/articles/333ef2dd6d9c51">Rubyのプロダクトコードの複雑度の変化を可視化する</a>です。</p> <p><a href="https://crieit.now.sh/upload_images/8df96ea7f5523b51dd0222ad1ab4a57e65703da03c379.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/8df96ea7f5523b51dd0222ad1ab4a57e65703da03c379.png?mw=700" alt="image" /></a></p> <p>こんにちは。今年は mruby や picoruby にも触れてみたかったのですがもう12月ですね……おかしいな……。</p> <h1 id="できたもの"><a href="#%E3%81%A7%E3%81%8D%E3%81%9F%E3%82%82%E3%81%AE">できたもの</a></h1> <p>遊んでみたい方はこちらのリンクからどうぞ。</p> <p><a target="_blank" rel="nofollow noopener" href="https://sonota88.github.io/dxopal-suikalike/index.html">https://sonota88.github.io/dxopal-suikalike/index.html</a></p> <ul> <li>マウス操作のみ対応しています</li> <li>ボール(正方形ですが)のサイズは 1 から 10 まで <ul> <li>球にするのが面倒だったため正方形で作り始め、面倒だったため正方形のまま完成としました。以下「ボール」と言い張ります。</li> </ul></li> <li>オリジナルに寄せる方向ではがんばってません。それっぽければヨシ。</li> </ul> <hr /> <p>リポジトリはこちら。効果音はパブリックドメインとします。何かに使えそうなら使ってください。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/dxopal-suikalike">https://github.com/sonota88/dxopal-suikalike</a></p> <h1 id="DXOpal + 物理演算 → スイカゲーム"><a href="#DXOpal+%2B+%E7%89%A9%E7%90%86%E6%BC%94%E7%AE%97+%E2%86%92+%E3%82%B9%E3%82%A4%E3%82%AB%E3%82%B2%E3%83%BC%E3%83%A0">DXOpal + 物理演算 → スイカゲーム</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/yhara/dxopal">https://github.com/yhara/dxopal</a></p> <p>DXOpal についてざっくり特徴を挙げると次のような感じでしょうか。</p> <ul> <li>手軽にゲームを作れるフレームワーク</li> <li>作ったゲームはブラウザで実行する <ul> <li>ここで <a target="_blank" rel="nofollow noopener" href="https://opalrb.com/">Opal</a> が使われる(Ruby のコードが JavaScript に変換されて実行される)</li> <li>OS などの環境に依存せず同じように動かせる</li> <li>ウェブで配信する形にすればダウンロードやインストールなしで使ってもらえる・遊んでもらえる・見てもらえる</li> <li>参考: <a target="_blank" rel="nofollow noopener" href="https://qiita.com/yhara/items/24141953af652ad96db0">DXOpalで作ったゲームを無料で公開する</a></li> </ul></li> </ul> <p>入門用のドキュメントとしては以下2つがおすすめです。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://magazine.rubyist.net/articles/0057/0057-GameProgramingWithDXOpal.html">Rubyで始めるゲームプログラミング - DXOpal編 -</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2018/opal">Opal Advent Calendar 2018</a> の「DXOpalでUndertaleっぽい画面を作る」シリーズ</li> </ul> <hr /> <p>DXOpal には物理演算ライブラリ(<a target="_blank" rel="nofollow noopener" href="https://brm.io/matter-js/">matter.js</a>)を利用した機能が実験的に用意されており、リポジトリの examples ディレクトリにサンプルがあります。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/yhara/dxopal/tree/master/examples/matter">https://github.com/yhara/dxopal/tree/master/examples/matter</a></p> <p>これを使って何かできないかなーと以前からボンヤリ考えていたのですが、今年はスイカゲームが流行りましたから、じゃあ作ろうかと。</p> <p>作り自体は割と簡単です。めんどくさいところ(ボール同士の接触判定や物理演算)は DXOpal と matter.js がやってくれるので基本部分は数時間で動くようになりました。</p> <p>できあがりのサイズはこのくらい。小さく作るのが好き。</p> <pre><code> $ wc -l *.rb 57 ball.rb 314 game.rb 119 main.rb 490 合計 </code></pre> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <p>以下、開発メモです。</p> <h2 id="物体の消去"><a href="#%E7%89%A9%E4%BD%93%E3%81%AE%E6%B6%88%E5%8E%BB">物体の消去</a></h2> <p>なるべく DXOpal 本体に手を加えない範囲に収めたかったのですが、物体を消去する処理は必要なのでここだけ追加しました。</p> <pre><code class="diff">--- a/lib/dxopal/sprite/physics.rb +++ b/lib/dxopal/sprite/physics.rb @@ -56,6 +56,15 @@ module DXOpal `Matter.World.addBody(#{Sprite._matter_engine}.world, body)` end + def remove_matter_body + Sprite._remove_matter_body(@_matter_body) + end + + def self._remove_matter_body(body) + _matter_sprites.delete(`body.id`) + `Matter.World.remove(#{Sprite._matter_engine}.world, body, null)` + end + # Return true if `physical_body=` is ever called def self.matter_enabled? # Note: we cannot use `!!` here because @matter_engine may be a JS object, </code></pre> <p>これを追加するために matter.js のドキュメントとソースを軽く眺めましたが、matter.js 絡みの手間はその程度で済みました。見様見真似の間に合わせなのでひょっとしたらおかしなことしてるかもしれません。</p> <h2 id="音量の変更"><a href="#%E9%9F%B3%E9%87%8F%E3%81%AE%E5%A4%89%E6%9B%B4">音量の変更</a></h2> <p>やっぱり音量調節もしたいということで。Sound クラスと SoundEffect クラスの代わりに少しいじったクラスを用意してそっちを使っています。DXOpal 本体に手を入れなくて済むので今回はこの方式にしました。</p> <h2 id="事前コンパイル"><a href="#%E4%BA%8B%E5%89%8D%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%AB">事前コンパイル</a></h2> <p>今回は事前コンパイルする方式で開発を進めました。以下の記事を参照してください。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/536c76d7383356592b92">Dockerを利用してDXOpalアプリケーションを手軽に事前コンパイルする</a></p> <h1 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h1> <ul> <li>DXOpal を使うとブラウザで動かせるゲームが手軽に作れて便利</li> <li>スイカゲームみたいなのを作った</li> </ul> <p>以下おまけです。</p> <h1 id="dxopal-demos"><a href="#dxopal-demos">dxopal-demos</a></h1> <p>今回作ったものとは無関係ですが DXOpal 関連ということでご紹介です。<br /> 思いつきで書いてみた小物をときどき追加しているリポジトリです。アルゴリズムの解説とか見るとこういうの作りたくなりますよね。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/dxopal-demos">https://github.com/sonota88/dxopal-demos</a></p> <hr /> <p>メタボール</p> <p><a href="https://crieit.now.sh/upload_images/90326c63d6ab5f7ad23a2f3fca2cf4dc65703db8a0b5a.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/90326c63d6ab5f7ad23a2f3fca2cf4dc65703db8a0b5a.gif?mw=700" alt="image" /></a></p> <hr /> <p>Marching squares</p> <p><a href="https://crieit.now.sh/upload_images/f60b1388598298537cd0325ab363572565703dce89530.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/f60b1388598298537cd0325ab363572565703dce89530.gif?mw=700" alt="image" /></a></p> <hr /> <p>連立方程式を解くやつ(ガウス・ジョルダンの消去法)</p> <p><a href="https://crieit.now.sh/upload_images/921466057ccb5721b86dedc21f0caac465703de3d557d.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/921466057ccb5721b86dedc21f0caac465703de3d557d.gif?mw=700" alt="image" /></a></p> <hr /> <p>フーリエ変換</p> <p><a href="https://crieit.now.sh/upload_images/49e4aff901f6635199db6b3907c99dfd65703df2e2cb3.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/49e4aff901f6635199db6b3907c99dfd65703df2e2cb3.gif?mw=700" alt="image" /></a></p> <h1 id="簡単なCPUシミュレータ"><a href="#%E7%B0%A1%E5%8D%98%E3%81%AACPU%E3%82%B7%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%82%BF">簡単なCPUシミュレータ</a></h1> <p>今回作ったものとは無関係ですが DXOpal 関連ということでご紹介です。</p> <p>DXOpal は「ゲームを作るためのフレームワーク」という位置づけにはなっていますが、いわゆる普通のゲームを作る以外にも</p> <ul> <li>Ruby でちょっとしたお絵描きやデータ可視化をしたい(特に動きのあるもの)</li> <li>ついでにマウスやキーボードからの入力も扱ってインタラクティブにしたい</li> <li>どうせなら音も出したい</li> <li>作ったものを手軽に他の人に見せたい、使ってもらいたい</li> </ul> <p>という場合にサッと使えて重宝しています。</p> <p>そのような「ゲームではないもの」の例として、以前簡単な CPU シミュレータを作ったことがありました。</p> <ul> <li>回路を描画して、</li> <li>マウスクリックでスイッチを切り替えられて、</li> <li>リレーが切り替わる時にカシャッと音が鳴る</li> </ul> <p>というものです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/a5d6d3539e0fb8040f74">Ruby+DXOpalでリレー式論理回路シミュレータを自作して1bit CPUまで動かした</a></p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234055/5a779825-a252-aad1-2aab-7d51fd708363.gif" alt="x00_1bit_cpu.gif" /></p> <p>これ作るの結構楽しかったのでみなさんもぜひ! と言いたい気持ち。</p> <hr /> <p>以下の書籍にもゲーム以外のアプリケーションの例が載っています。参考までに。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://nextpublishing.jp/book/11812.html">実践Opal</a>(<a target="_blank" rel="nofollow noopener" href="https://tatsu-zine.com/books/practical-opal">達人出版会</a>) <ul> <li>DXOpal を使ってお絵かきアプリを作る</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://nextpublishing.jp/book/9555.html">Pragmatic Opal Rubyで作るブラウザアプリケーション開発ガイド</a>(<a target="_blank" rel="nofollow noopener" href="https://tatsu-zine.com/books/pragmatic-opal">達人出版会</a>) <ul> <li>ICFPCビジュアライザ</li> </ul></li> </ul> <h1 id="今年 Ruby 関連で書いたもの"><a href="#%E4%BB%8A%E5%B9%B4+Ruby+%E9%96%A2%E9%80%A3%E3%81%A7%E6%9B%B8%E3%81%84%E3%81%9F%E3%82%82%E3%81%AE">今年 Ruby 関連で書いたもの</a></h1> <p>せっかくのアドベントカレンダーですので、今年書いたものを貼っておきます。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/372da20581c16cbd4eea">Lramaで簡単な自作言語のパーサを書いた</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/52d0e35dc5e6e112c02f">Ruby + PyCall でデータサイエンス100本ノック(構造化データ加工編)をやってみた</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/7c81beca2c86fdf21556">PyCall.rb + Pandas: DataFrame#query の代わりにS式っぽく書けないか試してみた</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/a7da299b84f17459967c">Ruby/StringScanner: scan_full, search_full 系メソッドの整理</a></li> </ul> <h1 id="去年の"><a href="#%E5%8E%BB%E5%B9%B4%E3%81%AE">去年の</a></h1> <p>去年のアドベントカレンダーではこういうのを書いていました。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/84e4c16e5602b9771e92">『RubyでつくるRuby』のMinRubyのパーサを書いた(手書きの再帰下降パーサ)</a></li> </ul> <p>「世は大パーサー時代!」より数カ月早く書いてしまってタイミングを逃した(?)感がなくもないですが、パーサにご興味あればこちらもどうぞ!</p> sonota486 tag:crieit.net,2005:PublicArticle/18579 2023-09-27T12:17:24+09:00 2023-09-27T12:17:24+09:00 https://crieit.net/posts/get-recording-time-using-ruby-and-rust 【Rust/Ruby】OBS Studioの録画時間をスクリプトから取得してみる <p>こんにちは、しきゆらです。<br /> 連休は出かけていてブログ書けませんでした。</p> <p>今回は、最近ちょっと触っているOBSをプログラムから操作したり情報を取得できるということを知ったので、サンプルを見ながら情報を取得してみようと思います。</p> <p>OBSのドキュメントは以下になります。 詳しく知りたい方はこの辺を参考にするとよいかなと思います。</p> <p><a target="_blank" rel="nofollow noopener" href="https://docs.obsproject.com/">Welcome to OBS Studio’s documentation! — OBS Studio 29.1.3 documentation https://docs.obsproject.com/</a></p> <p>上記を見るとC系での話が主なようですが、今回はよく使う言語であるRubyと、最近ちょっと触っているRustで書けることを目的として調べてみました。<br /> どうやらOBSはWebSocket経由で操作することができるようです。</p> <p>公式のリポジトリにもOBSのWebSocketプラグインがあるようで、v28以降のバージョンに対応しています。 手元の環境では、初期状態で入っていてちょっと設定してあげればすぐ使えるようになったので、初めはこの辺を触ってみるのが良いかなと思います。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/obsproject/obs-websocket">GitHub - obsproject/obs-websocket: Remote-control of OBS Studio through WebSocket https://github.com/obsproject/obs-websocket</a></p> <h2 id="Rubyから録画時間を取得する"><a href="#Ruby%E3%81%8B%E3%82%89%E9%8C%B2%E7%94%BB%E6%99%82%E9%96%93%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">Rubyから録画時間を取得する</a></h2> <p>上記を受けてRubyから操作するgemがないかな、と思って調べてみると非公式ながらありました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/onyx-and-iris/obsws-ruby">GitHub - onyx-and-iris/obsws-ruby: Ruby clients for OBS Studio WebSocket v5.0 https://github.com/onyx-and-iris/obsws-ruby</a></p> <p>まずは、RubyからOBSを操作するためにサンプルを見たり内部のスクリプトを見ながら録画時間を取得するコードを書いてみました。</p> <pre><code class="ruby">require "obsws" client = OBSWS::Requests::Client.new( host: "IPアドレスなど", port: ポート番号, password: "PWの文字列" ) loop do p client.get_record_status.output_timecode sleep 1 end </code></pre> <p>上記コードはシンプルに1秒ごとに録画時間を取得してターミナルへ表示するコードです。</p> <p>IPアドレスやポート番号などは、OBSの「ツール」>「WebSocketサーバ設定」から確認・設定可能です。</p> <p>実際にスクリプトを実行しつつOBSで録画すると、録画時間が表示されました。</p> <h2 id="Rustから録画時間を取得する"><a href="#Rust%E3%81%8B%E3%82%89%E9%8C%B2%E7%94%BB%E6%99%82%E9%96%93%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">Rustから録画時間を取得する</a></h2> <p>では、同様のことをRustでやってみます。<br /> RustからOBSのWebSocketを操作するcrateは、obs-websocketにリンクがあったこちらを使ってみました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/dnaka91/obws">GitHub - dnaka91/obws: The obws (obvious) remote control library for OBS https://github.com/dnaka91/obws</a></p> <p>サンプルや内部コード、エラーを修正しつつできたのがこちら。</p> <pre><code class="rust">use std::{thread, time}; use anyhow::Result; use obws::{Client, client, requests::EventSubscription}; #[tokio::main] async fn main() -> Result<()> { let sleep_time = time::Duration::from_secs(1); let config = client::ConnectConfig { host: "IPアドレス", port: ポート番号, password: Some("PW文字列"), event_subscriptions: Some(EventSubscription::all()), broadcast_capacity: Some(100), }; let client = Client::connect_with_config(config).await?; loop { let outputs = client.recording(); let status = outputs.status().await?; println!("outputs: {}", status.timecode); thread::sleep(sleep_time) }; Ok(()) } </code></pre> <p>サンプルコードから動かしてみたのですが、諸々エラーだったりcrateが足りなかったりと動かず悪戦苦闘しながらここまで持っていきました。<br /> Rustのお作法をきちんと理解できていなので躓いただけかもですが、動く状態まで持っていけてよかったなと。</p> <p>これにて、Rustでも同様にターミナルに録画時間を表示することができました。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>今回は、OBS Studioの操作をスクリプトからできることを知ったのでRuby/Rustで簡単なサンプルを作ってみました。<br /> どちらもライブラリが用意されているので、そこまで難しくはありませんでした。<br /> また、Rustの勉強にもなったのでちょっと自信が付いたかもしれません。</p> <p>今回はここまで。</p> <p>おわり</p> しきゆら tag:crieit.net,2005:PublicArticle/18545 2023-07-29T23:01:28+09:00 2023-07-31T23:48:38+09:00 https://crieit.net/posts/fix-chromedriver-v115-distribution-change 【Ruby/Selenium】v115からChromeDriverの配布元が変わったようなので対応した話 <p>こんにちは、しきゆらです。<br /> 数年ごとに時たま書いているSeleniumネタ、 今回は久々に大きく動かなくなったので調べつつ対応した記録を残しておきます。</p> <h2 id="webdriversが動かなくなった"><a href="#webdrivers%E3%81%8C%E5%8B%95%E3%81%8B%E3%81%AA%E3%81%8F%E3%81%AA%E3%81%A3%E3%81%9F">webdriversが動かなくなった</a></h2> <p>これまでは<code>webdrivers</code>というgemを使い、インストールされているChromeのバージョンにあったChromeDriver良しなに取得するようにしていました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/titusfortner/webdrivers">GitHub - titusfortner/webdrivers: Keep your Selenium WebDrivers updated automatically</a></p> <p>さっくり<code>webdrivers</code>の中身を見てみましたが、インストールしているChromeのバージョンを確認し、必要なDriverがなければ以下のサイトから該当バージョンのものを取得・配置するようです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://chromedriver.storage.googleapis.com/"></a></p> <p>「https://chromedriver.storage.googleapis.com/LATEST_RELEASE_114」のようにChromeのバージョン値を指定するとその最新版となるChromeDriverのバージョンを取得できるので、これをもとに取得する感じでした。</p> <p>ところが、先日Seleniumを動かしたらChromeDriverがない、とエラーが出て動かなくなっていました。<br /> 何事かと思って調べてみました。</p> <h3 id="ChromeDriverの配布先が変わった"><a href="#ChromeDriver%E3%81%AE%E9%85%8D%E5%B8%83%E5%85%88%E3%81%8C%E5%A4%89%E3%82%8F%E3%81%A3%E3%81%9F">ChromeDriverの配布先が変わった</a></h3> <p>ChromeDriverのサイトを見てみると、配布先が変更されたようです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://chromedriver.chromium.org/downloads">ChromeDriver - WebDriver for Chrome - Downloads</a></p> <blockquote> <p><strong>Latest ChromeDriver Binaries</strong></p> <ul> <li>Starting with M115 the latest Chrome + ChromeDriver releases per release channel (Stable, Beta, Dev, Canary) are available at <a target="_blank" rel="nofollow noopener" href="https://googlechromelabs.github.io/chrome-for-testing/">the Chrome for Testing availability dashboard</a>. For automated version downloading one can use the convenient <a target="_blank" rel="nofollow noopener" href="https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json">JSON endpoints</a>.</li> <li>The older releases can be found at the <a target="_blank" rel="nofollow noopener" href="https://chromedriver.chromium.org/downloads">Downloads</a> page.</li> </ul> </blockquote> <p>ということで<code>webdrivers</code>が参照していたサイトとは別のところで配布するようになったようです。</p> <p>ついでに調べていると、Chrome for Testingなるものが出てきました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://developer.chrome.com/blog/chrome-for-testing/">Chrome for Testing: reliable downloads for browser automation - Chrome Developers</a></p> <p>以前も何かのタイミングでちらっと見た気がするんですが、あまり詳しく見てはいなかったので今回読んでみました。</p> <p>どうやら、Chromeは自動更新なので開発者はテストをするタイミングによって意図しないバージョンになっていたりしてつらい、そうだテスト用に自動更新がないChromeを作ろう、ということのようです。</p> <p>ということでテスト用Chromeとそのバージョン向けのChromeDriverが以下のサイトで一緒に配布されるようになっていました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://googlechromelabs.github.io/chrome-for-testing/#stable">Chrome for Testing availability</a></p> <p>自前でChromeDriverを取得するスクリプトを組んでいる場合は、この辺の対応が必要になりそう。</p> <h2 id="対応方法"><a href="#%E5%AF%BE%E5%BF%9C%E6%96%B9%E6%B3%95">対応方法</a></h2> <h3 id="暫定対応"><a href="#%E6%9A%AB%E5%AE%9A%E5%AF%BE%E5%BF%9C">暫定対応</a></h3> <p><code>webdrivers</code>でも対応するPRが上がってますが、記載時点(2023/07/29)ではまだマージされてません。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/titusfortner/webdrivers/pull/249">Fixed Webdrivers::VersionError with chrome version greater than 115 by sadahiro-ono · Pull Request #249 · titusfortner/webdrivers</a></p> <p>こちらのissueで対応方法が記載されていました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/titusfortner/webdrivers/issues/247">Webdrivers trying to load a Chrome version that doesn't exist · Issue #247 · titusfortner/webdrivers</a></p> <p>現在はv114向けのChromeDriverでも動くようなので、コード内でChromeDriverのバージョンを決め打ちすることでその場しのぎをすることができます。<br /> ただ、これもこのバージョンで動作しなくなるのは時間の問題なのでいつまで使えるかは不明です。</p> <pre><code class="ruby">Webdrivers::Chromedriver.required_version = "114.0.5735.90" </code></pre> <h3 id="Selenium Manager"><a href="#Selenium+Manager">Selenium Manager</a></h3> <p>issueを見ていくと、Selenium Managerなるものが出てきました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.selenium.dev/documentation/selenium_manager/">Selenium Manager (Beta)</a></p> <p>まだBetaのようですが、Selenium自体に含まれる各WebDriverの取得などをしてくれるツールのようです。</p> <p>これまでは、手動で配布サイトから取得したり上記<code>webdrivers</code>のような別ツールでWebDriverを取得して使う必要がありましたが、Selenium Managerを使えば外部ツールを使わずにSeleniumだけで完結するようになりますね。</p> <p>使い方も、以下の3点を満たしていれば勝手に動いてくれるようです。</p> <ul> <li><code>Service</code>クラスでWebDriverのパスを指定していない</li> <li><code>webdrivers</code>のような外部のWebDriver管理ツールを使っていない</li> <li>環境変数<code>PATH</code>の中にWebDriverがない</li> </ul> <p>ということで、<code>webdrivers</code>が動かないのでこっちを使ってみることにしました。</p> <p>ここでは、タイトルの通りRuby環境である前提で記載しますが<br /> <code>selenium-webdriver</code>のgemを最新にしたうえで、上記3点を満たすように環境を整えるだけで勝手に使えるようになります。</p> <p>なお、<code>selenium-webdriver</code>のCHANGELOGを見てみると、4.6.0のころからSelenium Managerへ対応が入っているようです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/SeleniumHQ/selenium/blob/trunk/rb/CHANGES"></a></p> <p>手元では、<code>webdrivers</code>のみしか使っていないのでこれを削除するだけで利用できました。</p> <p><code>webdrivers</code>がない状態でSeleniumを使ってブラウザを立ち上げようとするとターミナル上に以下のように記載されてChromeが立ち上がるようになりました。</p> <pre><code class="ruby">2023-07-29 20:53:08 WARN Selenium applicable driver not found; attempting to install with Selenium Manager </code></pre> <p>Selenium Managerが良しなに動いてくれているようです。</p> <p>これで、対応はおしまいです。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>今回はSeleniumを使っていたらChromeDriverを取得できずに死んでしまったので、原因を調べつつ対応してみました。</p> <p>結果としては、外部ツールに頼ることなくSeleniumが擁してくれているSelenium Managerを使うことで無事動くようになりました。</p> <p>今回は、ここまで。</p> <p>おわり</p> しきゆら tag:crieit.net,2005:PublicArticle/18485 2023-06-25T08:41:34+09:00 2023-06-25T08:41:34+09:00 https://crieit.net/posts/ruby-lrama-parser Lramaで簡単な自作言語のパーサを書いた <p><a href="https://crieit.now.sh/upload_images/36b6f940e2f434c979b5ada813c8206f64977e25696d7.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/36b6f940e2f434c979b5ada813c8206f64977e25696d7.png?mw=700" alt="image" /></a></p> <p>先日 Ruby にマージされた LALR(1)パーサジェネレータ Lrama を使ってみました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/ruby/lrama">https://github.com/ruby/lrama</a></p> <p>参考: <a target="_blank" rel="nofollow noopener" href="https://techracho.bpsinc.jp/hachi8833/2023_05_15/130116">RubyにlramaがマージされてBison依存がなくなった(RubyKaigi 2023)|TechRacho by BPS株式会社</a></p> <h1 id="できたもの"><a href="#%E3%81%A7%E3%81%8D%E3%81%9F%E3%82%82%E3%81%AE">できたもの</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-c/tree/alt-parser-lrama">https://github.com/sonota88/vm2gol-v2-c/tree/alt-parser-lrama</a></p> <p>alt-parser-lrama ブランチに <code>mrcl_parser_lrama.y</code> が入っています。</p> <h1 id="概要"><a href="#%E6%A6%82%E8%A6%81">概要</a></h1> <p>Mini Ruccola は私がコンパイラ実装に入門するために作った自作言語とその処理系です。原始的だけどその分入門者(=私)視点では分かりやすい、という方向性のものです。私でも作れるコンパイラ。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2">https://github.com/sonota88/vm2gol-v2</a></p> <p>作ったときに書いた備忘記事:</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/f9cb3fc4a496b354b729">RubyでオレオレVMとアセンブラとコード生成器を2週間で作ってライフゲームを動かした話</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/2b95378b43a22109513c">(2週間ちょっとで)Rubyでかんたんな自作言語のコンパイラを作った</a></li> </ul> <hr /> <p>コンパイラのパーサ部分は元々手書きの再帰下降パーサだったのですが、他のパーサライブラリも試したくて Racc 版と Parslet 版のパーサを以前書きました。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/97ba1f0c377fbe86d5b1">Raccでかんたんな自作言語のパーサを書いた</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/edce05e86d3248f49401">Parsletでかんたんな自作言語のパーサを書いた</a></li> </ul> <p>今回の Lrama 版はこのシリーズの延長です。</p> <hr /> <p>Mini Ruccola コンパイラはレキサ・パーサ・コード生成器が独立しています。そのため、レキサとコード生成器は Ruby 製のものをそのまま使い、パーサだけ別の言語で書いて一緒に動かすなんてことも可能です。</p> <p>可能なのですが、C言語への移植版<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-c">github.com/sonota88/vm2gol-v2-c</a><br /> を使った方が楽なので今回はこっちを使いました。</p> <p>参考: <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/06/043607">素朴な自作言語のコンパイラをCに移植した</a></p> <hr /> <p>Racc 版パーサ実装と C移植版がすでにありますから、あとは Racc 版パーサを Lrama + C 向けに書き直せば一丁あがり、という寸法です。</p> <p>たとえば以下は関数定義の規則とアクションの記述の比較です。</p> <pre><code class="ruby"># Racc func_def : "func" IDENT "(" args ")" "{" stmts "}" { _, fn_name, _, args, _, _, stmts, _, = val result = ["func", fn_name, args, stmts] } </code></pre> <pre><code class="c">// Lrama func_def : TS_KW_FUNC TS_IDENT TS_PAREN_L args TS_PAREN_R TS_SYM stmts TS_SYM // "func" fn_name "(" args ")" "{" stmts "}" { NodeList* func_def = NodeList_new(); NodeList_add_str(func_def, "func"); NodeList_add_str(func_def, $2); NodeList_add_list(func_def, $4); NodeList_add_list(func_def, $7); $$ = func_def; } </code></pre> <p>こんな感じで読み換えていきました。どちらも Yacc から派生した LALRパーサジェネレータなのでよく似ていますね。</p> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <ul> <li>1日くらいでババッと書いたものなので雑だったり手抜きで済ませている部分があります。一応動いてる、という程度の出来です。 <ul> <li>警告もひとまず放置</li> </ul></li> <li>Yacc, Bison もそのうち触ってみようと思いつつ結局今まで触らずじまいだった <ul> <li>今回ちょっとやってみて雰囲気が知れてよかった</li> <li>Rubyソースコード完全解説の <a target="_blank" rel="nofollow noopener" href="https://i.loveruby.net/ja/rhg/book/yacc.html">第9章 速習yacc</a> を読んだらとりあえずなんとかなった</li> </ul></li> <li>Rubyには慣れているけどCは分からないという状態で LALR パーサジェネレータに入門したい人は、まず Racc で入門するのが良いのではないかと思います。Ruby の知識だけで使えるので、いきなり Yacc や Bison や Lrama に挑戦するよりはお手軽かと。 <ul> <li>Racc の作者(青木峰郎さん)による解説本『Rubyを256倍使うための本 無道編』もまだ中古で入手可能(<a target="_blank" rel="nofollow noopener" href="https://www.amazon.co.jp/dp/4756137091">Amazon</a>)</li> </ul></li> </ul> <h1 id="この記事を読んだ人は(ひょっとしたら)こちらも読んでいます"><a href="#%E3%81%93%E3%81%AE%E8%A8%98%E4%BA%8B%E3%82%92%E8%AA%AD%E3%82%93%E3%81%A0%E4%BA%BA%E3%81%AF%EF%BC%88%E3%81%B2%E3%82%87%E3%81%A3%E3%81%A8%E3%81%97%E3%81%9F%E3%82%89%EF%BC%89%E3%81%93%E3%81%A1%E3%82%89%E3%82%82%E8%AA%AD%E3%82%93%E3%81%A7%E3%81%84%E3%81%BE%E3%81%99">この記事を読んだ人は(ひょっとしたら)こちらも読んでいます</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/6a96d2bcea9d134e38b7">Ruby/Racc: パース時のスタックの動きをFlameGraphっぽくビジュアライズする</a></li> </ul> <p>Racc の気持ちが分かるように↓こういう図を描かせてみたもの。</p> <p><a href="https://crieit.now.sh/upload_images/7a727bbde73b2e30d4a57505e10347d264977e43df695.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7a727bbde73b2e30d4a57505e10347d264977e43df695.png?mw=700" alt="image" /></a></p> <hr /> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/816b6b8d7a362ef4e332">Ruby: 入れ子の配列だけをパースできるパーサを作る(手書きの再帰下降パーサ)</a></li> </ul> <p>メソッドの再帰呼び出しについてすでに知っていれば1時間もかからず書ける内容。比較的簡単で時間がかからないので、LALR パーサにこだわる事情がなければ先に再帰下降パーサで入門するのも良いと思います。</p> <hr /> <ul> <li><p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/84e4c16e5602b9771e92">『RubyでつくるRuby』のMinRubyのパーサを書いた(手書きの再帰下降パーサ)</a></p></li> <li><p><a target="_blank" rel="nofollow noopener" href="https://zenn.dev/sonota88/articles/06c540eda79b98">四則演算と剰余のみのexprコマンドをRubyで作ってみた</a></p></li> <li><p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/5902f255e196b5b8b048">正規表現エンジン(ロブ・パイクのバックトラック実装)をRubyで写経した</a></p></li> </ul> sonota486 tag:crieit.net,2005:PublicArticle/18443 2023-06-03T18:02:29+09:00 2023-06-03T18:02:29+09:00 https://crieit.net/posts/ruby-pycall-datscience-100knocks Ruby + PyCall でデータサイエンス100本ノック(構造化データ加工編)をやってみた <p>まだ誰もやってなさそうだったのでやってみました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/100knocks-preprocess/tree/ruby-pycall">github.com/sonota88/100knocks-preprocess/tree/ruby-pycall</a></p> <p>オリジナルのリポジトリ <a target="_blank" rel="nofollow noopener" href="https://github.com/The-Japan-DataScientist-Society/100knocks-preprocess">The-Japan-DataScientist-Society/100knocks-preprocess</a> をフォークして <code>ruby-pycall</code> ブランチを追加しています。</p> <p>GitHub でノートブックを閲覧したい場合はこちら:<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/100knocks-preprocess/blob/ruby-pycall/docker/work/answer/ans_preprocess_knock_Ruby_PyCall.ipynb">https://github.com/sonota88/100knocks-preprocess/blob/ruby-pycall/docker/work/answer/ans_preprocess_knock_Ruby_PyCall.ipynb</a></p> <hr /> <p>解答例が複数ある場合など一部TODOにしている箇所がありますが、100問全部動かすことができました(私は Python版からコピペして書き換えただけなので、これは PyCall.rb がすごい! という話です)。</p> <p>一応筆者について書いておくと、データサイエンスに関しては入門者レベルです。データフレームという概念は一応知ってるとか、Jupyter Notebook を触ったことはあるが日常的に使っているわけではない、という感じの人です。Python, Pandas, NumPy のいずれにも詳しくありません。</p> <p>:::note</p> <p>ちなみに Ruby + RedAmber 版は heronshoes さんが作成中とのことです。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/heronshoes/items/962e312146dac8673b49">RedAmberで書いたらこうなる - pandasのquery - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://speakerdeck.com/heronshoes/the-adventure-of-redamber-a-data-frame-library-in-ruby?slide=43">The Adventure of RedAmber - A data frame library in Ruby - Speaker Deck</a> <ul> <li>RubyKaigi 2023</li> </ul></li> </ul> <p>:::</p> <h1 id="動かし方"><a href="#%E5%8B%95%E3%81%8B%E3%81%97%E6%96%B9">動かし方</a></h1> <p>リポジトリとブランチが違う以外はオリジナルの<a target="_blank" rel="nofollow noopener" href="https://github.com/The-Japan-DataScientist-Society/100knocks-preprocess">データサイエンス100本ノック(構造化データ加工編)</a>の README で書かれている手順と同じです。</p> <pre><code class="sh">git clone --branch ruby-pycall https://github.com/sonota88/100knocks-preprocess.git cd 100knocks-preprocess docker compose up -d --build --wait </code></pre> <p><code>docker compose up</code> が完了するのを待ってからブラウザで <a target="_blank" rel="nofollow noopener" href="http://127.0.0.1:8888/">http://127.0.0.1:8888/</a> を開きます。</p> <p>左のメニューから <code>/work/answer/ans_preprocess_knock_Ruby_PyCall.ipynb</code> を選択してノートブックを開きます。</p> <p><a href="https://crieit.now.sh/upload_images/36b6f940e2f434c979b5ada813c8206f647b00eb4aee3.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/36b6f940e2f434c979b5ada813c8206f647b00eb4aee3.png?mw=700" alt="image" /></a></p> <p>左側に Table of Contents を表示させておくと各問題に移動したいときに便利です。</p> <p><a href="https://crieit.now.sh/upload_images/7a727bbde73b2e30d4a57505e10347d2647b00fda1de4.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7a727bbde73b2e30d4a57505e10347d2647b00fda1de4.png?mw=700" alt="image" /></a></p> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <h2 id="Ruby 向けのカスタマイズ"><a href="#Ruby+%E5%90%91%E3%81%91%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA">Ruby 向けのカスタマイズ</a></h2> <ul> <li>Python 版の Dockerfile をコピー+修正して Ruby 用の Dockerfile-ruby を用意する</li> <li>docker-compose.yml を修正して Dockerfile-ruby に切り替える</li> </ul> <p>やったのはこれだけですね。ここはほとんど手間がかかっていません。</p> <p>RubyData プロジェクトでメンテナンスされている <code>rubydata/datascience-notebook</code> という Docker イメージがありまして、</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/RubyData/docker-stacks">github.com/RubyData/docker-stacks</a></p> <p>これをベースイメージとして使うのがポイントです。自前でカスタマイズする必要がほとんどなく、たいへん助かりました。このイメージは便利なのでどんどん使わせてもらいましょう!(← この記事で一番強調したいのはここ)</p> <p>Python版の Dockerfile との差分はこんな感じ。</p> <pre><code class="diff">--- Dockerfile 2023-05-06 12:13:18.226946937 +0900 +++ Dockerfile-ruby 2023-05-06 12:13:18.242946463 +0900 @@ -1,5 +1,6 @@ -FROM jupyter/datascience-notebook:python-3.10.8 -#FROM jupyter/datascience-notebook:d53a302fbcd0 +# 2023-02-03 +FROM rubydata/datascience-notebook:24a7f04dfc46 + USER root ENV DEBCONF_NOWARNINGS yes @@ -29,4 +30,6 @@ && Rscript -e 'pak::repo_add(CRAN = "RSPM@2022-12-16"); pak::pak(c("DBI", "RPostgreSQL", "themis"))' \ && rm -rf Pipfile* /tmp/* /var/tmp/* +RUN pip install tabulate + HEALTHCHECK --interval=5s --retries=20 CMD ["curl", "-s", "-S", "-o", "/dev/null", "http://localhost:8888"] </code></pre> <p>ほぼベースイメージの差し替えだけで済んでいます。</p> <h2 id="DataFrame#query"><a href="#DataFrame%23query">DataFrame#query</a></h2> <p>Python版の解答例では DataFrame#query を使っている箇所がいくつかあるのですが、pandas.rb ではエラーになります(2023-05-13 時点)。そこで、次のように DataFrame#[] を使う方法で愚直に書き換えました。</p> <pre><code class="ruby"># P-004 Python版の解答例1 # df_receipt[['sales_ymd', 'customer_id', 'product_cd', 'amount']] \ # .query('customer_id == &quot;CS018205000001&quot; & amount &gt;= 1000') df_receipt[['sales_ymd', 'customer_id', 'product_cd', 'amount']] \ [(df_receipt['customer_id'] == "CS018205000001") & (df_receipt['amount'] >= 1000)] </code></pre> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/7c81beca2c86fdf21556">PyCall.rb + Pandas: DataFrame#query の代わりにS式っぽく書けないか試してみた</a> という別記事で書いたように他の対策もありますが、今回は素朴に DataFrame#[] を使うだけにしました。なるべく素のまま書く方針です。</p> <h1 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/mrkn/pycall.rb">mrkn/pycall.rb: Calling Python functions from the Ruby language</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://codezine.jp/article/detail/10251">Ruby-Pythonブリッジライブラリ「PyCall」を使ってRubyでデータ分析をしよう! (1/3)|CodeZine(コードジン)</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/RubyData/workshop-materials/blob/master/iruby_guides/users_guide.md">workshop-materials/users_guide.md at master · RubyData/workshop-materials · GitHub</a> <ul> <li>IRuby Notebook 入門用テキスト</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/stat/items/a383451e7f824e4c9627">PyCall Ruby版 Tips - Qiita</a></li> </ul> <h1 id="この記事を読んだ人は(ひょっとしたら)こちらも読んでいます"><a href="#%E3%81%93%E3%81%AE%E8%A8%98%E4%BA%8B%E3%82%92%E8%AA%AD%E3%82%93%E3%81%A0%E4%BA%BA%E3%81%AF%EF%BC%88%E3%81%B2%E3%82%87%E3%81%A3%E3%81%A8%E3%81%97%E3%81%9F%E3%82%89%EF%BC%89%E3%81%93%E3%81%A1%E3%82%89%E3%82%82%E8%AA%AD%E3%82%93%E3%81%A7%E3%81%84%E3%81%BE%E3%81%99">この記事を読んだ人は(ひょっとしたら)こちらも読んでいます</a></h1> <ul> <li><p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/aeacaf0ec7957715cdff">Ruby+PyCall.rbでLibreOffice Calcのオートメーションをやってみた(Ubuntu 18.04)</a></p></li> <li><p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/b586e96f7805a5695a34">Galaaz を触ってみた(TruffleRuby + ggplot2 で散布図を描いてみた)</a></p></li> </ul> sonota486 tag:crieit.net,2005:PublicArticle/18391 2023-02-23T15:03:54+09:00 2023-02-23T15:03:54+09:00 https://crieit.net/posts/ruby-numo-gnuplot-gem-scatterplot Ruby + Numo::Gnuplot(numo-gnuplot gem)で散布図を描く <p>@obelisk68 さんに教えてもらったサンプル(<a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/50609275668fcd7511e4#comment-0890d29e9583e74eba70">ruby_gnuplot の記事のコメント</a>, <a target="_blank" rel="nofollow noopener" href="https://obelisk.hatenablog.com/entry/2022/05/29/181912">ブログ</a>)で十分という気もしたのですが、一応自分でも触ってみました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://rubygems.org/gems/numo-gnuplot">https://rubygems.org/gems/numo-gnuplot</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/ruby-numo/numo-gnuplot">https://github.com/ruby-numo/numo-gnuplot</a></p> <h1 id="準備"><a href="#%E6%BA%96%E5%82%99">準備</a></h1> <ul> <li>gnuplot コマンドをインストール <ul> <li>Ubuntu 18.04 の場合は <code>sudo apt install gnuplot</code></li> </ul></li> <li>Numo::Gnuplot をインストール <ul> <li><code>gem install numo-gnuplot</code></li> </ul></li> </ul> <p>これだけ。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/ruby-numo/numo-narray">Numo::NArray</a> は必須ではありません(インストールも不要)。以下は Numo::NArray なしで試しています。</p> <h1 id="最低限の例"><a href="#%E6%9C%80%E4%BD%8E%E9%99%90%E3%81%AE%E4%BE%8B">最低限の例</a></h1> <p>PNG ファイルに出力する最低限の例です。</p> <pre><code class="ruby"># sample.rb require "numo/gnuplot" xs = [1, 2, 3, 4, 5] ys = [11, 22, 33, 44, 55] Numo.gnuplot do set terminal: "png" set output: "output.png" plot xs, ys end </code></pre> <pre><code class="sh"> # 実行 ... output.png が生成される ruby sample.rb </code></pre> <p><a href="https://crieit.now.sh/upload_images/1d02f3831d34666675afe13cd9b51f8763f7011c631ae.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/1d02f3831d34666675afe13cd9b51f8763f7011c631ae.png?mw=700" alt="image" /></a></p> <p>簡単ですね。<br /> ちょっと見えにくいですが、 両端の点は枠線に重なっています。</p> <h1 id="環境"><a href="#%E7%92%B0%E5%A2%83">環境</a></h1> <pre><code class="sh">Ubuntu Linux 18.04 gnuplot -V #=> gnuplot 5.2 patchlevel 2 ruby -v #=> ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux] gem: numo-gnuplot 0.2.4 </code></pre> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/rdp/ruby_gnuplot">ruby_gnuplot</a> と同様、インストールが楽なのが良い。gnuplot コマンドのインストールさえ済んでしまえば、他にインストール絡みでトラブルが起きる余地がなさそう。気分的に楽。</li> <li><code>numo/gnuplot.rb</code> ( https://github.com/ruby-numo/numo-gnuplot/blob/master/lib/numo/gnuplot.rb ) はコメントを除くと1,000行ちょいなので、サッと読める。ruby_gnuplot よりは大きめですが。 <ul> <li><code>Numo::Gnuplot#initialize</code> で <code>IO.popen</code> したプロセスにコマンドを送る方式</li> </ul></li> </ul> <hr /> <p>ruby_gnuplot との機能比較でいえば、以下のあたりが違うようです:</p> <ul> <li>Numo::NArray に対応している</li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/SciRuby/iruby">IRuby</a> に対応している</li> <li>irb で対話的に操作できる <ul> <li>gnuplot の対話型インターフェイスの代わりに使えるっぽい</li> </ul></li> </ul> <h1 id="自分が使いそうな機能についてメモ"><a href="#%E8%87%AA%E5%88%86%E3%81%8C%E4%BD%BF%E3%81%84%E3%81%9D%E3%81%86%E3%81%AA%E6%A9%9F%E8%83%BD%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6%E3%83%A1%E3%83%A2">自分が使いそうな機能についてメモ</a></h1> <ul> <li>グラフのタイトル指定</li> <li>複数の系列の描画</li> <li>数式のプロット</li> </ul> <p>などは <a target="_blank" rel="nofollow noopener" href="https://github.com/ruby-numo/numo-gnuplot">README</a> や <a target="_blank" rel="nofollow noopener" href="https://github.com/ruby-numo/numo-gnuplot/wiki/Introduction.ja">Introduction.ja · ruby-numo/numo-gnuplot Wiki</a> にサンプルがあるので、そっちを見てください。</p> <h2 id="軸タイトル"><a href="#%E8%BB%B8%E3%82%BF%E3%82%A4%E3%83%88%E3%83%AB">軸タイトル</a></h2> <pre><code class="ruby">Numo.gnuplot do set xlabel: "x軸のラベル" set ylabel: "y軸のラベル" # ... </code></pre> <h2 id="x軸, y軸の範囲指定"><a href="#x%E8%BB%B8%2C+y%E8%BB%B8%E3%81%AE%E7%AF%84%E5%9B%B2%E6%8C%87%E5%AE%9A">x軸, y軸の範囲指定</a></h2> <pre><code class="ruby">Numo.gnuplot do set xrange: 0..6 set yrange: 0..60 # ... </code></pre> <h2 id="凡例の系列名"><a href="#%E5%87%A1%E4%BE%8B%E3%81%AE%E7%B3%BB%E5%88%97%E5%90%8D">凡例の系列名</a></h2> <pre><code class="ruby">Numo.gnuplot do plot xs, ys, title: "series name" # ... </code></pre> <p><a href="https://crieit.now.sh/upload_images/b73ba4fbec5510c877d85d0cbf8d9c0063f7013aa98af.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b73ba4fbec5510c877d85d0cbf8d9c0063f7013aa98af.png?mw=700" alt="image" /></a></p> <h2 id="日本語フォント"><a href="#%E6%97%A5%E6%9C%AC%E8%AA%9E%E3%83%95%E3%82%A9%E3%83%B3%E3%83%88">日本語フォント</a></h2> <p>無指定だと日本語が豆腐になりました。</p> <pre><code class="ruby">Numo.gnuplot do set terminal: "png font 'VL Gothic,20'" # ... </code></pre> <p><a href="https://crieit.now.sh/upload_images/6426107e494ed6aca64b9388221cdf2d63f7014778647.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6426107e494ed6aca64b9388221cdf2d63f7014778647.png?mw=700" alt="image" /></a></p> <h2 id="点の代わりにラベルを表示する"><a href="#%E7%82%B9%E3%81%AE%E4%BB%A3%E3%82%8F%E3%82%8A%E3%81%AB%E3%83%A9%E3%83%99%E3%83%AB%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B">点の代わりにラベルを表示する</a></h2> <pre><code class="ruby">xs = [1, 2, 3, 4, 5] ys = [11, 22, 33, 44, 55] labels = ["A", "B", "C", "D", "E"] Numo.gnuplot do # ... plot xs, ys, labels, with: "labels" end </code></pre> <p><a href="https://crieit.now.sh/upload_images/06e4631861c41cafa09b2c4a22728f3f63f701524af8b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/06e4631861c41cafa09b2c4a22728f3f63f701524af8b.png?mw=700" alt="image" /></a></p> <h2 id="デバッグ出力"><a href="#%E3%83%87%E3%83%90%E3%83%83%E3%82%B0%E5%87%BA%E5%8A%9B">デバッグ出力</a></h2> <p><code>debug_on</code> を使うと、 gnuplot に送信しているコマンドを標準出力に出力することができます。</p> <pre><code class="ruby">xs = [1, 2, 3, 4, 5] ys = [11, 22, 33, 44, 55] Numo.gnuplot do debug_on set terminal: "png" set output: "output.png" plot xs, ys end </code></pre> <pre><code class="sh">$ ruby sample_debug.rb <set terminal png <set output "output.png" <plot '-' <1 11 <2 22 <3 33 <4 44 <5 55 <e </code></pre> <p>行頭の <code><</code> を取り除いてやれば、そのまま gnuplot に渡せるようです。</p> <pre><code class="sh">$ ruby sample_debug.rb | sed -e 's/^<//g' > sample.gs $ cat sample.gs set terminal png set output "output.png" plot '-' 1 11 2 22 3 33 4 44 5 55 e # スクリプトを gnuplot コマンドに渡して実行 $ gnuplot sample.gs </code></pre> <h1 id="まとめたもの"><a href="#%E3%81%BE%E3%81%A8%E3%82%81%E3%81%9F%E3%82%82%E3%81%AE">まとめたもの</a></h1> <p>せっかくなのでいろいろ含めたものも貼ってみます。<br /> @obelisk68 さんが書いてくださったのとほぼ同じ。</p> <pre><code class="ruby">require "numo/gnuplot" xs = [1.0, 1.8, 3.1] ys1 = [11, 25, 32] ys2 = [21, 35, 42] ys3 = [31, 45, 52] Numo.gnuplot do set terminal: "png font 'Noto Sans CJK JP,12'" set output: "output.png" set title: "グラフのタイトル" set xrange: 0..6 set yrange: 0..60 set xlabel: "x軸のラベル" set ylabel: "y軸のラベル" plot xs, ys1, { title: "系列1(点のみ)" }, xs, ys2, { title: "系列2(線のみ)", with: :lines }, xs, ys3, { title: "系列3(点と線)", with: :linespoints }, "x * 10", { title: "y = x * 10" } end </code></pre> <p><a href="https://crieit.now.sh/upload_images/0a564211e53eb0221887ee8da4ecd21163f7016136f2e.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/0a564211e53eb0221887ee8da4ecd21163f7016136f2e.png?mw=700" alt="image" /></a></p> <p><code>with: :lines</code> のように指定するか、または数式を使えば任意の直線が描画できるので、回帰直線もこれでいけますね(フィッティングの話については割愛)。</p> <h1 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/ruby-numo/numo-gnuplot-demo">ruby-numo/numo-gnuplot-demo: Ruby/Numo::Gnuplot Demo</a></p> <p>めちゃくちゃいっぱいある gnuplot 本家のサンプルを Numo::Gnuplot に書き直した版。README から辿れる情報ですが一応。</p> <h1 id="この記事を読んだ人は(ひょっとしたら)こちらも読んでいます"><a href="#%E3%81%93%E3%81%AE%E8%A8%98%E4%BA%8B%E3%82%92%E8%AA%AD%E3%82%93%E3%81%A0%E4%BA%BA%E3%81%AF%EF%BC%88%E3%81%B2%E3%82%87%E3%81%A3%E3%81%A8%E3%81%97%E3%81%9F%E3%82%89%EF%BC%89%E3%81%93%E3%81%A1%E3%82%89%E3%82%82%E8%AA%AD%E3%82%93%E3%81%A7%E3%81%84%E3%81%BE%E3%81%99">この記事を読んだ人は(ひょっとしたら)こちらも読んでいます</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/50609275668fcd7511e4">Ruby + ruby_gnuplot(gnuplot gem)で散布図を描く</a></p> <p>ruby_gnuplot もシンプルで良いと思いますよ。</p> <hr /> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/86afece3b2595648a656">SVG::Graph(svg-graph gem)で散布図を描く</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/b586e96f7805a5695a34">Galaaz を触ってみた(TruffleRuby + ggplot2 で散布図を描いてみた)</a></p> sonota486 tag:crieit.net,2005:PublicArticle/18373 2023-01-22T17:38:20+09:00 2023-01-22T17:44:40+09:00 https://crieit.net/posts/ruby-minruby-recursive-descent-parser 『RubyでつくるRuby』のMinRubyのパーサを書いた(手書きの再帰下降パーサ) <p>これは <a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2022/ruby">Ruby Advent Calendar 2022</a> の25日目の記事です。</p> <p>書籍 <strong>『RubyでつくるRuby ゼロから学びなおすプログラミング言語入門』</strong>(以下、 <strong>『RubyでつくるRuby』</strong> )で扱われているミニ言語 MinRuby のパーサを書いてみました。</p> <hr /> <p>『RubyでつくるRuby』は、Ruby を使った基本的なプログラミングの入門から始まり、後半ではミニ言語 MinRuby(Ruby のサブセット)のインタプリタを作って最終的に MinRuby言語で MinRuby インタプリタを書くところまでやってしまう、という内容の入門書です。</p> <hr /> <p>……と自分なりに内容紹介してみましたが、以下もあわせて参考にしていただければと思います。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://mametter.hatenablog.com/entry/20170315/p1">書籍『Ruby でつくる Ruby』が発売されます - まめめも</a> <ul> <li>著者の遠藤さん</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://golden-lucky.hatenablog.com/entry/2023/01/10/131903">『RubyでつくるRuby』の読み方(私論) - golden-luckyの日記</a> <ul> <li>ラムダノートの鹿野さん</li> </ul></li> </ul> <hr /> <p><a target="_blank" rel="nofollow noopener" href="https://www.lambdanote.com/products/ruby-ruby">RubyでつくるRuby ゼロから学びなおすプログラミング言語入門 – 技術書出版と販売のラムダノート</a></p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.lambdanote.com/products/ruby-ruby-ebook">PDF版のみの販売ページ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.amazon.co.jp/dp/4908686017">Amazon</a></li> </ul> <blockquote> <p>プログラミングを始めるなら、プログラミング言語を自分でつくってみるのがいちばん! 最低限の機能なら、こんなに簡単にインタプリタを作れます。よくわからなかったプログラミングも、裏側の仕組みから分かってしまえば怖くない! </p> <p>2016年9月から2017年1月にかけて<a target="_blank" rel="nofollow noopener" href="https://ascii.jp/serialarticles/1230449/">アスキーjpの「プログラミング+」コーナーで連載された大好評のWebコンテンツ『Rubyで学ぶRuby』</a>を、さらにわかりやすく紙版の書籍として編纂しなおして発売するものです。豊富なイラストもカラーで完全採録。</p> </blockquote> <ul> <li>プログラミング初心者を想定して丁寧に解説してある</li> <li>分量が多すぎず(144ページ)読み通しやすい</li> <li>評価器にフォーカスしており、パーサはスコープ外 <ul> <li>パースは <a target="_blank" rel="nofollow noopener" href="https://rubygems.org/gems/minruby">minruby gem</a> にまかせる</li> </ul></li> </ul> <p>たしか発売された頃に読んで「せっかくだからパーサも自作したい」と思った記憶があります。当時はパーサの作り方についての知識が足りずどうすればよいか分からなかったのですが、その後あれこれあって簡単なものなら作れるようになりました。</p> <h1 id="できたもの"><a href="#%E3%81%A7%E3%81%8D%E3%81%9F%E3%82%82%E3%81%AE">できたもの</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/cookpad-hackarade-minruby/tree/recursive-descent-parser">https://github.com/sonota88/cookpad-hackarade-minruby/tree/recursive-descent-parser</a></p> <pre><code class="terminal"> $ wc -l rcl_*.rb my_minruby_parser.rb 33 rcl_common.rb 65 rcl_lexer.rb 496 rcl_parser.rb 17 my_minruby_parser.rb 611 合計 </code></pre> <p>一応動いてはいます。advent calendar には間に合った……リファクタリングなどは冬休み以降の宿題にします。</p> <p>(2023-01-22 追記) interp.rb がパースできるようになりました。</p> <h1 id="動作例"><a href="#%E5%8B%95%E4%BD%9C%E4%BE%8B">動作例</a></h1> <pre><code class="terminal"> $ cat test3-4.rb n = 1 while n < 100 if n % 3 == 0 if n % 5 == 0 p("FizzBuzz") else p("Fizz") end else if n % 5 == 0 p("Buzz") else p(n) end end n = n + 1 end $ ruby my_minruby_parser.rb test3-4.rb [:stmts, [:var_assign, "n", [:lit, 1]], [:while, [:<, [:var_ref, "n"], [:lit, 100]], [:stmts, [:if, [:==, [:%, [:var_ref, "n"], [:lit, 3]], [:lit, 0]], [:if, [:==, [:%, [:var_ref, "n"], [:lit, 5]], [:lit, 0]], [:func_call, "p", [:lit, "FizzBuzz"]], [:func_call, "p", [:lit, "Fizz"]]], [:if, [:==, [:%, [:var_ref, "n"], [:lit, 5]], [:lit, 0]], [:func_call, "p", [:lit, "Buzz"]], [:func_call, "p", [:var_ref, "n"]]]], [:var_assign, "n", [:+, [:var_ref, "n"], [:lit, 1]]]]]] </code></pre> <h1 id="cookpad-hackarade-minruby"><a href="#cookpad-hackarade-minruby">cookpad-hackarade-minruby</a></h1> <p>まずは先行事例を軽く調べました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://techlife.cookpad.com/entry/2018/10/16/131000">Hackarade #04: Create Your Own Interpreter - クックパッド開発者ブログ</a></p> <blockquote> <p>完全セルフホスト:MinRubyでパーサを書き、minruby gemに依存せずにinterp.rb単体でセルフホストするようにした</p> </blockquote> <p>さすがのクックパッドさん。今回私はここまではやっていません。パーサを MinRuby で書くのもおもしろそう……。</p> <p>同記事により、 mame/cookpad-hackarade-minruby というリポジトリでテストケースが用意されていることを知りました。ありがたく有効活用させていただきましょう。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/mame/cookpad-hackarade-minruby">mame/cookpad-hackarade-minruby: material for Cookpad's Hackarade #4</a></p> <p>今回はこのテストケースを使い、自作パーサと minruby gem に同じ入力を与えて同じ結果になればヨシ! ということにしました。</p> <h1 id="方針"><a href="#%E6%96%B9%E9%87%9D">方針</a></h1> <p>パーサ入門でよくある再帰下降パーサにしました。</p> <p>専用のパーサライブラリを使うのに比べるとデメリットもあり本格的な言語実装ではあまり採用されない手法だと思いますが、入門者目線だと「全部自分で書いたぞ感」「全部把握できてる感覚」が得られるという、とても良い良さがあります。</p> <p>(ちなみに、Rust製 Ruby 実装 <a target="_blank" rel="nofollow noopener" href="https://github.com/sisshiki1969/ruruby">ruruby</a> のパーサは手書きだそうです。すごい……。)</p> <p>ここで選択肢が2つあり、</p> <ul> <li>(1) 何もないところから全部書く</li> <li>(2) Ruccola のパーサを改造する</li> </ul> <p>ちょっとだけ (1) を試した後、時間(というか計画性)がないこともあって (2) に切り替えました。<br /> さて、Ruccola というのがスッと出てきましたね。これはなんでしょうか。</p> <h1 id="Ruccola とは"><a href="#Ruccola+%E3%81%A8%E3%81%AF">Ruccola とは</a></h1> <p>Ruccola は私が自分の勉強のために作っている素朴な自作プログラミング言語です。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/ruccola">https://github.com/sonota88/ruccola</a></p> <p>最新の知見を盛り込んだキラリと光る言語……とかでは全然なく、現代の水準からするとかなり原始的なものです。自分の勉強用・入門用なので素朴でよいと割り切っています。</p> <p>2021年にセルフホストできた(詳しくは↓の記事を参照)後も趣味の盆栽プログラミング的にちびちびと機能追加やリファクタリングを続けています。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/1e683276541cf1b87b76">素朴な自作言語Ruccolaのコンパイラをセルフホストした - Qiita</a></p> <ul> <li>コンパイラを Ruby で書いている</li> <li>Ruccola言語のコードの見た目は Ruby っぽい</li> <li>なんとなくC言語っぽいものをイメージして作ったが、現時点では型がない(整数しかない)ので <a target="_blank" rel="nofollow noopener" href="https://ja.wikipedia.org/wiki/B%E8%A8%80%E8%AA%9E">B言語</a> や <a target="_blank" rel="nofollow noopener" href="https://ja.wikipedia.org/wiki/BCPL">BCPL</a> の方が近いのかも(詳しくないのでボンヤリした書き方)</li> </ul> <p>ざっくり書くとこんな感じです。このうち「見た目は Ruby っぽい」というのがポイントで、Ruccola のパーサ( <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/ruccola/blob/00fc70c2f76e56737e290b541a69e9c36f9bb968/rcl_parser.rb">202-12-25 時点のソース</a> )にいくつか手を加えれば MinRuby のパーサとして使えそうでした。</p> <p>コード例です。以下は Ruccola言語で書いた cat コマンド。</p> <pre><code class="ruby">def main() var EOF = -1; var c; while (true) c = getchar(); if (c == EOF) break; end write(c, 1); end end </code></pre> <p>だいたい Ruby ですね。中身は原始的なのに見た目は Ruby 風なので、書いているとちょっと不思議な気分になります。</p> <p>作者の目にはだいたい Ruby と同じに見えますが、Ruby に慣れている方には次のような点が目に付くのではないでしょうか。</p> <ul> <li>文末の <code>;</code> (必須)</li> <li>while の条件式を囲む <code>(</code> <code>)</code> (必須)</li> <li>変数宣言の var(必須)</li> </ul> <p>他にも関数呼び出しの <code>(</code> <code>)</code>、関数定義の仮引数を囲む <code>(</code> <code>)</code>、エントリポイントとなる main 関数も必須です。</p> <p>自分が言語実装ビギナーなので、難しいことをしなくていいように Ruby の文法にいろいろ制限を加えてこうなっています。Ruby と完全にコンパチではありませんが、構文ハイライトやエディタのインデント支援は Ruby 向けのものがだいたいそのまま使えています。</p> <p>ちなみに、Ruccola よりももっとコンパクトな <strong>mini-ruccola</strong>(元は vm2gol-v2 という名前だった)もあります。VM〜コンパイラを割と素朴に書いて1500行以下というもの。自分が欲しい(難しすぎず、簡単すぎず、読み書きに慣れている言語向けの)教材が見つけられず、じゃあ自分で作ってしまえとなって作ったもの。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2">https://github.com/sonota88/vm2gol-v2</a></p> <p>以前週刊Railsウォッチで紹介していただきました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://techracho.bpsinc.jp/hachi8833/2021_02_09/103838">週刊Railsウォッチ(20210209後編)Rubyでミニ言語処理系を作る、Kernel#getsの意外な機能、CSSのcontent-visibilityほか|TechRacho by BPS株式会社</a></p> <p>作った順番としては mini-ruccola の方が先で、最初にこっちで簡単なコンパイラ実装になんとか入門し、その後セルフホストに必要な機能を追加していく形で Ruccola を作っていきました。</p> <h1 id="主な変更点"><a href="#%E4%B8%BB%E3%81%AA%E5%A4%89%E6%9B%B4%E7%82%B9">主な変更点</a></h1> <h2 id="MinRuby の AST に合わせる"><a href="#MinRuby+%E3%81%AE+AST+%E3%81%AB%E5%90%88%E3%82%8F%E3%81%9B%E3%82%8B">MinRuby の AST に合わせる</a></h2> <p>Ruccola の AST は現時点では MinRuby と同じく入れ子の配列(のようなもの)で木構造を表す方式です。そこは同じ。ただ細かいところが違うので合わせていきます。</p> <p>簡単なところでいえば、たとえば以下は変数への代入です。</p> <pre><code class="diff">- [:set, var_name, expr] + [:var_assign, var_name, expr] </code></pre> <p>ものによってはこの程度の変更でOK。</p> <h2 id="演算子の優先順位"><a href="#%E6%BC%94%E7%AE%97%E5%AD%90%E3%81%AE%E5%84%AA%E5%85%88%E9%A0%86%E4%BD%8D">演算子の優先順位</a></h2> <p>Ruccola では自分でも手に負えるようにいろいろと単純化しています。演算子の優先順位もそのひとつで、たとえば <code>1 + 2 * 3</code> という式は <code>(1 + 2) * 3</code> として扱われます(単純に左結合とする)。</p> <p>えっそれでいいの? と思いましたか? 思いますよね。</p> <p>言語処理系の解説を見ると必ずといっていいほど演算子の優先順位の話題が出てきて、自作言語を作るには避けて通れないトピックであるかのように思われます。私もある時までそう思い込んでいたのですが、実はそんなことはありません……と思うんですよね(微妙な自信のなさ)。</p> <p>Ruccola では <code>1 + (2 * 3)</code> と解釈させたい場合は明示的に括弧で優先順位を指定して <code>1 + (2 * 3)</code> と書けばよい、ということにしています。たまに自分でも忘れていて素でびっくりします。まあ自分の勉強用の言語だからいいんですよこれで。最初から実装しなくてもよいので後回しにしています。Ruccola言語の記述力がまだ低いため、今入れてもコード量が膨れるデメリット(セルフホストしているのでばかにならず、下手に機能追加すると自分が困る)の方が大きそうで嫌かなという考えもあり。</p> <p>ちなみに、似たような方針を採っている言語として <a target="_blank" rel="nofollow noopener" href="http://middleriver.chagasi.com/electronics/vtl.html">VTL (Very Tiny Language)</a> という言語があると後から知りました。仲間がいた……。</p> <blockquote> <p>VTLでは,演算子の間に優先順位は存在せず,左から順番に演算が行われます.演算の順序を変えたい場合は括弧"()"をつけます.例えば,"2+3*4"の結果は"20"となり,"2+(3*4)"の結果は"14"となります.</p> </blockquote> <p>あ、ていうか優先順位を明示する言語といえば Lisp があるじゃないか、とこの記事を書いていて今思い至りました。Lisp、心強いですね。</p> <p>さて、Ruccola ではそれでいいとして、演算子の優先順位に対応しないと cookpad-hackarade-minruby のテストが通りません。なんとかしなければ。</p> <p>これについては自分にとって今回初めてという訳ではなく、以前単体で履修していたのでした。</p> <p><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/07/05/111103">四則演算と剰余のみのexprコマンドをRubyで作ってみた</a></p> <p>これを組み込めばいけるはず。</p> <p>test1-4.rb と test1-5.rb の間の飛躍が大きかったので小さなテストを追加しながら進めました。</p> <ul> <li>まずは一番優先順位の低い <code>1 == 2</code> から</li> <li>二番目に優先順位の低い <code><</code> を右側に加えた <code>1 == 2 < 3</code></li> <li>...</li> <li><code>1 == 2 < 3 + 4 * 5</code> が <code>(1 == (2 < (3 + (4 * 5))))</code> と解釈できればOK</li> <li>ここまでできたら test1-5.rb が通る</li> </ul> <p>という流れで進めることで意外とすんなり書き換え完了しました。</p> <p>参考: <a target="_blank" rel="nofollow noopener" href="https://docs.ruby-lang.org/ja/latest/doc/spec=2foperator.html">演算子式 (Ruby 3.1 リファレンスマニュアル)</a></p> <p>優先順位まわりについては書籍『<a target="_blank" rel="nofollow noopener" href="http://esolang-book.route477.net/">Rubyで作る奇妙なプログラミング言語</a>』もおすすめです。私はこの本の写経で入門しました。</p> <h2 id="if と case"><a href="#if+%E3%81%A8+case">if と case</a></h2> <p>Ruccola では if 文は case 文のシンタックスシュガーという扱いになっています。「case文が動けば if文はそのサブセット扱いにできるよね」という、大は小を兼ねる的な素朴な発想によるものです。あと Ruccola の前身の mini-ruccola は最初はパーサがなくて構文木を手書きするところからスタートしたため、その都合もあります。</p> <p>MinRuby では逆になっていて、 case 式は内部的に入れ子の if 式としてパースされます(『RubyでつくるRuby』 p85)。ここは今回『RubyでつくるRuby』を読み返してなるほどと思ったところでした。</p> <h2 id="改行の扱い"><a href="#%E6%94%B9%E8%A1%8C%E3%81%AE%E6%89%B1%E3%81%84">改行の扱い</a></h2> <p>Ruccola では現時点では if文の条件式全体を囲む括弧は省略不可です(case文、while文も同様)。また、代入や関数呼び出しなどの文の末尾にはセミコロンが必須です。改行はスペースと同じように字句解析の段階で捨てています。</p> <pre><code class="ruby">if (i == 10) p(1); end </code></pre> <p>一方 MinRuby では条件式を囲む括弧や関数呼び出しなどの末尾のセミコロンは省略可能です。改行の考慮が必要そう。</p> <pre><code class="ruby">if i == 10 p(1) end </code></pre> <p>改行を考慮したパースはこれまで経験がなく不確実な部分でしたが、結論からいえば特別な対応は不要で、適当に改行を読み飛ばすだけで cookpad-hackarade-minruby のテストは通りました(というか試しにやってみたら字句解析の段階で全部捨てても大丈夫だった)。</p> <p>今の実装では、式とみなせるまとまりの次に二項演算子が来たら「まだ式の続きがあるな」と判断し、そうでなければ「いったんそこで式が終わっているな」と判断する、という動作になっています。<br /> たとえば上のコード例だと <code>i == 10</code> の次に <code>p</code> というトークンが来ています。これは二項演算子ではありませんから、ここで式の区切りと判断され、<code>i == 10</code> と <code>p</code> 以降は別のまとまりだということになります。</p> <pre><code>i == 10 + ... ^ 二項演算子なのでまだ式が続いていると判断 i == 10 p ... ^ 二項演算子ではないので p からは別のまとまり </code></pre> <p>あくまで cookpad-hackarade-minruby のテストのカバー範囲と今回作ったパーサの実装の組み合わせでは問題なかったというだけで、Ruby に近づけていこうとするとどこかでちゃんとした対応が必要になってくるでしょうね。</p> <h2 id="配列・ハッシュ"><a href="#%E9%85%8D%E5%88%97%E3%83%BB%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5">配列・ハッシュ</a></h2> <p>Ruccola では配列リテラルや配列の要素にアクセスするための専用の構文は(まだ)ありません。ハッシュはサポート予定なし。</p> <p>なので配列・ハッシュまわりは新たに書きました。ここはすんなり書けてしまって特に書くことがありません。</p> <h2 id="TODO"><a href="#TODO">TODO</a></h2> <ul> <li>if, case まわりがいいかげんなのでもうちょいちゃんとやる</li> <li>interp.rb をパースできるようにする(ここまではやりたい)</li> </ul> <p>(2023-01-22 追記) 上記2つ完了しました。「これで完璧!」というものではありませんが、とりあえず interp.rb はパースできています。</p> <h1 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h1> <p>以上のように自作言語 Ruccola のパーサにいくつかの変更を加えることで MinRuby のパーサを作ることができました(ただし cookpad-hackarade-minruby のテストを通すところまで)。</p> <p>以前から MinRuby のパーサ作れないかなーとボンヤリ考えていたのでこの機会に実現できてよかったです。</p> <hr /> <p>以下、おまけ的な雑多な話題・メモなどです。</p> <h1 id="おまけ"><a href="#%E3%81%8A%E3%81%BE%E3%81%91">おまけ</a></h1> <h2 id="再帰下降パーサについて"><a href="#%E5%86%8D%E5%B8%B0%E4%B8%8B%E9%99%8D%E3%83%91%E3%83%BC%E3%82%B5%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">再帰下降パーサについて</a></h2> <p>再帰下降パーサについては書籍やウェブに解説がたくさんありますので「再帰下降 パーサ」「再帰下降 構文解析」などで調べてください。英語だと recursive descent parser です。</p> <p>今回作ったものはパーサだけで 500行くらいありますし、再帰下降パーサの一番最初の入門用としてはちょっと大きいかもしれません。そういう場合は分解してちょっとずつ手を付けるのがおすすめです。</p> <ul> <li>くりかえしの構造 <ul> <li>関数定義の仮引数、関数呼び出し時の引数、演算子を含む式、配列リテラル、ハッシュリテラルなど、いろんなところで登場する</li> <li><code>数</code> のあとに <code>, 数</code> をくりかえし</li> <li>例: <code>1, 2, 3</code></li> <li><code>数</code> のあとに <code>演算子 数</code> のくりかえし</li> <li>例: <code>1 + 2 + 3</code></li> </ul></li> <li><code>[[]]</code>, <code>[[[]]]</code> のような入れ子の構造</li> <li>演算子の優先順位</li> </ul> <p>上の方でも書いたように、演算子の優先順位は後回しにできますから、先にそれ以外の部分を作ってから後付けで追加するのもよいと思います。</p> <p>「MinRuby のパーサを書いてみたいけどいきなり全部書くのは無理そう……また今度にしよう」と思った方は、まず次のような簡単なものから手を付けてみるのはどうでしょう。どちらも50行くらいです。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/816b6b8d7a362ef4e332">入れ子の配列だけをパースできるパーサを作る(手書きの再帰下降パーサ)</a> <ul> <li><code>((), (()))</code> のような入れ子の括弧だけをパースできるパーサ。コード例があった方がよさそうと思って簡単なものを昨日急いで書きました。</li> <li>n年前の自分に「これで入門しろ」「最初はこれでいいと思うよ」といって送りつけるとしたらこれ</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/5902f255e196b5b8b048">正規表現エンジン(ロブ・パイクのバックトラック実装)をRubyで写経した</a> <ul> <li>バックトラック方式の正規表現エンジンもおすすめです。こっちも手軽。手軽だけどおもしろい。再帰下降パーサに考え方が似ています。</li> </ul></li> </ul> <h2 id="MinRuby 関連"><a href="#MinRuby+%E9%96%A2%E9%80%A3">MinRuby 関連</a></h2> <p>今回調べていて見つけたもの。</p> <p><a target="_blank" rel="nofollow noopener" href="https://speakerdeck.com/m_seki/extend-your-own-programming-language-rubykaigi-2018">https://speakerdeck.com/m_seki/extend-your-own-programming-language-rubykaigi-2018</a></p> <p>MinRuby に末尾呼び出し最適化を実装する話や Rinda + MinRuby の話。</p> <p><a target="_blank" rel="nofollow noopener" href="https://matsubara0507.github.io/posts/2019-05-16-minruby-with-patternmatch.html">https://matsubara0507.github.io/posts/2019-05-16-minruby-with-patternmatch.html</a></p> <p>パーサは「木を組み立てる」処理がメインなのでパターンマッチの出番はそんなにありませんが、評価器は「木を使う」処理がメインなのでパターンマッチを使うといい感じに書けますね。</p> <h2 id="関連書籍"><a href="#%E9%96%A2%E9%80%A3%E6%9B%B8%E7%B1%8D">関連書籍</a></h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="http://esolang-book.route477.net/">Rubyで作る奇妙なプログラミング言語</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://tatsu-zine.com/books/scheme-in-ruby">つくって学ぶプログラミング言語 RubyによるScheme処理系の実装 - 達人出版会</a></li> </ul> <p>『RubyでつくるRuby』もそうですが、どの本も古びていない、これからも通用する、使い回しのきく内容だと思います。Ruby に慣れていて言語処理系に興味があるという人におすすめです。</p> <h2 id="mal (Make a Lisp)"><a href="#mal+%28Make+a+Lisp%29">mal (Make a Lisp)</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/kanaka/mal">https://github.com/kanaka/mal</a></p> <p>Lisp に興味のある方には mal(Make a Lisp)もおすすめです。ちょうど『RubyでつくるRuby』の Lisp 版といった感じで、簡単な Lisp インタプリタを作ってセルフホストするまでが11ステップに分かれており、テストを通しながら作っていきます。入門者向けのシンプルな実装ですが、マクロや例外機構、末尾呼び出しの最適化まで含まれています。すでに <a target="_blank" rel="nofollow noopener" href="https://github.com/kanaka/mal/tree/03b6cfd45c99db651ece70381a7c0f596fc14336/impls/ruby">Ruby 版の実装</a>(主な部分は500行程度)もリポジトリに含まれていますから、そのまま写経してもいいですし、自力で書いて詰まったときに参考にすることもできます。</p> <p>ちなみに私は一度 Ruby で写経した後 LibreOffice Basic で書くということをやりました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/501f0efa437bc4d53cc7">LibreOffice BasicでLispインタプリタ(mal)を書いた - Qiita</a></p> <h2 id="PicoRuby"><a href="#PicoRuby">PicoRuby</a></h2> <p>入門者向けに作られているものではありませんが、Ruby 関連の小さめの処理系というと PicoRuby がありますね。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/picoruby">https://github.com/picoruby</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/picoruby/mruby-pico-compiler">https://github.com/picoruby/mruby-pico-compiler</a></p> <p>この記事の前日(2022-12-24)に開発者募集されていました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/picoruby/picoruby/wiki/%E3%80%90%E3%81%8A%E8%AA%98%E3%81%84%E3%80%91PicoRuby%E3%81%AE%E9%96%8B%E7%99%BA%E8%80%85%E3%82%92%E5%8B%9F%E9%9B%86%E3%81%97%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99">【お誘い】PicoRubyの開発者を募集しています · picoruby/picoruby Wiki</a></p> <p>Ruby よりは小さくて難しくなさそう?? 読めそうだったら参考のためにソース読んでみようかな……と思いつつまだ何もできていません。</p> <p>Lemon というパーサジェネレータを使ってるんですね。Lemon 知らなかった。SQLite が使っているものだそうです。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://sqlite.org/lemon.html">The Lemon LALR(1) Parser Generator</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/picoruby/picoruby/wiki/Proposal-for-the-Grant-Program-2020-of-Ruby-Association">Proposal for the Grant Program 2020 of Ruby Association · picoruby/picoruby Wiki</a></li> </ul> <h2 id="雑多"><a href="#%E9%9B%91%E5%A4%9A">雑多</a></h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/97ba1f0c377fbe86d5b1">Raccでかんたんな自作言語のパーサを書いた</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/edce05e86d3248f49401">Parsletでかんたんな自作言語のパーサを書いた</a></li> </ul> <p>mini-ruccola のパーサを Racc と Parslet を使って書いたもの。</p> <hr /> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/6870ef5fe5b2a9275ab7">自作言語のコンパイラにオレオレアセンブリではなくx86_64アセンブリを生成させる(関数呼び出しと足し算だけ) - Qiita</a></p> <p>コンパイラ作ったらやはりこれもやっておきたい、ということで(ちょっとだけ)やったみたもの。RISC-V 版や WebAssembly 版もやってみたい。</p> <hr /> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/a5d6d3539e0fb8040f74">Ruby+DXOpalでリレー式論理回路シミュレータを自作して1bit CPUまで動かした - Qiita</a></p> <p>言語処理系をやるとさらに下のレイヤーも気になってきますよね? ということでついでに紹介。これも楽しかったです。Ruby(と <a target="_blank" rel="nofollow noopener" href="https://yhara.github.io/dxopal/doc/ja/index.html">DXOpal</a>)で作れます!</p> <h2 id="今年 Ruby 関連で書いたもの"><a href="#%E4%BB%8A%E5%B9%B4+Ruby+%E9%96%A2%E9%80%A3%E3%81%A7%E6%9B%B8%E3%81%84%E3%81%9F%E3%82%82%E3%81%AE">今年 Ruby 関連で書いたもの</a></h2> <p>せっかくのアドベントカレンダーなので今年書いたものも並べてみます。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/b586e96f7805a5695a34">Galaaz を触ってみた(TruffleRuby + ggplot2 で散布図を描いてみた)</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/539c631b0f7dfb295fec">Ubuntu 22.04にJupyter NotebookとIRubyをインストール(pyenv, rbenv, Bundler を使用)</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/7e2ab396163c081657b1">Ruby + Victor でSVGお絵描き(簡単な散布図を描いてみた)</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/86afece3b2595648a656">SVG::Graph(svg-graph gem)で散布図を描く</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/50609275668fcd7511e4">Ruby + ruby_gnuplot(gnuplot gem)で散布図を描く</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/da02c6f9f85c5a33f0a1">Ruby + Numo::Gnuplot(numo-gnuplot gem)で散布図を描く</a></li> </ul> sonota486 tag:crieit.net,2005:PublicArticle/18359 2022-12-29T18:12:03+09:00 2022-12-29T18:20:09+09:00 https://crieit.net/posts/ruby-gnuplot-gem-scatterplot Ruby + ruby_gnuplot(gnuplot gem)で散布図を描く <p><a target="_blank" rel="nofollow noopener" href="https://rubygems.org/gems/gnuplot">https://rubygems.org/gems/gnuplot</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/rdp/ruby_gnuplot">https://github.com/rdp/ruby_gnuplot</a></p> <h1 id="準備"><a href="#%E6%BA%96%E5%82%99">準備</a></h1> <ul> <li>gnuplot コマンドをインストール <ul> <li>Ubuntu 18.04 の場合は <code>sudo apt install gnuplot</code></li> </ul></li> <li>ruby_gnuplot をインストール <ul> <li><code>gem install gnuplot</code></li> </ul></li> </ul> <p>これだけ。</p> <p>ただし、 Ruby 3.1.0 で matrix が bundled gem になったようなので、3.1.0 以降の場合は <code>gem install matrix</code> も必要です。<br /> 参考: <a target="_blank" rel="nofollow noopener" href="https://zenn.dev/shuichi/articles/bundled-gem-with-gemfile">[Ruby] Bundled gemsはGemfileに指定して使おう</a></p> <h1 id="最低限の例"><a href="#%E6%9C%80%E4%BD%8E%E9%99%90%E3%81%AE%E4%BE%8B">最低限の例</a></h1> <p>PNG ファイルに出力する最低限の例です。</p> <pre><code class="ruby"># sample.rb require "gnuplot" Gnuplot.open do |gp| Gnuplot::Plot.new(gp) do |plot| plot.terminal "png" plot.output "output.png" x = [1, 2, 3, 4, 5] y = [11, 22, 33, 44, 55] plot.data << Gnuplot::DataSet.new([x, y]) end end </code></pre> <pre><code class="sh"> # 実行 ... output.png が生成される ruby sample.rb </code></pre> <p><a href="https://crieit.now.sh/upload_images/36b6f940e2f434c979b5ada813c8206f63ad5a4b7d18e.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/36b6f940e2f434c979b5ada813c8206f63ad5a4b7d18e.png?mw=700" alt="image" /></a></p> <p>以上。簡単ですね。<br /> ちょっと見えにくいですが、 両端の点は枠線に重なっています。</p> <p>PNG 以外にも、 SVG や PDF として出力することもできます。</p> <h1 id="環境"><a href="#%E7%92%B0%E5%A2%83">環境</a></h1> <pre><code class="sh">Ubuntu Linux 18.04 gnuplot -V #=> gnuplot 5.2 patchlevel 2 ruby -v #=> ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux] gem: gnuplot 2.6.2 matrix 0.4.2 </code></pre> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <ul> <li>とにかくインストールが楽なのが良い。gnuplot コマンドのインストールさえ済んでしまえば、他にインストール絡みでトラブルが起きる余地がなさそう。気分的に楽 👌</li> <li>gnuplot でできることは大体何でもできるはず。たぶん。</li> <li><code>gnuplot.rb</code> ( https://github.com/rdp/ruby_gnuplot/blob/master/lib/gnuplot.rb ) はコメントを除くと300行くらいなので、サッと読める <ul> <li>gnuplot 用のスクリプトに変換して gnuplot コマンドに渡しているだけ</li> </ul></li> <li>「○○がやりたいけどどうすればいい?」となった場合は gnuplot 向けの情報を探す <ul> <li>gnuplot は枯れたプロダクトなので、情報はたくさんある</li> <li>すでに gnuplot に習熟している人は楽そう</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/red-data-tools/charty">Charty</a> のバックエンドとして使える?(まだ試していません) <ul> <li>(追記) 2022-05-29 現在では未対応でした</li> </ul></li> </ul> <p>非常に薄いラッパーなので、素の gnuplot をそのまま使うのと実はそんなに大差なくて、そこを踏まえた上で使う分には便利。<br /> 「gnuplot の使い方を覚えたいわけではないんだけどなあ……」という人には向かないかもしれません。</p> <h1 id="自分が使いそうな機能についてメモ"><a href="#%E8%87%AA%E5%88%86%E3%81%8C%E4%BD%BF%E3%81%84%E3%81%9D%E3%81%86%E3%81%AA%E6%A9%9F%E8%83%BD%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6%E3%83%A1%E3%83%A2">自分が使いそうな機能についてメモ</a></h1> <ul> <li>グラフのタイトル指定</li> <li>x軸, y軸の範囲指定、軸タイトル指定</li> <li>複数の系列の描画</li> <li>数式のプロット</li> </ul> <p>などは <a target="_blank" rel="nofollow noopener" href="https://github.com/rdp/ruby_gnuplot">README</a> にサンプルがあるので、そっちを見てください。</p> <h2 id="系列名の凡例"><a href="#%E7%B3%BB%E5%88%97%E5%90%8D%E3%81%AE%E5%87%A1%E4%BE%8B">系列名の凡例</a></h2> <p><code>DataSet.new</code> にブロックを付けると、ブロックパラメータとして DataSet のインスタンスが渡されるので、それを使って指定できる。</p> <pre><code class="ruby">plot.data << Gnuplot::DataSet.new([x, y]) { |ds| ds.title = "dataset" } </code></pre> <p><a href="https://crieit.now.sh/upload_images/7a727bbde73b2e30d4a57505e10347d263ad5aa0bf78c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7a727bbde73b2e30d4a57505e10347d263ad5aa0bf78c.png?mw=700" alt="image" /></a></p> <h2 id="日本語フォント"><a href="#%E6%97%A5%E6%9C%AC%E8%AA%9E%E3%83%95%E3%82%A9%E3%83%B3%E3%83%88">日本語フォント</a></h2> <p>無指定だと日本語が豆腐になりました。<br /> <code>gnuplot フォント 日本語 set terminal</code> のような検索ワードで調べると、 <code>Plot#terminal</code> で指定できることが分かります。</p> <pre><code class="ruby">Gnuplot.open do |gp| Gnuplot::Plot.new(gp) do |plot| plot.terminal "png font 'VL Gothic,20'" plot.output "output.png" x = [1, 2, 3] y1 = [11, 25, 32] plot.data << Gnuplot::DataSet.new([x, y1]) { |ds| ds.title = "系列1" } end end </code></pre> <p><a href="https://crieit.now.sh/upload_images/34a1b30f9276e1831c27ee024469926b63ad5aaf0c674.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/34a1b30f9276e1831c27ee024469926b63ad5aaf0c674.png?mw=700" alt="image" /></a></p> <h2 id="点の代わりにラベルを表示する"><a href="#%E7%82%B9%E3%81%AE%E4%BB%A3%E3%82%8F%E3%82%8A%E3%81%AB%E3%83%A9%E3%83%99%E3%83%AB%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B">点の代わりにラベルを表示する</a></h2> <pre><code class="ruby">Gnuplot.open do |gp| Gnuplot::Plot.new(gp) do |plot| plot.terminal "png" plot.output "output.png" x = [1, 2, 3] y = [11, 22, 33] label = ["A", "B", "C"] plot.data << Gnuplot::DataSet.new([x, y, label]) { |ds| ds.with = "labels" } end end </code></pre> <p><a href="https://crieit.now.sh/upload_images/0c359f4284f6db1f1eeab9708335763163ad5abc9417f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/0c359f4284f6db1f1eeab9708335763163ad5abc9417f.png?mw=700" alt="image" /></a></p> <hr /> <p>点とラベルを両方表示するのは若干面倒そう。</p> <p>In gnuplot, how to label each point in the plot with its coordinates? - Stack Overflow<br /> <a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/23177716/in-gnuplot-how-to-label-each-point-in-the-plot-with-its-coordinates">https://stackoverflow.com/questions/23177716/in-gnuplot-how-to-label-each-point-in-the-plot-with-its-coordinates</a></p> <h2 id="生成されたスクリプトだけ欲しい"><a href="#%E7%94%9F%E6%88%90%E3%81%95%E3%82%8C%E3%81%9F%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%A0%E3%81%91%E6%AC%B2%E3%81%97%E3%81%84">生成されたスクリプトだけ欲しい</a></h2> <p>以下のように <code>Gnuplot.open</code> にモンキーパッチを当てると、グラフの描画は行わずに gnuplot 用に生成されたスクリプトの出力だけを行うことができます。</p> <pre><code class="ruby">module Gnuplot def self.open(persist = true) io = $stdout yield io end end Gnuplot.open do |gp| Gnuplot::Plot.new(gp) do |plot| x = [1, 2, 3] y = [11, 22, 33] plot.data << Gnuplot::DataSet.new([x, y]) end end </code></pre> <pre><code class="sh">$ ruby sample_print_script.rb > sample.gs $ cat sample.gs plot '-' 1 11 2 22 3 33 e # スクリプトを gnuplot コマンドに渡して実行 $ gnuplot -persist sample.gs </code></pre> <h1 id="まとめたもの"><a href="#%E3%81%BE%E3%81%A8%E3%82%81%E3%81%9F%E3%82%82%E3%81%AE">まとめたもの</a></h1> <p>せっかくなのでいろいろ含めたものも貼ってみます。</p> <pre><code class="ruby">require "gnuplot" Gnuplot.open do |gp| Gnuplot::Plot.new(gp) do |plot| plot.terminal "png font 'Noto Sans CJK JP,12'" plot.output "output.png" plot.title "グラフのタイトル" plot.xrange "[0:6]" plot.yrange "[0:60]" plot.xlabel "x軸のラベル" plot.ylabel "y軸のラベル" x = [1, 2, 3] y1 = [11, 25, 32] y2 = [21, 35, 42] y3 = [31, 45, 52] plot.data << Gnuplot::DataSet.new([x, y1]) { |ds| ds.title = "系列1" } plot.data << Gnuplot::DataSet.new([x, y2]) { |ds| ds.title = "系列2(線のみ)" ds.with = "lines" } plot.data << Gnuplot::DataSet.new([x, y3]) { |ds| ds.title = "系列3(点と線)" ds.with = "linespoints" } # 数式 plot.data << Gnuplot::DataSet.new("x * 10") { |ds| ds.title = "y = x * 10" } end end </code></pre> <p><a href="https://crieit.now.sh/upload_images/dda408a296d12511a205c05e56b2e0e563ad58ecdcfc6.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/dda408a296d12511a205c05e56b2e0e563ad58ecdcfc6.png?mw=700" alt="image" /></a></p> <p><code>ds.with = "lines"</code> を使うか、または数式を使えば任意の直線が描画できるので、回帰直線もこれでいけますね(フィッティングの話については割愛)。</p> <h1 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h1> <p>Rubyのgnuplotで時系列データをプロット | Lifelog<br /> <a target="_blank" rel="nofollow noopener" href="https://ytkyk.info/blog/2016/07/21/rubyのgnuplotで時系列データをプロット/">https://ytkyk.info/blog/2016/07/21/rubyのgnuplotで時系列データをプロット/</a></p> <hr /> <p>gnuplot documentation<br /> <a target="_blank" rel="nofollow noopener" href="http://gnuplot.info/documentation.html">http://gnuplot.info/documentation.html</a></p> <p>日本語のPDFがあります。</p> <hr /> <p>Demos for gnuplot version 5.4<br /> <a target="_blank" rel="nofollow noopener" href="http://gnuplot.sourceforge.net/demo_5.4/">http://gnuplot.sourceforge.net/demo_5.4/</a></p> <p>豊富なサンプル。gnuplot マジ何でもできるな、という気持ちになります。</p> <h1 id="この記事を読んだ人は(ひょっとしたら)こちらも読んでいます"><a href="#%E3%81%93%E3%81%AE%E8%A8%98%E4%BA%8B%E3%82%92%E8%AA%AD%E3%82%93%E3%81%A0%E4%BA%BA%E3%81%AF%EF%BC%88%E3%81%B2%E3%82%87%E3%81%A3%E3%81%A8%E3%81%97%E3%81%9F%E3%82%89%EF%BC%89%E3%81%93%E3%81%A1%E3%82%89%E3%82%82%E8%AA%AD%E3%82%93%E3%81%A7%E3%81%84%E3%81%BE%E3%81%99">この記事を読んだ人は(ひょっとしたら)こちらも読んでいます</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/86afece3b2595648a656">SVG::Graph(svg-graph gem)で散布図を描く</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/b586e96f7805a5695a34">Galaaz を触ってみた(TruffleRuby + ggplot2 で散布図を描いてみた)</a></li> </ul> sonota486 tag:crieit.net,2005:PublicArticle/18319 2022-11-23T17:55:13+09:00 2022-11-29T17:08:40+09:00 https://crieit.net/posts/acea21ee905aaf3997a8f1856b504303 事故物件的なポモドーロ タイマー <p>たのまれたので、なんとかする予定のプログラム。</p> <p>ポモドーロタイマー。</p> <p>ポモドーロタイマーとは、ポモドーロがイタリア語でトマト、でタイマーはキッチンタイマーのタイマー。</p> <p>トマト キッチンタイマーなんだけど、そっちが主ではなくて、そのタイマーではかる時間のことを指している。<br /> なにかクリエーティブな作業をするときの時間配分で、2時間かけたい場合、25分間作業して、5分間休憩して、25分間作業して、5分間休憩して、<br /> 25分間作業して、5分間休憩して、25分間作業して、15分間休憩してというようにすると、いい感じで集中してられる、というライフハックのことをポモドーロタイマーというらしい。</p> <pre><code>end time 17:28:35 1 pomodoro = 25min, rest = 5min long rest = 15min 1 pomodoro 25min -> rest 5min -> 1 pomodoro 25min -> rest 5min 1 pomodoro 25min -> rest 5min -> 1 pomodoro 25min -> long rest 15min total 130min pomodoro: 1 total time: 16 min remain: 9 min now 15:35:08 </code></pre> <p>さっと作ってみてポモドーロタイマーを検索したらいっぱいありそう。</p> <p>たのまれたのは、たぶんそういうのじゃないのでしばらく時間がかかりそう。</p> <p>パピヨン本田のマンガを読んでリラックスしよう。</p> <p><a target="_blank" rel="nofollow noopener" href="https://twitter.com/papiyonhonda/status/1475425058684551176?s=20&t=LHttfuow8JjZmaKTEYmpQQ">https://twitter.com/papiyonhonda/status/1475425058684551176?s=20&t=LHttfuow8JjZmaKTEYmpQQ</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://mobile.twitter.com/papiyonhonda/status/1589904538404007936/photo/1">papiyonhonda</a></p> <p>基本的には、このようなかんじか。<br /> Ruby<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/uf7d5">https://rentry.co/uf7d5</a></p> <p>問題なければ、機能を付けていく。<br /> まだロジックを考えてないが、ひとまず間違いがないかチェック。</p> <p>うん、なんかまちがっているね。</p> <p>どこがおかしいかなということでチェックしていこう。</p> <pre><code class="ruby">1 require 'time' 2 3 pomo = ARGV[0].to_i # 1 pomodoro durations 4 interval = ARGV[1].to_i # rest durations 5 long_rest = ARGV[2].to_i # long rest durations 6 7 one_set = pomo*4 + interval*3 + long_rest # min 8 puts one_set # min 9 10 tempotime_0 = Time.new 11 tempotime = Time.new 12 mission_finish_time = tempotime_0 + one_set*60 # sec 13 puts tempotime_0 14 puts mission_finish_time 15 minutes = (mission_finish_time - tempotime_0).to_i.div(60) 16 puts "#{minutes/60}:#{minutes%60}" 17 exit </code></pre> <pre><code class="ruby">60 while seconds < 60 do 61 t = Time.new 62 63 if (t - tempotime > 1) </code></pre> <p>のとこは</p> <pre><code class="ruby">60 while seconds < 60 do 61 t = Time.new 62 63 if ((t - tempotime).to_i > 0) </code></pre> <p>こうしといて。</p> <p>ここもおかしくなるので、</p> <pre><code class="ruby">92 puts "remain: #{(((tempotime_0 + pomo*60)-t).to_i).div(60)} min #{(((tempotime_0 + pomo*60)-t).to_i).modulo(60)} sec" </code></pre> <p>これでいいんじゃない。</p> <pre><code class="ruby">92 puts "remain: #{((tempotime_0 + pomo*60*pomodoro + resttimes*60*(pomodoro - 1)) - t).to_i.div(60)} min #{(((tempotime_0 + pomo*60*pomodoro + resttimes*60*(pomodoro - 1)) - t).to_i).modulo(60)} sec" </code></pre> <p>いちおう、ベースはできた。基本的にはこれで良さそう。<br /> Ruby<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/pvmdk">https://rentry.co/pvmdk</a></p> <p>間違うところもよくわかったので、初めて Dart をどうやって書くのか読みながらヨチヨチと Ruby で書いたものと同じような感じにしてみる。<br /> Dart<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/369sd">https://rentry.co/369sd</a></p> tomato tag:crieit.net,2005:PublicArticle/18315 2022-11-10T19:08:50+09:00 2023-01-14T21:46:42+09:00 https://crieit.net/posts/Youtube-Un-programme-Ruby-merdique-qui-rassemble-des-informations-sur-les-vid-os-youtube-en-utilisant-l-algorithme-youtube-merdique グレートクソアルゴリズム | くだらない youtube アルゴリズムを使用して youtube ビデオに関する情報を収集するくだらない Ruby プログラム <p><a target="_blank" rel="nofollow noopener" href="https://crieit-net.translate.goog/posts/Youtube-Un-programme-Ruby-merdique-qui-rassemble-des-informations-sur-les-vid-os-youtube-en-utilisant-l-algorithme-youtube-merdique?_x_tr_sl=ja&_x_tr_tl=en&_x_tr_hl=ja&_x_tr_pto=wapp">translate</a></p> <p><a href="https://crieit.net/posts/c2b7c645c32fda0b2cffd3aea91d6a01#狂ったアルゴリズムは チートされる">クソアルゴリズム</a></p> <p><a href="https://crieit.now.sh/upload_images/1e2d4057a6bad42b7364c8cd67d12d3b636b9e02e07f5.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/1e2d4057a6bad42b7364c8cd67d12d3b636b9e02e07f5.png?mw=700" alt="" /></a></p> <h1 id="クソアルゴリズムにたいして、クソアルゴリズムを利用して、必要なものだけを選別していくものを書いてみる。"><a href="#%E3%82%AF%E3%82%BD%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0%E3%81%AB%E3%81%9F%E3%81%84%E3%81%97%E3%81%A6%E3%80%81%E3%82%AF%E3%82%BD%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%A6%E3%80%81%E5%BF%85%E8%A6%81%E3%81%AA%E3%82%82%E3%81%AE%E3%81%A0%E3%81%91%E3%82%92%E9%81%B8%E5%88%A5%E3%81%97%E3%81%A6%E3%81%84%E3%81%8F%E3%82%82%E3%81%AE%E3%82%92%E6%9B%B8%E3%81%84%E3%81%A6%E3%81%BF%E3%82%8B%E3%80%82">クソアルゴリズムにたいして、クソアルゴリズムを利用して、必要なものだけを選別していくものを書いてみる。</a></h1> <p>え、突然なに ? と思うかもしれないが、やっぱり怒ってるんだな。<br /> <a target="_blank" rel="nofollow noopener" target="_blank" rel="nofollow noopener" href="http://www.zariganiworks.co.jp/korejanairobo/">これじゃないロボ</a>ってわかるかなぁ。</p> <blockquote> <p>欲しかったロボはこれじゃない!世界中から子供たちの悲痛な叫びが聞こえる情操教育玩具。グッドデザイン賞受賞の伝説的玩具。<br /> コレジャナイロボ(The Original Model)<br /> <a target="_blank" rel="nofollow noopener" href="https://www.assiston.co.jp/1595">https://www.assiston.co.jp/1595</a></p> </blockquote> <p>わかんないと思うな。</p> <h2 id="コレジャナイ ユーチューブ"><a href="#%E3%82%B3%E3%83%AC%E3%82%B8%E3%83%A3%E3%83%8A%E3%82%A4+%E3%83%A6%E3%83%BC%E3%83%81%E3%83%A5%E3%83%BC%E3%83%96">コレジャナイ ユーチューブ</a></h2> <p>何を怒っているか整理すると、Youtube 検索結果について。</p> <p>簡単に言うと、探したいものがあって、探してるときに、あなたの探してるものと関係ありそうなもの教えてあげる的に頼んでもない集合知をほいって添えられて、それを断るすべがないということについて。</p> <p>探しているものがはっきりしていて、その言葉で検索かけているときに、ほいっ、あなたの前回見た動画から他の人が見たのこれだから、こんなの面白いみたいよ、どぞー !! って検索結果に混ぜられるの意味あると思うのか ?? まともに考えて。それ、おすすめ映画をレコメンドするアルゴリズムだよね。それ、バカでしかないからやめてほしいんだ。</p> <h2 id="それ、バカでしかないからやめてほしいんだ youtube . . ."><a href="#%E3%81%9D%E3%82%8C%E3%80%81%E3%83%90%E3%82%AB%E3%81%A7%E3%81%97%E3%81%8B%E3%81%AA%E3%81%84%E3%81%8B%E3%82%89%E3%82%84%E3%82%81%E3%81%A6%E3%81%BB%E3%81%97%E3%81%84%E3%82%93%E3%81%A0+youtube+.+.+.">それ、バカでしかないからやめてほしいんだ youtube . . .</a></h2> <p>例えば、<code>ransomware</code> というキーワードで検索したとして、それと前回たまたま見た何かの動画とは全く関係ない趣向で、今検索してるのに、じゃあこれもって一言も <code>ransomware</code> のことなんて発言しない youtuber のたくさん視聴された関連動画を検索結果に混ぜてくるのって、「機械学習してるからー」てことを人間が配慮してあげないとしたら、意味不明のバカでしかない。</p> <p>意味不明のバカでしかない . . .</p> <p>意味不明のバカな結果を出すアルゴリズムを権威的に出してくるって、意味不明なバカレベルである。だから、やめて、と思うだけなんだな。<br /> 他人の行動も、過去の自分のトレンドも全く関係がない TPO が読めないアルゴリズムって、ただの邪魔だ、ということ。<br /> そんなことは当たり前過ぎるのに、なぜか当然のように諦めさせらるとっても不毛なシステムだ。<br /> これがなんでもかんでも Collaborative filtering 。</p> <p>この配慮のない他人の行動をどんなときにも当てはめようとしてくる様式をクソアルゴリズムと呼ばずにはおれない。</p> <p>でも、クソとかバカとかいうのも、どうにもならないわかりきったことで、単に Google が正しくキュレーションされたものより、てっとりばやく消費される季節ネタのようなバズを見えるとこに置いた方が広告の流入になるという方針なだけで、そういった正攻法はかつて創業者によって「情報の精度が落ちる要因」とされているので、クソなことをわかってやっていて、かつて 2000 年代に蔓延したアホみたいなインデックス型のサーチエンジン並みのクオリティを実現するアルゴリズムを新参のカウンターとして、知的に駆逐した彼ら google 自身が「今」作っているということ。</p> <p>The Age of PageRank is Over<br /> 09 Nov, 2022<br /> Vladimir Prelovac<br /> CEO, Kagi Inc.<br /> <a target="_blank" rel="nofollow noopener" href="https://blog.kagi.com/age-pagerank-over">https://blog.kagi.com/age-pagerank-over</a></p> <p>もちろん、そんな 20 年以上レイドバックしたテクは 22 年以上前の<a href="https://crieit.net/posts/c2b7c645c32fda0b2cffd3aea91d6a01">板フロート掲示板王子</a>によってチートされている。「クソをクソだと見抜けない人が使っている」ということが、クソの臭い嗅ぎ王子には見透かされたと言っていい。たぶん、世界中同じような状況じゃないかと思う。だって、結局古いんだもん。</p> <p>というところまででクソアルゴリズムを悪く言うのはここまでにして、じゃあ、どうすればいいの ?<br /> 自分の決めたキーワードとの関連はどうやって判断するのか ? を考えてみる。</p> <h2 id="キーワードと youtube 動画の相関は、タイトルにキーワードが含まれるか ? だけで判断するということにする。"><a href="#%E3%82%AD%E3%83%BC%E3%83%AF%E3%83%BC%E3%83%89%E3%81%A8+youtube+%E5%8B%95%E7%94%BB%E3%81%AE%E7%9B%B8%E9%96%A2%E3%81%AF%E3%80%81%E3%82%BF%E3%82%A4%E3%83%88%E3%83%AB%E3%81%AB%E3%82%AD%E3%83%BC%E3%83%AF%E3%83%BC%E3%83%89%E3%81%8C%E5%90%AB%E3%81%BE%E3%82%8C%E3%82%8B%E3%81%8B+%3F+%E3%81%A0%E3%81%91%E3%81%A7%E5%88%A4%E6%96%AD%E3%81%99%E3%82%8B%E3%81%A8%E3%81%84%E3%81%86%E3%81%93%E3%81%A8%E3%81%AB%E3%81%99%E3%82%8B%E3%80%82">キーワードと youtube 動画の相関は、タイトルにキーワードが含まれるか ? だけで判断するということにする。</a></h2> <p>含まれていたら、関連動画としてリストに追加するし、含まれていなければそれ以上関係性を考慮しない。これだけのストレートなルールを設定する。<br /> なので、<strong>youtube 動画のタイトルが web ページのデータ上のどこにあるのかを割り出すことが必要</strong>。</p> <p>キーワードで youtube 検索するには、</p> <p><a target="_blank" rel="nofollow noopener" target="_blank" rel="nofollow noopener" href="https://www.youtube.com/results?search_query=ransomware">https://www.youtube.com/results?search_query=ransomware</a></p> <p>で、get する。そうすると、検索結果を表示するリダイレクトが youtube ページで行われる。</p> <p><a href="https://crieit.now.sh/upload_images/c7f4777602b710c4097ff1781f2c2303636c61830680c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/c7f4777602b710c4097ff1781f2c2303636c61830680c.png?mw=700" alt="" /></a></p> <p>この行為を Ruby コードで書くと、</p> <h3 id="code : 01"><a href="#code+%3A+01">code : 01</a></h3> <pre><code class="ruby">require 'uri' require 'net/http' words = "ransomware" keywords = URI.encode_www_form(search_query: words) target = 'https://www.youtube.com/results?' << keywords resp_0 = Net::HTTP.get_response(URI.parse(target)) </code></pre> <p>ページのなかの、<code><script></code> のうちの1つに検索結果の情報が詰まっている。</p> <p><code><script></code> というタグはいくつもあって、その <strong>34 番目</strong>が検索結果の JSON に該当するよ。<br /> <strong>44 番目</strong>になったかも ?</p> <h3 id="code : 02"><a href="#code+%3A+02">code : 02</a></h3> <pre><code class="ruby">require 'nokogiri' doc = Nokogiri::HTML.parse(resp_0.body, nil,'utf-8') script_tag = doc.css('script') json_str = "" script_tag.each_with_index {|element,i| if i == 33 then json_str = element.to_s[58..-11] #<script nonce="fX_rKtuwcvo7T-wFeZz4CQ">var ytInitialData = end } doc = nil </code></pre> <p><strong>34 番目</strong>の <code><script></code> に入っているものを取り出すと、 JSON データ構造としては余計なスクリプトが含まれているので、後に JSON としてパースするのに邪魔になるので、<code>element.to_s[58..-11]</code> というように、ノードからテキストにして、インデックスを使ってスライスして、JSON データとして扱える strings にします。この youtube レクチャーを参考にしましたよ。</p> <p><a target="_blank" rel="nofollow noopener" href="https://youtu.be/QNLBBGWEQ3Q">Python Web Scraping: JSON in SCRIPT tags : John Watson Rooney</a></p> <p><strong>Python code:</strong><br /> <a href="https://crieit.now.sh/upload_images/26d0de6370fbe42940027436f916009663709fc93d713.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/26d0de6370fbe42940027436f916009663709fc93d713.png?mw=700" alt="image" /></a><br /> <a href="https://crieit.now.sh/upload_images/9bbcc2323c41efdc25de88ec00c1cf2463709f2ca78bc.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9bbcc2323c41efdc25de88ec00c1cf2463709f2ca78bc.png?mw=700" alt="image" /></a></p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/u89yc">https://rentry.co/u89yc</a><br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/t94yo">https://rentry.co/t94yo</a></p> <h3 id="code : 03"><a href="#code+%3A+03">code : 03</a></h3> <pre><code class="ruby">require 'json' begin script33 = JSON.parse(json_str) rescue => e puts e # return nil end </code></pre> <p>JSON を parse するとは hash にするということなので、key と value のひたすら折り重なる廻廊となる、key も value(s) もあらかじめ知ってたら、スムーズだけれども、この JSON のデータ構造は知らない、知りたくないという場合は、いったん JSON をテキストファイルにして、vim エディターでよく見る。必要なら編集してキーワードサーチして、じっと見る。必要なら何時間も、何日間も見る。<br /> 大体わかったら、JSON らしくないレギュラーエクスプレッションズで処理できる。<br /> 取り出したいのは videoId と title 。</p> <p>videoId は、url 上ではこうなっている。<br /> <code>https://www.youtube.com/watch?v=</code> + videoId<br /> なので基本的な url から videoId を取り出すのは、<code>watch?v=</code> 以降の <strong>11 文字</strong>を切り出したらいい。</p> <p>rf.<br /> <a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/3452546/how-do-i-get-the-youtube-video-id-from-a-url">stackoverflow.com : How do I get the youtube video-id from a URL</a></p> <p>これが基本事項だけども、以下は JSON データから <code>"videoId"="●●●●●●●●●●●"</code> というところから抜き出していくコードになっている。</p> <h3 id="code : 04"><a href="#code+%3A+04">code : 04</a></h3> <pre><code class="ruby">script33 = JSON.parse(json_str) # JSON to Hash videoid_list = [] # temp work space for youtube 'video-id' s videotitle_list = [] # temp work space for youtube 'video title' s script33.each do |y,x| if y == 'contents' x.each do |yy,xx| match = xx.to_s.match(/(\"videoId\"=\>.{13})/) if match != nil temp = $&[11..-1] videoid_list.push(temp) while $'.match(/(\"videoId\"=\>[^\[].{12})/) != nil do videoid_list.push($&[11..-1]) end end match2 = xx.to_s.match(/\"title\"=\>\{\"runs.+?\[\{\"text\"\=\>(.*?)\}\],/) if match2 != nil temp = $1 videotitle_list.push(temp) while $'.match(/\"title\"=\>\{\"runs.+?\[\{\"text\"=\>(.*?)\}\],/) != nil do videotitle_list.push($1) end end end end end </code></pre> <p>youtube でキーワード検索した結果の <code>videoId</code> と、 <strong>title ぽいもの</strong>が、いくつづつかそれぞれ配列に入る。<br /> <code>videoId</code>については以下のページの一番最初のほうで少し書いたので参考にしてほしい。</p> <blockquote> <p><strong>アルゴリズムはチートされる 注目と広告、アテンション エコノミー / attention economy</strong><br /> <a href="https://crieit.net/posts/c2b7c645c32fda0b2cffd3aea91d6a01">https://crieit.net/posts/c2b7c645c32fda0b2cffd3aea91d6a01</a></p> </blockquote> <p>ここからは、データは重複していく可能性があることに気をつけていく。<br /> <strong>title ぽいもの</strong>は、いちばん最後に <strong>title じゃないもの</strong>が配列にプッシュされているので取り除いておく。これは、Hash の処理で取り出さずにレギュラーエクスプレッションズの文字列処理で条件を書いてスキャンしたので起こったことなので、きっちり Hash で取り出せばうまいこといくと思われる。ただ、今回はレギュラーエクスプレッションズの処理にした。</p> <h3 id="code : 05"><a href="#code+%3A+05">code : 05</a></h3> <pre><code class="ruby">videoid_list.uniq! videotitle_list.pop # trash videotitle_list.uniq! </code></pre> <p>strings の値、それぞれの配列の値、並び方は、とにかくよーく確かめてね。<br /> 確かめないと、以後の行程で全く意味ないからね。</p> <h3 id="code : 06"><a href="#code+%3A+06">code : 06</a></h3> <pre><code class="ruby">id_list = [] title_list = [] videoid_list.each_with_index {|content,ind| if videotitle_list[ind] != nil # puts &quot;-&quot;*20 # puts &quot;#{ind} https://www.youtube.com./watch?v=#{content[1..-2]}&quot; # puts videotitle_list[ind] mmmm = videotitle_list[ind].match(/#{words}/i) if mmmm != nil id_list.push(content) title_list.push(videotitle_list[ind]) end else # puts &quot;-&quot;*20 # puts &quot;no title found&quot; # puts &quot;#{ind} https://www.youtube.com./watch?v=#{content[1..-2]}&quot; end } videoid_list.clear videotitle_list.clear </code></pre> <p>検索した結果のタイトルにキーワードにした <code>ransomware</code> が含まれていればリストに追加するし、キーワードが含まれていない場合は無関係という判断でリストから外します。<br /> 残すリストをそれぞれの配列 <code>id_list = []</code> <code>title_list = []</code> に追加していく。</p> <h3 id="code : 07"><a href="#code+%3A+07">code : 07</a></h3> <pre><code class="ruby">id_list = id_list.zip(title_list) </code></pre> <p>zip してひとまとめにしておく。</p> <p>こうなってるかな。</p> <h3 id="code : 08"><a href="#code+%3A+08">code : 08</a></h3> <pre><code class="ruby">id_list.each_with_index do |list,ind| puts "#{ind}: videoId => #{list[0]}" puts "#{ind}: title => #{list[1]}" end </code></pre> <p>じゃあ、まず、この第一回目の検索の結果を使って、10 スレッドづつ https get するようにしてテスト。</p> <h3 id="code : 09"><a href="#code+%3A+09">code : 09</a></h3> <pre><code class="ruby">#mute = Mutex.new counter = 0 db_counter = 0 while id_list.size > 0 && counter < 20000 do counter += 1 threads = [] 10.times do |k| if id_list.size > 0 threads << Thread.new do #mute.synchronize do tempwork = id_list.shift if tempwork == nil next end target = "https://www.youtube.com/watch?v=" << tempwork[0][1..-2] id_title_list = work(target,words) if id_title_list != nil id_list.concat(id_title_list) end #end end end end threads.each(&:join) id_list.uniq! puts id_list.size end </code></pre> <h3 id="code : 10"><a href="#code+%3A+10">code : 10</a></h3> <p>work 関数<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/dzyu8">https://rentry.co/dzyu8</a></p> <p>work 関数は https get から始まる <strong>code : 01</strong> ~ <strong>code : 04</strong> とよく似ているが、今度は最初の https get で得たキーワード検索結果の JSON とは違っているので、<code><script></code> の順番も違い、<strong>41 番目</strong>の <code><script></code> から JSON データをとってきている。<br /> ということで当然 JSON のデータ構造も、キーワード検索結果の <strong>code : 04</strong> ものとは別物なので、そこから videoId や title の値をスクレイプするレギュラーエクスプレッションズも新たなものになっている。</p> <p><strong>44 番目</strong>になったかも ?</p> <h3 id="code : 11"><a href="#code+%3A+11">code : 11</a></h3> <p><strong>code : 01</strong> ~ <strong>code : 10</strong> までを全部まとめて、さらに SQLite3 データベースに保存していくようにするとこうなる。</p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/359r5">https://rentry.co/359r5</a></p> <p>ここまでで、ようやく半分。Step 1 として、これを補完する Step 2。</p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/b4ugy">https://rentry.co/b4ugy</a></p> <p>これで、全部の半分。<br /> 並べて見ると</p> <h4 id="Step1"><a href="#Step1">Step1</a></h4> <pre><code class="ruby"># encoding: UTF-8 require 'net/http' require 'uri' require 'sqlite3' require 'time' require 'json' require 'nokogiri' SQL =<<EOS create table youtube ( id INTEGER PRIMARY KEY, videoid text, chan_id text, publ_id text, title text ); EOS system("mkdir" ,"youtube__") db = SQLite3::Database.open("./youtube__/youtube.db") db.execute(SQL) words = "ransomware" keywords = URI.encode_www_form(search_query: words) target = 'https://www.youtube.com/results?' << keywords resp_0 = Net::HTTP.get_response(URI.parse(target)) doc = Nokogiri::HTML.parse(resp_0.body, nil,'utf-8') script_tag = doc.css('script') json_str = "" script_tag.each_with_index {|element,i| if i == 33 then json_str = element.to_s[58..-11] #<script nonce="fX_rKtuwcvo7T-wFeZz4CQ">var ytInitialData = end } doc = nil script33 = JSON.parse(json_str) videoid_list = [] videotitle_list = [] script33.each do |y,x| if y == 'contents' x.each do |yy,xx| match = xx.to_s.match(/(\"videoId\"=\>.{13})/) if match != nil temp = $&[11..-1] videoid_list.push(temp) while $'.match(/(\"videoId\"=\>[^\[].{12})/) != nil do videoid_list.push($&[11..-1]) end end match2 = xx.to_s.match(/\"title\"=\>\{\"runs.+?\[\{\"text\"=\>(.*?)\}\],/) if match2 != nil temp = $1 videotitle_list.push(temp) while $'.match(/\"title\"\=\>\{\"runs.+?\[\{\"text\"\=\>(.*?)\}\],/) != nil do videotitle_list.push($1) end end end end end videoid_list.uniq! videotitle_list.pop # trash scan videotitle_list.uniq! id_list = [] title_list = [] videoid_list.each_with_index {|content,ind| if videotitle_list[ind] != nil # puts &quot;-&quot;*20 # puts &quot;#{ind} https://www.youtube.com./watch?v=#{content[1..-2]}&quot; # puts videotitle_list[ind] mmmm = videotitle_list[ind].match(/#{words}/i) if mmmm != nil id_list.push(content) title_list.push(videotitle_list[ind]) end else # puts &quot;-&quot;*20 # puts &quot;no title found&quot; # puts &quot;#{ind} https://www.youtube.com./watch?v=#{content[1..-2]}&quot; end } videoid_list.clear videotitle_list.clear id_list = id_list.zip(title_list) def work(target,words) begin resp_1 = Net::HTTP.get_response(URI.parse(target)) rescue => e puts e sleep 1 return nil end doc = Nokogiri::HTML.parse(resp_1.body, nil,'utf-8') script_tag = doc.css('script') json_str = "" script_tag.each_with_index {|element,i| if i == 40 then json_str = element.to_s[58..-11] end } script_tag = nil title_tag = doc.css('title') doc = nil mmmm = title_tag[0].to_s.match(/#{words}/i) if mmmm == nil return nil end title_tag = nil begin script40 = JSON.parse(json_str) rescue => e puts e return nil end videoid_list2 = [] videotitle_list2 = [] script40.each {|y,x| if y.to_s == "contents" match1 = x.to_s.match(/\{\"title\"=\>\{\"runs\"=\>\[\{\"text\"=\>\"(.*?)\"/) if match1 != nil # puts&quot;&quot; # puts&quot;-&quot;*30 # puts $1 # puts $~ # puts&quot;-&quot;*30 tempstrings = $' while tempstrings.match(/\"title\"=\>\{\"accessibility\"=\>\{\"accessibilityData\"=\>\{\"label\"=\>"(.*?)\"\}\},/) do if $0 == nil break end # puts&quot; _&quot;*20 # puts&quot;&quot; # puts $1 tempstrings = $' videotitle_list2.push($1) match_videoid = /\"commandMetadata\"=\>\{\"webCommandMetadata\"=\>\{\"url\"=\>\"\/watch\?v=(.{11}\"),/ =~ $' if match_videoid != nil # puts (&quot;\&quot;&quot; + $1) videoid_list2.push("\"" + $1) end end else # puts &quot;-&quot;*30 # puts y x # puts &quot;can't find the title&quot; next end end } videoid_list2.uniq! videotitle_list2.uniq! videoid_list3 = [] videotitle_list3 = [] videoid_list2.each_with_index {|content,ind| if videotitle_list2[ind] == nil # puts &quot;-&quot;*30 # puts &quot;error&quot; # puts ind,content # puts &quot;https://www.youtube.com./watch?v=#{content[1..-2]}&quot; # puts &quot;-&quot;*30 next end mmmm = videotitle_list2[ind].match(/#{words}/i) if mmmm == nil # puts &quot;-&quot;*30 # puts ind,content # puts videotitle_list2[ind] # puts &quot;skip&quot; next end #puts "-"*30 #puts ind,content videoid_list3.push(content[0..-1]) #puts "https://www.youtube.com./watch?v=#{content[1..-2]}" #puts videotitle_list2[ind] videotitle_list3.push(videotitle_list2[ind]) } videoid_list2.clear videotitle_list2.clear ziped_list = videoid_list3.zip(videotitle_list3) videoid_list3.clear # ziped_list.each_with_index do |list,ind| # puts &quot;#{ind}: #{list[0]}&quot; # puts &quot;#{ind}: #{list[1]}&quot; # end return ziped_list end #youtube = Struct.new("Youtube", :videoid, :title, :date) #youtube_data = youtube.new("video_id","title","date") #mute = Mutex.new counter = 0 db_counter = 0 while id_list.size > 0 && counter < 20000 do counter += 1 threads = [] 10.times do |k| if id_list.size > 0 threads << Thread.new do #mute.synchronize do tempwork = id_list.shift if tempwork == nil next end target = "https://www.youtube.com/watch?v=" << tempwork[0][1..-2] id_title_list = work(target,words) if id_title_list != nil id_list.concat(id_title_list) end #end end end end threads.each(&:join) id_list.uniq! db.transaction do id_list.each_with_index {|data,num| if num == db_counter v_id = data[0] title = data[1].delete("\t\r\n") sth = db.prepare("insert into youtube (id,videoid,title) values(?,?,?)") sth.execute(db_counter,v_id,title) db_counter += 1 end } end puts id_list.size end </code></pre> <h4 id="Step 2"><a href="#Step+2">Step 2</a></h4> <pre><code class="ruby"># encoding: UTF-8 require 'net/http' require 'uri' require 'sqlite3' require 'time' require 'json' require 'nokogiri' # ./youtube__/youtube.db # #SQL =<<EOS #create table youtube ( # id INTEGER PRIMARY KEY AUTOINCREMENT, # videoid text, # chan_id text, # publ_id text, # title text # ); #EOS db = SQLite3::Database.open("./youtube__/youtube.db") def working(target,id_pack) puts target begin resp_0 = Net::HTTP.get_response(URI.parse(target)) rescue =>e puts e.message sleep 1 return nil end doc = Nokogiri::HTML.parse(resp_0.body, nil,'utf-8') #title_tag = doc.css('title') script_tag = doc.css('script') json_str = "" script_tag.each_with_index {|element,i| if i == 40 then json_str = element.to_s[58..-11] #<script nonce="fX_rKtuwcvo7T-wFeZz4CQ">var ytInitialData = end } doc = nil begin script40 = JSON.parse(json_str) rescue => e puts e return nil end ids = id_pack.new("videoid","publisheddate","channelid","title") script40.each {|y,x| if y.to_s == "contents" puts"-"*30 puts "URL: #{target}" ids.vi = target.sub("https://www.youtube.com/watch?v=","") match_date = x.to_s.match(/\"dateText\"=\>\{\"simpleText\"=\>\"(.{10})/) if match_date != nil ids.da = match_date[1] ch_id = $'.match(/\"browseId\"=\>\"(.*?)\",/) if ch_id != nil #puts "channelId: #{$1}" ids.ch = $1 end end match1 = x.to_s.match(/\{\"title\"=\>\{\"runs\"=\>\[\{\"text\"=\>\"(.*?)\"/) if match1 != nil # puts&quot;&quot; # puts&quot;-&quot;*30 #puts "title: #{$1}" ids.ti = $1 # puts $~ end end } #struct data return ids end threads = [] iiii = 0 mute = Mutex.new id_pack = Struct.new("Id_pack",:vi,:da,:ch,:ti) lastid = db.execute("SELECT id FROM youtube order by id DESC limit 1") db.execute("SELECT id,videoid FROM youtube").each do |videoid| row = videoid[1] num = videoid[0] puts "#{row} #{num}" str1 = row.gsub("\"","") if iiii < 10 && num < lastid[0][0] iiii += 1 threads << Thread.new do target = 'https://www.youtube.com/watch?v=' << str1 ids = working(target,id_pack) if ids != nil mute.synchronize do db.transaction do v_id = str1 date = ids.da chid = ids.ch title = ids.ti sth = db.prepare("update youtube set chan_id=?, publ_id=? where id=?") sth.execute(chid,date,num) end end end end else iiii = 0 target = 'https://www.youtube.com/watch?v=' << str1 ids = working(target,id_pack) if ids != nil mute.synchronize do db.transaction do v_id = str1 date = ids.da chid = ids.ch title = ids.ti sth = db.prepare("update youtube set chan_id=?, publ_id=? where id=?") sth.execute(chid,date,num) end end threads.each(&:join) end end end exit </code></pre> <p>Step 1 で ransomware というキーワードで検索して、タイトルのなかに ransomware という言葉が含まれる youtube 動画の videoId , title の情報が youtube というデータベースのテーブルに保存されます。</p> <p>Step 2 で タイトルのなかに ransomware という言葉が含まれる youtube 動画の youtube というデータベースのテーブルから読み出された youtubeId をもとに、channnel id , published された日付が youtube テーブルに追記されます。</p> <p><strong>youtube table</strong></p> <pre><code> id INTEGER PRIMARY KEY, videoid text, chan_id text, publ_id text, title text </code></pre> <p>こういうデータベースができあがるようになりました。<br /> タイトルと年月日時で、年代の古いものから並べるなどに使えるデータです。<br /> Step 1 , 2 は、ひとつにまとめることができますね。<br /> ひとつにまとめて、さらに動画の長さのデータもあればいいと思います。</p> <p>youtube での動画の長さは、</p> <pre><code>"duration": "PT4M13S" </code></pre> <p>というように埋め込まれているようです。<code>PT</code> から始まって分と秒で表されています。</p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/dyxuo">https://rentry.co/dyxuo</a></p> tomato tag:crieit.net,2005:PublicArticle/18295 2022-09-19T15:01:30+09:00 2022-09-19T15:07:22+09:00 https://crieit.net/posts/ubuntu-2204-jupyter-notebook-iruby-pyenv-rbenv-bundler Ubuntu 22.04にJupyter NotebookとIRubyをインストール(pyenv, rbenv, Bundler を使用) <ul> <li><del>それぞれのバージョンが上がった以外は<a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/06/07/112841">前回の Ubuntu 18.04 のとき</a>からあまり変わっていません</del> <ul> <li>Bundler の binstubs を利用する手順を追加しました</li> </ul></li> <li>手軽に試したい場合は公式の Docker イメージや Binder を利用するのが良いと思います(<a target="_blank" rel="nofollow noopener" href="https://github.com/SciRuby/iruby/blob/master/README.md">IRuby の README</a> を参照)</li> </ul> <hr /> <p><strong>大事なので2回書きます。手軽に試したい場合は公式の Docker イメージを使うか、Binder を使いましょう(<a target="_blank" rel="nofollow noopener" href="https://github.com/SciRuby/iruby/blob/master/README.md">IRuby の README</a> を参照)。 下記のような面倒なインストール手順は不要ですぐ使えます。</strong></p> <h1 id="バージョンなど"><a href="#%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E3%81%AA%E3%81%A9">バージョンなど</a></h1> <pre><code>Ubuntu 18.04(コンテナ外の作業環境) anyenv pyenv Python 3.10.3 rbenv Ruby 3.1.2 jupyter 1.0.0 jupyter_core 4.10.0 notebook 6.4.11 iruby 0.7.4 </code></pre> <h1 id="Docker の用意"><a href="#Docker+%E3%81%AE%E7%94%A8%E6%84%8F">Docker の用意</a></h1> <p>Docker イメージを作るのが目的ではなく、まっさらな状態に戻してあれこれ試すために使います。<br /> Docker なしでも大体同じだと思います。</p> <pre><code class="sh"># Dockerfile FROM ubuntu:22.04 RUN apt-get update \ && apt-get install -y sudo git wget build-essential nano RUN useradd --create-home --gid sudo --shell /bin/bash user1 \ && echo 'user1:pass' | chpasswd \ && echo 'Defaults visiblepw' >> /etc/sudoers \ && echo 'user1 ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers USER user1 WORKDIR /home/user1 CMD ["/bin/bash"] </code></pre> <p>参考: <a target="_blank" rel="nofollow noopener" href="https://qiita.com/iganari/items/1d590e358a029a1776d6">Dockerコンテナ内にsudoユーザを追加する - Qiita</a></p> <p>ユーザ名 <code>user1</code> は適当なものです。適宜読み替えます。以下同様。</p> <p>イメージをビルドしてコンテナを起動。</p> <pre><code>docker build -t ubuntu_jupyter:22.04 . docker run --rm -it -p8888:8888 ubuntu_jupyter:22.04 bash </code></pre> <p>以下はコンテナ内で作業しています。</p> <h1 id="anyenv, rbenv, pyenv のインストール"><a href="#anyenv%2C+rbenv%2C+pyenv+%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">anyenv, rbenv, pyenv のインストール</a></h1> <pre><code class="sh">git clone https://github.com/anyenv/anyenv ~/.anyenv ## 一応バージョン指定 ( cd ~/.anyenv; git checkout v1.1.4; git status ) echo 'export PATH="$HOME/.anyenv/bin:$PATH"' >> ~/.bashrc echo 'eval "$(anyenv init -)"' >> ~/.bashrc exec bash -l ## メッセージにしたがって実行 yes | anyenv install --init anyenv install rbenv anyenv install pyenv ## 一応バージョン指定 ( cd ~/.anyenv/envs/rbenv; git checkout v1.2.0; git status ) ( cd ~/.anyenv/envs/pyenv; git checkout v2.2.5; git status ) exec bash -l </code></pre> <h1 id="Ruby のインストール"><a href="#Ruby+%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">Ruby のインストール</a></h1> <pre><code>sudo apt install -y libssl-dev zlib1g-dev rbenv install 3.1.2 </code></pre> <p>Docker のポートマッピングの確認。先に疎通確認しておきます。</p> <pre><code class="sh">rbenv shell 3.1.2 gem install webrick ruby -run -e httpd -- --port=8888 --bind-address=0.0.0.0 . </code></pre> <p>ホスト側から http://localhost:8888/ にアクセスできることを確認して Ctrl-C で止める。</p> <h1 id="Python のインストール"><a href="#Python+%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">Python のインストール</a></h1> <pre><code class="sh">sudo apt install -y libffi-dev libsqlite3-dev env PYTHON_CONFIGURE_OPTS='--enable-shared' pyenv install 3.10.3 </code></pre> <p><code>env PYTHON_CONFIGURE_OPTS='--enable-shared'</code> は後で PyCall を使うための指定<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/mrkn/pycall.rb#note-for-pyenv-users">https://github.com/mrkn/pycall.rb#note-for-pyenv-users</a></p> <h1 id="Jupyter Notebook のインストール"><a href="#Jupyter+Notebook+%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">Jupyter Notebook のインストール</a></h1> <pre><code class="sh">## ディレクトリと rbenv, pyenv の用意 mkdir ${HOME}/jupyter cd ${HOME}/jupyter pwd #=> /home/user1/jupyter rbenv local 3.1.2 pyenv local 3.10.3 ruby -v #=> ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux] python -V #=> Python 3.10.3 </code></pre> <pre><code class="sh">## bundle init みたいなもの ## &quot;venv.d&quot; は任意のディレクトリ名 python -m venv venv.d ## 常に bundle exec してるみたいなモードになる ## モードを抜けたい場合は deactivate を実行する . venv.d/bin/activate ## bundle add みたいなもの pip install jupyter jupyter notebook --no-browser --ip=0.0.0.0 ## ログイン用のトークン付きのURLが表示されるので、ホスト側のブラウザで開く </code></pre> <p><code>--ip=0.0.0.0</code> を指定しているのは Docker コンテナ内で実行してホスト側から参照するため。</p> <p>確認できたら Ctrl-C で止める。</p> <h1 id="IRuby のインストール"><a href="#IRuby+%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">IRuby のインストール</a></h1> <p>基本的には<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/SciRuby/iruby">https://github.com/SciRuby/iruby</a><br /> の説明を参考にしてインストールしました。</p> <p>ただし、gem を Bundler で管理したいので、そのための手順を加えています。</p> <p>Bundler 環境で iruby を実行するための方法として</p> <ul> <li>binstubs を使う方法</li> <li>ラッパースクリプトを使う方法</li> </ul> <p>の2つを試しました。 binstubs を使う方が手順が簡単です。</p> <h2 id="binstubs を使う場合"><a href="#binstubs+%E3%82%92%E4%BD%BF%E3%81%86%E5%A0%B4%E5%90%88">binstubs を使う場合</a></h2> <pre><code class="sh">sudo apt install -y libtool libffi-dev ruby ruby-dev make sudo apt install -y libzmq3-dev libczmq-dev bundle init bundle config set --local path 'vendor/bundle' bundle add ffi-rzmq iruby pycall rake bundle binstubs iruby ## ./bin/iruby が生成される bin/iruby register --force ## ~/.local/share/jupyter/kernels/ruby/kernel.json ## が生成される cat ~/.local/share/jupyter/kernels/ruby/kernel.json #=> {"argv":["/home/user1/jupyter/bin/iruby","kernel","{connection_file}"],"display_name":"Ruby 3.1.2","language":"ruby"} ## この状態で jupyter 起動 jupyter notebook --no-browser --ip=0.0.0.0 ## → Ruby 3.1.2 のノートブックが新規作成できるようになる ## (メニューに表示されるようになる) </code></pre> <p><a href="https://crieit.now.sh/upload_images/a28566d432610fa1952ea6c4f741348c63280709d5558.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/a28566d432610fa1952ea6c4f741348c63280709d5558.png?mw=700" alt="image" /></a></p> <h2 id="ラッパースクリプトを使う場合"><a href="#%E3%83%A9%E3%83%83%E3%83%91%E3%83%BC%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%82%92%E4%BD%BF%E3%81%86%E5%A0%B4%E5%90%88">ラッパースクリプトを使う場合</a></h2> <pre><code class="sh">sudo apt install -y libtool libffi-dev ruby ruby-dev make sudo apt install -y libzmq3-dev libczmq-dev bundle init bundle config set --local path 'vendor/bundle' bundle add ffi-rzmq iruby pycall rake bundle exec iruby register --force ## ~/.local/share/jupyter/kernels/ruby/kernel.json ## が生成される cat ~/.local/share/jupyter/kernels/ruby/kernel.json #=> {"argv":["/home/user1/jupyter/vendor/bundle/ruby/3.1.0/bin/iruby","kernel","{connection_file}"],"display_name":"Ruby 3.1.2","language":"ruby"} ## この状態で jupyter 起動 jupyter notebook --no-browser --ip=0.0.0.0 ## → Ruby 3.1.2 のノートブックが新規作成できるようになる ## (メニューに表示されるようになる) </code></pre> <p><a href="https://crieit.now.sh/upload_images/a28566d432610fa1952ea6c4f741348c63280709d5558.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/a28566d432610fa1952ea6c4f741348c63280709d5558.png?mw=700" alt="image" /></a></p> <p>しかし、実際に新規作成を行うと次のようなエラーが jupyter を起動したターミナルに出てうまく動かない。</p> <pre><code>[I 05:17:21.886 NotebookApp] KernelRestarter: restarting kernel (3/5), new random ports /home/user1/.anyenv/envs/rbenv/versions/3.1.2/lib/ruby/3.1.0/rubygems.rb:265:in `find_spec_for_exe': can't find gem iruby (>= 0.a) with executable iruby (Gem::GemNotFoundException) from /home/user1/.anyenv/envs/rbenv/versions/3.1.2/lib/ruby/3.1.0/rubygems.rb:284:in `activate_bin_path' from /home/user1/jupyter/vendor/bundle/ruby/3.1.0/bin/iruby:25:in `<main>' </code></pre> <p>これはおそらく rbenv + bundler 環境で実行できていないせいなので、iruby コマンドのラッパー <code>iruby.sh</code> を用意して対処してみる。<br /> (他に良い方法があるかもしれませんが、とりあえずこれで動きました。)</p> <pre><code class="sh">cat <<'EOB' > iruby.sh #!/bin/bash JUPYTER_DIR=~/jupyter export PYENV_ROOT="${HOME}/.anyenv/envs/pyenv" export LIBPYTHON=${PYENV_ROOT}/versions/3.10.3/lib/libpython3.10.so.1.0 export PYTHON=${JUPYTER_DIR}/venv.d/bin/python # これでもいい? # export PYTHON=${PYENV_ROOT}/shims/python export RBENV_ROOT="${HOME}/.anyenv/envs/rbenv" export PATH="${RBENV_ROOT}/bin:${PATH}" eval "$(rbenv init -)" rbenv shell 3.1.2 BUNDLE_GEMFILE=${JUPYTER_DIR}/Gemfile \ bundle exec iruby "$@" EOB </code></pre> <pre><code class="sh">## 実行権限を付ける chmod u+x iruby.sh ## iruby のパスを修正 nano ~/.local/share/jupyter/kernels/ruby/kernel.json { "argv": [ "/home/user1/jupyter/iruby.sh", ...ここだけ修正 "kernel", "{connection_file}" ], "display_name":"Ruby 3.1.2", "language":"ruby" } ## もう一度 jupyter を起動 jupyter notebook --no-browser --ip=0.0.0.0 </code></pre> <p>これで ruby カーネルが動くようになった。</p> <h1 id="IRuby のノートブックを使う"><a href="#IRuby+%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%88%E3%83%96%E3%83%83%E3%82%AF%E3%82%92%E4%BD%BF%E3%81%86">IRuby のノートブックを使う</a></h1> <p>基本的な使い方については <a target="_blank" rel="nofollow noopener" href="https://github.com/RubyData/workshop-materials/blob/master/iruby_guides/users_guide.md">IRuby Notebook 利用者ガイド</a> が参考になります。</p> <p>matplotlib を使っている箇所があるので、下記でインストールしておく必要があります。</p> <pre><code>pip install matplotlib bundle add matplotlib </code></pre> <h1 id="ライブラリの追加"><a href="#%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%AE%E8%BF%BD%E5%8A%A0">ライブラリの追加</a></h1> <h2 id="Python のライブラリを追加したい場合"><a href="#Python+%E3%81%AE%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%97%E3%81%9F%E3%81%84%E5%A0%B4%E5%90%88">Python のライブラリを追加したい場合</a></h2> <pre><code class="sh">pip install lib_foo </code></pre> <h2 id="Ruby のライブラリを追加したい場合"><a href="#Ruby+%E3%81%AE%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%97%E3%81%9F%E3%81%84%E5%A0%B4%E5%90%88">Ruby のライブラリを追加したい場合</a></h2> <pre><code class="sh">bundle add lib_foo </code></pre> <p>カーネルを再起動(ノートブックのページのメニューの Kernal → Restart)すると require できるようになる。</p> <h1 id="バージョン詳細"><a href="#%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E8%A9%B3%E7%B4%B0">バージョン詳細</a></h1> <p>pip freeze</p> <pre><code>argon2-cffi==21.3.0 argon2-cffi-bindings==21.2.0 asttokens==2.0.5 attrs==21.4.0 backcall==0.2.0 beautifulsoup4==4.11.1 bleach==5.0.0 cffi==1.15.0 debugpy==1.6.0 decorator==5.1.1 defusedxml==0.7.1 entrypoints==0.4 executing==0.8.3 fastjsonschema==2.15.3 ipykernel==6.13.0 ipython==8.3.0 ipython-genutils==0.2.0 ipywidgets==7.7.0 jedi==0.18.1 Jinja2==3.1.2 jsonschema==4.5.1 jupyter==1.0.0 jupyter-client==7.3.0 jupyter-console==6.4.3 jupyter-core==4.10.0 jupyterlab-pygments==0.2.2 jupyterlab-widgets==1.1.0 MarkupSafe==2.1.1 matplotlib-inline==0.1.3 mistune==0.8.4 nbclient==0.6.2 nbconvert==6.5.0 nbformat==5.4.0 nest-asyncio==1.5.5 notebook==6.4.11 packaging==21.3 pandocfilters==1.5.0 parso==0.8.3 pexpect==4.8.0 pickleshare==0.7.5 prometheus-client==0.14.1 prompt-toolkit==3.0.29 psutil==5.9.0 ptyprocess==0.7.0 pure-eval==0.2.2 pycparser==2.21 Pygments==2.12.0 pyparsing==3.0.8 pyrsistent==0.18.1 python-dateutil==2.8.2 pyzmq==22.3.0 qtconsole==5.3.0 QtPy==2.1.0 Send2Trash==1.8.0 six==1.16.0 soupsieve==2.3.2.post1 stack-data==0.2.0 terminado==0.13.3 tinycss2==1.1.1 tornado==6.1 traitlets==5.1.1 wcwidth==0.2.5 webencodings==0.5.1 widgetsnbextension==3.6.0 </code></pre> <p>jupyter --version</p> <pre><code>Selected Jupyter core packages... IPython : 8.3.0 ipykernel : 6.13.0 ipywidgets : 7.7.0 jupyter_client : 7.3.0 jupyter_core : 4.10.0 jupyter_server : not installed jupyterlab : not installed nbclient : 0.6.2 nbconvert : 6.5.0 nbformat : 5.4.0 notebook : 6.4.11 qtconsole : 5.3.0 traitlets : 5.1.1 </code></pre> <p>cat Gemfile.lock</p> <pre><code>GEM remote: https://rubygems.org/ specs: data_uri (0.1.0) ffi (1.15.5) ffi-rzmq (2.0.7) ffi-rzmq-core (>= 1.0.7) ffi-rzmq-core (1.0.7) ffi io-console (0.5.11) irb (1.4.1) reline (>= 0.3.0) iruby (0.7.4) data_uri (~> 0.1) ffi-rzmq irb mime-types (>= 3.3.1) multi_json (~> 1.11) native-package-installer mime-types (3.4.1) mime-types-data (~> 3.2015) mime-types-data (3.2022.0105) multi_json (1.15.0) native-package-installer (1.1.4) pycall (1.4.1) rake (13.0.6) reline (0.3.1) io-console (~> 0.5) PLATFORMS x86_64-linux DEPENDENCIES ffi-rzmq (~> 2.0) iruby (~> 0.7.4) pycall (~> 1.4) rake (~> 13.0) BUNDLED WITH 2.3.7 </code></pre> <h1 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h1> <ul> <li>2018-04 <a target="_blank" rel="nofollow noopener" href="https://github.com/RubyData/workshop-materials/blob/master/iruby_guides/installation_guide.md">IRuby インストールガイド (RubyData/workshop-materials)</a></li> <li>2017-05 <a target="_blank" rel="nofollow noopener" href="https://qiita.com/methane/items/5afdabd513a18049c34f">Rubyist が pyenv を使うときに知っておいてほしいこと - Qiita</a></li> </ul> <hr /> <ul> <li>2019-12 <a target="_blank" rel="nofollow noopener" href="https://qiita.com/kojix2/items/456259f40d5563005cd1">Rubyでグラフを描画するツール GR.rb の紹介 - Qiita</a></li> </ul> <h1 id="この記事を読んだ人は(ひょっとしたら)こちらも読んでいます"><a href="#%E3%81%93%E3%81%AE%E8%A8%98%E4%BA%8B%E3%82%92%E8%AA%AD%E3%82%93%E3%81%A0%E4%BA%BA%E3%81%AF%EF%BC%88%E3%81%B2%E3%82%87%E3%81%A3%E3%81%A8%E3%81%97%E3%81%9F%E3%82%89%EF%BC%89%E3%81%93%E3%81%A1%E3%82%89%E3%82%82%E8%AA%AD%E3%82%93%E3%81%A7%E3%81%84%E3%81%BE%E3%81%99">この記事を読んだ人は(ひょっとしたら)こちらも読んでいます</a></h1> <ul> <li><p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/86afece3b2595648a656">SVG::Graph(svg-graph gem)で散布図を描く</a></p></li> <li><p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/da02c6f9f85c5a33f0a1">Ruby + Numo::Gnuplot(numo-gnuplot gem)で散布図を描く</a></p></li> <li><p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/b586e96f7805a5695a34">Galaaz を触ってみた(TruffleRuby + ggplot2 で散布図を描いてみた)</a></p></li> </ul> sonota486 tag:crieit.net,2005:PublicArticle/18292 2022-09-06T02:40:41+09:00 2022-09-20T14:35:24+09:00 https://crieit.net/posts/BBS-programming たよりない BBS programming <p><a target="_blank" rel="nofollow noopener" href="https://crieit-net.translate.goog/posts/BBS-programming?_x_tr_sl=ja&_x_tr_tl=en&_x_tr_hl=en&_x_tr_pto=wapp">transrate to</a></p> <h2 id="掲示板 : Bulletin board system をプログラムする。"><a href="#%E6%8E%B2%E7%A4%BA%E6%9D%BF+%3A+Bulletin+board+system+%E3%82%92%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%81%99%E3%82%8B%E3%80%82">掲示板 : Bulletin board system をプログラムする。</a></h2> <p><strong>Bulletin board system</strong><br /> <a target="_blank" rel="nofollow noopener" href="https://en.wikipedia.org/wiki/Bulletin_board_system">https://en.wikipedia.org/wiki/Bulletin_board_system</a></p> <p><strong>youtube 資料</strong><br /> 古いの<br /> <a target="_blank" rel="nofollow noopener" href="https://youtu.be/Dddbe9OuJLU">https://youtu.be/Dddbe9OuJLU</a></p> <p>わりと最近 ( 温故知新 )<br /> <a target="_blank" rel="nofollow noopener" href="https://youtu.be/wqqhSz2fLVw">https://youtu.be/wqqhSz2fLVw</a></p> <p>いろんな言語で書いてみたいが、web framework を使って プログラムする方法を知るために、まず、Ruby と web framework を sinatra にして、どうやって掲示板をプログラミングするかを並べていく。</p> <p><strong>sinatra</strong><br /> <a target="_blank" rel="nofollow noopener" href="https://sinatrarb.com/">https://sinatrarb.com/</a></p> <p>Ruby と Sinatra での Hello World までは、</p> <p><a href="https://crieit.net/posts/Ruby-Sinatra-web">できるだけわかりやすい Ruby + Sinatra で web アプリ</a></p> <p>Hello World までのことが理解できたら、手順はわかるので、BBS って何だっけ?ということを浅く、ふかーく掘ってみると拡張するアイデアがでる基礎がならされてくる。<br /> まず、なんだっけ?というとこから、これを見てコピペで実行した。</p> <blockquote> <p><strong>100行未満かつGo標準ライブラリだけで作る掲示板</strong><br /> 著者:クジラ飛行机<br /> <a target="_blank" rel="nofollow noopener" href="https://news.mynavi.jp/techplus/article/gogogo-9/">https://news.mynavi.jp/techplus/article/gogogo-9/</a></p> </blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://www.tohoho-web.com/ex/golang.html">Go 言語でのコンパイルまでの手順</a>を知っていれば、全くくじけず掲示板プログラムのミニマムな仕様が感じられるいい記事だと思った。</p> <p>その後、二週間か三週間くらい、<a target="_blank" rel="nofollow noopener" href="https://www.reddit.com/r/programming/comments/23umjd/4chan_source_code_leak/">4chan</a> <a target="_blank" rel="nofollow noopener" href="https://en.m.wikipedia.org/wiki/8chan">8chan</a> の事件、なぜ <a target="_blank" rel="nofollow noopener" href="https://www.google.com/search?q=京都市バス事件 あめぞう&newwindow=1&sxsrf=ALiCzsYiYV3nSBQM_kQ3X7lkeKCmTWrcYA:1662551080471&ei=KIQYY_m7HNDZhwOh8rD4Dg&ved=0ahUKEwj578m0zYL6AhXQ7GEKHSE5DO8Q4dUDCA4&uact=5&oq=京都市バス事件 あめぞう&gs_lcp=Cgdnd3Mtd2l6EAMyBQgAEIAEOgoIABBHENYEELADOgcIABCABBAESgQIQRgASgQIRhgAUMoHWKcTYJwcaAFwAXgAgAFfiAH4BJIBATeYAQCgAQHIAQrAAQE&sclient=gws-wiz">2 チャンネルが開始</a>されたのか、<a target="_blank" rel="nofollow noopener" href="https://ja.wikipedia.org/wiki/西鉄バスジャック事件">関連事件</a>など記事や映像を掘って読み続けた。</p> <p>The case for anonymity online<br /> <a target="_blank" rel="nofollow noopener" href="https://www.ted.com/talks/christopher_moot_poole_the_case_for_anonymity_online?language=en">https://www.ted.com/talks/christopher_moot_poole_the_case_for_anonymity_online?language=en</a></p> <blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://en.m.wikipedia.org/wiki/Fredrick_Brennan">Fredrick Brennan</a> @[email protected]</p> <p>https://twitter.com/fr_brennan/status/1197461945307131905?s=20&t=od7Bucc3KHDWPnixxI1Bcg</p> <p>https://mobile.twitter.com/fr_brennan/status/1566445269771755523<br /> <a target="_blank" rel="nofollow noopener" href="https://twitter.com/infinitechan/status/848139757758562304?s=20&t=XDUEGtuxavkH4t-m8kYI6A">https://twitter.com/infinitechan/status/848139757758562304?s=20&t=XDUEGtuxavkH4t-m8kYI6A</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/hcfrm">Leaked voting machine BIOS passwords may implicate Q-friendly county clerk | Ars Technica</a></p> <p>Who Owns 4chan?<br /> <a target="_blank" rel="nofollow noopener" href="https://www.wired.com/story/who-owns-4chan/">https://www.wired.com/story/who-owns-4chan/</a></p> </blockquote> <p>掲示板を、使えるレベルにするには、どうやってコメントを掃除するかという点が重要なことだ、きっと。<br /> 古典的な chat のルールや機能をよく確認するべき。minecraft や、<a target="_blank" rel="nofollow noopener" href="https://wiki.minetest.net/Privileges/ja">minetest の privileges</a> などが参考にがなるかもしれない。<br /> 管理者ではなく、ユーザー側でブロックしたい書き込みを非表示にする <a target="_blank" rel="nofollow noopener" href="https://help.twitter.com/ja/using-twitter/blocking-and-unblocking-accounts">twitter のようなブロック機能</a>は、オールドスクールな BBS にはなかったが今後あったほうがいい。</p> <blockquote> <p>華原朋美さん「ヤフコメさん達には傷つけられてきた」法的処置明かす 投稿者はどうやって特定する?<br /> <a target="_blank" rel="nofollow noopener" href="https://archive.ph/z1o2c">https://archive.ph/z1o2c</a><br /> <a target="_blank" rel="nofollow noopener" href="https://youtu.be/s0o3rlwsjOk">https://youtu.be/s0o3rlwsjOk</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://news.yahoo.co.jp/newshack/inside/nonfiction_award_2021.html">今日この賞が発表されて、明日からYahoo!ニュースのコメント欄は荒れるでしょう。</a><br /> <a target="_blank" rel="nofollow noopener" href="https://youtu.be/F-TI3UONAHY">https://youtu.be/F-TI3UONAHY</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://twitter.com/judo_gonoi/status/1568055692023705602?s=20&t=Fvazn5DcjEiwBx89yng2eA">「もうYahooのコメントも怖いです。」</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://twitter.com/narita_yusuke/status/1567714001294921728?s=20&t=cO0-6ole4g71HLPZBtParw">「来世ではヤフコメとTwitterで不眠不休で人に文句つけつづける生活を送ってみたい」</a></p> </blockquote> <p>hashtag #dropkiwifarms<br /> <a target="_blank" rel="nofollow noopener" href="https://en.m.wikipedia.org/wiki/Kiwi_Farms">https://en.m.wikipedia.org/wiki/Kiwi_Farms</a><br /> 速報: CloudflareによるKiwi Farmsのブロック https://web.gnusocial.jp/post/2022/09/20/</p> <p>reddit:Aaron Swartz<br /> <a target="_blank" rel="nofollow noopener" href="https://www.troddit.com/r/aaronswartz">https://www.troddit.com/r/aaronswartz</a></p> <p>さらに、日本の掲示板文化(主に perl で作られる BBS 1995 - 2006 )について<br /> 『あやしいわーるどの歴史』(2006/09/28 現在)<br /> <a target="_blank" rel="nofollow noopener" href="http://f16.aaacafe.ne.jp/~stwalker/">http://f16.aaacafe.ne.jp/~stwalker/</a></p> <hr /> <p>広告表示なし</p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/m953n">https://rentry.co/m953n</a></p> <hr /> <h1 id="たよりない BBS programming"><a href="#%E3%81%9F%E3%82%88%E3%82%8A%E3%81%AA%E3%81%84+BBS+programming">たよりない BBS programming</a></h1> <p>とりあえず、<a target="_blank" rel="nofollow noopener" href="https://matz.rubyist.net/20050420.html#p02">ローカルホストで戰います</a>。</p> <p>アドレス <code>http://127.0.0.1</code> のポートナンバー 4567<br /> <code>localhost:4567</code></p> <p>詳細は、<br /> できるだけわかりやすい Ruby + Sinatra で web アプリ<br /> <a href="https://crieit.net/posts/Ruby-Sinatra-web">https://crieit.net/posts/Ruby-Sinatra-web</a><br /> をクリアするとわかります。</p> <p><a href="https://crieit.now.sh/upload_images/2862c8dd49e72077e4c96240d41a05bd631d807e6d151.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/2862c8dd49e72077e4c96240d41a05bd631d807e6d151.png?mw=700" alt="image" /></a></p> <p>色は違いますが、掲示板の感じはこうなります。</p> <h2 id="Files &amp; Directories"><a href="#Files+%26amp%3B+Directories">Files & Directories</a></h2> <pre><code>BBS ├── bbs.rb ├── bbsdata.db ├── lockfile ├── views │ ├── badrequest.erb │ ├── board.erb │ ├── ca.erb │ ├── created.erb │ ├── layout.erb │ ├── login.erb │ ├── loginfail.erb │ └── logout.erb │ ├── public │ └── css │ └── style.css │ │ #[ bundle init ] ├── Gemfile │ #[ bundle install ] ├── nender #auto generated └── Gemfile.lock #auto generated </code></pre> <hr /> <h2 id="BBS directories"><a href="#BBS+directories">BBS directories</a></h2> <h3 id="BBS/Gemfile"><a href="#BBS%2FGemfile">BBS/Gemfile</a></h3> <pre><code class="bundler">#frozen_string_literal: true source "https://rubygems.org" gem "activerecord" gem "sqlite3" gem "sinatra" gem "webrick" </code></pre> <hr /> <h2 id="All in one piece"><a href="#All+in+one+piece">All in one piece</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/n7v9x">https://rentry.co/n7v9x</a></p> <h2 id="BBS/views Directory"><a href="#BBS%2Fviews+Directory">BBS/views Directory</a></h2> <p>どれも数行だけのコード。</p> <h3 id="BBS/views/layout.erb"><a href="#BBS%2Fviews%2Flayout.erb">BBS/views/layout.erb</a></h3> <p>コードへのリンク<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/ngg3s">https://rentry.co/ngg3s</a></p> <h3 id="BBS/views/login.erb"><a href="#BBS%2Fviews%2Flogin.erb">BBS/views/login.erb</a></h3> <p>コードへのリンク<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/gyd6v">https://rentry.co/gyd6v</a></p> <h3 id="BBS/views/logout.erb"><a href="#BBS%2Fviews%2Flogout.erb">BBS/views/logout.erb</a></h3> <p>コードへのリンク<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/t92tk">https://rentry.co/t92tk</a></p> <h3 id="BBS/views/loginfail.erb"><a href="#BBS%2Fviews%2Floginfail.erb">BBS/views/loginfail.erb</a></h3> <p>コードへのリンク<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/2fnu9">https://rentry.co/2fnu9</a></p> <h3 id="BBS/views/badrequest.erb"><a href="#BBS%2Fviews%2Fbadrequest.erb">BBS/views/badrequest.erb</a></h3> <p>コードへのリンク<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/og7h7">https://rentry.co/og7h7</a></p> <h3 id="BBS/views/ca.erb"><a href="#BBS%2Fviews%2Fca.erb">BBS/views/ca.erb</a></h3> <p>コードへのリンクコードへのリンク<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/nh37f">https://rentry.co/nh37f</a></p> <h3 id="BBSviews/created.erb"><a href="#BBSviews%2Fcreated.erb">BBSviews/created.erb</a></h3> <p>コードへのリンク<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/x6ofg">https://rentry.co/x6ofg</a></p> <h3 id="BBS/views/board.erb"><a href="#BBS%2Fviews%2Fboard.erb">BBS/views/board.erb</a></h3> <p>コードへのリンク<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/tab3d">https://rentry.co/tab3d</a></p> <hr /> <h2 id="BBS/public Directory"><a href="#BBS%2Fpublic+Directory">BBS/public Directory</a></h2> <h3 id="BBS/public/css/style.css"><a href="#BBS%2Fpublic%2Fcss%2Fstyle.css">BBS/public/css/style.css</a></h3> <p>コードへのリンク<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/wbw99">https://rentry.co/wbw99</a></p> <hr /> <h2 id="BBS Directory"><a href="#BBS+Directory">BBS Directory</a></h2> <h3 id="BBS/bbs.rb"><a href="#BBS%2Fbbs.rb">BBS/bbs.rb</a></h3> <pre><code class="ruby">require 'digest/sha2' require 'active_record' require 'sinatra' set :environment, :production set :sessions, exprre_after: 70000, secret: 'assdffgfhjjkklaslllg' ActiveRecord::Base.establish_connection(:adapter=>'sqlite3',:database=>'bbsdata.db') class BBSdata < ActiveRecord::Base self.table_name = 'bbsdata' end class Account < ActiveRecord::Base self.table_name = 'accounts' end # keepers able to delete scum comments. class Keeper < ActiveRecord::Base self.table_name = 'keepers' end class Kickaccount < ActiveRecord::Base self.table_name = 'kickaccounts' end # html sanitizer helpers do def h(text) Rack::Utils.escape_html(text) end end get '/' do redirect '/login' # create account end get '/ca' do # create account erb :ca end get '/ca2' do # create account @message = "Change username.The username is already in use." erb :ca end post '/create' do @username = h(params[:username]) @username.gsub!("\"|\\","") @username.gsub!(/;/,"") rawpassword = h(params[:passwd]) rawpassword.gsub!("\"|\\","") rawpassword.gsub!(/;/,"") begin f = Account.find(@username) if f.id == @username puts "Change username.The username is already in use." redirect '/ca2' end rescue =>e puts "OK.Username has accepted." end lock = File.open("lockfile","r+") lock.flock(File::LOCK_EX) algorithm = "100" rand = Random.new spice = Digest::SHA256.hexdigest(rand.bytes(20)) hashed = Digest::SHA256.hexdigest(rawpassword + spice) ca = Account.new # create account ca.id = @username ca.salt = spice ca.hashed = hashed ca.algorithm = algorithm ca.save lock.close erb :created end get '/login' do erb :login end get '/created' do session[:username] = @username erb :created end get '/board' do @u_name = session[:username] if @u_name == nil redirect '/badrequest' end begin kicked_account = Kickaccount.all kicked_account.each do |kv| if kv.userid == @u_name puts "kick out: #{kv.userid}" redirect '/logout' end end rescue => e puts "error" end @admin = "" puts "you are #{@u_name}." begin roomkeeper = Keeper.find(@u_name) @admin = roomkeeper.id puts "you are roomleeper." rescue => e puts "you are not roomkeeper." end all_data = BBSdata.all if all_data.count == 0 @table = "<tr><td>This BBS is Empty.</td></tr>" else @table = "" kicked = [] all_data.reverse_each do |one| if one.k == 1 kicked << one.userid kicked.uniq! end n1 = one.userid.size n2 = one.entry.size str1 = "" str2 = "" n1.times do str1 += "■" end n2.times do str2 += "■" end @table = @table + "<tr>" @table = @table + "<td style=\"witdh: 5%\">#{one.id}</td>" if one.v == 0 @table = @table + "<td>[#{one.userid}]</td>" else @table = @table + "<td>[#{str1}]</td>" end @table = @table + "<td><font size=\"-2\">#{Time.at(one.writedata)}</font></td>" if (one.userid == @u_name && one.v == 0) || (@admin != "" && one.v == 0) @table = @table + "<td><form action=\"/delete\" method=\"post\">" @table = @table + "<input type=\"hidden\" name=\"id\" value=\"#{one.id}\">" @table = @table + "<input type=\"hidden\" name=\"_method\" value=\"delete\">" @table = @table + "<input type=\"submit\" value=\"Delete\"></form></td>" else @table = @table + "<td></td>" end @table = @table + "</tr>" if one.v == 0 @table = @table + "<tr><td style=\"word-break: break-word\" colspan=\"4\">#{one.entry}</td></tr>\n" else @table = @table + "<tr><td style=\"word-break: break-word\" colspan=\"4\">#{str2}</td></tr>\n" end if @admin != "" && @admin != one.userid && !kicked.include?(one.userid) @table = @table + "<tr><td><font size=\"-2\">#{one.addr}</font></td>" @table = @table + "<td><form action=\"/kick\" method=\"post\">" @table = @table + "<input type=\"hidden\" name=\"bbsdataid\" value=\"#{one.id}\">" @table = @table + "<input type=\"hidden\" name=\"_method\" value=\"delete\">" @table = @table + "<input type=\"submit\" value=\"Kick\"></form></td></tr>\n" elsif @admin != "" && @admin != one.userid && kicked.include?(one.userid) @table = @table + "<tr><td><font size=\"-2\">#{one.addr}</font></td>" @table = @table + "<td><form action=\"/kickback\" method=\"post\">" @table = @table + "<input type=\"hidden\" name=\"bbsdataid\" value=\"#{one.id}\">" @table = @table + "<input type=\"hidden\" name=\"_method\" value=\"delete\">" @table = @table + "<input type=\"submit\" value=\"Kickback\"></form></td></tr>\n" end @table = @table + "<tr><td colspan=\"4\">_____________________________________________________________________________</td></tr>" end end erb :board end post '/new' do lock = File.open("lockfile","r+") lock.flock(File::LOCK_EX) maxid = 0 begin last = BBSdata.last if last.id > maxid maxid = last.id end rescue =>e puts maxid end new_comment = BBSdata.new new_comment.id = maxid + 1 new_comment.userid = h(session[:username]) new_comment.entry = h(params[:entry]) new_comment.writedata = Time.now.to_i new_comment.v = 0 # invisible : 1 new_comment.addr = @env['REMOTE_ADDR'] new_comment.k = 0 # kicked : 1 new_comment.save lock.close redirect '/board' # return to BBS end delete '/kick' do lock = File.open("lockfile","r+") lock.flock(File::LOCK_EX) maxid = 0 begin last = Kickaccount.last if last.id > maxid maxid = last.id end rescue => e puts "maxid: #{maxid}" end kicked = Kickaccount.new kicked.id = maxid + 1 username = "" begin f1 = BBSdata.find(h(params[:bbsdataid])) kicked.userid = f1.userid username = f1.userid kicked.addr = f1.addr kicked.save rescue => e puts "f1 Error" end begin f2 = BBSdata.where(userid: "#{username}") f2.each do |matched_one| puts matched_one.id matched_one.k = 1 matched_one.save end rescue => e puts "f2 Error" end lock.close redirect '/board' end delete '/kickback' do lock = File.open("lockfile","r+") lock.flock(File::LOCK_EX) puts (h(params[:bbsdataid])) username = "" begin f1 = BBSdata.find(h(params[:bbsdataid])) username = f1.userid begin f2 = Kickaccount.all f2.each do |matched_one| if matched_one.userid == f1.userid matched_one.destroy end end rescue => e puts "f2 Error" end rescue => e puts "f1 Error" end begin f3 = BBSdata.where(userid: "#{username}") f3.each do |m| puts m.id m.k = 0 m.save end rescue => e puts "f3 Error" end lock.close redirect '/board' end delete '/delete' do lock = File.open("lockfile","r+") lock.flock(File::LOCK_EX) begin s = BBSdata.find(h(params[:id])) # s.destroy s.v = 1 # invisible s.save rescue => e puts "Delete Error" end lock.close redirect '/board' end get '/logout' do session.clear erb :logout end get '/loginfail' do session.clear erb :loginfail end ################################# get '/*' do redirect '/' end ################################# #-------------------------------- post '/auth' do user = h(params[:uname]) user.gsub!("\"","") user.gsub!(/;/,"") pass = h(params[:pass]) pass.gsub!("\"","") pass.gsub!(/;/,"") redirect_flag = checkFlag(user, pass) if redirect_flag == "true" session[:username] = user redirect '/board' end redirect '/loginfail' end def checkFlag(check_username,check_password) redirect_flag = "fail" # go to "/loginfail" begin cf = Account.find(check_username) db_username = cf.id db_spice = cf.salt db_hashed = cf.hashed check_hashed = Digest::SHA256.hexdigest(check_password + db_spice) if check_hashed == db_hashed redirect_flag = "true" puts "go to \"/board\"" end rescue => e redirect_flag = "error" puts "go to \"/loginfail\"" end return(redirect_flag) end </code></pre> <h3 id="BBS/bbsdata.db"><a href="#BBS%2Fbbsdata.db">BBS/bbsdata.db</a></h3> <h4 id="initdb scripts"><a href="#initdb+scripts">initdb scripts</a></h4> <pre><code>create table bbsdata ( id integer primary key, userid varchar(20), entry varchar(150), writedata integer, v integer, addr varchar(20), k integer ); create table accounts ( id char(20) primary key, salt varchar(40), hashed varchar(70), algorithm char(5) ); create table keepers ( id char(20) primary key, salt varchar(40), hashed varchar(70), algorithm char(5) ); create table kickaccounts ( id integer primary key, userid char(20), addr varchar(20) ); </code></pre> <p>データベースのテーブル雛型をつくる。</p> <pre><code>~/BBS$ sqliter3 bbsdata.db < initdb </code></pre> tomato tag:crieit.net,2005:PublicArticle/18281 2022-08-14T18:34:08+09:00 2022-08-14T20:57:55+09:00 https://crieit.net/posts/js-filtermap-can-remove-element 【JS】flatMapで不要なものを削除したい <p>こんにちは、しきゆらです。今回は、flatMapの処理の中で不要な要素が出てきた場合にそれを排除する方法を知ったのでメモしておきます。</p> <p><a target="_blank" rel="nofollow noopener" rel="noreferrer noopener" href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap" target="_blank">https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap</a>こちらのページの「<a target="_blank" rel="nofollow noopener" rel="noreferrer noopener" href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap" target="_blank">map()のアイテムの追加と削除</a>」の項目にある通り、処理の中で空配列([])を返すと、その要素の処理を削除することができるようです。</p> <pre><code class="javascript">let array = [1,2,3,4,5,6,7,8]; array.flatMap( (item) => { if (item % 2 === 0) { return item * 2; } else { return []; } }) // => [4, 8, 12, 16] </code></pre> <p>なお、同様の処理をmapで行うと、そのまま要素が空配列に置き換わってしまいました。(それはそう)</p> <pre><code class="javascript">let array = [1,2,3,4,5,6,7,8]; array.map( (item) => { if (item % 2 === 0) { return item * 2; } else { return []; } }) // =>[Array(0), 4, Array(0), 8, Array(0), 12, Array(0), 16] </code></pre> <p>ということで、flatMapの場合は処理の中で不要な要素を削除ができるようです。</p> <p>なぜ、flatMapで空配列を返すと要素を削除することができるのかというのは</p> <blockquote> <p>flatMap()はmap()の後にflat()を行うのと同じ<br /> 参照元: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap</p> </blockquote> <p>とある通り、処理の中でflat()をかけるので空配列は消えてしまいます。ということで、flatMapの中で空配列を返してあげれば要素を削除することができるよ、という話でした。</p> <p>・・・で、これで終わるとしょうもないので、Rubyで同様のことができるのかやってみました。<a target="_blank" rel="nofollow noopener" href="https://docs.ruby-lang.org/ja/latest/method/Enumerable/i/collect_concat.html" target="_blank" rel="noreferrer noopener">https://docs.ruby-lang.org/ja/latest/method/Enumerable/i/collect_concat.html</a></p> <pre><code class="ruby">irb(main):001:0> a = [1,2,3] => [1, 2, 3] irb(main):002:0> b = [-1, 0, 9] => [-1, 0, 9] irb(main):003:0> list = [a, b] => [[1, 2, 3], [-1, 0, 9]] irb(main):004:0> list.flat_map{|array| array.select{|i| i < 0<span>}</span><span>}</span> => [-1] irb(main):005:0> list.flat_map{|array| array.include?(-1)? []: array} => [1, 2, 3] </code></pre> <p>こちらも同様で、空配列を返すと削除される動きをしています。ただ、リファレンスにはそのような記載はないので、当たり前だよね、として書かれていないのかなと。</p> <h2 id="不要な要素をはじく場合、flatMapとmap+αはどっちが速いのか"><a href="#%E4%B8%8D%E8%A6%81%E3%81%AA%E8%A6%81%E7%B4%A0%E3%82%92%E3%81%AF%E3%81%98%E3%81%8F%E5%A0%B4%E5%90%88%E3%80%81flatMap%E3%81%A8map%2B%CE%B1%E3%81%AF%E3%81%A9%E3%81%A3%E3%81%A1%E3%81%8C%E9%80%9F%E3%81%84%E3%81%AE%E3%81%8B">不要な要素をはじく場合、flatMapとmap+αはどっちが速いのか</a></h2> <p>この手の処理はmap()メソッドの前後で特定の値をはじいたりしていましたが、どちらが速いのでしょう。気になったので、ついでに調べてみました。ここでもJSとRubyで測ってみました。</p> <p>例としては微妙ですが、-5から5の間の乱数の中で0以上の場合に3倍する処理をflatMapと他の処理で処理時間を計測してみます。</p> <h3 id="JSの場合"><a href="#JS%E3%81%AE%E5%A0%B4%E5%90%88">JSの場合</a></h3> <p>こんな感じの雑さで試してみます。</p> <p>なお、処理の中で不要なコードが混じってますが一応2つの処理で同じ結果が返ってくるかを確認したかったのでequalArrayという雑チェック関数を作ってます。</p> <pre><code class="javascript">import * as Benchmark from "benchmark"; const filterMapTest = (array) => { return array.filter((item) => { return item >= 0; }).map(item => item*3) } const flatMapTest = (array) => { return array.flatMap((item) => { if(item >= 0) { return item * 3; } else { return []; } }) } const equalArray = (a, b) => { if(!Array.isArray(a)) return false; if(!Array.isArray(b)) return false; if(a.length !== b.length) return false; for(let i = 0; i < a.length; i += 1) { if(a[i] !== b[i]) return false; } return true; } // initialize const minNum = -5; const maxNum = 5; const array = [...Array(99*99)].map(_ =>; Math.floor(Math.random() * 10) - 5); let suite = new Benchmark.Suite // benchmark suite .add("filter + map", () => { filterMapTest(array) }) .add("flatMap", () => { flatMapTest(array) }) .on("cycle", (event) => { console.log(String(event.target)) }) .on("complete", function() { console.log("Fastest is " + this.filter("fastest").map("name")); }) .run({async: true}) const filterTest = filterMapTest(array); const flatTest = flatMapTest(array); console.log(equalArray(filterTest, flatTest)) </code></pre> <p>実行結果はこんな感じでした。</p> <pre><code class="zsh">$ > node dist/bench.js true filter + map x 21,538 ops/sec ±18.58% (87 runs sampled) flatMap x 1,673 ops/sec ±0.93% (96 runs sampled) Fastest is filter + map </code></pre> <p>大きな差はなさそうですが、flatMapよりもfilter+mapのほうが速そうです。</p> <h3 id="Rubyの場合"><a href="#Ruby%E3%81%AE%E5%A0%B4%E5%90%88">Rubyの場合</a></h3> <p>こんな感じの雑さで試してみました。</p> <pre><code class="ruby">require "benchmark" def map_uniq(array) array.map { |item| item.negative? ? next : item * 3}.compact end def flatmap(array) array.flat_map{|item| item.negative? ? []: item * 3 } end array = Array.new(99*99){ rand(-5...5) } Benchmark.bmbm do |r| r.report("flatMap") { flatmap(array) } r.report("map_uniq") { map_uniq(array) } end </code></pre> <p>実行結果はこんな感じでした。</p> <pre><code class="zsh">Rehearsal -------------------------------------------- flatMap 0.001012 0.000000 0.001012 ( 0.001012) map_uniq 0.000562 0.000000 0.000562 ( 0.000562) -----------------------------------total: 0.001574sec user system total real flatMap 0.000815 0.000000 0.000815 ( 0.000814) map_uniq 0.000564 0.000000 0.000564 ( 0.000563) </code></pre> <p>よく考えなくても、flatMapのほうは無駄に配列生成して処理スキップさせているので遅いよなぁ、という印象でした。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>今回は、JSのflatMapで不要な要素をはじいて処理することができる、ということを知ったのでメモしました。また、気になったので、同様な処理がRubyでもできるか確認したり、flatMapで不要要素を弾く場合とfilterなどをかけた場合の処理時間の差を見てみました。</p> <p>今回はここまで。おわり</p> しきゆら tag:crieit.net,2005:PublicArticle/18276 2022-08-12T02:30:19+09:00 2022-08-19T02:56:39+09:00 https://crieit.net/posts/Ruby-Sinatra-web できるだけわかりやすい Ruby + Sinatra で web アプリ <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/u5pcm">https://rentry.co/u5pcm</a></p> <h2 id="Sinatra"><a href="#Sinatra">Sinatra</a></h2> <p>web アプリって何かよくわからなかったので、Sinatra に入門。</p> <p><a target="_blank" rel="nofollow noopener" href="https://sinatrarb.com/intro-ja.html">https://sinatrarb.com/intro-ja.html</a></p> <h2 id="web アプリ"><a href="#web+%E3%82%A2%E3%83%97%E3%83%AA">web アプリ</a></h2> <p>web サーバーを起ちあげて、ブラウザでアクセスして何らかの処理をしたりしなかったりするものを web アプリと呼ぶようだ。<br /> 処理をしたりしなかったりする部分をフレームワークで、決まりきったパターンでプログラムを書けるというジャンルにあり、そのなかでもシンプルなのが Sinatra らしい。<br /> python だと <a target="_blank" rel="nofollow noopener" href="https://qiita.com/dauuricus/items/6e67d352fb4ee551a62e">cherrypy</a> と似ているのかもしれない。perl だと <a target="_blank" rel="nofollow noopener" href="https://perldancer.org/">Dancer</a> というフレームワーク は sinatra にパッと見たところはよく似ている。</p> <h2 id="web アプリを作ってみる"><a href="#web+%E3%82%A2%E3%83%97%E3%83%AA%E3%82%92%E4%BD%9C%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B">web アプリを作ってみる</a></h2> <p>"webrick" "sinatra" を使って、もっとも単純な処理をする web アプリを作ってみる。もっとも単純な処理だから、基本的にとても短いコードになるので概要をつかむのにはいいはずだ。</p> <h3 id="所要時間: ゆっくりやってみて 30 分程度。"><a href="#%E6%89%80%E8%A6%81%E6%99%82%E9%96%93%3A+%E3%82%86%E3%81%A3%E3%81%8F%E3%82%8A%E3%82%84%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%A6+30+%E5%88%86%E7%A8%8B%E5%BA%A6%E3%80%82">所要時間: ゆっくりやってみて 30 分程度。</a></h3> <p>( web アプリって何か全く知らない場合、どうなってるか理解するには 1 時間から 2 時間程度かかると思います。が、全く知らなくてもわかるので、そのうち。 )</p> <p>プロジェクトのディレクトリを作る。</p> <pre><code>~/HelloWorld $ mkdir HelloWorld </code></pre> <p><a target="_blank" rel="nofollow noopener" href="https://bundler.io/guides/getting_started.html#getting-started">bundler</a> のインストール。</p> <p>( bundler については、一体何するものなのかは、調べてみてください。python だと、<a target="_blank" rel="nofollow noopener" href="https://docs.python.org/ja/3/library/venv.html">venv</a> みたいなものかなぁ。わかってなくても、他の言語やってみたりしたらわかるかもしれない。)</p> <pre><code>~/HelloWorld $ bundle config set path 'vender/bundle' ~/HelloWorld $ gem update ~/HelloWorld $ gem install bundler </code></pre> <p>bundler<br /> <a target="_blank" rel="nofollow noopener" href="https://github-com.translate.goog/rubygems/bundler?_x_tr_sl=auto&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=wapp">https://github.com/rubygems/bundler</a></p> <hr /> <p>プロジェクトのディレクトリに移動。</p> <pre><code>~/HelloWorld $ cd HelloWorld </code></pre> <pre><code>~/HelloWorld $ bundle init </code></pre> <p>Gemfile が作られる。( Go 言語の <code>go mod init</code> みたいな感じだなぁ。)</p> <pre><code>~/HelloWorld $ ls Gemfile </code></pre> <hr /> <p>Gemfile を編集する。</p> <pre><code>~/HelloWorld $ vim Gemfile </code></pre> <pre><code>#frozen_string_literal: true source "https://rubygems.org" #gem "rails" gem "webrick" gem "sinatra" </code></pre> <pre><code>~/HelloWorld $ bundle install </code></pre> <p>"webrick" と "sinatra" がインストールされる。この二つが依存しているプログラムも同時にインストールされる。</p> <blockquote> <p>WEBrick<br /> <a target="_blank" rel="nofollow noopener" href="https://docs.ruby-lang.org/ja/latest/library/webrick.html">https://docs.ruby-lang.org/ja/latest/library/webrick.html</a><br /> 要約<br /> 汎用HTTPサーバーフレームワークです。HTTPサーバが簡単に作れます。<br /> WEBrick はサーブレットによって機能します。サーブレットとはサーバの機能をオブジェクト化したものです。ファイルを読み込んで返す・forkしてスクリプトを実行する・テンプレートを適用するなど、「サーバが行なっている様々なこと」を抽象化しオブジェクトにしたものがサーブレットです。サーブレットは WEBrick::HTTPServlet::AbstractServlet のサブクラスのインスタンスとして実装されます。<br /> WEBrick はセッション管理の機能を提供しません。<br /> NOTE: WEBrick は Ruby 3.0 で標準ライブラリから削除されました。Ruby 3.0 以降で WEBrick を使いたい場合は rubygems から利用してください。</p> </blockquote> <hr /> <p>views ディレクトリを作る。</p> <pre><code>~/HelloWorld $ mkdir views </code></pre> <p>hello.rb, layout.erb, index.erb<br /> 3 つファイルを作る。</p> <pre><code>~/HelloWorld $ vim hello.rb </code></pre> <p>views ディレクトリに、layout.erb, index.erb というファイルを作っていきます。</p> <pre><code>~/HelloWorld $ cd views ~/HelloWorld/views $ vim layout.erb </code></pre> <pre><code>~/HelloWorld/views $ vim index.erb </code></pre> <pre><code>. HelloWorld # ディレクトリ ├── hello.rb ├── views # ディレクトリ │ ├── index.erb │ └── layout.erb │ </code></pre> <p><a href="https://crieit.now.sh/upload_images/e433b0da290749a242d7fe9d937d062062f5312de1ca8.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e433b0da290749a242d7fe9d937d062062f5312de1ca8.png?mw=700" alt="Hello sinatra." /></a></p> <pre><code>~/HelloWorld $ dir Gemfile Gemfile.lock hello.rb vender views ~/HelloWorld $ dir views index.erb layout.erb </code></pre> <p>こんな感じです。</p> <pre><code>. HelloWorld # ディレクトリ ├── hello.rb ├── views # ディレクトリ │ ├── index.erb │ └── layout.erb │ # [ bundle init ] したらばできるファイル ├── Gemfile # 編集するファイル # [ bundle install ] したらばできるファイル ├── nender # auto generated └── Gemfile.lock # auto generated </code></pre> <hr /> <h3 id="Web アプリの実行"><a href="#Web+%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E5%AE%9F%E8%A1%8C">Web アプリの実行</a></h3> <p>準備できたので、hello.rb を実行したい。<code>bundle exec ruby hello.rb</code> で WEBrick ウェブサーバーをたちあげる。</p> <pre><code>~/HelloWorld/views $ cd HelloWorld </code></pre> <pre><code>~/HelloWorld $ bundle exec ruby hello.rb [2022-08-12 02:07:49] INFO WEBrick 1.7.0 [2022-08-12 02:07:49] INFO ruby 3.1.2 (2022-04-12) [arm-linux-androideabi] == Sinatra (v2.2.1) has taken the stage on 4567 for production with backup from WEBrick [2022-08-12 02:07:49] INFO WEBrick::HTTPServer#start: pid=2090 port=4567 </code></pre> <p>chrome ブラウザーでアクセスする。<code>localhost:4567</code> とアドレスバーに入力して <code>Enter</code> キー。<code>localhost:4567</code> は、 <code>http://127.0.0.1:4567</code> のことです。4567 は sinatra のデフォルトのポートナンバーです。</p> <p><strong>アドレス http://127.0.0.1 のポートナンバー 4567 = localhost:4567</strong></p> <p><a href="https://crieit.now.sh/upload_images/bf1158c30b8630527bbe43453ed3233462f539a9f28b1.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/bf1158c30b8630527bbe43453ed3233462f539a9f28b1.png?mw=700" alt="image" /></a></p> <hr /> <p>どうなっているか見ていくと、</p> <p>hello.rb の '/' の指し示すのは localhost へアクセスしてきた場合、それはすなわち <code>127.0.0.0/</code> で、index.html に相当するファイルへのアクセスということになる。<br /> すると、そこに get でアクセスしてきたら、erb : index を返しますと定義している。</p> <pre><code class="ruby">get '/' do erb :index end </code></pre> <p><code>:index</code> とは、layout.erb でひな型を用意されているものに、index.erb で用意されている内容が埋め込まれたもののようだ。</p> <p>layout.erb のなかの</p> <pre><code><%= yield %> </code></pre> <p>が、index.erb の</p> <pre><code>Hello Sinatra. </code></pre> <p>に置き換わっているものが <code>:index</code> にあたる。</p> <p><a href="https://crieit.now.sh/upload_images/fa24c967c33e1da58f3f7386dd1d5b6a62f63d17db05c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/fa24c967c33e1da58f3f7386dd1d5b6a62f63d17db05c.png?mw=700" alt="image" /></a></p> <p><a href="https://crieit.now.sh/upload_images/fa24c967c33e1da58f3f7386dd1d5b6a62f6375404dab.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/fa24c967c33e1da58f3f7386dd1d5b6a62f6375404dab.png?mw=700" alt="image" /></a></p> <p>hello.rb が実行されると、layout.erb というのが、プロトタイプというか、テンプレのフォーマットで、そこに index.erb が読み込まれるという感じ。</p> <p><strong>get localhost:4567 で :index ( :index は、index.erb をテンプレートの layout.erb に流し込んだもの )</strong> という感じ。</p> <hr /> <h3 id="プログラムコード コピペ用。"><a href="#%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%82%B3%E3%83%BC%E3%83%89+%E3%82%B3%E3%83%94%E3%83%9A%E7%94%A8%E3%80%82">プログラムコード コピペ用。</a></h3> <h5 id="HelloWorld/hello.rb"><a href="#HelloWorld%2Fhello.rb">HelloWorld/hello.rb</a></h5> <pre><code class="ruby">require 'sinatra' set :environment, :production get '/' do erb :index end </code></pre> <h5 id="HelloWorld/views/layout.erb"><a href="#HelloWorld%2Fviews%2Flayout.erb">HelloWorld/views/layout.erb</a></h5> <pre><code class="html"><html> <head> <title>Sinatra framework</title> </head> <body> <h1>hello sinatra</h1> <%= yield %> </body> </html> </code></pre> <h5 id="HelloWorld/views/index.erb"><a href="#HelloWorld%2Fviews%2Findex.erb">HelloWorld/views/index.erb</a></h5> <pre><code class="ruby">Hello Sinatra! </code></pre> <p>もういちどファイルとディレクトリの構図を登場させておきます。</p> <pre><code>. HelloWorld # ディレクトリ ├── hello.rb ├── views # ディレクトリ │ ├── index.erb │ └── layout.erb │ </code></pre> <p>web アプリの実行は、HelloWorld ディレクトリの hello.rb を実行します。</p> <pre><code>~/HelloWorld $ bundle exec ruby hello.rb </code></pre> <p>似たような Hello World が1分30秒で説明されている。</p> <p><a target="_blank" rel="nofollow noopener" href="https://youtu.be/MgEgTu6NnWg">youtube</a></p> tomato tag:crieit.net,2005:PublicArticle/18245 2022-07-17T13:24:18+09:00 2022-07-17T13:24:40+09:00 https://crieit.net/posts/mal-lisp-template-engine malでかんたんなテンプレートエンジンを書いてみた <p>これは <a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2021/lisp">Lisp Advent Calendar 2021</a> の25日目の記事です。Qiita に書いていた記事のクロスポストです。</p> <hr /> <p>言語非依存なテンプレートエンジンがあったらいいなと昔からボンヤリ考えていた(切実にほしいという程ではない)のですが、<a target="_blank" rel="nofollow noopener" href="https://github.com/kanaka/mal">mal(Make a Lisp)</a>で作ったらどうだろうと思って試しにやってみました。「なんかやってみたくなってやってみた」系の記事です。</p> <p>Lisp を書くのは年に数回という感じの人が書いていますので、拙いところは大目に見ていただければと思います。</p> <p>↓ちなみに去年のアドベントカレンダーではこんなのを書きました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/501f0efa437bc4d53cc7">LibreOffice BasicでLispインタプリタ(mal)を書いた - Qiita</a></p> <h1 id="できたもの"><a href="#%E3%81%A7%E3%81%8D%E3%81%9F%E3%82%82%E3%81%AE">できたもの</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/malten">https://github.com/sonota88/malten</a></p> <p><code>sonota88/mal</code> をサブモジュールとして使っているので、 <code>git clone --recursive ...</code> でクローンする必要があります。</p> <h1 id="Ruby版"><a href="#Ruby%E7%89%88">Ruby版</a></h1> <p>Ruby 版だとこんな感じ。</p> <pre><code class="ruby">template = <<TEMPLATE <h1>品目一覧</h1> <p>購入日: <%= date %></p> <table> <tr> <th>ID</th> <th>品名</th> <th>価格</th> </tr> <% (map (fn* [item] (do %> <tr> <td><%= (get item "id") %></td> <td><%= (get item "name") %></td> <td><%= (- (get item "price") (if (get item "discount") (get item "discount") 0)) %> 円</td> </tr> <% )) items) %> </table> TEMPLATE context = { date: "2021-12-25", items: [ { id: 1, name: "foo", price: 100, discount: nil }, { id: 2, name: "bar", price: 200, discount: nil }, { id: 3, name: "baz", price: 300, discount: 50 } ] } rendered = Malten.render(context, template) print rendered </code></pre> <p><code><% ... %></code> または <code><%= ... %></code> の中にコードを書きます。ERB や JSP に倣ってベタなスタイルにしましたが、コードの部分が mal なので、見たことあるような、ないような、怪しげな雰囲気ですね。</p> <hr /> <p>出力も一応貼っておきます。</p> <pre><code class="html"><br /><h1>品目一覧</h1> <p>購入日: 2021-12-25</p> <table> <tr> <th>ID</th> <th>品名</th> <th>価格</th> </tr> <tr> <td>1</td> <td>foo</td> <td>100 円</td> </tr> <tr> <td>2</td> <td>bar</td> <td>200 円</td> </tr> <tr> <td>3</td> <td>baz</td> <td>250 円</td> </tr> </table> </code></pre> <h1 id="Java版"><a href="#Java%E7%89%88">Java版</a></h1> <p>Java 版も作ってみました(テンプレートは同じなので省略)。</p> <pre><code class="java"> String template = getTemplate(); Context context = new Context(); context.map.put("date", "2021-12-25"); { List<Map<String, Object>> items = new ArrayList<>(); items.add(new Item(1, "foo", 100, null).toPlain()); items.add(new Item(2, "bar", 200, null).toPlain()); items.add(new Item(3, "baz", 300, 50).toPlain()); context.map.put("items", items); } String rendered = Malten.render(context, template); System.out.println(rendered); </code></pre> <p>大体似たような感じですね。</p> <h1 id="概観"><a href="#%E6%A6%82%E8%A6%B3">概観</a></h1> <p>大雑把な処理の流れ。</p> <pre><code>テンプレートテキスト ↓ ↓レンダリング用コード生成 ↓ レンダリング用コード(mal のコード) ↓ ↓eval(ここでコンテキストを参照) ↓ 出力テキスト </code></pre> <h1 id="レンダリング用コード生成"><a href="#%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0%E7%94%A8%E3%82%B3%E3%83%BC%E3%83%89%E7%94%9F%E6%88%90">レンダリング用コード生成</a></h1> <p>たとえば、次のようなテンプレートテキストがあったとして、</p> <pre><code class="erb"># 品目一覧 <% (map (fn* [item] (do %> - <% (print (get item "name")) %> <% (print (get item "price")) %> 円 <% )) items) %> </code></pre> <p>これを変換すると次のような mal のコードになる。元のテンプレートと比べて眺めると何やってるかなんとなく分かりますよね。</p> <pre><code class="clojure">(print "# 品目一覧\n\n") (map (fn* [item] (do (print "\n\n- ") (print (get item "name")) (print " ") (print (get item "price")) (print " 円\n\n") )) items) </code></pre> <p>下請けの関数などは除いて骨組部分だけ貼ります。</p> <pre><code class="clojure">;; malten.mal ;; <% ... %> の中身の長さを求める (def! mal-code-length (fn* [rest] (let* [iter (fn* [rest2 pos] (if (s#start-with? rest2 "%>") pos (iter (s#rest rest2) (+ pos 1)))) ] (iter rest 0)))) ;; <% ... %> の部分の処理 (def! gen-renderer-code (fn* [rest buf acc] (let* [len (mal-code-length rest)] (gen-renderer-text (s#drop rest (+ len 2)) "" ; clear buf (cons (if (s#start-with? rest "=") (list 'code-print (s#substring rest 1 len)) (list 'code (s#substring rest 0 len))) (cons (list 'text buf) acc)))))) (def! gen-renderer-text (fn* [rest buf acc] (if (nil? rest) (cons (list 'text buf) acc) ; end of iteration (if (s#start-with? rest "<%") (gen-renderer-code (s#drop rest 2) ; "<%" を除去 buf acc) (gen-renderer-text (s#drop rest 1) (str buf (s#first rest)) acc))))) (def! to-code (fn* [part] (let* [ type (nth part 0) content (nth part 1) ] (cond (= type 'code) content (= type 'code-print) (str "(print " content ")\n") (= type 'text) (str "(print " (pr-str content) ")\n"))))) (def! gen-renderer (fn* [template] (let* [reversed-parts (gen-renderer-text template "" ; buf '() ; acc ) ] (l.foldr (fn* [part acc] (str acc (to-code part))) "" reversed-parts)))) </code></pre> <p>軽い用途向けでも、たとえば入力が 1000 文字を越えると動きません、では困るので TCO(末尾呼び出しの最適化)が効くように書く必要があります。理解があやふやだったのですが、今回 mal の TCO のしくみについておさらいして、ある程度書いて慣れることができて良かったです。</p> <h1 id="レンダリング用コードの eval"><a href="#%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0%E7%94%A8%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE+eval">レンダリング用コードの eval</a></h1> <p>基本的には eval するだけですが、コンテキストは mal の型に合わせて変換してあげる必要があります。Ruby 版だとこんな感じ。</p> <pre><code class="ruby"># ruby/mal.rb def self.to_mal_val(v) case v when Array # List は mal 側で用意されているクラス List.new(v.map { |el| to_mal_val(el) }) when Hash v .to_a .map { |k, _v| [k.to_s, to_mal_val(_v)] # Java版に合わせてキーを String にしている } .to_h else v end end </code></pre> <h1 id="core への追加"><a href="#core+%E3%81%B8%E3%81%AE%E8%BF%BD%E5%8A%A0">core への追加</a></h1> <p>文字列処理をするための最低限の関数として <code>s#first</code>, <code>s#rest</code>, <code>s.cons</code> と、改行なしで print する <code>print</code> を追加しました。</p> <p>Ruby 版であればこんなの。</p> <pre><code class="ruby"># ruby/mal.rb ADDITIONAL_CORE_FUNCS = { :print => lambda { |x| print x }, :"s#first" => lambda { |_self| _self[0] }, :"s#rest" => lambda { |_self| if _self.size <= 1 nil else _self[1..-1] end }, :"s.cons" => lambda { |first, rest| if rest.nil? first else first + rest end } } </code></pre> <p>あとはこれを <code>$core_ns</code> に追加してあげればOK。</p> <pre><code class="ruby"># {mal}/impls/ruby/stepA_mal.rb のこの部分 $core_ns .merge(ADDITIONAL_CORE_FUNCS) </code></pre> <p>他にも便利な関数を追加してリッチにしていくと、mal のレイヤーで書く部分が楽になったりパフォーマンスが改善されたりすると思うのですが、移植コストとのトレードオフになるでしょう。</p> <hr /> <p>というわけで、 mal のコードを一度書くだけ(※1)で 86 の言語(※2)で同じように動く(※3)テンプレートエンジンが手に入りました。</p> <p>※1: 実際は各言語ごとに多少手を入れる必要あり<br /> ※2: 2021-12-25 現在<br /> ※3: たぶん。試してないです。</p> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <ul> <li>動いたので満足(という程度の試みです)</li> <li>2日くらいで書いたプロトタイピングです。細かいとこは雑。</li> <li>自分用のツールなどでは様子を見つつ使っていこうかなという気持ちになった</li> <li>テンプレートエンジン、ミニマムな機能だけでよければ簡単。「試しに何か書いてみたい」というときのお題としてもお手頃で、実用できそうな雰囲気があるのも良いです。</li> <li><code>pr-str</code> は言語依存の機能で実装されている <ul> <li>たとえば Ruby 版は inspect、 Java 版では commons-lang3 の StringEscapeUtils を使っている</li> <li>コーナーケースが気になる場合は mal で書き直すとよさそう</li> </ul></li> <li>遅い。文字列の処理で、一度文字のリストにばらして加工してまた文字列に戻す(<code>s#to-chars</code> と <code>s.from-chars</code>)ということをやっていて、遅いです。<br /> 文字列版の car, cdr, cons だけあればあとはリスト用の関数に丸投げできるよね、というのを(やったことなかったので)試してみたかったのでした。今回やってみて気が済みました。<br /> 直交性はあるけど普通に計算量的に厳しい、という体験ができました。後で書き直すかも。</li> <li>たとえば Java でテンプレートエンジン自作するとなると、コード生成まではいいとして実行どうするんだろ、コンパイルしないといけないよね? <a target="_blank" rel="nofollow noopener" href="https://www.ne.jp/asahi/hishidama/home/tech/java/JavaCompiler.html">JavaCompiler</a><br /> を使えばできそう? とかのあたりがめんどくさそうだなーと思って実際に作るとこまで至っていなかったのですが(やったことないので億劫)、Java で書かれたインタプリタで動かせばコンパイルのことを考えなくてよくなると気付いて、そっか、そういう手があるのか、なるほどとなりました。で、そういうのを思いついたときにサッと使える mal は便利。</li> </ul> <h2 id="Java版だとhashmapのキーとしてシンボルが使えない"><a href="#Java%E7%89%88%E3%81%A0%E3%81%A8hashmap%E3%81%AE%E3%82%AD%E3%83%BC%E3%81%A8%E3%81%97%E3%81%A6%E3%82%B7%E3%83%B3%E3%83%9C%E3%83%AB%E3%81%8C%E4%BD%BF%E3%81%88%E3%81%AA%E3%81%84">Java版だとhashmapのキーとしてシンボルが使えない</a></h2> <p>詳しく調べてませんがメモ。</p> <p>Java版は hashmap のキーとして文字列を期待しているらしく、エラーになります。</p> <pre><code>Mal [java] user> { 'a 123 } Uncaught java.lang.ClassCastException: mal.types$MalList cannot be cast to mal.types$MalString: mal.types$MalList cannot be cast to mal.types$MalString # 文字列ならOK user> { "a" 123 } {"a" 123} </code></pre> <p>Ruby 版だとシンボルでもOK。</p> <pre><code>Mal [ruby] user> { 'a 123 } {a 123} </code></pre> sonota486 tag:crieit.net,2005:PublicArticle/18212 2022-06-10T12:06:32+09:00 2022-06-19T10:52:23+09:00 https://crieit.net/posts/pycall pycall ( python 用の機械学習向けのライブラリーを Ruby で使えるようにした Ruby gem ) を機械学習向けのライブラリーじゃないもので使う。 <p><a target="_blank" rel="nofollow noopener" href="https://crieit-net.translate.goog/posts/pycall?_x_tr_sl=ja&_x_tr_tl=en&_x_tr_hl=ja&_x_tr_pto=wapp">translate to </a></p> <p><a href="https://crieit.net/posts/why">_why ( === why the lucky stiff )</a> のことを知って、Why's ( poignant ) Guide to Ruby <a target="_blank" rel="nofollow noopener" href="https://en.m.wikipedia.org/wiki/Why's_%28poignant%29_Guide_to_Ruby#:~:text=why's%20%28poignant%29%20Guide%20to%20Ruby,%20sometimes%20called%20w%28,Creative%20Commons%20Attribution-ShareAlike%20license.">:wikipedia</a> の日本語訳版 翻訳 青木靖を勝手に epub に再編集しはじめてずっとずっと 何日も作業していると ( 訳 ( どころか全てが丁寧 ) が悪いわけではなく 2 バイト文字とそうでない文字がくっついてると自分はディスクレシアのようにゲシュタルト崩壊になるので、そういうところとアーカイブされているオリジナルに近いサイトで生きているものにリンクを張り替えたりということを調べていた。つまり 2004 年くらいにレイドバックして文章の意味をおしはかっている期間が続いて )、<a target="_blank" rel="nofollow noopener" href="https://jp.quora.com/naze-Ruby-ha-kihon-teki-ni-shi-nde-iru-node-shou-ka">Ruby</a> が気になり始めた。</p> <p>Rf. http://www.aoky.net/articles/why_poignant_guide_to_ruby/</p> <p><a target="_blank" rel="nofollow noopener" href="https://en.m.wikipedia.org/wiki/Sung_Tongs">2004</a></p> <p>Scala の古本を買って読みたいと考えていたのに、なかなか進めないうちに Ruby のことにひきよせられていく。しかも、20 年近く前の。<br /> バージョン 1.8 以前。</p> <p>Ruby についてはよく知らないが、知らないうちに書いてると、これ基本的なかたこと英語になっているよなということがわかる言語で、その後に python をわからないなりに書くと、どうしても間違うところがあって、そこが最も Ruby の気に入っているポイントだった、半年から 1 年まえあたり。<br /> その間違う、お気に入りポイントは、「 <a target="_blank" rel="nofollow noopener" href="https://docs.ruby-lang.org/ja/latest/method/Kernel/m/p.html"> p </a>」。<br /> python でも自然に <code>p</code> で書き続けて、実行すると当然エラーになるが、見直してもわからない。あってる、あってるのに ... とイライラしはじめて、コーヒーをいれながら豆の香りをかいでると、嗤いだしてしまう。<br /> 「 p 」だ。</p> <p>それくらい p は自然に手になじんでしまう。というところが、すごいなと感じていて、<a target="_blank" rel="nofollow noopener" href="http://www.nct9.ne.jp/m_hiroi/func/ocaml13.html">クラス</a>とか、Ruby を使ったフレームワークだとかにはまるで興味が持てないので、とりあえず Ruby についてはもういいと思っていた。というところで、ふいに _why の存在で、急激に、そういう人がフィーリングが合ったという、その背景について知りたくなった。<br /> フラタニティー的な一色世界に見えてたけど、そうでもないの?と、アンチポストモダンと位置づけられたりする _why のその手作り風のおもしろさは、<a target="_blank" rel="nofollow noopener" href="http://www.aoky.net/articles/why_poignant_guide_to_ruby/chapter-2.html">Ruby のどのへんに共感したのだろうか</a>。</p> <p>1999 年発行の <a target="_blank" rel="nofollow noopener" href="https://logmi.jp/tech/articles/322453">matz</a> の本を買おうかどうか、結局読まないとなった紙の本がふえると、かなり精神的ダメージとなって責めてくるので、何日か迷っている。</p> <p>迷っている間に、おそらく初期の Ruby ユーザーマニュアルを見てみる。<br /> もともと matz が日本語で書いたものを英語にしたものとして _why が<a target="_blank" rel="nofollow noopener" href="https://poignant.guide/book/">本で紹介したもの</a>だが、当然、リンク切れで、同じとされているもののうちこちらが、ディスクレシアでもなんとかなりそうな感じがしたので、それを無難に翻訳してもらうものを書いた。たぶん、古いためか翻訳前の日本語のテクストは現在公開されていないようだ。まあ、日本語のテクストだと読めないかもしれない、ゲシュタルト崩壊で。おおよその古いプログラミング言語本はゲシュタルト崩壊しやすいデザインで止まっていて、現在もそれを継承していることが多い ( と思います )。</p> <p>Rf. http://www.math.bas.bg/bantchev/place/ruby-ug.html</p> <p>python</p> <pre><code class="python">!pip install googletrans==4.0.0-rc1 !python -m pip install requests beautifulsoup4 import requests,re from bs4 import BeautifulSoup from googletrans import Translator translator = Translator() url = "http://www.math.bas.bg/bantchev/place/ruby-ug.html" res = requests.get(url) soup = BeautifulSoup(res.text, "html.parser") ptag_list_0 = soup.find_all('p') for index,lines in enumerate(ptag_list_0): if(re.match(r'\w',lines.text) != None ): texts = re.sub(r'\.\s+','. ',lime.text) texts = re.sub(r'\?\s+','? ',texts) texts = re.sub(r'\!\s+','! ',texts) texts = re.sub(r'\,\s+',', ',texts) print(texts) try: translated = translator.translate(texts, dest='ja') print(index, translated.text) ptag_list_0[index].string = translated.text except IndexError: # check #res2 = requests.get(url) #soup2 = BeautifulSoup(res2.text, "html.parser") #ptag_list_2 = soup2.find_all('p') # # #for index,lines in enumerate(ptag_list_2): # print(lines.text) # print(ptag_list_0[index].text) metatag = soup.new_tag('meta') metatag.attrs['charset'] = "utf-8" soup.head.append(metatag) import os filename = os.path.basename(url) with open(filename, "wb") as f_output: f_output.write(soup.prettify("utf-8")) </code></pre> <p>web サイトのソースのなかの p タグで囲まれた文字列を順番に google の翻訳にかけると、だいたい文章が都合よく翻訳できる、という前提で、反対に、都合悪いというのは、それ ( <code><p></code> タグ ) 以外のとこがプログラムコードを表現した箇所だったりしたら、コードのメソッドが直訳されたり、ピリオドが 。になったりで利用しづらくなる。つまり、自然言語のできるだけ訳してほしいところだけを選んで機械翻訳したいので、大まかに選別するということだ。</p> <p>そして、その前提で、じゃあ、p タグのところだけ機械的に翻訳して、またもとあった HTM ソースに戻したら、70% くらい翻訳されてるんじゃない?という予想を立ててやってみたというものだ。</p> <p>もちろんそのままを web ページ翻訳にしてもいいけど、そうすると都合悪いとこもその時点でwebサービスの機械学習の結果の精度が完全にいいとこまできてないと払拭できないので、直していく作業がでてくる。読めなくなるし。<br /> だから、<code><p></code> から <code></p></code> までだけ、あとは絶対に触らなくていいからねと <a target="_blank" rel="nofollow noopener" href="https://vielhuber-de.translate.goog/en/blog/google-translation-api-hacking/?_x_tr_sl=en&_x_tr_tl=ja&_x_tr_hl=ja">google tanslate</a> に依頼して、翻訳済みの p タグ内テキストを HTML に書き戻すというプログラムになっている。</p> <p><a target="_blank" rel="nofollow noopener" href="https://0bin.net/paste/7Shvy8cs#XuXpgMp0-t3HUu/idtJSO16Y3JpHDG5ydCoY9+JEhuW">例えばこのページでやってみるとこうなる。</a></p> <p>やってみておかしいなと思ったのは、python beautifulsoup4 だとパースされたオブジェクト ( 上の例では <code>doc</code> のことを指す ) って深いコピーじゃなく、element として変更すると ( 上の例では <code>ptag_list_0</code> ) <a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/65106473/how-to-change-make-changes-and-update-source-code-using-beautifulsoup">オブジェクトの持つ値も変更されている</a>んだということだった。奇妙に感じたがそういうものだった。</p> <hr /> <p>PyCall 実験</p> <p>PyCall は Ruby 深層学習のために python をバインド ( ではなく、ブリッジらしいけど、バインド?) する感じで、python 用の機械学習のライブラリを Ruby で使えるようにする目的で用意されているので、機械学習のライブラリ以外はどうなのかな?と、この上の python コードで使用したライブラリを Ruby で呼んでみる。</p> <pre><code class="ruby">html = `curl -s 'http://www.math.bas.bg/bantchev/place/ruby-ug.html'` require 'oga' #require 'nokogiri' require 'pycall/import' include PyCall::Import pyfrom 'googletrans',import: :Translator translator = Translator.new doc = Oga.parse_html(html) #doc = Nokogiri::HTML.parse(html) doc.css('p').each {|x| # puts &quot;#{x.text}&quot; if x.text != "" && (x.text.match?(/^\w/) || x.text.match?(/^\(/)) x.text.gsub!(/^s*/,'') x.text.to_s.gsub!(/\.\s*/,'') begin translated = translator.translate(x.text,dest='ja') p x['text'] = translated.text rescue => evar p $! end else # p x.text end } p doc.to_xml #p doc.to_html </code></pre> <p>できますね。</p> <p>では、<a target="_blank" rel="nofollow noopener" href="https://qiita.com/dauuricus/items/7da2e5f14c965da18106">vosk はどうだろう</a>?</p> tomato tag:crieit.net,2005:PublicArticle/18201 2022-05-30T01:28:42+09:00 2022-06-09T21:40:45+09:00 https://crieit.net/posts/why `_why` <p><a href="https://crieit.now.sh/upload_images/cdb45b7bb89840a83fd1f2cbd91754396294082c08e91.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/cdb45b7bb89840a83fd1f2cbd91754396294082c08e91.jpg?mw=700" alt="http://poignant.guide/book/chapter-6.html" /></a><br /> Why’s (Poignant) Guide to Ruby <a target="_blank" rel="nofollow noopener" href="https://en.m.wikipedia.org/wiki/Why's_%28poignant%29_Guide_to_Ruby">:wikipedia</a></p> <blockquote> <p>The book is unusual among programming books in that it includes much strange humor and many narrative side tracks which are sometimes completely unrelated to the topic. Many motifs have become inside jokes in the Ruby community, such as references to the words "chunky bacon". The book includes many characters which have become popular as well, particularly the cartoon foxes and Trady Blix, a large black feline friend of why's, who acts as a guide to the foxes (and occasionally teaches them some Ruby).</p> </blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/_why">https://rentry.co/_why</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://crieit-net.translate.goog/posts/why?_x_tr_sl=ja&_x_tr_tl=en&_x_tr_hl=ja&_x_tr_pto=wapp">translate to</a></p> <p>13年前に<a target="_blank" rel="nofollow noopener" href="http://www.slate.com/articles/technology/technology/2012/03/ruby_ruby_on_rails_and__why_the_disappearance_of_one_of_the_world_s_most_beloved_computer_programmers_.html">ネット自殺</a>した(ネットダンシャリ?自殺って言うと自殺みたいだけど、ネット上の自分の痕跡を消し去るということの比喩としてだから、生物的に死んだというわけじゃないけど、<a target="_blank" rel="nofollow noopener" href="https://www.urbandictionary.com/define.php?term=infosuicide">インターネット上での痕跡を自ら消し去ったこと</a>について "dead" とか "death" とか形容される。つまり、逆に言うとそれ以前がかなりアクティブでいきいきとして活動している感じが影響を与えていたアーティストであり)、Ruby プログラマー<code>_why</code> のことをたまたま昨日知って(xpath や html の parse について調べていて <a target="_blank" rel="nofollow noopener" href="https://github.com/hpricot/hpricot">Hpricot</a> というパーサーがあることに気がついて、なんだろう?と思ったから)、朝までずっと <code>_why</code> こと<strong>why the lucky stiff</strong> の事と、別件で片手キーボード( frogpad などの片手で全てタイピングできる仕様のキーボードについて)のことをネットで追っていて、起きてからずっと <code>_why</code> が書いた、彼が消し去った彼の本のコピー(再配布可能、商用利用可能のデータだから第三者によってサルベージされて公開されているが、あくまでも <code>_why</code> 自身は彼の過去ワークはすべて消し去って音信不通となっているような感じ) を読んでいた。<br /> 日陰にひきこもって、冷めたコーヒーを飲みつつ。</p> <p>とても暑いので、ネコも洗って乾かした。</p> <p><a href="https://crieit.now.sh/upload_images/2d71bdeb21c54ea55e353d52f24a5ea66298d97a21927.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/2d71bdeb21c54ea55e353d52f24a5ea66298d97a21927.png?mw=700" alt="why the lucky stiff" /></a></p> <p><strong>why the lucky stiff</strong> については知らなかったが、知ったことでいろいろ謎だったことがつながった。</p> <p>ここ最近古本で取り寄せた、<a target="_blank" rel="nofollow noopener" href="https://www.oreilly.co.jp/community/blog/2009/04/unidentified-popular-figure.html">ぶ厚めのオライリーの Ruby 本について、なんでこんなインディーよりのグラフティみたいな挿絵が一枚入ってるのかな?と思ってたが、やはりそれはよく見るとwhy the lucky stiff の絵だった。</a><a target="_blank" rel="nofollow noopener" href="https://github.com/shoes/shoes-deprecated">shoes という GUI があるらしいこと</a>は知っていて、それも もともと <code>_why</code> によって作られていた。</p> <p>しかし、Ruby について全く知らなかったので、だいたい 2009 年くらいまでの本を取り寄せて Ruby 関係の本は 5 冊になったが、どれも「仕事で使える達人の」?とかいうのは範囲ではなく、選んだのは言語の詳細のリファレンスとか、サンプルのコード見て雰囲気だけつかめそうなものだったので逆に薄く濃いものだったためか、なんとなく言語周辺のカルチャーを感じさせるものだった。2009 年くらいのとしたのは、<a target="_blank" rel="nofollow noopener" href="https://jp.quora.com/naze-Ruby-ha-kihon-teki-ni-shi-nde-iru-node-shou-ka">その頃まではたぶんなんでも Ruby で、という流行りがあったのだろうと推定</a>していたからだけども、これも根拠なくそんな気がしたからというだけだ。</p> <p>(最初に、何も知らずに注文する本を選んで、<a target="_blank" rel="nofollow noopener" href="https://www.fukkan.com/fk/CartSearchDetail?i_no=68323271">恋するプログラム-Rubyでつくる人工無脳<br /> 秋山智俊</a> ... 2005 年 、<a target="_blank" rel="nofollow noopener" href="https://tatsu-zine.com/books/esoteric-language-programing-in-ruby">Ruby で作る奇妙なプログラミング言語 原 悠</a> ... 2008 年 、<a target="_blank" rel="nofollow noopener" href="https://www.amazon.co.jp/初めてのRuby-Yugui/dp/4873113679">初めての Ruby Yugui</a>(キリン) ... 2008 年初版2014年初版第十刷、という順で届いて、半年くらいたって、<a target="_blank" rel="nofollow noopener" href="https://www.amazon.co.jp/プログラミング言語-Ruby-まつもと-ゆきひろ/dp/4873113946">プログラミング言語 Ruby David Flanagan</a> ... 2009 年、一番最近で、<a target="_blank" rel="nofollow noopener" href="https://tatsu-zine.com/books/ruby-under-a-microscope-ja">Ruby のしくみ</a> 2014年 。そして、どれもちゃんと読んでないけど、どれも的確に良いと思う。注意散漫でチラチラとしか読めてないので、他の本のこともしらないけれど、これから Ruby ってどんな言語がゼロから知りたいって場合は、<a target="_blank" rel="nofollow noopener" href="https://www.amazon.co.jp/初めてのRuby-Yugui/dp/4873113679">初めての Ruby</a> が一冊あればいいんじゃないかと思っていた。 )</p> <p><code>_why</code> の本 <strong>Why’s (Poignant) Guide to Ruby</strong> <a target="_blank" rel="nofollow noopener" href="https://en.m.wikipedia.org/wiki/Why's_%28poignant%29_Guide_to_Ruby">:wikipedia</a> は漫画で、面白そうな物語になっていて 2003 年から 2007 年あたりまでの作品のような感じ(全く確かではない)。ネット上にいろいろ ( 愛されて ) 保存 ( 保管 ) されてて、ダウンロード可能になっている。</p> <p><a target="_blank" rel="nofollow noopener" href="http://www.aoky.net/articles/why_poignant_guide_to_ruby/expansion-pak-1.html">本の中に登場</a>する <a target="_blank" rel="nofollow noopener" href="https://www.ruby-lang.org/en/news/2004/12/26/ruby-182-released/">Ruby の最新版は 1.8.2 だから ...</a></p> <p><strong>why the lucky stiff</strong> ( = <code>_why</code>) は、理由は不明だが 2009 年 8 月 19 日に、すべての著作を消したと言われていて、2009 年という時代背景を日本で起こったことでふりかえってみると、リーマンショックの約一年後で winny 騒動の裁判で</p> <blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://internet.watch.impress.co.jp/docs/news/320251.html">「 2009 年 10 月 8 日、大阪高等裁判所は、一審の京都地裁判決を破棄し、金子に無罪を言い渡した」</a></p> </blockquote> <p>文言は<a target="_blank" rel="nofollow noopener" href="https://ja.m.wikipedia.org/wiki/Winny事件">wikipedea :Winny 事件</a> から。</p> <p>インディーゲーム cave story</p> <blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://ja.m.wikipedia.org/wiki/洞窟物語">「 2008 年10月3日、ニンテンドー・オブ・アメリカのプレスリリース、および開発者のサイトにおいて、海外向けにWiiウェア版の配信が発表された(日本国内での配信は未定)。2010 年 9 月には、北米向けにニンテンドーDSiウェア版の配信が発表された。」</a></p> </blockquote> <p>などが、日本で起こったこと。マイケル・ジャクソンというスターが亡くなったのも 2009 年らしい。そんな 2009 年。</p> <p><strong>「ホワイの(感動的)Rubyガイド」青木靖 訳</strong><br /> <a target="_blank" rel="nofollow noopener" href="http://www.aoky.net/articles/why_poignant_guide_to_ruby/index.html">http://www.aoky.net/articles/why_poignant_guide_to_ruby/index.html</a></p> <p>epub download<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/vkr8s">https://rentry.co/vkr8s</a></p> <p>mislav によるサルベージ版 github<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/mislav/poignant-guide">https://github.com/mislav/poignant-guide</a></p> <blockquote> <p>On August 19, 2009 Why the Lucky Stiff removed every trace of his work from the Web, including this book: the Poignant Guide to Ruby.<br /> I've salvaged the book from the Internet archive and re-published it. The original work was under the Attribution-ShareAlike license.</p> </blockquote> <p><a target="_blank" rel="nofollow noopener" href="http://poignant.guide/">http://poignant.guide/</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://archive.org/details/TheSoundtrackToWhyspoignantGuideToRuby/Chap2-ThisBookIsMadeofRabbitsAndLemonade.mp3">https://archive.org/details/TheSoundtrackToWhyspoignantGuideToRuby/Chap2-ThisBookIsMadeofRabbitsAndLemonade.mp3</a></p> <blockquote> <p>The soundtrack to "why's (poignant) guide to ruby" tutorial that was taken offline. The Internet Archive's version is still available here:<br /> <a target="_blank" rel="nofollow noopener" href="http://web.archive.org/web/20070704022347rn_1/poignantguide.net/ruby/">http://web.archive.org/web/20070704022347rn_1/poignantguide.net/ruby/</a><br /> <a href="https://crieit.now.sh/upload_images/8e1388e35819258a7f29f54bb60c0cb862995c2c4201f.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/8e1388e35819258a7f29f54bb60c0cb862995c2c4201f.jpg?mw=700" alt="image" /></a></p> </blockquote> <p>実際おもしろい。少し自分をマイノリティーサイドに表現していて、完全に人と上手に付き合えないという性質を告白した上で、ストーリーが進んでいく。まだ最初の方しか読んでいないけど。一人もくもくと創造していく創作物という感じ。インディーズ game のような雰囲気で、一体どんなこと考えているんだ?という好奇心がうまれる。</p> <blockquote> <p>Chapter 2: Kon'nichi ha, Ruby -d: How Books Start<br /> One problem here. I don’t get along well with people. I don’t hold hands very well.</p> </blockquote> <p><a href="https://crieit.now.sh/upload_images/28be8148925559ca3b5fa789b28246a762942a15142b0.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/28be8148925559ca3b5fa789b28246a762942a15142b0.png?mw=700" alt="chapter 3: Regular Expressions" /></a></p> <p>Ruby についてほとんど知らないので、ストーリーの流れでプログラムの言語を教えるという作りで(それは、<a target="_blank" rel="nofollow noopener" href="https://www.fukkan.com/fk/CartSearchDetail?i_no=68323271">恋するプログラム</a> もやや近い)、おそらくいちばん最初に読むべき内容であって、よく知らないと、その本自体の情報にいきつかないので、存在が見えないという幻の本かもしれないし、いまちょうどいい。絵がすごくかわいい。</p> <p>![Chapter5:The Mechanisms of Name-Calling]<a href="https://crieit.now.sh/upload_images/f494e07d32c008e3cc2788ef2c4141a16294074cea432.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/f494e07d32c008e3cc2788ef2c4141a16294074cea432.gif?mw=700" alt="Chapter5:The Mechanisms of Name-Calling" /></a>(poignant.guide/images/my.daughters.organ-2.gif)</p> <p>紙の本も出ていて、<a target="_blank" rel="nofollow noopener" href="https://www.lulu.com/search?adult_audience_rating=00&page=1&pageSize=10&q=_why+the+lucky+stiff">Lulu からのオンデマンド</a>か、海外発送の古本かというところ。紙で欲しい。2 冊くらい。</p> <hr /> <p><a target="_blank" rel="nofollow noopener" href="https://www.smashingmagazine.com/2010/05/why-a-tale-of-a-post-modern-genius/">Diogo Terror may 15, 2010<br /> _Why: A Tale Of A Post-Modern Genius</a></p> <blockquote> <p>QUICK SUMMARY ↬ Why the Lucky Stiff was one of the brightest and most inspiring programmers in activity. He became famous through a series of blogs and through the incredible amount of open-source projects that he maintained over the course of more than seven years.</p> </blockquote> <hr /> <p><a target="_blank" rel="nofollow noopener" href="https://viewsourcecode.org/why/index.html">_why's Estate</a></p> <blockquote> <p>Hey, welcome to my collection of why the lucky stiff links. Everything _why has published on the internet should be accessible from here. It works sort of like a museum that sells maps. Many of his abandoned writings are mirrored locally here, and everything else is through external links. To download the Estate perhaps for local viewing, get why.zip (5.8 MB).</p> </blockquote> <hr /> <p><a target="_blank" rel="nofollow noopener" href="https://viewsourcecode.org/why/hacking/theLittleCodersPredicament.html">The Little Coder's Predicament</a> by why the lucky stiff june 10, 2003</p> <hr /> <p><strong>Why The Lucky Stiff Documentary</strong><br /> <a target="_blank" rel="nofollow noopener" href="https://youtu.be/64anPPVUw5U">youtube</a></p> <blockquote> <p>This is the documentary about Why The Lucky Stiff, an artist and software innovator who disappeared from the Internet in 2009, taking down all his code and artwork ("infosuicide"). Ten years in the making, this documentary presents a portrayal of the artist, his influence and the possible reasons behind his disappearance.<br /> Years later, _why released more art and his own explanation for his disappearance, streamed live to printers connected to the Internet. A very innovative artist, he will be missed but his art lives on.<br /> <a href="https://crieit.now.sh/upload_images/b15dc4072f9409de3f764fbb67f3aeb86295716a1a548.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b15dc4072f9409de3f764fbb67f3aeb86295716a1a548.png?mw=700" alt="Why the lucky stiff documentary" /></a><br /> <a href="https://crieit.now.sh/upload_images/94ff30484741e50986ea1b96099faf25629571191b8e3.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/94ff30484741e50986ea1b96099faf25629571191b8e3.png?mw=700" alt="Why the lucky stiff documentary" /></a></p> </blockquote> <p>このドキュメンタリーの冒頭とラストに出演しているのは Ruby の Nokogiri(XML / HTML パーサーで web<br /> スクレイピングに使われる) <a target="_blank" rel="nofollow noopener" href="https://en.m.wikipedia.org/wiki/Nokogiri_%28software%29">:wikipedia</a> の作者 Aaron Patterson と思われる。Nokogiri と同等の用途に使われてい(た)るもののうちの一つ <a target="_blank" rel="nofollow noopener" href="https://github.com/hpricot/hpricot">Hpricot</a> のオリジネーターが <code>_why</code> だから対比としてキャスティングされているのかもしれない。</p> <hr /> <p>ところで、ほんとうにチャプター 8 "Heaven's Harp" は存在しないんだろうか?<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/mislav/poignant-guide">https://github.com/mislav/poignant-guide</a></p> <blockquote> <p>Chapter 8: Heaven's Harp was never recovered. I suspect _why started drawing it, but never finished or published.1</p> </blockquote> <hr /> <p>_why 's github mirror<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/whymirror/">https://github.com/whymirror/</a></p> <hr /> <p>Madison Ruby 2013 CLOSURE by Steve Klabnik<br /> <a target="_blank" rel="nofollow noopener" href="https://youtu.be/MaWHVceDbFo">https://youtu.be/MaWHVceDbFo</a></p> <p>Why The Lucky Stiff Documentary に出演していた 2 ブロックモヒカンのひとの「この男を知っていますか ?」というところから始まるトーク。<br /> 何を話しているかは、python で読める。<br /> <img src="https://rentry.co/7e5gz/png" alt="https://rentry.co/7e5gz" /></p> <h4 id="CLOSURE"><a href="#CLOSURE">CLOSURE</a></h4> <p><a target="_blank" rel="nofollow noopener" href="https://www.dropbox.com/s/c0n2dljz8anih62/CLOSURE.pdf?dl=0">https://www.dropbox.com/s/c0n2dljz8anih62/CLOSURE.pdf?dl=0</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://blog.sinakhalili.com/posts/closure/">Book review: CLOSURE by why the lucky stiff | Sina's blog</a></p> <p>このブックレヴューは python と Ruby で協力して読んでみよう。</p> <pre><code class="ruby">html = `curl -s 'https://blog.sinakhalili.com/posts/closure/'` require 'oga' list_p = [] doc = Oga.parse_html(html) doc.css('p').each_with_index {|x,index| if x.text != "" && x.text.match?(/^\w/) list_p << "#{x.text}" elsif x.text.match?(/^\(/) list_p << "#{x.text}" else # p x.text end } require 'pycall/import' include PyCall::Import pyfrom 'googletrans',import: :Translator translator = Translator.new list_p.each_with_index {|lines,index| lines.sub!(/^s*/,'') lines.to_s.gsub!(/\.\s*/,'') # p lines puts begin translated = translator.translate(lines,dest='ja') puts "#{index} #{lines}" puts "#{index} #{translated.text}" rescue => evar p $! end } </code></pre> tomato tag:crieit.net,2005:PublicArticle/18175 2022-04-26T17:01:11+09:00 2022-05-04T13:28:56+09:00 https://crieit.net/posts/493a752726fa6c47c162fcb7379c2c6d 気象庁の公開データを node.js と chrome ブラウザ (browserify) で表示する。 <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/nc73d">https://rentry.co/nc73d</a></p> <p>雨なので気象庁の公開データを Javascript を使って見てみる。</p> <h2 id="pathCode (json) を表示してみる"><a href="#pathCode+%28json%29+%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">pathCode (json) を表示してみる</a></h2> <p>気象データを問い合わせるのに、定数になっている日本全国の場所のコード( pathCode )を見てみる。<br /> なぜなら、それをしらないとエリアごとの気象データを問い合わせできない。</p> <h3 id="javascript"><a href="#javascript">javascript</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://ipfs.infura.io/ipfs/QmPrjHjXe6pSwh39Qjh326uLQWsoWtBJqNvdtPnMxsRDHq"><code>tenki_area.js</code></a></p> <pre><code class="javascript">const axios = require("axios"); const url = "https://www.jma.go.jp/bosai/common/const/area.json"; (get_area = async () => { try { const response = await axios.get(url); console.log(response.data); } catch (error) { console.error(error); } })(); </code></pre> <p>terminal:</p> <pre><code class="bash">> node tenki-area.js > tenki-area.json </code></pre> <p>Rf:<br /> <a target="_blank" rel="nofollow noopener" href="https://mindtech.jp/?p=1754">https://mindtech.jp/?p=1754</a></p> <h3 id="Ruby"><a href="#Ruby">Ruby</a></h3> <pre><code class="ruby">require "json" require "net/http" area_code_url = "https://www.jma.go.jp/bosai/common/const/area.json" response = Net::HTTP.get_response(URI.parse(area_code_url)) h_area = JSON.parse(response.body) pp h_area["offices"] </code></pre> <p><a target="_blank" rel="nofollow noopener" href="https://ipfs.infura.io/ipfs/QmT3YkWhro7xMQ3uNwF4QNXUnt6aAjoGuaupzKg5ZtdqNs">tenki_area.rb</a></p> <pre><code class="ruby">require "json" require "net/http" area_code_url = "https://www.jma.go.jp/bosai/common/const/area.json"; response1 = Net::HTTP.get_response(URI.parse(area_code_url)) h_area_code = JSON.parse(response1.body) uri = "https://www.jma.go.jp/bosai/forecast/data/forecast/" #area = "260000" #Kyoto h_area_code["offices"].each {|v| area = v[0] begin response2 = Net::HTTP.get_response(URI.parse("#{uri}#{area}.json")) h_area_data = JSON.parse(response2.body) h_area_data.each {|vv| pp vv["publishingOffice"] pp vv["reportDatetime"] vv["timeSeries"].each {|x| x["areas"].each {|xx| pp xx['area']} } puts puts "--------------------------------------------------" puts } rescue puts end } </code></pre> <h3 id="Python"><a href="#Python">Python</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://ipfs.infura.io/ipfs/Qmat48qKAeh6LovrqfzdisYBhbAtmKb5dpPHXZvxrisM7P">tenki_area.py</a></p> <pre><code class="python">import requests import json area_code_url = "https://www.jma.go.jp/bosai/common/const/area.json"; headers = {"content-type": "application/json; charset=utf-8"} response = requests.get(area_code_url, headers=headers) data = response.json() print(json.dumps(data['offices'],ensure_ascii=False,indent = 4)) </code></pre> <h3 id="Nim"><a href="#Nim">Nim</a></h3> <pre><code class="nim">#nim -d:ssl import httpclient,strutils,uri import json const area_code_url = "https://www.jma.go.jp/bosai/common/const/area.json" let client0 = newHttpClient() let response = client0.get(area_code_url) let jsObj = parseJson(response.body) client0.close() echo jsObj["offices"].pretty() </code></pre> <h2 id="get weather data"><a href="#get+weather+data">get weather data</a></h2> <p>Rf:<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/youtoy/items/932bc48b03ced5a45c71">https://qiita.com/youtoy/items/932bc48b03ced5a45c71</a></p> <h3 id="Ruby"><a href="#Ruby">Ruby</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://ipfs.infura.io/ipfs/QmPsnpodrqshrnwJAd3AjQdgmzxHcoWvTa6F1UZVYPjjxq">tenki.rb</a></p> <p>例えば京都という場所の天気を問い合わせる。</p> <h3 id="javascript"><a href="#javascript">javascript</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://ipfs.infura.io/ipfs/QmZBPSgQwSspdQeaFpCCo9fPfrrbKfZi1A3j6aW8cY8W6Y"><code>tenki.js</code></a></p> <pre><code class="javascript">const axios = require("axios"); const url = "https://www.jma.go.jp/bosai/forecast/data/forecast/"; const area = "260000"; // Kyoto (getWeatherForecast = async () => { try { const response = await axios.get(`${url}${area}.json`); console.log(response.data); console.log(response.data[0].publishingOffice); console.log(response.data[0].timeSeries[0].areas); for(const area of response.data[0].timeSeries[0].areas){ console.log(`----${area.area.name}----`); for(const weather of area.weathers){ console.log(weather); } } } catch (error) { console.error(error); } })(); </code></pre> <p>terminal:</p> <pre><code class="bash">> node tenki.js </code></pre> <pre><code>> node tenki.js [ { publishingOffice: '京都地方気象台', reportDatetime: '2022-04-26T17:00:00+09:00', timeSeries: [ [Object], [Object], [Object] ] }, { publishingOffice: '京都地方気象台', reportDatetime: '2022-04-26T17:00:00+09:00', timeSeries: [ [Object], [Object] ], tempAverage: { areas: [Array] }, precipAverage: { areas: [Array] } } ] 京都地方気象台 [ { area: { name: '南部', code: '260010' }, weatherCodes: [ '300', '311', '100' ], weathers: [ '雨 所により 夜遅く 雷を伴い 激しく 降る', '雨 昼前 から くもり 後 晴れ 所により 明け方 まで 雷を伴い 激しく 降る', '晴れ' ], winds: [ '南の風 後 南西の風', '南西の風 後 北西の風', '北の風' ] }, { area: { name: '北部', code: '260020' }, weatherCodes: [ '300', '311', '100' ], weathers: [ '雨 所により 夜遅く 雷を伴い 激しく 降る', '雨 昼前 から くもり 後 晴れ 所により 明け方 まで 雷を伴い 激しく 降る', '晴れ' ], winds: [ '南の風 やや強く 海上 では 南の風 強く', '南西の風 後 北東の風  海上 では 南西の風 やや強く', '北の風' ], waves: [ '1.5メートル 後 2メートル', '2メートル 後 1.5メートル', '1 .5メートル' ] } ] ----南部---- 雨 所により 夜遅く 雷を伴い 激しく 降る 雨 昼前 から くもり 後 晴れ 所により 明け方 まで 雷を伴い 激しく 降る 晴れ ----北部---- 雨 所により 夜遅く 雷を伴い 激しく 降る 雨 昼前 から くもり 後 晴れ 所により 明け方 まで 雷を伴い 激しく 降る 晴 </code></pre> <p>これをサンプルにして Javascript を学習する。</p> <h2 id="browserify"><a href="#browserify">browserify</a></h2> <p>node.js で動いたプログラムを browserify によってブラウザで動作するようにする。</p> <p><a target="_blank" rel="nofollow noopener" href="https://browserify.org/">https://browserify.org/</a></p> <blockquote> <p>Browsers don't have the require method defined, but Node.js does. With Browserify you can write code that uses require in the same way that you would use it in Node.</p> </blockquote> <p>インストール<br /> terminal:</p> <pre><code>> npm install -g browserify </code></pre> <p><a target="_blank" rel="nofollow noopener" href="https://ipfs.infura.io/ipfs/QmW7gBLMnDdpEy4rp9NVp5jWnmsFwoxYyvSKA1y5fXtVzd"><code>tenki_2.js</code></a></p> <p>他の言語である println のように使っているコンソール.ログがブラウザでどうなるのかわからないので、require しておいてみる。</p> <pre><code class="javasript">const console = require("console"); const axios = require("axios"); const url = "https://www.jma.go.jp/bosai/forecast/data/forecast/"; const area = "260000"; // Kyoto (getWeatherForecast = async () => { try { const response = await axios.get(`${url}${area}.json`); // console.log(response.data); // console.log(response.data[0].publishingOffice); // console.log(response.data[0].timeSeries[0].areas); for(const area of response.data[0].timeSeries[0].areas){ console.log(`----${area.area.name}----`); for(const weather of area.weathers){ console.log(weather); } } } catch (error) { console.error(error); } })(); </code></pre> <p><code>tenki_2.js</code> をブラウザ仕様の javascript ファイル <code>bundle.js</code> に変換する。</p> <p>terminal:</p> <pre><code class="bash">> browserify tenki_2.js -o bundle.js </code></pre> <p><a target="_blank" rel="nofollow noopener" href="https://ipfs.infura.io/ipfs/Qmd3cJNFTft8gbA444XgefmWXFGvY4QECVqYyicQ1dbs5C"><code>bundle.js</code></a><br /> <a target="_blank" rel="nofollow noopener" href="https://pastebin.com/szscGNsR">https://pastebin.com/szscGNsR</a></p> <p>html ファイルを作って、 bundle.js を読み込むようにする。<br /> 例えば tenki という名前でフォルダを作って、index.html, bundle.js を tenki フォルダに配置する。</p> <p><a target="_blank" rel="nofollow noopener" href="https://ipfs.infura.io/ipfs/QmW5oGdEBSJJpazfds7LSqyMttxNYj8BVBphtfJYpP7wWv"><code>index.html</code></a></p> <pre><code class="html"><!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>tenki_area browserify example</title> <script src="bundle.js"></script> </head> <body> </body> </html> </code></pre> <p>chrome ブラウザーで tenki フォルダの中の html ファイルを開く。</p> <p>chrome browser:</p> <p><code>CTRL</code> + <code>shift</code> + <code>i</code></p> <p>DevTools</p> <p><code>index.html</code>:</p> <p><a href="https://crieit.now.sh/upload_images/d6edfe05648ad36e61e2c39bcce18cb06267ee2adf460.JPG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d6edfe05648ad36e61e2c39bcce18cb06267ee2adf460.JPG?mw=700" alt="'chromebrowser developper tools'" /></a></p> tomato tag:crieit.net,2005:PublicArticle/17966 2022-02-04T21:49:20+09:00 2022-02-06T23:26:22+09:00 https://crieit.net/posts/d31bd6bca80198716bb2f002a715f976 タイトルから書籍情報を探す。 <p>データを詰め込んだデータベースから、本のタイトルを取り出して、もしあれば著者名を取り出して、国会図書館のNDL サーチで、タイトルを検索して、これかな ?? という書籍情報をデータベースに詰め込むプログラム。</p> <p>何をするためかというと、タイトルしかわからないマンガコミックの出版社の情報や、ISBN など詳細をたどって得るため。ISBN からタイトル情報へ行き着くものはあるが逆のものは知らないため。</p> <p>それ以上説明は省くが、こちらからさかのぼっていくと、きっとわかる。<br /> <a href="https://crieit.net/boards/manga-B/fc4a0259928ca001b58935ddd7cbb322">https://crieit.net/boards/manga-B/fc4a0259928ca001b58935ddd7cbb322</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://note.com/haywhnk/n/n5195d2660f53">さかのぼる</a></p> <pre><code class="ruby">require "faraday" require "faraday/net_http" require "net/http" require 'nokogiri' require 'sqlite3' require 'time' require 'date' class NdlSearch def get_book_info(title, creator = nil) data = [] query = { :mediatype => 1, :cnt => 100 } query[:title] = title query[:creator] = creator if creator if creator == '' puts "::::::::::::::::::::::::::::::::::::" puts 'author :??' end puts print "query :#{query}" puts response = ndl_get('/api/opensearch', query) xml = Nokogiri::XML(response.body) xml.remove_namespaces! items = xml.xpath('//item') unless items.any? then puts puts 'ndl has no item' data << {"totalResults"=>"0"} else #pp items.to_s items.each do |item| #puts #puts "item :", item book = {} threads1 = [] # concurrency item.children.each do |c| threads1 << Thread.new { key = c.name next if key == 'text' val = "#{c.content}" label = c.attribute("type") if label label = "#{label}".gsub(/^dcndl:|^dcterms:/,'') book[label] ||= [] book[label] << val unless book[label].include?(val) val = "#{label}:#{val}" end book[key] ||= [] book[key] << val unless book[key].include?(val) } end threads1.each{ |thr| thr.join } book = book.map {|key,val| [key, val.join(',')]}.to_h data << book end end data end private def ndl_get(path, pram) con = Faraday.new(:url => 'https://iss.ndl.go.jp') do |f| f.request :url_encoded #f.response :logger f.adapter :net_http end con.get path, pram end end #DB SQL =<<EOS create table tbl_bookdata ( id INTEGER PRIMARY KEY AUTOINCREMENT, book_title text, url text, author text, creatortranscription text, volume text, seriestitle text, publisher text, isbn text, date text, W3CDTF integer, mangathank_title text, ex_id integer, tags text ); EOS count = 0 new_db = SQLite3::Database.open("bookdata_fbay_py_3.db") #new_db = SQLite3::Database.open("bookdata_fbay_py.db") db = SQLite3::Database.open("../gotest/fbay_python3.db") #db = SQLite3::Database.open("fbay_python3.db") #new_db = SQLite3::Database.open("bookdata.db") #db = SQLite3::Database.open("mangathank_new.db") #new_db.execute(SQL) temp_author = '' temp_title = '' $index = 0 db.execute("select id from tbl_manga order by id desc limit 1 ;") do |data| $index = data[0].to_i print "last id : '#{$index}'" puts end looptimes = $index new_db.execute("select id from tbl_bookdata order by id desc limit 1 ;") do |data| count = data[0].to_i end start = count + 1 #threads = [] #m = Mutex.new (start..looptimes).each do |api| # (0..4).each do |pac| fiber = Fiber.new do if count >= looptimes - 1 then break end # threads &lt;&lt; Thread.new { # m.synchronize{ count += 1 puts puts "::::::::::::::::::::::::::::::::::::::::" print 'id:',count,' ' search_data = db.execute("select book_title,author,title,id,tags from tbl_manga where id ='#{count}' ;") if search_data.empty? then puts 'empty' end *book_data = search_data.pop #book_data[0] #=> book_title #book_data[1] #=> author #book_data[2] #=> title #book_data[3] #==> id #book_data[4] #==> tags mangathank_title = book_data[2].to_s.gsub(/\'/, "\'\'") tags = book_data[4].to_s.gsub(/\'/, "\'\'") puts book_data[2] if book_data[2] == "null" then p count pp book_data # new_db.execute(&quot;insert into tbl_bookdata (book_title, author, mangathank_title, ex_id, tags ) values('book_title:nothing','author:nothing','#{mangathank_title}','#{book_data[3]}','#{tags}');&quot;) new_db.execute("insert into tbl_bookdata (id, book_title, author, mangathank_title, ex_id, tags ) values('#{count}','book_title:nothing','author:nothing','#{mangathank_title}','#{book_data[3]}','#{tags}');") else author_data = book_data[2].to_s.slice(/((?<=\[).*?(?=\]))/) #puts "author_dat:#{author_data}" if author_data != nil author_data.gsub!(/\ x\ /,' ') author_data.sub!(/((?<=[\p{Hiragana}\p{Han}\p{Katakana}])x(?=[\p{Hiragana}\p{Han}\p{Katakana}]))/,' ') author_data.gsub!(/\(|\)/,"\(" =>' ',"\)"=>'') author_data.gsub!(/×/,' ') author_data.gsub!(/\ &/,' ') end if /(\ )/.match(author_data) then #/(\S+$)/.match(author_data) #person = /(?<=['\ '])\S.*$/.match(author_data) #str_array = person.to_s.split str_array = author_data.to_s.split person = str_array.pop else person = author_data.to_s end print("author_data: ", author_data , " person: " , person) puts num = book_data[2].to_s.slice(/((?<=第)\d+(?=巻|卷$))/) #num = /((?<=第)\d+(?=巻$))/.match(book_data[0].to_s) #book_data_0 = book_data[0].to_s.sub(/((?=第).*巻)/,'') book_data_0 = book_data[2].to_s.gsub(/((?=第).*(巻|卷))/,'') book_data_0.gsub!(/((?=第).*話)/,'') book_data_0.gsub!(/(.(?<=\()文庫版(?=\)).)/,'') book_data_0.gsub!(/(.(?<=\[)文庫版(?=\]).)/,'') book_data_0.gsub!(/文庫版/,'') book_data_0.gsub!(/フルカラー版/,'') book_data_0.gsub!(/カラー版/,'') book_data_0.gsub!(/(.(?<=\()完(?=\)).)/,'') book_data_0.gsub!(/(.(?<=【).*(?=】).)/,'') book_data_0.gsub!(/(.(?<=\[).+?(?=\]).)/,'') book_data_0.lstrip! book_data_0.rstrip! if book_data_0 == temp_title then if str_array then person = temp_author end else temp_title = book_data_0 temp_author = person end if num != nil then num = num.to_i book_data_0 += ' ' + num.to_s end puts #puts book_data[0] puts book_data_0 puts ndl_search = NdlSearch.new onemore = 'true' while onemore == 'true' do res = ndl_search.get_book_info( book_data_0,person ) onemore = 'false' # puts res[0] if res[0] == nil then puts "res: empty" book_data_0.gsub!(/\'/,"\'\'") puts book_data_0 # new_db.execute(&quot;insert into tbl_bookdata (book_title, author, mangathank_title, ex_id,tags ) values('#{book_data_0}','#{author_data}','#{mangathank_title}','#{book_data[3]}','#{tags}');&quot;) new_db.execute("insert into tbl_bookdata (id, book_title, author, mangathank_title, ex_id,tags ) values('#{count}','#{book_data_0}','#{author_data}','#{mangathank_title}','#{book_data[3]}','#{tags}');") onemore = 'false' else done = false res[0..99].each_with_index do |book,index| if done == true then break end not_book = false book.each do |key, val| if key == "extent" then puts "#{key}:#{val}" if /ビデオ|DVD|dvd|ディスク/.match?(val) then not_book = true puts '' puts 'SKIP' break end end end if not_book == true then next end if book != "null" then # puts &quot;res:#{book}&quot; # puts &quot; :#{index}&quot; book.each do |key, val| # puts &quot;#{key}:#{val}&quot; if key == 'totalResults' then #puts #print "no match title name #{person} ",book_data[3],' ' book_data_0.gsub!(/\'/,"\'\'") #puts book_data_0,person,mangathank_title unless str_array.nil? then if str_array.size > 0 then person = str_array.shift puts print "#{person} ?" puts puts onemore = 'true' #sleep 3 break else # new_db.execute(&quot;insert into tbl_bookdata (book_title, author, mangathank_title, ex_id,tags ) values('#{book_data_0}','#{author_data}','#{mangathank_title}','#{book_data[3]}','#{tags}');&quot;) new_db.execute("insert into tbl_bookdata (id, book_title, author, mangathank_title, ex_id,tags ) values('#{count}', '#{book_data_0}','#{author_data}','#{mangathank_title}','#{book_data[3]}','#{tags}');") onemore = 'false' #sleep 10 break end else # new_db.execute(&quot;insert into tbl_bookdata (book_title, author, mangathank_title, ex_id, tags) values('#{book_data_0}','#{author_data}','#{mangathank_title}','#{book_data[3]}','#{tags}');&quot;) new_db.execute("insert into tbl_bookdata (id, book_title, author, mangathank_title, ex_id, tags) values('#{count}', '#{book_data_0}','#{author_data}','#{mangathank_title}','#{book_data[3]}','#{tags}');") onemore = 'false' end break end if key == 'title' then temp_author = person puts "☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆" puts " #{count}" puts "" puts "#{key}:#{val}" title = val.to_s.gsub(/\'/, "\'\'") new_db.execute("insert into tbl_bookdata (id, mangathank_title, ex_id, tags ) values('#{count}', '#{mangathank_title}','#{book_data[3]}','#{tags}');") new_db.execute("update tbl_bookdata set book_title = '#{title}' where id = '#{count}';") elsif key == 'author' then puts "#{key}:#{val}" author = val.to_s.gsub(/\'/, "\'\'") new_db.execute("update tbl_bookdata set author = '#{author}' where id = '#{count}';") elsif key == 'creatorTranscription' then puts "#{key}:#{val}" creatortranscription = val.to_s.gsub(/\'/, "\'\'") new_db.execute("update tbl_bookdata set creatortranscription = '#{creatortranscription}' where id = '#{count}';") elsif key == 'volume' then volume = val.to_s.gsub(/\'/, "\'\'") new_db.execute("update tbl_bookdata set volume = '#{volume}' where id = '#{count}';") elsif key == 'link' then url = val new_db.execute("update tbl_bookdata set url = '#{url}' where id = '#{count}';") elsif key == 'publisher' then puts "#{key}:#{val}" publisher = val.to_s.gsub(/\'/, "\'\'") new_db.execute("update tbl_bookdata set publisher = '#{publisher}' where id = '#{count}';") elsif key == 'ISBN' then puts "#{key}:#{val}" puts "" puts "☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆" isbn = val.to_s.gsub(/\'/, "\'\'") new_db.execute("update tbl_bookdata set isbn = '#{isbn}' where id = '#{count}';") elsif key == 'seriesTitle' then puts "#{key}:#{val}" seriestitle = val.to_s.gsub(/\'/, "\'\'") new_db.execute("update tbl_bookdata set seriestitle = '#{seriestitle}' where id = '#{count}';") elsif key == 'date' then puts "#{key}:#{val}" published_date = val.to_s.gsub(/\'/, "\'\'") new_db.execute("update tbl_bookdata set date = '#{published_date}' where id = '#{count}';") elsif key == "W3CDTF" then puts "#{key}:#{val}" puts "" w3cdtf = val.to_s.gsub(/\'/, "\'\'") new_db.execute("update tbl_bookdata set W3CDTF = '#{w3cdtf}' where id = '#{count}';") else #new_db.execute("update tbl_bookdata set author = '', creatortranscription = '', volume = '', url = '', publisher = '', isbn = '', seriestitle = '' ;") onemore = 'false' done = true end end else onemore = 'false' puts "error" mangathank_title = book_data[2].to_s.gsub(/\'/, "\'\'") # new_db.execute(&quot;insert into tbl_bookdata (author, mangathank_title, ex_id ) values('#{author_data}','#{mangathank_title}','#{book_data[3]}');&quot;) new_db.execute("insert into tbl_bookdata (id, author, mangathank_title, ex_id ) values('#{count}','#{author_data}','#{mangathank_title}','#{book_data[3]}');") end end end end end # } # } end fiber.resume # threads.each {|th| th.join} end </code></pre> tomato tag:crieit.net,2005:PublicArticle/17923 2022-01-07T12:34:32+09:00 2022-01-31T21:18:21+09:00 https://crieit.net/posts/BANK ある一つのサイト についての <h1 id="思い出"><a href="#%E6%80%9D%E3%81%84%E5%87%BA">思い出</a></h1> <p>過去 6 ヶ月間、ひたすらひとつの違法マンガサイトを見ていた。</p> <p>ずっと見ていたのは公開されたマンガコンテンツではなく、サイト運営者が作ってるデータをひたすら見ていたので、11 月の 4 日、15 時くらいにサイトをたたんだのを確認した。<sup id="fnref:100"><a href="#fn:100" class="footnote-ref" role="doc-noteref">1</a></sup></p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225786/e0ec8e48-727f-bf85-14b5-bb3ff3625497.png" alt="Screenshot_20211104-164634.png" /></p> <p>おそらくこの翻訳記事 2021年11月04日 14時00分</p> <blockquote> <p>Manga Publisher Wants to Sue Huge Piracy Network, Needs Google's Help * TorrentFreak<br /> <code>https://torrentfreak.com/manga-publisher-wants-to-sue-huge-piracy-network-needs-googles-help-211101/</code></p> </blockquote> <p>注) 'Shueisha’s application and proposed orders/subpoenas can be found here (1,2,3,4 pdf)' の書類が興味深い。</p> <p><a target="_blank" rel="nofollow noopener" href="https://gigazine.net/news/20211104-manga-shueisha-googles-piracy-mangabank/">https://gigazine.net/news/20211104-manga-shueisha-googles-piracy-mangabank/</a></p> <p>が公開されたことで、サイト運営者が違法性を自認して、サイト閉鎖を決定したのではないかと思われる。</p> <p>2021.12月中頃、mangabank.org とはまた別のドメインで再開している。<br /> 画像ファイルのあるアドレスのドメインを whois したところ、cloudflare の管轄のホストではなさそうな感じ。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225786/119a01df-c5bf-ba57-9f8d-50947320e8ec.jpeg" alt="Screenshot_20211230-001601.jpg" /></p> <hr /> <p>過去に書いたこのような記事は、当時、宣伝につながらないように配慮して明記しなかったが、全て漫画 BANK についてのことだった。</p> <p>以下、2021年11月04日 以前</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/dauuricus/items/563cbcc9776f66cb672e">https://qiita.com/dauuricus/items/563cbcc9776f66cb672e</a></p> <p>6 月の時点では 5 万ページぼどだと思っていたが 10 月には 6 万ページ以上あることがわかった。</p> <p>この 6 万ページというのはマンガコンテンツの漫画の総ページ数のことではなくて、URL のことで、そのひとつの URL に 30 点から 300 点ぐらいの画像ファイルの URL が埋め込まれている。<br /> その画像一枚が、漫画のスキャン画像ファイルの一点に相当する。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225786/592fa176-0e08-7dd1-4769-4baef444ba24.png" alt="Screenshot_photo-586353998.png" /></p> <p>画像点数にすると、空想すると数えたことないおおきな数になるので、いずれいつか数えようと数えなかったが、その 6 万ページについては 著者 / 漫画のタイトル / 公開されていた URL / アップデート日時の情報 / 付与されていたタグ のデータのセットを記録した。</p> <p>スキャン画像のファイルが漫画 BANK の見ているページに読み込まれるようにページのソースの中に URL が埋め込まれていて、観測した限りのその URL は、全てが cloudflare がホスティングしているドメインだった<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">2</a></sup> ( 2021年 7月の調べ )</p> <p>これが全てではないが、数千のアドレスから集計すると、こちらのドメインに収束した。</p> <pre><code>0 ssl.appx.buzz 1 ssl.asiax.cloud 2 ssl.stagingy.store 3 ssl.lsw.buzz 4 ssl.advx.cloud 5 ssl.appuru.store 6 ssl.lss.buzz 7 ssl.remon.store 8 ssl.lsq.buzz 9 ssl.lsb.buzz 10 ssl.appsx.cloud 11 ssl.lsh.buzz 12 ssl.raichi.store 13 ssl.lsr.buzz 14 ssl.akaax.com 15 ssl.axax.cloud 16 ssl.lsk.buzz 17 ssl.lsy.buzz 18 ssl.zqap.cloud 19 ssl.skyly.cloud 20 ssl.akax.cloud 21 ssl.zmqx.cloud 22 ssl.lssaq.cloud 23 ssl.lsm.buzz 24 ssl.nexc.store </code></pre> <p>参照: 🥝<a target="_blank" rel="nofollow noopener" href="https://zenn.dev/kurocat/articles/55565f019754cb">ページの中から lazy load の画像 URL を抽出する。</a></p> <p>画像の著作権情報<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">3</a></sup>と、公開されている cloudflare のドメインの画像ファイルアドレスのリストがあれば、すんなり停止できるのだろうなと<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">4</a></sup>考えていたが、ずいぶんとほったらかしているな、あれ ? ゼンゼンナニモシナイノ ? と不思議に思えた。<br /> たしか 2019 年<sup id="fnref:0"><a href="#fn:0" class="footnote-ref" role="doc-noteref">5</a></sup>までサイトブロッキング<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">6</a></sup>しかない!<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">7</a></sup> という主張さえあったのだが<sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">8</a></sup>、その前にどこまでどうしたのかは、主張からはさっぱりわからないので、「どこまでどうできるのか」がずっと気にかかっていた。</p> <p>サイトブロッキング法制化、中間まとめは先送り<br /> 浅川 直輝 日経 xTECH/日経コンピュータ<br /> 2018.09.20<br /> <a target="_blank" rel="nofollow noopener" href="https://xtech.nikkei.com/atcl/nxt/column/18/00001/01044/">https://xtech.nikkei.com/atcl/nxt/column/18/00001/01044/</a></p> <p>朝日新聞デジタル<br /> 海賊版サイト対策、まとまらず 検討会議は無期限延期に<br /> 上田真由美 川本裕司 2018年10月16日 0時10分<br /> <a target="_blank" rel="nofollow noopener" href="https://www.asahi.com/articles/ASLBH5W88LBHUCLV00L.html">https://www.asahi.com/articles/ASLBH5W88LBHUCLV00L.html</a></p> <p>過去のしりきれとんぼになっている<strong>インターネット上の海賊版対策に関する検討会議 第9回会合 議事録</strong>を読んでいると <strong>とりまとまらない</strong>報告となった理由が熱くて面白かった。「両論併記をしない」という主張についての理由が議事録には残っている。</p> <p><a href="https://crieit.now.sh/upload_images/1ad698b79781afe59ebc1f9702c5222161dd706b56a71.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/1ad698b79781afe59ebc1f9702c5222161dd706b56a71.png?mw=700" alt="image" /></a></p> <p>Rf.インターネット上の海賊版対策に関する検討会議 第9回会合 議事録</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.kantei.go.jp/jp/singi/titeki2/tyousakai/kensho_hyoka_kikaku/index.html">https://www.kantei.go.jp/jp/singi/titeki2/tyousakai/kensho_hyoka_kikaku/index.html</a></p> <hr /> <blockquote> <p>Mangabank “Suffers DDoS Attack” & Disappears Following Legal Action<br /> November 9, 2021 by Andy Maxwell</p> </blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://torrentfreak.com/mangabank-suffers-ddos-attack-disappears-following-legal-action-211109/">https://torrentfreak.com/mangabank-suffers-ddos-attack-disappears-following-legal-action-211109/</a></p> <p>注) 'A declaration filed with the court by Shueisha ( pdf ) contains a copy of Cloudflare's response to the DMCA subpoena filed earlier this year.' のところが興味深い。</p> <p><a target="_blank" rel="nofollow noopener" href="https://torrentfreak-com.translate.goog/mangabank-suffers-ddos-attack-disappears-following-legal-action-211109/?_x_tr_sl=en&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=nui">翻訳</a></p> <p>漫画 BANK のアップロードして公開されていたタイトル名には命名規則があり、文字列抽出して、タイトルから検索して、間違った書籍情報をひっぱってくるものを選んでよくみると、簡単な間違いをしているものがあった。そして、これは間違いようなさそうなスペル間違いだったりなので、なにか、その前の段階 ? のもとで間違っていて、改正すると整合がとれないので、そのままにしているのではないかと思われた。<br /> なにと比べるのか。<br /> つまり、コピーしてきて、世の中では、そのタイトル名が通用していて、それは、オリジナルのタイトルからすると間違っているが、ファイル名としては正しいというような場合が考えられる。</p> <p>つまり、コピーなのだと思う。自らスキャンして、自らファイル名をつけて、アップロードしてるわけではないという場合、そうなるかなと思う。<br /> そう考えると、そのファイル名で、普通に検索すると、同じ間違ったタイトル名がひっかかるので、そいうことなのだ。</p> <p>だとするならば、なんらかの理由で漫画をスキャンしてアップロードする人間がいて、それを告知する、そしてそのデータを加工するなどする人間がいるというエコサイクルのなかで、ネット上のアップロードファイルから回収されたものを展示しているということになるので、そのセレクトセンスによって傾向がみられるということである。<br /> かなり古い漫画も選ばれていて、そのタイトルを知っているということは特徴的でもある。</p> <p>とはいえ、興味は運営者が誰なのか...というところには全く無く、データがどうなってんのかな? どういう風に作ってるのかな? 全部のデータはいくつあるのか知るにはどういうアプローチでやるのかなというところに重点があり、ではどこから来たデータで、どういうモチベーションで出てきたファイルなんだろうかということに疑問をもちはじめた。</p> <p>なんらかのモチベーションで日本の漫画をスキャニングし、ネットワークにアップロードする行為から始まり、ローカライズ(翻訳)し、漫画の言語を替えて、きれいに文字をのせて公開する一連のことを、<a href="https://crieit.net/posts/bbb085470b763b7e8bcb423f4b235d64#fn:10">スキャンレーション</a>と呼んでいるらしい。</p> <p><a href="https://crieit.net/posts/4daedd75f0cec1b328d4e661d9337bd3">ある一つのサイトについての 現在</a> へつづく</p> <p>せきららなコード(ちょっと古くなってるけど、今の状況に合わせて少しだけ修正したら使える)</p> <p><a target="_blank" rel="nofollow noopener" href="https://kuroca.hatenablog.com/">https://kuroca.hatenablog.com/</a></p> <hr /> <div class="footnotes" role="doc-endnotes"> <hr /> <ol> <li id="fn:100" role="doc-endnote"> <p>2021/11/17 はこんな状況 https://pastebin.com/fTcHK0ti <a href="#fnref:100" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:5" role="doc-endnote"> <p><a target="_blank" rel="nofollow noopener" href="https://zenn.dev/kurocat/articles/eb233dc31bb285">違法と思われるマンガ Thank(仮称)の HTML の構造をよく確認する</a> <a href="#fnref:5" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:4" role="doc-endnote"> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/dauuricus/items/cd7143f84ad03fe17752">本の ISBN 情報なしに、本の題名から書籍データを抽出したいということ。</a> <a href="#fnref:4" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:6" role="doc-endnote"> <p><a target="_blank" rel="nofollow noopener" href="https://www.nikkei.com/article/DGXMZO3631981010102018CR8000/">権利侵害記事、保存も違法 東京地裁が米社に仮処分 2018年10月10日</a> <a href="#fnref:6" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:0" role="doc-endnote"> <p><a target="_blank" rel="nofollow noopener" href="https://www.kantei.go.jp/jp/singi/titeki2/tyousakai/kensho_hyoka_kikaku/index.html">インターネット上の海賊版対策に関する検討会議</a> <a href="#fnref:0" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:1" role="doc-endnote"> <p><a target="_blank" rel="nofollow noopener" href="https://internet.watch.impress.co.jp/docs/special/1128898.html">海賊版サイトをブロッキングするための5つの手法 Internet Society 日本支部 2018年6月22日</a> <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:2" role="doc-endnote"> <p><a target="_blank" rel="nofollow noopener" href="https://xtech.nikkei.com/atcl/nxt/column/18/00001/00469/">カドカワ川上量生社長が語る、サイトブロッキングの必要性 浅川 直輝 日経xTECH/日経コンピュータ</a> <a href="#fnref:2" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:7" role="doc-endnote"> <p><a target="_blank" rel="nofollow noopener" href="https://www.sankei.com/article/20190114-NB33HYAHTZOORB6CZY7BGLQ36M/">海賊版「ブロッキング」法制化断念 政府、広告抑制など総合対策で対応 2019/1/14</a> <a href="#fnref:7" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> </ol> </div> tomato