20210322のGoに関する記事は4件です。

Goで暗号化する備忘録(AES-128-CBC)

はじめに

Goで暗号化・復号の処理を書いたときに(主にパディングで)手こずったのでメモ。

暗号化

流れ

AES-128-CBCによる暗号化は以下のような流れで行う。

  • 秘密鍵 (128 bit) の用意
  • IV (128 bit) の生成
  • パディング
  • 暗号化

秘密鍵 (128 bit) の用意

省略。

IV (128 bit) の生成

IVは長さ16のランダムな byte 配列になる。この長さはAESにおけるブロックサイズなので、定数 aes.BlockSize が使える。

iv
import (
    "crypto/aes"
    "crypto/rand"
)

func GenerateIV() ([]byte, error) {
    iv := make([]byte, aes.BlockSize)
    if _, err := rand.Read(iv); err != nil {
        return nil, err
    }
    return iv, nil
}

パディング

平文 []byte の長さが16の倍数ではない可能性がある場合、16の倍数にするためにパディングする必要がある(パディングする場合は16の倍数でも16 byte足す必要がある)。
PKCS#7は l bytes のパディングを行うときに l 個の l を追加する方式。

padding
import (
    "bytes"
    "crypto/aes"
)

func Pkcs7Pad(data []byte) []byte {
    length := aes.BlockSize - (len(data) % aes.BlockSize)
    trailing := bytes.Repeat([]byte{byte(length)}, length)
    return append(data, trailing...)
}

暗号化

ここまでで用意した GenerateIV()Pkcs7Pad() を使って実際に暗号化処理を行う。

encrypt
import (
    "crypto/aes"
    "crypto/cipher"
)

func Encrypt(data []byte, key []byte) (iv []byte, encrypted []byte, err error) {
    iv, err = GenerateIV()
    if err != nil {
        return nil, nil, err
    }
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, nil, err
    }
    padded := Pkcs7Pad(data)
    encrypted = make([]byte, len(padded))
    cbcEncrypter := cipher.NewCBCEncrypter(block, iv)
    cbcEncrypter.CryptBlocks(encrypted, padded)
    return iv, encrypted, nil
}

復号

復号するときも BlockMode 構造体を作って CryptBlocks() を呼ぶのは暗号化時と同じ。
パディングの削除は、最後の要素をintキャストした個数分だけ削ればよい。

decrypt
import (
    "crypto/aes"
    "crypto/cipher"
)

func Pkcs7Unpad(data []byte) []byte {
    dataLength := len(data)
    padLength := int(data[dataLength-1])
    return data[:dataLength-padLength]
}

func Decrypt(data []byte, key []byte, iv []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }
    decrypted := make([]byte, len(data))
    cbcDecrypter := cipher.NewCBCDecrypter(block, iv)
    cbcDecrypter.CryptBlocks(decrypted, data)
    return Pkcs7Unpad(decrypted), nil
}

動作確認

opensslコマンドと比較する。

main.go
import (
    "encoding/base64"
    "encoding/hex"
    "fmt"
)

func main() {
    text := "this is test message"
    fmt.Println("Input:", text)
    plain := []byte(text)
    keyString := "645E739A7F9F162725C1533DC2C5E827"
    key, _ := hex.DecodeString(keyString)
    fmt.Println("Key:", keyString)
    iv, encrypted, _ := Encrypt(plain, key)
    fmt.Println("IV:", hex.EncodeToString(iv))
    fmt.Println("Encrypted:", base64.StdEncoding.EncodeToString(encrypted))
    decrypted, _ := Decrypt(encrypted, key, iv)
    fmt.Printf("Decrypted: %s", decrypted)
}
$ go run main.go
Input: this is test message
Key: 645E739A7F9F162725C1533DC2C5E827
IV: 71fbf00383b6e214dc08b8b94183cf30
Encrypted: z41UoV93PIS0OYElzUd7nwA9TO6XxSDlf9N+P4nFuJw=
Decrypted: this is test message

opensslコマンド。echo-n をつけないと改行で結果がずれる。

