Category: C++

【Atcoder日記】ABC #365,367のC問題【二分探索、辞書順】

こんにちは。RockinWoolです。久しぶりの投稿になりました。メンタルを病んでパソコンに向かえない日々が続いておりましたが、なんとか乗り切ることができました。そして先日ついに茶色コーダーになることができました。さて、今回もAtcoderの挑戦日記になります。 二分探索法の問題 #365のC問題は色々計算して閾値を求めるよりも、二分探索で無骨に求める方が速さ的にも良いという(クソ)問題でした。解けなかったのは素直に悔しい。こういう閾値を求める系の問題が出てきたら二分探索法を疑うようにしなければ。二分探索法をコードは以下に示します。 これでmidに改良の余地がある限りはmidが調整されていき、最終的に一意に求まるという関数ができます。この考え方はよーく覚えておきたい。 低い方から辞書順に並べる問題 Atcoder367のC問題は初見では解くことができませんでした。そもそも解き方を知らないと対策できない系の問題でしたね。肝心となるのは「辞書順に低い方から並べる」という部分です。こういう数列の問題が出てきたら再起関数の実装問題なんだという合図のようです。 この関数の形は基本的に使いまわししたほうが良さそうなので、今後も同様の問題が出てきたら使えるようにしておきたいですね。 まとめ 二分探索法と辞書順に並べる問題の解き方について復習しました。目指せ緑コーダー!

【渋滞学】セル・オートマトンの実装

頭痛のひどいRockinWoolです。しかし、時期的にはかなり忙しいのでプログラミングもはかどっています。最近の趣味は公園で散歩をしながら肩をほぐすことですね。さて、ちょっと前に渋滞学という本を読んだのですが、その中にあったセル・オートマトンの実装をずっとやってみたいなと思いながらやっていなかったのでやりました。今回はpybind11を使ってセル・オートマトン部分をC++で作り、作図部分やデータ整理部分をpythonに任せる形にしています。実装はGithubにこっそり上げて置きます。 プログラムの構成 まず、メインプログラムは実験を行ってデータをcsvファイルに出力するmain.pyとcsvを元に作図をするdraw.pyの2つから構成することにしました。このようにすれば、main.pyはpybind11を使って連携するがdraw.pyはピュアpythonの実装で良くなるので便利でした。ざっとフォルダ階層を書くと下のような感じです。 CA.cppはCA.hppで定義された関数をpythonから使用できるようにしていて、細かい実装はcaHistory.cppに書いてあります。余談ですが、CAはCellAutomatonの略、historyとつけているのはセル・オートマトンでどの位置にエージェントがいたかどうかを返すことを明確にしたかったからです。 main.pyの概要 まずは基本となるmain.pyの解説から。難しい処理はC++に任せてしまっているので中身は超単純です。1~100個のエージェントを召喚してセル・オートマトンをして、その情報を保存することを2回繰り返しています。1回目は1個から始めて100個まで増やして混雑具合を見ています。2回目はその逆で100個から減らしていってます。cl.caHistory()でセル・オートマトンの実験をしていますが、その時に計算される混雑の回数congestionはゲッターであるget_congestion()を実装して入手する形になっています。これはpybind11の仕様でメンバ変数を直接参照できないことと、メンバ変数は基本的にprivateにしてゲッターを使ってアクセスするというベストプラクティスに沿った実装をしていることの2点があります。 それでは、このmain.pyで使用されるcaHistory(), get_congestion(), add_agent(), remove_agent()を実装していきましょう。 CA.cppの実装 こちらも中身は単純で、pybindのincludeを行う部分と関数をpython側に共有させる部分の2つから構成されています。書き方が少し独特ですが4つの関数が公開されるのがわかります。 CA.hppの内容 先程の4つの関数のプロトタイプ宣言を行っている部分です。ちなみに前回の記事で書きましたとおり、コンストラクタも作らないといけないのでpublicには5つの関数が登録されています。またprivateにはそれらの関数が使う情報を登録しています。update()関数はセル・オートマトンの具体的動作を規定していて、前のセルにエージェントがいたら止まるなどの挙動を定義しています。これをcaHistory()ではint回呼び出してシミュレートするわけですね。 caHistory.cppの実装 記事のほとんどがこのプログラムの内容で埋まってしまっていますが、実際はかなり単純なプログラムです。ほとんどの分量はupdate()関数の条件文で埋まっているのでcaHistory(), add_agent(), remove_agent(), get_congestion()の本文は2~5行程度になっています。それぞれ与えられた役割が動作するようになっています。 draw.pyによる描画結果 draw.pyの実装は大した内容では無いので割愛します。main.pyを実行して得られたcsvファイルをベースにdraw.pyが書いた図が下記です。 まとめ 今回はpybind11を使ってセル・オートマトンを実装してみました。内容が少しヘビーになってしまったかもしれませんが、興味のある方はコードの改善点などドシドシ送ってください。それでは。

