3/19に息子が産まれました

https://blog.hatena.ne.jp/kyuridenamida/kyuridenamida.hatenablog.com/edit?entry=6801883189092325081

↑の記事忘れて書いてしまった 以下、上記の記事よりは丁寧なものの適当な殴り書き

Valorantをやめなければいけない理由、ぼかしてましたがそれです。 子育て頑張っています。でも寝つきが結構いいのでねんね期にこっそりValorantやってます。ランクはできないけど息抜き程度に...

お金の使い方として良かったものランキング

やってよかったこと + 他

  • 出産直後に妻への感謝のお手紙
  • 写真共有は「みてね」が定番らしく、実際かなり便利でいい
  • adenandanaisのガーゼおくるみかなりよかった

あけましておめでとうございます

あけましておめでとうございます。ご無沙汰しております。なんと4~5年ぶりの記事です。2019年にいなくなっていましたがそこからは自身にコロナ耐性なさすぎるせいかほぼ失われた期間という感じで、ハイライトとしては

  • 2021年10月に結婚した
  • 2022年2月にValorant始めた

くらいですね。

さて、以下に去年を振り返りつつ今年の抱負を述べます。

去年の振り返り

去年は自分的にはValorantの年でした。

iwiwi先生に2年前に誘われてドハマりして、実プレイ時間で合計1500時間超 を費やしたであろうValorantのImmortalは達成したし、仲の良い人たちとプレミアに参加して優勝もできたので、FPSについてやり残したことはないです。2023年9月の分布によるとImmortalは大体上位0.7%らしいので、比較するのもあれですが多分AtCoderでいう黄色上位~橙くらいらしいです。俺Valorantのほうが得意らしい。

この事実と表裏一体な後悔を赤裸々に晒すと、労働中もValorantのことが頭の片隅にあるせいで、相対的にソフトウェア開発への熱意と感覚を失ってしまったことです。この道一本で食ってきた人間なのでこれはいけない。 プレミアで一緒に遊んでいる人にもValorantハマりながらも博士号取ったり、労働で成功していたり、子育てやりつつ、それの全部を達成している人間がいるので、それを考えると俺には社不の血が流れていることを実感します。どれか一つでも達成できるのか?俺は。

年の瀬にXで見た"「なんの中毒になるか」がADHDの人生のすべてだと思う。"というツイートがぶっささり未だにそのことが頭の中で反芻しています。

(ところで今さらですが、恐らく競プロ界隈で知り合った皆さんは僕が突然FPSにハマったことに驚いているかもしれませんが、実は黒歴史ブログを参照すると中学生時代は狂ったようにFPSをやっていた形跡があり、自分的には驚きはないです。Valorantを通じて昔の友人と再び出会えたり、そもそも当時何かと接点があったYukishiroさんや岸さんを配信で毎回のようにお見掛けしてすごく嬉しかったです。多分あっちは覚えてないと思うんですけど、なんだか当時趣味としてFPSをやっていた人たちがそれを生業にしているという事実にかなり興奮していました。本当はオフライン大会の会場とかでファンボとしていつかご挨拶できたらいいなと思ってたんですけど、微妙に機会がなかったですね。)

今年の抱負

今年は、全部はまだ言えないですが今までとは全く新しい経験をする年になると思います。

Valorantは控え目というか恐らく近々(UPD 1/2)完全引退になると思います。ちょっと前まで3月限界ギリギリまでやろうとしてましたけど、30歳になる年であることを考えると良い引き際かなと思ってます。家庭もあるし、いろいろ衰えてくるし...てかまあ記憶とかに関しては既に衰えてて定点とかは全くだめです。手元にエージェント毎のマニュアルでも用意したら別かもしれない。競技シーンとかは追うかもしれない。

プレイヤーとして帰ってくることは...無いんじゃないかなと思いますが、あるとしても数年後ですね。ただFPSへの情熱自体はあるので、Valorantの便利ツール開発(チートではない, ビデオ解析とか)のほうをやっているかもしれない。

Valorantの代わりに、3月頃から家庭が忙しくなる予定なのでそちらに専念します。

家庭に専念しつつ、注力したい自己研鑽カテゴリとしては2点あって、ソフトウェア開発と英語です。まあ仕事にダイレクトに関連しています。 ソフトウェア開発への意欲はValorantに完全に奪われた2年だったので、2年分キャッチアップしないといけないです。 英語に関しては、デザインに関する議論の場で英語でのコミュニケーション能力が相変わらず労働の足枷になっている実感があるので改善していきたいです。テクニカルライティングや語彙も併せて強化していきます。