$ echo -n 'this is test message' | openssl aes-128-cbc -e -base64 -nosalt -K 645E739A7F9F162725C1533DC2C5E827 -iv 71fbf00383b6e214dc08b8b94183cf30
z41UoV93PIS0OYElzUd7nwA9TO6XxSDlf9N+P4nFuJw=

同じ結果が得られた。

参考文献

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TinyGo の開発版のビルド方法と、ビルドせずに開発版バイナリを手に入れる方法

TinyGo に限らず、ですが、リリース版が出た後も開発は続いています。
リリース版では動かなくて、その後の開発版では修正された、というケースも多々あるのが現状です。

このページでは、 TinyGo の開発版バイナリを手に入れるためにの方法を説明します。

結論

タイトル通りですが、以下のいずれかを行うとよいです。

  1. 自分でビルドする
  2. PR 内で作成されたバイナリを使う (できればマージされたものを)

自分でビルドする

以下に手順が書かれています。
LLVM のビルドに時間がかかりますが、それ以外はぼちぼちという所。
windows でもすんなりビルドできますが、現時点で注意点があります (後述) 。
なお、 macos は試していません。

windows 版で LLVM をビルドするときの注意点

chocolatey を使って choco install mingw で gcc をインストールするように書かれていますが、 2021/03/22 時点の最新版である mingw 10.2.0 ではビルドできません。

以下のようにして 8.1.0 をインストールするようにしてください。

choco install mingw -Version 8.1.0
  or
choco install mingw -Version 8.1.0 --allow-downgrade

LLVM ビルド時間

core / thread が少なめだと 1h ~ 2h 程度かかるかと思います。
気長に待ってください。
windows だと time の結果が real しかでないので参考程度に。

# Ryzen 7 3700X + 32GB + windows 10
real    18m15.488s
user    0m0.000s
sys     0m0.000s

# Ryzen 7 PRO 4750GE + 32GB + ubuntu 20.04 (clang-11)
real    16m5.010s
user    240m7.789s
sys     6m37.188s

# Ryzen 7 PRO 4750GE + 32GB + windows 10
real    21m7.242s
user    0m0.000s
sys     0m0.015s

# Ryzen 7 PRO 3700U + 16GB + windows 10
real    66m8.359s
user    0m0.000s
sys     0m0.031s

# Intel Core i5-5200U + ubuntu
(データが残ってないけど 120min 前後だった気がします)

PR 内で作成されたバイナリを使う

自分ではビルドしたくないけど、最新版を使いたいという人はこちら。
とはいえ開発版なので、何かあった時は issue 書くなり twitter で #tinygo つけてつぶやくなりしてみて欲しいところ。

基本的には merge された PR を以下から探して、一番新しい PR の結果を取得するのが良いです。

上記リンクから Merged になっている (紫色のアイコンがついているもの) 最新のものを選びます。

image.png

その後は OS 毎に分岐します。

linux + macos

まずは上記で調べた最後に Merge された PR を開きます。
開いた PR 内の下のほうから、以下の場所にある View details をクリックします。

image.png

ci/circleci: build-linux あるいは ci/circleci: build-macos の右側の Details をクリックします。
Log in with GitHub などでログインします。

image.png

ARTIFACTS をクリックします。

image.png

開発版のバイナリがあります。

image.png

windows

上記で調べた最後に Merge された PR 番号が必要です。
今回は #1699 でした。
以下のページから最後に実行された #1699 の CI ページを探します。

image.png

画面右のほうの 1 published をクリックします。

image.png

画面右のほうの ・・・ 的なボタンから Download artifacts からダウンロードできます。
カーソルを近くに合わせないとボタンが現れないので注意。

image.png

まとめ

TinyGo の開発版のビルド方法と、ビルドせずに開発版バイナリを手に入れる方法は以下になります。

  1. 自分でビルドする
  2. PR 内で作成されたバイナリを使う (できればマージされたものを)

最後に

TinyGo について不具合や分からないところがあれば、できれば報告してほしいです。
あるいは、 #tinygo をつけて tweet してくれればある程度拾います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Goで見るHTTP/2

HTTP/3も出てきて、QUICトランスポートを使っていこうという時期ですが、
そもそもHTTP/2も知らないでHTTP/3に進めるのか?
ということでHTTP/2を理解するためにあれこれやってみた事を記事にしました。