【pybind11】クラスを登録するには

こんにちは。RockinWoolです。今日はC++とpythonの連携をするにあたって少し躓いた部分があったので紹介していきたいと思います。未だによくわかっていないバグもあったので、その辺で悩んでいる人たちへの一助になれれば幸いです。それでは早速見ていきましょう。 CMakeLists.txtの書き方 CMake Error at CMakeLists.txtのエラー fatal error: pybind11/pybind11.h: そのようなファイルやディレクトリはありません CmakeLists.txtにちゃんと書いているにも関わらずpybind11が見つからないというエラー。実はこのエラーはadd_libraryとtarget_link_librariesが無い普通のc++ファイルをビルドした場合には発生しないエラーです。原因は不明ですが、main.cppの最初に#include <pybind11/pybind11.h>を書けば大丈夫で、クラスを定義しているヘッダファイル(例えばutil.hのように名前をつけていた)に#include <pybind11/pybind11.h>を書くとバグることがわかりました。もしかしたらライブラリにするcppファイルとmain.cppのフォルダ階層が異なるとうまく下位プログラムからpybind11を呼び出せない感じなのかもしれません。とにかくmain.cppに書きましょう。 TypeError: build.{library}.{class}: No constructor defined! 上記2つのエラーを乗り越えて無事にビルドが完了しても、pythonからうまく呼び出せるかは別問題です。そして、pythonから呼び出す際にはコンストラクタの実装が必要不可欠のようです。なので、コンストラクタを定義してあげましょう。さらにコンストラクタを登録する必要もあります。main.cppにコンストラクタを登録する方法は下記を参考に。 まとめ 書き出してみるとちゃんと解決策のあるエラーだったのですが、C++はこういう仕様が隠れていて調べないとわからないみたいなエラーが多くて難しいですね。その点PYthonはかんたんだなと改めて感じます。それではここまで見てくださってありがとうございました。次回もお楽しみに。

【Atcoder日記】ABC #336

こんにちはRockinWoolです。最近は結構頑張って活動できていると思います。頭痛も寒さに慣れたからか軽減できていますしね。さて、1週間前の話になりますがABC#336を解いてみたので、解説と比較していこうと思います。今回は45分で3問も解けたので自分を褒めたい一方で、4問目で完全にスタックしたので解法を確認していこうと思います。それでは。 A問題 龍文字列 なんのことは無い平凡なA問題です。oをN個付ければ良いだけなのでかんたんでした。 B問題 CTZ 2進数化した時に、末尾に何個0が連続するかを算出する問題です。一般的に使われるc++のライブラリではstring型で出てこないため0の個数を確認することができませんでした。そこで、自作でstring型の2進数を返すtoBinaryを作ってやれば解決します。 ちなみに公式ではbitsライブラリを使って下記のように解いていました。 まずint nがbitとして扱えることに驚き、次に1を左にi個シフトするのを1<<iと表現するのがびっくりですね。あとは、左にi桁シフトした1とnでandを取って0でなければ0がi個あることになるという仕組みです。1<<iの挙動に関しては下記プログラムで確認しました。 これだとn=3で8, n=2で4, n=1で2, n=0で1を出力します。左にn個シフトしていることがわかりますね。 C問題 EvenDigits 5個の数字{0,2,4,6,8}のみを使って低い順に並べて、i番目に低い数字を答えるという問題。これは{0,1,2,3,4}だったとしたら5進数を答えれば良いと気づけたので、B問題のtoBinaryを流用して5進数を算出する関数を作ればクリアです。 コードの質と量は全然違いますが、公式でもまったく同じ解き方をしていたので嬉しいですね。公式から学べることとしては、for(int i=Fifnary.size()-1;i>=0;i–)の部分をreverse(Fifnary.begin(),Fifnary.end())と書けたということでしょうか。 D問題 ピラミッド数列 ピラミッド数列を考えるときにまず困ったのは1,1,2,2,3,2,2,1のように、もともとの数列の中心を軸にピラミッドを作ると121が算出されるけれど、本当の最大ピラミッドは12321のパターンです。結局これが解けなかったのでこの問題でスタックしました。さて、公式の回答をここで見てみましょう。 解説を読んだのですが、かなり複雑で難しいです。解説を写経してなんとか意味はわかったのですが、これを時間内に解くとなるとなにかが必要ですね。 まとめ D問題に初めて挑戦してみましたが、かなり難しいですね。これからも精進していつかは解けるようになりたいものです。それではまた次回!

C++の備忘録: クラスを作ってファイルを分ける時に詰まったこと

