VSCodeでnotebookセルを実行したときのauto scrollを無効にする

VSCodeではNotebook形式のファイルも扱えて色々便利なのですが、デフォルト設定では各セルを実行したときに勝手にスクロールされてしまい、 直前にどこを実行したのかわからなくなることがあり、無効にする方法を探してみました。 以下の設定を追加で無効にできました。

settings.jsonに以下を追加する。

{
  // これを追加する
  "notebook.scrolling.revealNextCellOnExecute": "none",
}

ソースはこちら github.com

goの文字列比較でハマった

バッファからバイト文字をスライスに移した際に、string関数を使って文字列変換しても、スライスの余りの部分(0初期化した値)も文字変換されてしまうため、それを取り除く必要がある。

buf := make([]byte, 128) // \x00で初期化される

b, err := io.Read(buf) 
if err != nil {
    panic(err)
}

string(b) == "hello" // false

strings.TrimRight(b, "\x00") == "hello" // true

Juliaでbitflyerの価格データをリアルタイムに取得する

少し前にJuliaのHTTP.jlを使ってbitflyerからリアルタイムに価格データを取得するコードを書いたので、備忘録として書き残します。 ※Javascriptなどのサンプルコードは公式ページ(https://bf-lightning-api.readme.io/docs/endpoint-json-rpc)にて提供されています。

HTTP.jlがWebSocketsモジュールを提供しているので、これを使います。

using HTTP
using HTTP.WebSockets
using URIs
using JSON3

# wss://ws.lightstream.bitflyer.com/json-rpc
url = URI(scheme="wss", host="ws.lightstream.bitflyer.com", path="/json-rpc") |> string
# 取得する仮想通貨のコード
product_code = "BTC_JPY"

WebSockets.open(url) do ws
    try
        # JSONRPCのプロトコルに従って、
        # コネクション時にsubscribeをリクエストする
        msg = Dict(
            "version" => "2.0",
            "method" => "subscribe",
            "params" => Dict(
                "channel" => "lightning_ticker_$(product_code)",
            ),
        )
        @debug msg
        send(ws, JSON3.write(msg)) # リクエスト
        
        @info "open"
       
        for msg in ws # レスポンスデータ
            @show msg
        end
    catch e
        error(e)
    finally
        close(ws)
        @info "closed"
    end
end

接続に成功すると、次のようなJSONが逐次送られてきます。

"{\"jsonrpc\":\"2.0\",\"method\":\"channelMessage\",\"params\":{\"channel\":\"lightning_ticker_BTC_JPY\",\"message\":{\"product_code\":\"BTC_JPY\",\"state\":\"RUNNING\",\"timestamp\":\"2023-08-17T07:25:01.3680432Z\",\"tick_id\":59827584,\"best_bid\":4197736.0,\"best_ask\":4198155.0,\"best_bid_size\":0.01,\"best_ask_size\":0.10,\"total_bid_depth\":390.953720690000,\"total_ask_depth\":338.76347988,\"market_bid_size\":0.0,\"market_ask_size\":0.0,\"ltp\":4198155.0,\"volume\":1211.068567520000,\"volume_by_product\":1211.068567520000,\"preopen_end\":null,\"circuit_break_end\":null}}}"

あとは必要に応じ型を用意して、JSON3.readjson文字列ををparseすればOK

Juliaで並列処理

juliaではBase.Threadsモジュールが標準で提供されていて、簡単にマルチスレッド処理を実装することができます。 ここでは、次の2つのマクロを紹介します。

  • @threads
  • @spawn

@threads
for文を渡すと、マルチスレッド化してくれます。

using Base.Threads

n = 1000000
x = randn(n)
y = randn(n)
z = zeros(n)

# マルチスレッドで実行
@threads for i in 1:length(z)
    z[i] = x[i] * y[i]
end

マルチスレッドを利用するには実行時オプション-threads nで設定する必要があります。 実行可能なスレッド数を確認するにはThreads.nthreds()メソッドを呼び出すとスレッド数が返ります。


@spawn
マクロに続く式を実行するタスク*1を生成して、空きスレッドで実行します。

for i in 1:10
    task = @spawn some_work()
end

@spawnを付与した関数や式はtask(Task型)を返します。@spawnで生成したタスクは、非同期で実行されます。 タスク化したものが返り値を返す場合はfetch関数が便利です。fetchを使うと、タスクが完了するまで待機させることができます。 ただ、IOをブロックしてしまうので、ブロックさせたくない場合はBase.@asyncでそれ自体を別タスクで非同期実行する必要があります。


サンプルコード

複数の音声データをダウンロードする処理を、マルチスレッドかつマルチタスクで実行するコードを書いてみました。

# download.jl
using HTTP
using Base.Threads


# 指定のURLからファイルをダウンロードする
function download(url, timeout=180)
    filename = basename(url)
    try
        @info "threadid=$(threadid()): download start $(filename)"
        res = HTTP.get(url, readtimeout=timeout)
        @info "threadid=$(threadid()): download finished $(filename)"
        return (filename=filename, filecontent=res.body)
    catch e
        @error e
        return
    end
end


function main()
    urls = [
        "https://archive.org/download/ThePianoMusicOfMauriceRavel/01PavanePourUneInfanteDfuntePourPianoMr19.mp3",
        "https://archive.org/download/ThePianoMusicOfMauriceRavel/02JeuxDeauPourPianoMr30.mp3",
        "https://archive.org/download/ThePianoMusicOfMauriceRavel/03SonatinePourPianoMr40-Modr.mp3",
        "https://archive.org/download/ThePianoMusicOfMauriceRavel/04MouvementDeMenuet.mp3",
        "https://archive.org/download/ThePianoMusicOfMauriceRavel/05Anim.mp3",
    ]
    tasks = Task[]
    for url in urls
        # 逐次ダウンロードを開始する
        task = @spawn download(url)
        push!(tasks, task)
    end

    @sync for task in tasks
        @async begin
            result = fetch(task)
            result === nothing && ErrorException("download is failed.")
            path = joinpath(dirname(@__FILE__), "tmp", result.filename)
            try
                open(path, "w") do file
                    write(file, result.filecontent)
                end
            catch e
                error(e)
            end
        end
    end
end

main()
  • @__FILE__は実行ファイルパスを取得するマクロ
  • begin ~ endは複数の式を一つのブロック化するときに役立ちます。スコープは作らないので、ローカルスコープが欲しい場合はlet ~ endを使います。
  • @async ~begin~endブロックを非同期実行します。@syncで全体をラップすることで、@asyncで非同期実行された式がすべて完了するまでfor~endのスコープを抜けるのを待つことができます。

上記コードをスレッド数を指定して上記スクリプトを実行するには julia --threads 4 download.jl と実行すれば、マルチスレッド&マルチタスクでファイルダウンロードが実行されます. この例は、非同期実行すれば十分な内容ではあると思いますが、@spawnを使うと簡単に非同期かつマルチスレッドな実行が実現できるということがわかります。

HTTP.jlで簡単なWebAPIを作る

JuliaでWebAPIサーバの作り方を勉強しているので、備忘録も兼ねて書き連ねていきます。

HTTP.jlを使うと簡単にWebサーバを建てることができます。 Genie.jlというJulia製のWebフレームワークがありますが、今回は使用しません。 なお、Genie.jlもHTTP.jlをベースに書かれているそうです。

まずは、インデックスルート"/"に対する簡単なHTTPサーバ構成を示します。

using HTTP

# ルータを用意
const ROUTER = HTTP.Router()

# ハンドラーを定義
function index(req::HTTP.Request)
    @show req
    return HTTP.Response(200, ["Content-Type" => "text/plain"], "hello, world")
end

# ルータにハンドラを登録する
HTTP.register!(ROUTER, "GET", "/", index)


# ローカルアドレス
address = Sockets.localhost
# ポート番号
port = 8080
# HTTPサーバ起動
HTTP.serve(ROUTER, address, port)
  • http://localhost:8080/にアクセスするとhello, worldが返ってきます。
  • ctrl + cで停止できます。

上記の例ではインデックスパス"/"に対してGETメソッドを受けたときのハンドラとしてindex関数を登録しています。 ハンドラ関数には引数HTTP.Request型を取る関数で、戻り値にはHTTP.Response型を返します。

こんな感じで、HTTP.jlを使うと簡単にWebサーバを建てることができます。

続いてこれをリクエストボディにJSONを返すAPIに書き換えていきます。

using JSON3

# indexを書き換える
function index(req::HTTP.Request)
    body = Dict{Symbol,String}(:message => "hello, world")
 # ヘッダーにJSONを返すことを示す
    headers = ["Content-Type" => "application/json"]
    return HTTP.Response(200, headers, JSON3.write(body))
end

# 再登録
HTTP.register!(ROUTER, "GET", "/", index)

# 再起動
HTTP.serve(ROUTER, address, port)

これで、http://localhost:8080/にアクセスすると{"message": "hello, world"}というJSONを返すようになります。 まず、JSON3パッケージを使うとJuliaのオブジェクトをJSONシリアライズすることができます。

# Julia -> JSON文字列
json = JSON3.write(Dict(:message => "message", :data => "data"))

# JSON文字列からJuliaオブジェクト
JSON3.read(json)
println(json.message)
println(json.data)

おまけ

ここで、JSON3.readが返すオブジェクトは汎用的なオブジェクトで、Juliaのユーザ定義型(構造体)にデシリアライズするためにはStructTypes.jlというパッケージを使うと実現できます。※Juliaの構造体に明示的にparseすることで、エラーチェックなどもついでに行うことができます。

using StructTypes

# ユーザ型を定義
struct RequestBody
    name::String
    email::String
end
# StructTypeに型を登録する
StructTypes.StructType(::Type{RequestBody}) = StructTypes.Struct()

body = """{
    "name": "taro",
    "email": "taro@example.com"
}"""
obj = JSON3.read(body, RequestBody)
println(obj) # RequestBody("taro", "taro@example.com")

正規表現のメモ

正規表現の基本

jsベースで記述します。

.: 任意の1文字が1個
+: 直前の文字が1回以上繰り返す
?: 直前の文字が0個または1個
*: 直前の文字が0回以上繰り返す
^: 先頭が次の文字に一致する
$: 末尾が前の文字に一致する

(): グループ化・複数の条件などをひとまとめにすることができる

注意点 - 数字指定などにおいて最後に$を指定しないと先頭が一致する限りtrueとなってしまう

// 数字のチェック

// 半角数字
[0-9]

// 0パディングを許さない整数
/^([1-9][0-9])$/

// 0または3桁以下の整数
/^([1-9][0-9]{0,2}|0)$/

// 任意の実数(整数部一桁時のみ0を許す, 小数部省略可)
/^([1-9]\d*|0)(\.d*)?$/

// 任意の桁数
/^([1-9]\d{0,3}|0)(\.d{0,1})?$/

Java Collections API の備忘録

Javaのコレクション周りの備忘録

目次

Collections API

  • Java8から使える

List

動的配列

// Listの初期化
List<Integer> list = new ArrayList<>(); // Java 7以降

// 値の追加
list.add(1);
list.add(2);

// indexに対応する要素の取得
list.get(0);

list.remove(1); // 1番目の要素を削除

Listの初期化パターン

// 固定リストとして初期化
List<String> list = Arrays.asList("Apple", "Banana", "Orange");

// 可変リストとして初期化
List<String> list = new ArrayList<>(Arrays.asList("Apple", "Banana", "Orange"));

// staticイニシャライザを用いる
List<String> list = new ArrayList<>() {
    {
        add("Apple");
        add("Banana");
    }
}

Map

Map<String, Integer> map = new HashMap<>();
map.put("banana", 100);
map.put("apple", 200);
map.put("orange", 300);


Set<String> set = new HashSet<>();
set.add("fruits");
set.add("rice");
set.add("meet");
set.add("vegetable");

Stream API

特徴

map

List<Integer> list = Arrays.asList(1,2,3,4,5);

List<Integer> expon = list.map(i ->  Math.exp(i)).collect(Collectors.toList());

filter

List<Integer> list = Arrays.asList(1,2,3,4,5);

List<Integer> oddNumbers = list.stream().filter(i -> i % 2 != 0).collect(Collectors.toList());

グループ化して処理する

class Food {
    private String name;
    private String category;
    private Integer price;
    
    public Food(String name, String category, Integer price) {
        this.name = name;
        this.category = category;
        this.price = price;
    }
   
    public String getCategory() {
        return this.category;
    }
    
    public Integer getPrice() {
        return this.price;
    }
}

List<Food> foods = new ArrayList<>();
foods.add(new Food("りんご", "フルーツ", 100));
foods.add(new Food("ばなな", "フルーツ", 50));
foods.add(new Food("白米", "炭水化物", 50));
foods.add(new Food("餅", "炭水化物",  200));
foods.add(new Food("鱈", "魚", 300));
foods.add(new Food("鮭", "魚", 300));
foods.add(new Food("牛肉", "肉", 400));
foods.add(new Food("豚肉", "肉", 300));

// カテゴリごとに集計する
Map<String, Integer> aggregated = foods.stream().collect(
            Collectors.groupingBy(
                Food::getCategory, 
                Collectors.summingInt(Food::getPrice)
            )
        );
aggregated.forEach((category, subtotal) -> {
    System.out.println(category + " => " + subtotal); // フルーツ => 150...
});