また、HTTP/3はそもそもまだ仕様も策定中(まもなく確定?)でライブラリ等で気楽に使える状況にも無いので、
この先プロジェクトで使っていくことがあるとしたらまずはHTTP/2かなというのも調べてみた理由です。
HTTP/3自体HTTP over QUICとしてHTTP/2の機能からQUICと重複する部分を切り落として効率化したプロトコルなので、
今後HTTP/3の利用が本格化した際にもキャッチアップが楽になるだろうと。

HTTP/2の概要は HTTP/2 の概要 (オライリーのHigh Performance Browser Networkingからの抜粋記事) 等がわかりやすいですが、
幾つか基本的な事を書いておきます。

HTTP/2の概要の概要

基本的な構成要素はHTTP/1.1と同じ

HTTP/2であっても、HTTPを構成する メソッド,ヘッダ,ステータス,ボディ といった要素は変わりません。
というかこのあたりのシンタックスを維持したままHTTP/1.1で生じていた様々な課題を解決しようと生まれたのがHTTP/2です。

なので、普通に使う分には今までと殆ど変わりはありません。(というか意識してないだけで多分使ってます。)
では何が違うのか。

大きな変更点

HTTP/2での主な変更点は下記の通り。

  • メッセージ構造がテキストベースからバイナリベースになった
  • 単一のTCP接続で複数のストリームを持ち、多重に送受信が出来るようになった
  • ストリーム内での優先順位設定や、サーバーサイドプッシュなどの新たな機能の実装
  • ヘッダーが圧縮されるようになった

その他、仕様上は強制では無いものの多くのサーバーがTLS必須になっています。
HTTP/2はそれ以前のHTTP/1系と互換性が無い全く別のプロトコルになっているため、
通信経路上にあるプロキシが自分が解釈出来ないプロトコルとして遮断してしまう事があるようです。
TLSを使えば通信内部は完全に隠蔽されプロキシが通信内容を解釈する余地が挟まれないため、
安定した通信が出来るようになるというわけです。

GoのHTTP/2対応

Goはバージョン1.6から標準でHTTP/2に対応しています。
HTTP/2の実装はgolang.org/x/net/http2に置かれていますが、直接呼び出して使う必要はありません。
殆どの場合は、HTTP/2だからと言って特別な事は何もなく利用できます。

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Hello world!!")
    })

    err := http.ListenAndServeTLS(":3000", "server.crt", "server.key", nil)
    if err != nil {
        log.Fatal(err)
    }
}

*手元で確認する場合は適当にサーバー側の証明書とキーを生成して使ってください。

これだけで、HTTP/2サーバとして機能します。
ブラウザから呼び出してみましょう。

ブラウザのデベロッパーツールで確認するとプロトコルにHTTP/2が使われているのがわかります。
dev-tool.png

ネゴシエーション時にお互いにHTTP/2に対応しているかを確認(NPN/ALPN)しており、
可能であれば何もせずともHTTP/2が使用されています。

バイナリフレーミングレイヤーを見る

普通に使う分にはそれほど意識する事はありませんが、実際ライブラリの中身ではHTTP/2対応で様々な事が行われています。

大きな変更点として挙げていた バイナリベース の部分をもう少し掘り下げて見て行きます。

JSON文字列をPOSTするクライアントと、それを受け画像データをチャンクで返すサーバを作り、
少し細かい動きを見て行きましょう。

使ったコードの置き場はこちら

クライアントの実装

クライアント側は単にメッセージをPOSTして、受信した画像データを保存するだけです。

func main() {

    client := http.Client{
        Transport: &http2.Transport{},
    }

    resp, err := client.Post("https://localhost:3000", "application/json", bytes.NewReader([]byte("{\"message\":\"hello\"}")))
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    file, err := os.Create("image.png")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    io.Copy(file, resp.Body)
    log.Printf("Protocol Version: %s\n", resp.Proto)
}

実行時にGODEBUG=http2debug=2を指定する事で詳細なデバッグログが得られ、
送受信しているフレームの詳細を見ることが出来ます。
以下ではこちらのログと合わせて動きを追って行きます。

サーバーの実装