こんにちはRockinWoolです。C++のプログラムが大きくなりそうだったので、クラスを作ってわかりやすく管理してやろう!と思ったわけですよ。しかし、なんか見知らぬエラーが連発してなんじゃこりゃってなったので、対策方法をメモして残しておきます。先に言っておきますが、かなり初心者レベルのことに今更気づいてドヤ顔しているので内容は結構恥ずかしいです。 おかしい。クラスを読み取ってくれない。 最近読んだリーダブルコードにもありました通り、本質的では無いコードはutilに突っ込んでメインは読みやすくしたい。そのためにはクラス化してpublicな関数とprivateな関数に分けてやろう!と意気込んだわけです。しかし、問題が。下記のようなプログラム構成にしたのですが、関数の定義が見つからないよというエラーが出てしまうのです。 「関数が定義されていない」というのであれば関数は定義されていないのでしょう。さて、何が問題だったのか? CMakeLists.txtにライブラリファイルの登録をする必要があった 上記のミスはCMakeLists.txtに設定を追記していなかったことにより発生しておりました。下記にChatGPTとの会話を載せておきます。簡単に言えばadd_executable以外にもadd_libraryとtarget_link_librariesの2つを設定する必要がありました。 したがって、CMakeLists.txtは次のように書き換える必要があります。 ヘッダファイルからのincludeは不要 さて、上記のエラーを解決するのにはかなりの時間を要しました。というのも、このCやC++の仕様が気持ち悪くて何回躓いても納得できない!過去の自分と同じエラーで引っかかったので悔しいを通り越して怒りになっているのですが、サンプルプログラムを作ったりしながら試行錯誤を続けているうちに気づくことができたのは偉かった。エラーの原因ですが、utils.hに#include util/myclass.cppを書いていたのが原因でした。utils.hを読んだ時にクラスの定義より先にmyclass.cppの中のMyclass::hoge()関数を読もうとして、「Myclassなんて無いんだけど?」ってなったのが先のエラーです。確かにあとから見ればそんなの当たり前に感じますよね。でも、これってCMakeLists.txtに追記した内容を理解していないと遭遇してしまうと思うんです。pythonを書き慣れている私からすると、親子関係にあるプログラムは親でimport宣言をして子の関数を呼び出せるようにするのが当たり前。ということはutils.hで宣言する関数も実際に書かれているmyclass.cppからimportしなければならないのでは?と考えてしまうのです。(今でもその感覚は抜けていない)なので、ヘッダファイルで行っているのはあくまで関数のプロトタイプ宣言であり、utils.hとmyclass.cppでライブラリを作ったあと、utils.hとmain.cppでメインを作って、それらをリンクさせるという一連のCMakeLists.txtの動作を理解しないと、この違和感は払拭できないわけです。 まとめ 結局このエラーを解消するのに半日使ってしまったので、今となっては完全にやる気が削がれてしまいました。しかし、この経験をバネに日記を書いて次回につなげて行こうと思います。それではここまで長々とお付き合いいただきありがとうございました。

【Atcoder日記】ABC #333

こんにちは。ようやく茶色帯が見えてきたRockinWoolです。次でようやく参加10回目となってRateも上がりやすくなるはずなので、ここらで本腰を入れて頑張っていきたい所存です。さて、今回はAtcoder Beginner Contest #333付近の回答と詰まった部分をまとめてみました。直近の正答数は3, 3, 2と来ているので、次は4問クリアできるようにしたいです。 #330-A Three-Threes NをN個並べるという超単純な問題。過去1簡単だったのでは?と感じました。2分ほどで正答できました。 #330-B Pentagon 問題自体は解けないことも無いのですが、B問題にしては難しかった気がします。入力がstring型なので、それを数字に置き換える関数を用意してあげる必要がありました。もしかしたらChar型の扱いに長けた人なら’A’で引いた値をint型で扱えば良いとか気づくのかもしれませんが、エラーが怖かったので力技で変換しました。 B問題にしては行数が多いので、結構難易度が高かった印象です。ちなみに半分以下の行数で済ましている人もいて、その人の回答はこんな感じでした。 ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);の行は単純にパフォーマンス向上のために記載している呪文と思えば良さそう。C標準の標準入出力との同期を解除してcinやcoutを使用する際に呼び出されないようにするそうだけど、ChatGPT曰く保守性の観点から競プロ以外では推奨しないとのこと。whileの必要性はよくわからないけど、ともかく文字列を読み取って2つの文字間の絶対値を取れば長さになるという算段は同じ。かなりスッキリ書けていて羨ましいですね。ちなみに、自分はここで何回かREを連発しました。原因はmain関数のreturn値を1にしていたせいです。気をつけましょう。 #330-C Repunit Trio 時間以内にとけなかったのですが、解答をみたら衝撃の短さだった問題。ともかく1の桁の数字は3になるという法則があるので、1が0~N個、2が0~M個、3が1~L個の出力になっていて、なるべく少ない個数で構成されるようにすれば良いとのこと。いや、気づかないよそんなの。 おわりに とりあえず3問正答を目指して精進しようと思っていますが、最後の問題は美しすぎる解き方でしたね。こうやって脳みそを使えればもっと高みを目指せるんだろうなあ。もっと勉強しなければと思いました。それでは、また次回。