抱負に対する評価指標

ということで様々な目標に対する今年の自身の達成評価指標をまとめて書いておきます。年の瀬にまた見に来ます。

  • 家庭をうまくやる (ここは諸事情でぼんやり 4/27UPD これです→ 3/19に息子が産まれました - kyuridenamidaのブログ

  • TOEIC 950 以上取る

  • テクニカルライティングに関する本を1冊読む
  • AtCoderアルゴコンに月1回以上参加する (unratedでもよい)
  • AHCに3回以上参加する
  • 個人開発したソフトウェアを他人に使ってもらう

ISUCON 9 参加記

時間はすげえ適当、本当に適当

@akenshoさんと@li_sakuさんとチームluminousで出た。 このメンバーで出るのは一昨年に続き二回目。

最終スコアは11.7kくらい通過できたらいいな〜。

使用した言語

Go

解析ツールの準備

@akenshoさんが全てやった。

  • alp: 遅い/頻繁なhttpリクエストを把握するため
  • myprofiler: 頻繁なMySQLクエリを把握するため
  • pprof: GOアプリのボトルネックを把握するため
  • mysqlのslowquery報告機能: 遅いクエリを把握するため

本番前

一ヶ月前

そろそろ練習しないとな〜と思ってISUCONをやっては忘れるGoに触る

一週間前

isukariの練習しなきゃとかいうツイートをした(ニアピン賞) https://twitter.com/kyuridenamida/status/1168077007088803841

本当にisucariで笑ってしまったが、さすがにこれは容易に予想が出来たのでセーフ。

本番

開始前

09:30にAtCoder社に行って準備をする(早起き!) 10分遅延してくれてなんとか朝ごはん食べる暇ができた。ありがとう。

00:00 開始

とりあえずルールを把握したり、gitリポジトリを作ったり、色々。

isuconというローカルユーザーを作ってもらっていたのを無視してrootでgitを操作していたらチームメンバーがgitをisuconユーザーから操作できなくなっていたらしく、罵倒される。

01:00 - 様々なツールの整備が完了

何もしていないGOの参考実装に対するベンチを走らせる。2210点くらい。 pprofとかも走らせる。

pprofがpdfとかで表示されてCool! ありがとうsakuさん

01:30 最初の気づき bcryptは遅い

pprofの結果bcryptが遅すぎることに気づく。暗号化ハッシュ化-、やめるか!w(←この時点ではこれが違反になるというルールを把握していない)

「初期ユーザーの数は3!wハッシュ強度の変更は容易!w」みたいな議論をするが明らかにMySQLのテーブルを見ると嘘であることが分かる。4000コある。だめ。 初期のデータベースにはハッシュしかないのでそれから平文を復元することは不可能だが、以下のようなマイグレーションプランを考える。

  • 初期4000ユーザーに対するリクエストで来るパスワードを盗聴して、ログインに成功したらそれを別のテーブルpassword_listに保存する。
  • 平文パスワードがpassword_listテーブルに存在したらそれを使って、そうでなかったらbcryptで今まで通り比較。
  • 新しく追加されるユーザーについては無視する。
  • password_listは消さない

これでベンチを走らせるたびにbcryptの負荷が下がる最高の機構が完成。僕は高速化のためにこんなことしてるWebサービスが実在したら心底軽蔑するし絶対に使いたくないんだけど「これはISUCON、OK」という気持ちを強く持った。

とりあえず平文保存実装を僕がやって、akenshoさんがansibleによる様々なもの(Goコンパイラとか)の整備にとりかかる。

ところで作業中にinitial.sqlをpushしようとして無理やりgit addした結果、initial.sql神隠しにあい、また罵倒される。どうして...

03:00 平文保存のコードが一応完成

ベンチ走らせたらバグる。チームメイトに罵られる。Ω\ζ°)チーン sqlxわからん。データバインディングに失敗しているようにも見えるが、なぜ...?

03:30 バグが取れる

バグの原因わかんね〜って一生悩んでたら本当にあった怖い話としてGOは先頭が大文字か小文字でvisibilityが変わるという話をakenshoさんに教えてもらう。