先ほどのようにListenAndServeTLSを使ってしまうと中の動きが追い辛いため、
Lisnerを取得して

    cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
    if err != nil {
        log.Fatal("load key pair error:", err)
    }

    tlsCfg := &tls.Config{
        Certificates: []tls.Certificate{cert},
        NextProtos:   []string{"h2"},
    }

    l, err := tls.Listen("tcp", ":3000", tlsCfg)

    if err != nil {
        log.Fatal("listen for tls over tcp error:", err)
    }
    defer l.Close()

クライアントからのアクセスを待ち、生TCPコネクションを取得します。

    conn, err := l.Accept()
    if err != nil {
        log.Fatal("accept connection error:", err)
    }

HTTP/2ではプロトコル使用の確認のため、最初にコネクションプリフェイスを送信します。
プリフェイスは"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"と定義されており、
これが期待と異なる場合にはサーバーはコネクションエラーとして扱わなければなりません。
RFC7540 3.5

    const preface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
    b := make([]byte, len(preface))
    if _, err := io.ReadFull(conn, b); err != nil {
        log.Fatal("read from conn error:", err)
    }
    if string(b) != preface {
        log.Fatal("invalid preface:", string(b))
    }

ここから先はHTTP/2の世界なので、バイナリフレームでのやりとりが行われます。
HTTPの基本要素となる メソッド,ヘッダ,ステータス,ボディ も全てフレームに内包されます。

バイナリ化された通信

クライアント側のデバッグログを確認すると、ヘッダーを圧縮(HPACK)しHADERフレームに、
ボディのデータをDATAフレームに書き込んでいます。

http2: Transport creating client conn 0xc00040c000 to [::1]:3000
http2: Framer 0xc0003fa0e0: wrote SETTINGS len=18, settings: ENABLE_PUSH=0, INITIAL_WINDOW_SIZE=4194304, MAX_HEADER_LIST_SIZE=10485760
http2: Framer 0xc0003fa0e0: wrote WINDOW_UPDATE len=4 (conn) incr=1073741824
http2: Transport encoding header ":authority" = "localhost:3000"
http2: Transport encoding header ":method" = "POST"
http2: Transport encoding header ":path" = "/"
http2: Transport encoding header ":scheme" = "https"
http2: Transport encoding header "content-type" = "application/json"
http2: Transport encoding header "content-length" = "19"
http2: Transport encoding header "accept-encoding" = "gzip"
http2: Transport encoding header "user-agent" = "Go-http-client/2.0"
http2: Framer 0xc0003fa0e0: wrote HEADERS flags=END_HEADERS stream=1 len=52
http2: Framer 0xc0003fa0e0: wrote DATA flags=END_STREAM stream=1 len=19 data="{\"message\":\"hello\"}"

GoではFramer構造体が用意されており、フレームデータの読み書きを担ってくれます。

    framer := http2.NewFramer(conn, conn)

    frames, err := readFrames(framer)
    if err != nil {
        log.Fatal("read frames error:", err)
    }

フレームの種類は下記の通り。詳しくはRFCを参照してください。

種類 データ
HEADERS ヘッダー
DATA ボディ
PRIORITY 優先度
RST_STREAM エラーコード
SETTINGS 各種設定値
PUSH_PROMISE プッシュ開始予約
PING PING
GOAWAY コネクション終了通知
WINDOW_UPDATE ウインドウ値の更新
CONTINUATION HEADERS/PUSH_PROMISEの続きのデータ

Framerからフレームデータを読み出して行きます。
ストリームの最終フレームはEND_STREAMフラグを持っているので、そこまで読んでいきます。

func readFrames(framer *http2.Framer) ([]http2.Frame, error) {
    frames := make([]http2.Frame, 0)
    for {
        frame, err := framer.ReadFrame()
        if err != nil {
            return frames, err
        }
        frames = append(frames, frame)
        if frame.Header().Flags.Has(http2.FlagDataEndStream) {
            return frames, nil
        }
    }
}

では早速フレームのデータを見てみましょう。
現在のストリームのStreamIDと受信したJSON文字列を確認します。

    var streamID uint32
    var data []byte
    for _, frame := range frames {
        if headersframe, ok := frame.(*http2.HeadersFrame); ok {
            streamID = headersframe.StreamID
        }
        if headersframe, ok := frame.(*http2.DataFrame); ok {
            data = headersframe.Data()
        }
    }
    fmt.Printf("StreamID: %v, Message: %s\n", streamID, string(data))
