【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問正答を目指して精進しようと思っていますが、最後の問題は美しすぎる解き方でしたね。こうやって脳みそを使えればもっと高みを目指せるんだろうなあ。もっと勉強しなければと思いました。それでは、また次回。

C++の備忘録#2: 配列の中身チェッカー、CSV入出力のあれこれ

こんにちは。寒くなるにつれて頭痛の酷さが増してきている気がするRockinWoolです。運動不足が影響しているのではないかと思って今日は外を歩いてみたり、2年ぶりくらいにカラオケをしてみたり、風呂に長めに入ってみたりしたけれど効果はありませんでした。仕方無いのでC++の備忘録の第二弾をやって少しでも成長を感じていこうと思います。 配列の中身チェッカ 関数のオーバーロードの練習を兼ねて作ったプログラムです。もし引数がvector<int>だったら、その中身を1行で出力します。vector<vector<int>>だったら全要素を順番に表示します。まずは引数がvector<int>だった場合。 解説するほどの場所は無いかもしれませんが、vec.size()で配列の長さを取得しているところが工夫ですかね。次はvector<vector<int>>の場合。 同じvector_checker()という名前ですが、引数が2次元だとこちらが呼ばれます。工夫はvec.size()-1の範囲でコロンを打つようにしている部分ですかね。これらのプログラムは挙動が想像しやすいように書けたと思うのでGood jobだと思います。 失敗事例: vectorでは無い場合 次はバグを生んだプログラムの紹介です。下記のプログラムのバグを見つけてみましょう。 このプログラムを回した際の出力は1,2,3,4,5,0,937289472,-494252404,になります。何か知らない数が生まれてきていますね。ちなみにこれをChatGPTに相談してみたところの回答が下記になります。 The issue you’re encountering is related to how arrays are passed as pointers in C++. When you pass an array…

【日記】スパゲティコードの治療#002【断念】

こんにちは。RockinWoolです。寒くなるにつれ頭痛もひどくなってきている気がしますが、皆さんも暖房をケチらずに温かい部屋で過ごしましょう。さて、今回のお題はとあるスパゲティコードの治療を諦めた話をしようと思います。このプログラムの何がいけなかったのか。自己分析をしながら話していこうと思います。 治療断念の決断: 存在意義が無い 皆さんの中でも、自分の書いたプログラムに対し「破棄をする」という決断をすることは珍しいと思います。どんなコードでも一度書いたものは大切に使いまわしたいし、複雑なコードであればあるほど時間をかけてつくった愛着から捨てられなくなりがちです。ですが、今回の私のプログラムは救いようがありませんでした。なぜなら、作者本人からしてみても、そのプログラムを回して何をしたかったのかがわからなかったからです。 スパゲティコードは維持するだけで負債になりうる さて、半年前に書いたと思われるこのコード。内容を確認してみようとすると大きな問題にぶつかります。そもそも、かなり読みにくいのです。これを頑張って解読したのですが、結局何がしたかったのかは半分くらいしかわかりませんでした。しかも、ここで動作チェック中にバグを発見してしまいました。このバグを治すかどうかを時間と天秤にかけて、このコードをメンテしつづけるくらいならば破棄した方が人生にプラスであると考えました。つまり、治しても価値の無いものを治すくらいであれば、破棄した方が良いのです。 どうすれば回避できた? 今回の問題はコードの構成も去ることながら、コードを使って何を実現したかったのかという「目的」の部分が欠落していた点が一番大きな要因として挙げられます。単純な動作確認プログラムの延長であったことはわかるのですが、何の動作を確認してどうしたかったのかが残って無い以上、推測の範囲でしか当時の自分の気持ちを読み解くことができないのです。したがって、このような事態を防ぐためには「必要なもの」を「ドキュメントを残しながら」作っていくことが必要です。どんなプログラムも書いている時は、自分にとって必要なプログラムであるはず。この存在意義を残していないと、あとになって大切であるかもしれないそのプログラムを捨てるしかなくなってしまう。今回はそれを学ぶことができました。 動くものを作って、いつでも使えるようにする 使いきりのプログラムを作ると、必ず半年後にはその存在意義や実装の詳細を忘れてしまうでしょう。使い切りにせず、常に使えるようにドキュメントを作ったり、動作方法を簡単にする、GUIを作るなど、工夫しようとおもいました。それと、そもそも動かないプログラムは破棄しましょう。今回動かないプログラムを治療しようと思いましたが、当時の知識を失っている以上どうしようもなかったです。せめて動くプログラムを管理しましょう。 まとめ: 定期的なプログラムの破棄は大切である 締めになりますが、今回のプログラムを破棄するという判断は間違っていないと信じたいです。そもそも一生プログラムを書き続けたら、そのうちGitHubはゴミコードでいっぱいになってしまいます。定期的に破棄し続ける心構えを持てればいいなと思います。それでは、また次回。