type PasswordList struct {
    ID             int64     `db:"id"`
    userID         int64     `db:"user_id"`
    plainPassword  string    `db:"plain_password"`

type PasswordListItem struct {
    ID            int64  `db:"id"`
    UserID        int64  `db:"user_id"`
    PlainPassword string `db:"plain_password"`
}

おわかりいただけただろうか。

この違いがsqlxのバインディングに影響していて、頭を捻っていた。 僕はこれに関して「Go言語さんサイドの仕様決定プロセスに問題がある」という脳内で責任転嫁済みだったのだが、現実ではチームメイトに罵られれる。 いやGoやってなくてすみません。多分一生自力では気づけなかった気がする。

03:30 ベンチ走らせまくり

平文パスワードを集めるためにひたすらベンチを走らせる。MySQLコネクションがさっきの変更によりリークしてたりして冷える。ごめん。直すから許して。

指定した負荷度が低かったため50コくらいずつしか増えない。 先が遠いけど終了までには4000コ溜まってるだろうという気持ちになる。

途中で負荷を上げたらよいことにチームメイトが気づき負荷を0→2に上げる。一回にいっぱい集まって最高。

もっかいpprofを走らせる

04:00 第2の気づき: getNewCategoryListが遅い

bcryptくんの存在感が薄くなってるなあ。次は「getNewCategoryList、遅いぞ!w」とpprofに罵られる。

コードを読むと重そうなクエリ + N+1問題が起きている。 明らかに怪しそうなN+1問題からやっつけようという気持ちになる。N+1問題は二つの関数によって引き起こされていた。

  • getCategoryByID
  • getSimpleUserByID

とりあえず完全にメモリにのっけてよさそうなcategory周りからキャッシュしていく。

04:00 第3の気づき: MySQLのチューニングが効きそう

コネクションリークの対処とか考えてるときにチームメイトが気づいた気がする。 akenshoさんが色々設定する。 チームメンバーが何か良いことをする度にありがとうございますって連呼してたら「お前の目標は感謝なのか?」と罵られる。

04:20 大量に平文パスワードをかきあつめおわる + MySQLのチューニングをしたりする

チームメイトがめっちゃMySQLのメモリとかを弄っている。いい感じ。 7000点くらい、スコアがよくなる。やったぜ。3位。

04:30 getCategoryByIDをバグらせる

またバグらせてベンチ死んでチームメイトに罵られる。ごめん許して。

他にも、PRがあったほうがCR出来て便利だろ!って罵られる。許して。

とりあえずケツを叩かれながらさっさと直して動くようにした。

04:35 getCategoryByIDが直る

これだけだとN+1問題の完全解消にならないのでベンチにはさほど影響がなかったはず。

04:40 getSimpleUserByIDのオンメモリ化に取り組む

getSimpleUserは基本的にはオンメモリにできて、唯一出品数だけはリアルタイムに変わりそう。これも頑張ってオンメモリにする。 concurrent mapを使ったことがなかったがとりあえず https://github.com/orcaman/concurrent-map これを使わせてもらった。初めて使うのでドキドキした。 よく考えるとincrementはアトミックにやらないということに実装してるときに気づき、地獄に落ちなかった自分に感動。 https://github.com/uber-go/atomic を導入する。どっちも使ったことがないライブラリでドキドキワクワク。例に漏れずGoわからなさすぎて苦労する。

05:00 第4の気づき: getNewCategoryList内のクエリは非効率的

僕がアプリを弄っている間にsakuさんとakenshoさんが発見してくれた。

saku「explainしたらサイアクな感じ。とりあえず思考停止でindex貼っていい?」

まあやってみて早くなればOKなのでとりあえず思考停止indexを貼って頂く。

05:00 第5の気づき: 平文保存はルール違反

_sakuが気づいた。マニュアルにも書いていたし、運営からも「平文保存はやめろ」とだけお達しが来る。わかるな〜〜〜〜 そもそも暗号化ハッシュ化関数が満たすべき性質についての言及が一切無いので、困ってしまった。 とりあえずこのままだと失格になってしまうし、暗号化しないといけないのだが、適当に性質を仮定して出戻りが起きたらサイアクなので、慎重にやることにした。

sakuにサポートチャットで「@運営 『パスワードを平文で保存する』という意味は、ユーザが入力したパスワードをそっくりそのまま保存する、という意味と同値ですか?」と聞いてもらう。

しかし長らく返事がない、他の参加者の別の質問には反応している、困ったなあ...

06:00 インデックスを貼ったりしおわる

改善する。負荷も4にしてみる。 たしかスコアが上がった? 9000点くらいになった?

06:00 再度平文保存に関する質問をする。

ここまで返事がないと見逃しているのかと思い、もう一度sakuさんに質問してもらう。

saku「@運営 『パスワードを平文で保存する』という意味は、ユーザが入力したパスワードをそっくりそのまま保存する、という意味と同値ですか?」

再度返事がない。理由はわからないが返事の保留は意図的なものだと判断して諦めることに。

06:20 getSimpleUserByIDのオンメモリ化が完了

全員でデバッグしてちゃんと動いて、感動... 11500点くらいが出る。

06:30 独自暗号化関数の実装

質問の答えが相変わらず返ってこないので、そろそろ平文で保存しているものを暗号として保存することにする。(追記:本来やるべきはハッシュ化でしたが平文じゃなくなればいいので暗号化でも良いことにする) ISUCONに適した独自の暗号化関数を実装することにした。 どういうものかというと

def create_encrypted_pasword(original_password string) string {
    return original_password + "#";
}

これを用いたログインは平文比較とほぼ同等の速度でありかつ強度も用途に適しているという判断になった。

func insertPlainPassword(userID int64, password string) error {
    _, err := dbx.Exec(
        "INSERT INTO `password_list` (`user_id`, `plain_password`) VALUES (?, ?)",
        userID,
        password + "#", // 暗号化
    )
    return err
plainPass, err := getPlainPassword(u.ID) //実ははプレインではありません。

if plainPass != password + "#" { // 復号化
    err = bcrypt.ErrMismatchedHashAndPassword
} else {
    err = nil
}

誤解を産む命名を含むロジックではあるが、コメントにもあるように、明らかにプレインではない。

このあと、平文は破棄して、新暗号でデータベースに保存することにする。DBのクエリを叩くのが怖かったので完全に収集し直す戦略。 収集しはじめの頃点数が2000点台まで落ちてびっくりした。bcryptやっぱりボトルネックだったんだね。

07:00 凍結・再起動試験等に着手しはじめる

大体4000コの新暗号による暗号化済みパスワードの収集が終わる。点数が元に戻ってくる。 ISUCON最後のほうは色々試したりして事故ったら元も子もないので、危ない橋を渡らないようにしようという雰囲気が漂う。 とりあえず再起動試験をやろうという話になる。個人的にオンメモリの出品数カウントとかめちゃくちゃこわいし大賛成だった。

再起動試験をするとmysqlが先に起動していないという理由でisucari.golang.serviceが死んでいることが分かる。 これは出品カウント以前の問題だったし再起動試験して良かった〜。

07:30 再起動試験に成功

再起動試験に成功し、出品カウントもうまくいってる。やった〜

07:50 最後のベンチ走行

再起動した直後のフレッシュな状態でベンチマークを走らせる。最高得点11700くらいが出る。 やった〜もうおしまいおしまい。放置。最終的にインスタンス数は1でした。

片付け。

08:00 終了

楽しかった〜!!スコアリング方式のおかげか超ベンチが安定していたし、快適だった。運営に感謝

終了。AtCoder社のホワイトボードにsnukeのイラストを描いて去る。 あとは祈るだけ。これでパスワードの平文保存に関して質問はしたし(しかも二回も!)、厳密な定義が存在してそれによって失格になったら普通に悲しいけど運営のフェア精神を全面的に信頼しているため頼む頼む

結果発表 通過してました!

ISUCON9 2日目結果と本選出場者決定のお知らせ : ISUCON公式Blog

通過してました!本戦がんばります!

余談: しょぼい暗号化(とかハッシュ化)を使うことの是非に対する本音

運営がbcryptに改善の余地を設けたのが意図的なのかについて考えていたんですが、この記事を読む限りISUCON8の本戦ではbcryptの使用を明記されていたように見えます。

http://isucon.net/archives/52598691.html

パスワードのbcrypt
パスワードをbcryptでハッシュ化していましたが、これがアプリケーションがCPUをつかうほぼ唯一の箇所でした。

また「パスワードはbcryptでハッシュ化しなければならない」と仕様に明記されていたため対策できないような錯覚をさせていましたが、コスト(ストレッチング回数)に関しては何も言及していないためコストを最小値にすると大幅にCPUが改善するものでした。

今回そのような指定はありませんでした。考えられる可能性は

  1. 単純に今回も同じことを仕様に書く予定だったが書き忘れた
  2. 意図的にbcryptを他の方法に置き換えることを想定した

どちらにしろ 1.に関しては早い段階で訂正のチャンスがあったのにしなかったということは2.の線が濃厚で、それを強く信じています。 返事が無かったことに関する理由は謎です。

問題文から入力コードを自動生成するツール(atcoder-tools)の紹介

この記事はAtCoder関連サービス Advent Calendar 2018 17日目の記事です. adventar.org

大変遅くなりました。@kyuridenamidaです。よろしくお願いします。

atcoder-toolsというものを開発しています。 atcoder-toolsの主要な機能を一言で言えば問題文に基づいた入力コードの自動生成機能です。

実際にはそれだけではなくサンプル入出力のダウンロード機能・サンプルテスト機能もあるのでAtCoderのコンテスト用ひな型作成ツールととらえてもらったほうが良いかもしれません。

github.com

歴史

2年前

エイプリルフールに初期バージョンをリリースしました。 しかし当時はソフトウェア開発初心者にもほどがあったのでコードはひどいしテストもないしカスタマイズも効かないし、なんなら簡単なインストール方法もありませんでした。 それでも使ってくれていた人が少しいたのが嬉しかったですね。

去年

結局自分としては現状に満足していた&書いたコードが読めずにメンテする気がなくなってたというのが理由で半ば放置気味になっていました。 @asi1024がCIツール(Travis CI)を導入してくれたりしたけど、僕がガバガバだったのであんまり機能してませんでした。

今年

10月くらいにそろそろメンテしないとやばいなと思い始めました。かなりだらだら型アノテーションを追加したり、コード構造を変えたりしていました。

そうこうしてるうちに同僚の@blue_jamさんとかがコントリビュートしてくれて、こんなひどいクオリティのコードを読んでPR出してもらうのかなり申し訳ないなと思ったのでリファクタを終わらせようと思いました。

結局自分に発破をかけるためにアドベントカレンダーに登録したはいいものの遅刻してしまい、12/25にようやくリファクタが終わって、12/26にPyPIデビューしpip install atcoder-toolsできるようになりました。そして今アドベントカレンダーを書いています。

使い方

asciicast

インストール

pip intall atcoder-tools

サンプルダウンロード・入力部分つきコードを生成する

atcoder-tools gen [contest_id]でできます。 例えばatcoder-tools gen arc001でARC 001のコンテストデータがダウンロードできます。

デフォルトの格納先ルートは{$HOME}/atcoder-workspace/になっています。そこの下に{コンテスト名}/{問題番号}/で保存します。これは--workspace引数で変更できます。

以下に実行時引数の詳細を書いておきます。

--without-login 練習時に自分のアカウントでログインする必要がない時に指定する。

--lang cpp or java。自動生成に使うコードの言語設定。

--template 入力フォーマットを推定できたときに使うテンプレートコードのパス。デフォルトは適当にリポジトリに存在する僕が書いたやつが使われます。

--replacement これは入力フォーマットを推定できなかったときに代用として生成されるコードのパス。

--parallel 問題ダウンロードの並列化。手元のmacでは動かなかった。

--save-no-session-cache クッキーをローカルに保存しないようにする

カスタムテンプレートを使う

--template--replacementにテンプレートを指定できます。

ジェネレータ部分のpythonコードをカスタムするとかは今のところできません。PR送ってほしいです。 テンプレート内では以下のPlaceholderが使えます。コードの例 - ${input_part} e.g. 生成された入力コードを格納するところです。 例↓

int N; 
cin >> N; 
vector<int> A(N-1+1);
for(int i = 0 ; i <= N-1 ; i++) cin >> A[i]; 
string B; cin >> B;
  • ${actual_arguments} 型なし実引数列です。例↓
N, A, B
  • ${formal_arguments} 型つきパラメータ列です。例↓
int N, vector<int> A, string B

サンプルの一括テスト

atcoder-tools testで今いるディレクトリ内の実行ファイルとサンプル入出力に対してのテスト結果を出力できます。 引数はないです。実行ファイルが複数あった場合は警告しつつどれか一つを選んで実行します。

現状の入力フォーマット解析の仕組み

備忘録もかねて書いておきます。 基本的な方針としては、頑張って入力フォーマットを型なしの中間形式に変換した上でサンプルを流し込んで型推論をします。

パターン

解析する入力形式は次に述べる三種類の入力パターンの連接(列)として表現可能だと仮定してパターン列を推論することを目論見ます。 表現できない場合は残念ですが推論失敗です。今後のパターン追加に期待です。

単一パターン

一度しか現れないパターンです。NとかMとかです。

変数名を一つだけ持ちます。

タプル列パターン

タプル的な入力の繰り返しに対する入力パターンです。 入力の順に並んだ変数名のリストと一つのインデックス(*)を持ちます。

(*) インデックス ここでのインデックスは添え字としてありうる最小の値と最大の値を持つデータです。

たとえば、

a_1 b_1
a_2 b_2
:
a_n b_n

を変数a,bに対しての入力がこの順に添え字が[1,n]の範囲で繰り返されていると表現するパターンです。

a_1 a_2 ... a_n

とか

a_1
a_2
:
a_n

とかはタプル列で変数の数が1である特殊ケースとして扱っています。

二次元パターン

二次元配列に対する入力パターンです。

変数名と行と列に対する二つのインデックスを持ちます。

c(1,1) c(1,2) ... c(1, W)
:
c(H,1) c(H, 2) ... c(H, W)

cが [1,H]×[1,W]の範囲で繰り返されているふうに表現するパターンです。

パターン列の探索

全探索です。でもインデックス部分には必ずそれまでに入力した変数もしくは定数しか現れないという妥当な仮定をおくと早く終わります。 とりあえず擬陽性を許す方針でありえそうなパターン列を列挙して、そのあとサンプルを流し込んで整合性を確かめる感じですね。 それでほとんど一意に定まるか解なしで終わります。

精度

入力とサンプル入出力が取得できた問題かつABC, ARC, AGCの問題に絞ってプログラムが推論に成功したと主張した判断したケースは93.8%(976/1041)でした。 一つ一つの推論結果が正しいかどうかについては手間を惜しんで精査していません。なので本当は正しくないけど正しいと判断されているケースもあって、実は93.8%は精度の上界であるという主張しかできません。とはいえ、定性的にはおおむね正しそうに見えます。

不正解なのに正解と判断されてしまう例は型に関するものが特に多くて、例えば整数っぽい見た目の文字列(ビット列とか)が与えられる問題でそれを単なる整数として解釈しているケースなどがありますが、それをなんとかするのは今後の課題です。

各問題に対するその推論結果のテスト結果が以下のURLから参照できます。

atcoder-tools/answer.txt at master · kyuridenamida/atcoder-tools · GitHub

今後やりたいこと

中間形式の仕様策定

現状コードを生成するに当たって

  1. (入力フォーマット, サンプル群)→中間形式
  2. 中間形式→コード生成

というふうに処理を分離しています。 この分離そのものはよいと思うんですが、肝心の中間形式の仕様をもう少しまともに策定しておくと他の人もそれに則ることができて便利なのかなって思ってます。区切り文字情報とか。

  • 問題文から中間形式への変換を頑張る人は、何かしらの形で各コンテストサイトの問題からその中間形式に落とし込むロジックを作るのに集中すればよい。
  • コード生成をしたい人は中間形式からのコード変換だけに集中すればよい。

提出機能の復活

ペナルティがモリモリ増えるけどそれはそれとして自動提出は便利なので復活させたい。

online-judge-tools へのコード自動生成機能マージ

online-judge-toolsは@kimiyukiさんが作っている問題URLを指定するとよしなにサンプルとかが入ったディレクトリを作ってくれるツールです。

きちんとメンテされているしAtCoder以外のサービスにも対応しているので、データのダウンロード部分とか自動テスト部分は正直atcoder-tools側で独立に開発する必要はないのかなって思ったりしています。

ただ、submit機能とかは積極的に廃止予定らしいのでそのへんの思想の違いを理由に機能としてはatcoder-toolsにも残り続けるかもしれない。

リポジトリを英語にする

英語でREADME.md書くの意味ねーなって思って全消ししたけど、やっぱりAtCoderは海外ユーザー増えているし海外ユーザーにも使ってもらいたいから英語にしたい。

個人的にやってみたいこと

他のコンテストサイトにおける入力コードの自動生成

機械学習とかパターンマッチングを使ってCodeforcesの入力自動生成にチャレンジしてみたいというのが今の野望です。

AGC 029

最近言語能力がかなり低下している自覚があるので、物を書いて感覚を取り戻そうと思います。

A, B, Cを解いて304位でした。Bでハマりまくった上にDを誤読してしまい、めちゃくちゃでした。レーティングは下がるし悲しい。二回連続でAGCひどい順位取ってる気がする。

A

問題 白黒(B/W)のオセロが一直線にN(≦2*105) コ並んでいる。B->Wの順で隣接するオセロの色を反転させることを繰り返すとき、最大で何回操作ができるか。

解法 転倒数。

最大というのは実は無視できる。つまり、どんな順番で反転させても回数は変わらない。その回数はi < j かつ (Si, Sj) = ('B, 'W') であるような組の数に等しくて、これは転倒数そのもの。 転倒数の数え上げは、一般のO(N log N)のものを使っても良いがそれは明らかに牛刀なので、値が2種類なことを利用してO(N)で解くと良い。 具体的には、転倒数は普通に左から配列を舐めて、'W'が現れたときにそれまでに現れた'B'の数を足して上げる。

B

問題: N(≦2*105)コの要素が与えられる。各要素は1以上109以下の整数である。要素X,Yの和が2べきのときだけその要素同士のペアを作れる。同じ要素が複数のペアに使われないようにペアを作るとき、作れるペアの数はいくつか。

解法: 貪欲。

本番では重複する数を一つにまとめてから、ペアが作れるかどうかでグラフを作るとそれは森になることを利用して解いた。 基本的には、葉の要素から貪欲に親とマッチさせていくことで最大のペア数が達成できる。しかし、2べき要素(1,2,4,8,...)については、同じ数同士でマッチさせるという追加の選択肢があるので困る。 でも、2べき要素を根とした木を考えると、その選択肢について最後に考えても良いことが分かる。従って、この木で普通に葉から貪欲して、最後にその2べき要素が2つ以上余ったらそれらをペアにする。 二つの異なる2べき要素は1つの木に含まれないことを確認したいが、これは2で割る回数が一致していないとペアに出来ないことから問題ない。

実際はグラフとかを意識しなくて良い方法があり、ソートして最大要素から貪欲にペアを作っていけば良かったらしい...

作れるペアを列挙するパートと、それを実際に使うか使わないか決めるパートを分けて考えることにする。 X≦Yとなるペア(X,Y)だけを考えても一般性を損なわないので、それを全て列挙することを考える。実は、各数に対してペアとして考える数は1つ(2べきを達成する最小の正整数)で、それで全てのペアを列挙できる。

なんで1つを考慮するだけでいいかを説明する。整数Yとそれとペアになるべき整数 X = 2k - Y (Y < 2k ≦ 2Y)を考えるとすると、選び方からX≦Yなので(X,Y)はペアになりうる。 一方、2番目に小さいX' = 2k+1 - Yもペア候補ではあるが、X' > Yとなってしまうため、考えなくて良い。

Yが大きい順にペア(X,Y)を使うか使わないか決定していくと最大数が求まる。 このとき、ペア(X,Y) が使えるなら必ず使って損はない。というのは以下の理由に基づく。

  • 使わなかったらそのYは未来永劫使われないので損をする可能性がある。
  • 使うとして、Xを含む別のペア(X,Z)を作るのを妨げてしまう可能性はあるが、それによる損失は高々1つの別のペア(X,Z)が作れなくなるだけなので、少なくとも損はしない。

C

問題: 文字列{Si}がN(≦2*105)コある。S1 < S2 < ... < Snという関係が成り立っている。i番目の文字列の長さAiのみを知っているとき、文字列に使われる文字の種類数として最小としてあり得るのは何か。

解法: 二分探索 + シミュレーション

文字の種類数の最小は、二分探索で見つけます。 なので、二分探索の中では、種類数kで、S1, ..., Snが存在するかどうかを判定すれば良い。

存在するかどうかを判定するのはシミュレーションを用いれば良い。 つまりできるだけ辞書順で小さい文字列を貪欲に割り当てて行けば良い。

これは以下のアルゴリズムで行える。n種類の文字からなるアルファベットを{0, 1, 2, ..., n-1}と表すことにする。 明らかに、S_1は0をA_1コ繰り返したものを考えれば良い。 i ≧ 2 に対してどうするか考える。

  • A{i-1} < A_i のとき、S_iをS{i-1}の末尾に0を必要数足したものにすれば良い。
  • A_{i-1} ≧ A_i のとき、S_iから必要数末尾を削った後、末尾の文字を一つアルファベット順で大きいものにしてあげればよい。もしその文字がアルファベット順で最大なときは、繰り上がりが発生するのでそれをシミュレーションする。このシミュレーションは連長圧縮をしておくことで文字種類数が2以上のときは愚直にやっても高速に動作する。なぜならO(log N)桁くらいしか繰り上がりの影響を受けないため。

もし繰り上がりに失敗して長さA_iが達成できなくなったらシミュレーションは失敗、そうでなければ成功となる。

文字種類数が1のケースだが、これはA1 < A2 < ... < Anのときかつそのときに限り達成できる。

D

誤読

E

読んでない

F

読んでない

感想

書いて思ったんですけどやっぱり言語だめですね・・・

ISUCON2017参加記

akenshoさんとli_sakuと参加、最終スコア20万3000点くらい。最高得点は21万5000点くらい。(よく覚えて)ないです。 1日目5位?で最終順位20位でした。2日目ありえん魔窟みが深い。18位まで通過なのでギリギリとはいえ予選落ちは予選落ちです。

1年前:

  • よすぽとぽてちと参加し、gitリポジトリを破壊して正の点数が取れず終了「なんやこのコンテストは...」

3ヶ月前:

  • けんしょーさん「GO言語勉強しといて」ぼく「この一年MySQLとかちょっと触ったしいけるやろw GO言語もアメリカ生活どうせ暇あるやろwやったろw」

3ヶ月間:

  • アメリカ観光サイコー

帰国後:

  • GO全然わからん(絶望)

1週間前:

  • みんなでPixiv ISUCONで初練習する。めたんさんのpixiv isucon攻略記を見ながらakenshoさんがちょっとずつ便利なもの準備してるのにli_sakuとだらだらする。結局GOがまともに書けず。最悪です。

数日前:

  • コドフェスと本戦日程被っていることが発覚。しゃあないけど予選は絶対楽しいし頑張りたいし、通ったら考えよ。

本番開始前:

  • GOの変数宣言の仕方さえ分からないけど読めるし雰囲気は分かる。大丈夫。

本番途中:

時系列は適当。実際チームでやったことたくさん他にあるけど僕が携わった部分ばっか頭に残ってるため許して。

  • ぼく&さく「けんしょーさんサーバー設定とチューニングよろです」 けんしょーさん「304とか返せる秘伝のたれ貼りました」
  • ぼく「なんか公開鍵周りがバグっててgitにpushできない(大騒ぎ)」さく「ちゃんとエラーメッセージを見て判断して」ぼく「すみません」
  • とりあえず静的配信のwebapp書き換えよろです←さくさんに書いてもらう
  • チーム「配信する画像のミラーリングとか辛いしまずはDB+(Webapp+nginx)の2台構成にしちゃいましょう」
  • 暇やしindex貼ろう
  • は?なんだこのsleepは!?消そ!
  • 既存のDBに入ってる静的ファイルデータを用意するコードがGOで書けない...せや! curl しちゃおw
  • やったー静的配信できたー。
  • チーム「nginx(主に画像配信),(nginx+webapp),DBの3台構成にしちゃいましょう」
  • ぼく「けんしょーさんpprofの使い方教えて(泣きつく)
  • けんしょーさん「なんかslowquery動かん」ぼく「コード読めば多分OK」
  • ちまちま/messageとか/fetchのN+1問題っぽいの破壊していく。13万点くらいになってきた〜
  • ん!?fetchは加点しないってなんや!sleep復活させるぞ!
  • スコア21万超えたワオ
  • なんか暫定3位やんけやったー
  • sleep2秒にしたろ!←7万点!?点数下がった!?なんで!?
  • sleep1秒に戻そ!←3万点!?ファーwww
  • チーム「これもうわかんねえな。どうせベンチマークが混雑してて仕方ないやつやろ。21万のときのwebappでガチャ引いていい点数出たらやめるか」
  • 三人は幸せな20万点を取って終了(予選落ち)

反省点:

  • 慢心と準備。
  • 遅くなったのはベンチマークが原因ではなく恐らく僕たちのサーバーのリソースが何か悪いことになってたのが原因なので原因解析もきちんとせず諦めたのは本当に本当にダメダメだったと思います。
  • nginxに詳しくなりたいね。
  • GOでFizzBuzzはおろかHello Worldさえ書けないのがやばい。

良かった点:

  • 基本的にほとんどバグらなかったのでベンチマークで胃が痛くなるとかがなかった。
  • Pixiv ISUCONで仕入れた知識が役に立った。
  • 解析ツールは便利。