StreamID: 1, Message: {"message":"hello"}

ストリームID=1 でPOSTしたJSONメッセージがデータフレームから取得出来ているのがわかります。

サーバーはプリフェイスが問題ない事を確認したら、まずはSETTINGフレームを返します。
これは空でも良いとされているので、今回はとりあえず空で返します。

    framer.WriteRawFrame(http2.FrameSettings, 0, 0, []byte{})
http2: Framer 0xc0003fa0e0: read SETTINGS len=0
http2: Transport received SETTINGS len=0
http2: Framer 0xc0003fa0e0: wrote SETTINGS flags=ACK len=0

クライアントに送信する画像データを開いて、レスポンスヘッダ情報をHPACK圧縮します。

    pic, err := ioutil.ReadFile("image.png")
    if err != nil {
        log.Fatal(err)
    }
    hbuf := bytes.NewBuffer([]byte{})
    henc := hpack.NewEncoder(hbuf)
    henc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"})
    henc.WriteField(hpack.HeaderField{Name: "content-length", Value: strconv.Itoa(len(pic))})
    henc.WriteField(hpack.HeaderField{Name: "content-type", Value: "image/png"})

    fmt.Printf("Encoded Header: %d Byte\n", len(hbuf.Bytes()))

ヘッダデータは16バイトに圧縮されています。

Encoded Header: 16 Byte

圧縮したヘッダーデータをHEADERSフレームに書き込みます。

    err = framer.WriteHeaders(http2.HeadersFrameParam{StreamID: streamID, BlockFragment: hbuf.Bytes(), EndHeaders: true})
    if err != nil {
        log.Fatal("write headers error: ", err)
    }

HTTP/1.1ではヘッダーに"Transfer-Encoding: chunked"を指定して転送エンコードを行っていましたが、
HTTP/2では単純にDATAフレームに分割して送信するだけで効率の良いデータストリーミングが行えます。
最後のDATAフレームにEND_STREAMフラグを立てて送れば、クライアント側はそれを検知して通信を終了します。

    for _, chunk := range chunkBy(pic, 1024) {
        framer.WriteData(streamID, false, chunk)
    }
    framer.WriteData(streamID, true, []byte{})
func chunkBy(items []byte, chunkSize int) [][]byte {
    var chunks [][]byte
    for chunkSize < len(items) {
        items, chunks = items[chunkSize:], append(chunks, items[0:chunkSize:chunkSize])
    }

    return append(chunks, items)
}

クライアント側のデバッグログではヘッダーデータ受信後、続けてチャンク化された画像データを受信しているのが確認できます。
(データ内容はあんまり長ったらしいので省略しています。)
クライアントは逐次受信したデータを処理し、途中何度かWINDOW_UPDATEフレームで空いたバッファサイズを
サーバーに通知してフロー制御を行っています。

http2: Framer 0xc0003fa0e0: read HEADERS flags=END_HEADERS stream=1 len=16
http2: decoded hpack field header field ":status" = "200"
http2: decoded hpack field header field "content-length" = "15229"
http2: decoded hpack field header field "content-type" = "image/png"
http2: Transport received HEADERS flags=END_HEADERS stream=1 len=16
http2: Framer 0xc0003fa0e0: read DATA stream=1 len=1024 data="\x89PNG\..."
http2: Transport received DATA stream=1 len=1024 data="" (768 bytes omitted)
http2: Framer 0xc0003fa0e0: read DATA stream=1 len=1024 data="<TS\xa3k\..."
...
http2: Framer 0xc0003fa0e0: wrote WINDOW_UPDATE stream=1 len=4 incr=5120
...
http2: Framer 0xc0003fa0e0: wrote WINDOW_UPDATE stream=1 len=4 incr=5120
...
http2: Framer 0xc0003fa0e0: read DATA flags=END_STREAM stream=1 len=0 data=""
http2: Transport received DATA flags=END_STREAM stream=1 len=0 data=""
http2: Framer 0xc0003fa0e0: wrote WINDOW_UPDATE stream=1 len=4 incr=4989
Protocol Version: HTTP/2.0

最後に

こうやって一連の流れを追ってみると、HTTP/2もやっている事自体はシンプルかつ、より効率的なプロトコルになっている事がわかります。
今回は触れていませんが、一本のTCPコネクションでストリームを多重化していたり、双方向通信も行えるので、
今後サーバの内側でもHTTP/2を使ってサービス同士の通信を行い効率化されていくんだろうなと思います。

とはいえ今回の様な実装をプロジェクトの中でする機会はおそらくないと思いますが、
HTTP/2がどの様に動作しているのかをある程度把握しておけば、今後gRPCなどを扱う場面で
勘が利くようにもなるでしょうし、実装して動かして見る事で理解の助けにもなるものと思います。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

今週のアルゴリズム:最短経路の計算!(Go2/Crystal/Kotlin/Nim/Juliaでpermutation iterator)

Permutation Iterator next_permutation URL
C#/Go/Python/Ruby/VB まとめ含む http://qiita.com/cielavenir/items/ac96da5e3040c2edb78c
Boo/C#/Nemerle/VB http://qiita.com/cielavenir/items/0e07a024049017f7dea2
Java/Groovy/Scala/Fortran/Pascal http://qiita.com/cielavenir/items/4347fd0c6c69fa60804a
D/Go/JavaScript/Lua/R http://qiita.com/cielavenir/items/9e821d04f574a6623d03
Perl HSP/PHP/Python/Ruby http://qiita.com/cielavenir/items/006a878292c5be209231
Go2/Crystal/Kotlin/Nim/Julia http://qiita.com/cielavenir/items/74f8d547437154c14e72
V/Rust http://qiita.com/cielavenir/items/cec1812999547909a9e4
D/Nemerle/JavaScript/Perl6/PHP C/C++/Crystal/Go2/Julia
Kotlin/Nim/Perl/Perl6
(github参照)

Go2

ジェネリクスにより、sort.Interfaceを使って頑張る必要がなくなった。ありがたい。

というかこの記事の更新をしようと思ったのはジェネリクスのおかげ--;;;

tyama_codeiq608_iter.go2
//usr/bin/env go run $0 $@;exit
package main
import "fmt"

type ordered interface {
    type int, int8, int16, int32, int64,
        uint, uint8, uint16, uint32, uint64, uintptr,
        float32, float64,
        string
}

func reverse[T any](a []T,start int,size int){
    for end:=start+size-1;start<end;start++ {
        z:=a[start]
        a[start]=a[end]
        a[end]=z
        end--
    }
}
func permutation[T ordered](x []T, n int) <- chan []T{
    ch := make(chan []T)
    go func(){
        if 0<=n&&n<=len(x) {
            a:=make([]T,n)
            for i:=0;i<n;i++ {a[i]=x[i]}
            for {
                {
                    b:=make([]T,n)
                    for i:=0;i<n;i++ {b[i]=a[i]}
                    ch <- b
                }
                reverse(a,n,len(a)-n)
                i:=0
                for i=len(a)-2;i>=0;i-- {if a[i]<a[i+1] {break}}
                if i<0 {
                    //reverse(a,0,len(a))
                    break
                }
                k:=i
                for i=len(a)-1;i>=k+1;i-- {if a[k]<a[i] {break}}
                l:=i
                z:=a[k]
                a[k]=a[l]
                a[l]=z
                reverse(a,k+1,len(a)-(k+1))
            }
        }
        close(ch)
    }()
    return ch
}

func main(){
    N:=6
    e0:=make([]int,N*2)
    f0:=make([]int,N*2)
    i:=0
    r:=0
    for i=0;i<N;i++ {e0[N+i]=1;f0[N+i]=1}
    e:=make([]int,N*2+1);
    f:=make([]int,N*2+1);
    for _e:=range permutation[int](e0,len(e0)) {
        for i=0;i<N*2;i++ {e[i+1]=e[i]+_e[i]}
        for _f:=range permutation[int](f0,len(f0)) {
            for i=0;i<N*2;i++ {
                f[i+1]=f[i]+_f[i]
                if e[i]==f[i]&&e[i+1]==f[i+1] {break}
            }
            if i==N*2 {r++}
        }
    }
    fmt.Println(r)
}

Crystal

RubyにはEnumerator.new{|y|}があるけどCrystalにはない。Channel(T)にinclude Iterator(T)させれば良いと思った。なんで標準でincludeされていないんだろう。。

tyama_codeiq608_iter.cr
#!/usr/bin/env crystal
class Array(T)
    def unique_permutation(n=self.size) : Channel(Array(T))
        nxt=Channel(Array(T)).new
        spawn{
            if n<0||self.size<n
                nxt.close
                next
            end
            a=self.sort
            nxt.send a[0,n]
            loop{
                a=a[0,n]+a[n..-1].reverse
                k=(a.size-2).downto(0).find{|i|a[i]<a[i+1]}
                if k.is_a?(Nil)
                    nxt.close
                    break
                end
                l=(a.size-1).downto(k+1).find{|i|a[k]<a[i]}.not_nil!
                a[k],a[l]=a[l],a[k]
                a=a[0,k+1]+a[k+1..-1].reverse
                nxt.send a[0,n]
            }
        }
        nxt
    end
    def partial_sum
        self.reduce([0]){|s,e|s << s.last+e}
    end
end

class Channel(T)
    include Iterator(T)
    def next
        begin
            receive
        rescue ClosedError
            stop
        end
    end
end

N=6
P=([0]*N+[1]*N).unique_permutation.to_a
#各Pは経路を表す。1が縦、0が横を表す。
r=0
P.each{|e0|
    e = e0.partial_sum
    #数学の座標系のように左下をA、右上をBとすると、eの各インデックスiにおいて、x座標がi-e[i]、y座標がe[i]となる。
    P.each{|f0|
        f = f0.partial_sum
        r+=1 if (N*2).times.none?{|i|
            #i番目の座標とi+1番目の座標が等しければ、「道に重複がある」とみなせる。
            e[i]==f[i] && e[i+1]==f[i+1]
        }
    }
}
p r # 100360

Kotlin

fun <T: Comparable<T>>を使う点以外は多段階選抜の答案を見ながらさくっと。
※いつものことながらkotlin-native対応しています

tyama_codeiq608_iter.kt
//usr/bin/env kscript $0 $@;exit
fun <T>reverse(a: Array<T>,start: Int,size: Int){
    val end=start+size-1
    for(i in 0..size/2-1){
        val z=a[start+i]
        a[start+i]=a[end-i]
        a[end-i]=z
    }
}
fun <T: Comparable<T>>unique_permutation(a0: Array<T>,n: Int):Sequence<Array<T>>{
    return sequence seq@{
        if(n<0||a0.size<n)return@seq
        var a=a0.copyOf()
        while(true){
            yield(a.sliceArray(0..n-1))
            reverse(a,n,a.size-n)
            var k = -1
            for(i in (a.size-2) downTo 0 step 1)if(a[i]<a[i+1]){k=i;break}
            if(k<0){
                //reverse(a,0,a.size)
                break
            }
            var l = -1
            for(i in a.size-1 downTo k+1 step 1)if(a[k]<a[i]){l=i;break}
            val z=a[k];a[k]=a[l];a[l]=z
            reverse(a,k+1,a.size-(k+1))
        }
    }
}
fun <T: Comparable<T>>unique_permutation(a: Array<T>):Sequence<Array<T>>{
    return unique_permutation(a,a.size)
}
fun main(args: Array<String>){
    val N=6
    var r=0
    val e0=Array<Int>(N*2){0}
    val f0=Array<Int>(N*2){0}
    for(i in 0..N-1){e0[N+i]=1;f0[N+i]=1}
    val e=Array<Int>(N*2+1){0}
    val f=Array<Int>(N*2+1){0}
    for(e_ in unique_permutation(e0)){
        for(i in 0..N*2-1)e[i+1]=e[i]+e_[i]
        for(f_ in unique_permutation(f0)){
            run outer@{
                for(i in 0..N*2-1){
                    f[i+1]=f[i]+f_[i]
                    if(e[i]==f[i]&&e[i+1]==f[i+1])return@outer
                }
                r++
            }
        }
    }
    println(r)
}

Nim

tyama_codeiq608_iter.nim
#[
/usr/bin/env nim c --nimcache:$HOME/.nimcache --opt:speed --run $0 $@;exit
#]#

import algorithm
import sequtils

proc unique_permutation[T](a0:seq[T],n0:ref int=nil):iterator:seq[T] =
 return iterator:seq[T] =
  var n=len(a0)
  if n0!=nil:
   n=n0[]
  if n<0 or len(a0)<n: return
  var a=a0.sorted
  yield a[0..n-1]
  while true:
   a=concat(a[0..n-1],a[n..len(a)-1].reversed)
   var k = -1
   block blk:
    for i in countdown(len(a)-2,0):
     if a[i]<a[i+1]:
      k=i
      break blk
    #a.reverse
    return
   for l in countdown(len(a)-1,k):
    if a[k]<a[l]:
     let z=a[k]
     a[k]=a[l]
     a[l]=z
     break
   a=concat(a[0..k],a[k+1..len(a)-1].reversed)
   yield a[0..n-1]

let N=6
var e0=concat(repeat(0,N),repeat(1,N))
var f0=concat(repeat(0,N),repeat(1,N))
#各Pは経路を表す。1が縦、0が横を表す。
var r=0
var e=repeat(0,N*2+1)
var f=repeat(0,N*2+1)
for e1 in unique_permutation(e0):
 for i in countup(0,N*2-1): e[i+1]=e[i]+e1[i]
 #数学の座標系のように左下をA、右上をBとすると、eの各インデックスiにおいて、x座標がi-e[i]、y座標がe[i]となる。
 for f1 in unique_permutation(f0):
  for i in countup(0,N*2-1): f[i+1]=f[i]+f1[i]
  block blk:
   for i in countup(0,N*2-1):
    if e[i]==f[i] and e[i+1]==f[i+1]:
     break blk
   r+=1
   #i番目の座標とi+1番目の座標が等しければ、「道に重複がある」とみなせる。
writeLine(stdout,r) # 100360

Julia

tyama_codeiq608_iter.jl
#!/usr/bin/env julia

function reverse(a,start::Int,size::Int)
    local end_=start+size-1
    while start<end_
        local z=a[start]
        a[start]=a[end_]
        a[end_]=z
        end_-=1
        start+=1
    end
end

function next_permutation(a0,n::Int)
    local ch = Channel{typeof(a0)}(8)
    function _producer()
        if n<0||length(a0)<n
            close(ch)
            return
        end
        local a=a0[:]
        while true
            put!(ch,a[1:n])
            reverse(a,n+1,length(a)-n)
            local i=length(a)-1
            while i>0
                if a[i]<a[i+1]
                    break
                end
                i-=1
            end
            if i<=0
                #reverse(a,1,length(a))
                break
            end
            local k=i
            i=length(a)
            while i>=k
                if a[k]<a[i]
                    break
                end
                i-=1
            end
            local l=i
            local z=a[k]
            a[k]=a[l]
            a[l]=z
            reverse(a,k+1,length(a)+1-(k+1))
        end
        close(ch)
    end
    @async _producer()
    return ch
end

Base.@ccallable function julia_main(args::Vector{String})::Cint
    local N=6
    local e0=Array{Int}(undef,N*2)
    local f0=Array{Int}(undef,N*2)
    local i=1
    local r=0
    while i<=N
        e0[N+i]=1
        f0[N+i]=1
        i+=1
    end
    sort!(e0)
    sort!(f0)
    local e=Array{Int}(undef,N*2+1)
    local f=Array{Int}(undef,N*2+1)
    for e_ in next_permutation(e0,length(e0))
        for i in 1:N*2
            e[i+1]=e[i]+e_[i]
        end
        for f_ in next_permutation(f0,length(f0))
            i=1
            while i<=N*2
                f[i+1]=f[i]+f_[i]
                if e[i]==f[i]&&e[i+1]==f[i+1]
                    break
                end
                i+=1
            end
            if i>N*2
                r+=1
            end
        end
    end
    println(r)
    return 0
end

if get(ENV, "COMPILE_STATIC", "false") == "false"
    julia_main(ARGS)
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む