少ないリソースを酷使する

低レイヤーとレトロPC(PC98,MSX)が好きな情報学生

岩手でITコミュニティを作るということ。

初めましての方は初めまして、岩手でIoTLT盛岡というイベントを主催しながら、岩手県内の色んなITコミュニティに首を突っ込んでいる大将です。
本記事は第二弾 地方IT勉強会 Advent Calendar 2019の1日目の記事です。
地方IT勉強会 Advent Calendar 2019 の1日目はすでに @tomio2480 さんが公開しています。 11月が30日までだったことを忘れていて危うくすっぽかすところでした。(遅刻しているので実質すっぽかしてる)

記事が長いので最初に紹介しますが、 明日の第二弾 地方IT勉強会 Advent Calendar 2019担当はKeita Fukuiさんです!

本記事で書く事

自分の主催しているIoTLT盛岡を中心に、あくまで私の主観による「岩手でITコミュニティを作ってみてどうか」という現状の"お気持ち"を綴っていきます。今この文章を書いている時点ではどれぐらいの文量を書くかわかりませんがきっと長くなるので、その文量こそが熱量と思いの強さだと思っていただいてブックマークと拡散だけして頂いてブラウザバックしていただいても構いません。
結論としては 「誰になんと言われようとコミュニティは立てただけですごい。」 という ”それは、そう。” なことを書いてます。以上です。
12月末ぐらいまで東京にいるので近い人はご飯に誘ってくれると喜びます。

コミュニティ・イベント宣伝するしかねぇ

コミュニティ・イベントの主催者は当たり前ですが、隙さえあれば自分のコミュニティ活動の宣伝をします。
ということで記事の内容に飽きられないうちに自分が主催しているコミュニティ活動と、岩手県内のITコミュニティ活動の宣伝をします。
追記:「いわて Web+DB Press を読む会」や、岩手県内に複数道場をもつ「東北Tech道場」などのコミュニテイもあります。書き漏れでした。悪気はないので許してください・・・
こう見ると岩手はコミュニティ多いですね。

IoTLT盛岡・一関

東京では1回の開催で安定して50人以上、多い時で200人近く人を集めるIoTLTの岩手版を公式開催しています。
LTとは"Lightning Talks"の略で、うちのイベントでは1人5分くらいのフリートークを行ってもらっています。
2018年の12月から始め、毎月開催地を盛岡一関交互に移しながら、IoTに関係あったりなかったりする情報の共有を行っています。
楽しいよ!
盛岡の主催が私(大将)で、一関の主催が一関高専高橋源輝さんです。
一関開催では岩舘電気株式会社 様に協賛していただき会場費や交通費の支援をしていただいています。

connpass(本家IoTLT): https://iotlt.connpass.com/
Facebook: https://www.facebook.com/groups/iotlt.iwate/

IWDD

2006年2月から毎月続いている岩手では1番老舗かも知れないWeb制作者のための情報交換コミュニティです。
残念ながら私は参加したことはないですが、今月(2019/12/14)で157回目の開催と、とてつもない安定感と安心感を感じます。
Webに興味がある、勉強している人にはもってこいのコミュニティだと思います!
connpass: https://iwdd.connpass.com/

サイバーセキュリティの勉強会(笑) or ITの勉強会(笑)

@NaokiYoshidaさんが月一回?不定期?で開催しているIT勉強会・LT会です。
イベント名に「サイバーセキュリティ」と付いてたり付いてなかったりしますが実際はテーマフリーです。
その上「退出,途中参加,コスプレ自由」「外部に接続できない無線LAN(WEP)を設置するので、適宜パスワードを解析してください」という非常にゆるふわ?な雰囲気のある勉強会です。
CTFやSecHack365のお話も聞けます。
休日に情報共有したり作業するにはもってこいのコミュニティだと思います!

connpass(過去開催): https://iwate-it.connpass.com/event/152264/

Iwate.Unity

Unityを中心とした勉強会&ゆるもく会です。LTもあります!
PCさえ持ち込めば、Unity未インストールでも当日インストール補助してくれるそうです! とてもありがたき。
私もUnity触ってたことあるので参加してみたいなと思っています。
ここで集まった人でUnityインターハイに出場とか面白そうだなと思ったり・・・
ゲーム作ってみたい!という方にはおすすめのコミュニティです!

connpass(過去開催): https://iwateunity.connpass.com/event/150640/

Iwate Developers Community(IwaDev)

今月(2019/12/28)に初イベントを開催するらしいです。
最初のイベントは「ジャンルを問わず、ご自身がエンジニアにとって有益と思われる情報であれば何でもOKなLT会」のようです。楽しそうですね!
懇親会もあるようなので今から私もイベントを楽しみにしています。
connpass: https://iwadev.connpass.com/

岩手県立大学LT会(IPULT)

我が母校である岩手県立大学で開催されているLT会。
正確にはITコミュニティではなく、本当のテーマフリー(一人暮らしの料理についてや、おすすめのゲームを紹介したり)のLT会を開催しています。
分野の縛りもなければ、学内者である縛りもないので多分誰でも参加できます。
最近公式ツイッターの中の人が変わって愉快な感じになっています。
Twitter: https://twitter.com/iwatepu_lt
ホームページ: https://iwate-pu-lt.github.io

持ちネタ

このツイートが思いの外広がって、県外のLT会などで自分が主催するイベントを紹介すると「あー、あの!!見たことある!!!」と言われるようになりました。
よくネタにされます。ありがとうございます・・・?

どうしてコミュニティ活動を開始した?

端的に言えば、同じ話題で盛り上がれる馴れ合いの場が欲しかったのです。
大学3年生の頃、インターン活動やなんやかんやで東京に行く機会が多く、そのついでにLT会に行ったのがきっかけでした。
自分の知らない情報が知れるのはもちろんのこと、技術に興味があるもの同士で技術談義であーだこーだ話せるのが楽しかったのです。
その当時、母校である岩手県立大学では、自分が陰キャおとなしかったこともあってかコンピュータの話題で話し合える人がいなく、オタク特有の早口をしたい自分はとても悶々としていました。
それならば、「LT会」でなくとも「エンジニア食事会」や「エンジニア飲み会」みたいなワイワイ話せるコミュニティを立ち上げてもよかったのですが、IoTLT盛岡を立ち上げるに至ったもう一つの理由は"運"でした。
ちょうどその頃参加した学生・社会人エンジニアによるLT会、「manifes 2018」というイベントで優勝?して自分のLT能力に自信がついた・楽しさを覚えたこと。
(お酒を飲んだ後に行われた受賞者インタビュー: https://techplay.jp/column/347)
IoTLT創設者であるdotstudio株式会社代表の菅原のびすけさんと知り合ったこと。
その時調子に乗っていたことなどが重なり「IoTLT盛岡」を立ち上げるに至りました。

岩手のコミュニティの現状

現状をつらつら振り返ります。現状から考える私のお気持ちは後の章でお話します。

IoTLT岩手の参加者数と層

参加者数は別に重要ではないと言えればかっこいいですが、やはりイベントを主催すれば参加者数の動向は気になるもの。(少なくとも私は)
IoTLT盛岡と一関の参加者数を見てみましょう。
f:id:T-takeda:20191202015210p:plain 一関はある程度安定しています。というのも一関には一関高専があり、高専の学生参加が多いです。高専の先生や東北大学の先生、企業の方など社会人の参加も結構多いです。
岩舘電気株式会社 様のご支援も非常に助かっています。

f:id:T-takeda:20191202015257p:plain 盛岡は、立ち上げ時に必死の告知と営業で人を集めたものの、リピーターを作れず右肩下がりになりました。
参加者の層も2回目以降は社会人の参加率はほぼ0になり、学生中心で集まっていました。
そして、5回目の記念に0人を記録しました。
その次の回では多くの方に情けをかけてご支援をいただき、変則的にIoTLT北日本を主催させていただきました。
東は北海道、西は東京から参加していただき、社会人率も一気に上がりました。リピーターが増えていただけると嬉しい。

f:id:T-takeda:20191202015617j:plain
IoTLT北日本の様子

新たなコミュニティ

私がIoTLT盛岡を立ち上げた当初は単発で行われるイベント以外に、定期的にイベントを開催しているITコミュニティは「IWDD」と「いわて Web+DB Press を読む会」ぐらいでした(私が観測した中では)。
しかしながら、2019年に入ってから「ITの勉強会(笑)」や「Iwate.Unity」、「IwaDev」といったコミュニティが一気に立ち上がりました。
それぞれのイベントに個性があり分野もばらけていて、県内で同志を探している人にとっては選択肢が増えて非常に嬉しいと思います。
IoTLT盛岡について「IoTに分野を絞っているから人が集まらないのではないか」と指摘されたことが100万回ほどありましたが、別にうちのイベントに集まらなくても、うちのイベントのモデルを見て、自分の立ち上げたいテーマでコミュティを発足する人が出るだろうと思っていました。
”県内にITコミュニティが複数ある”という雰囲気を作れるだけで、自分も好きなテーマ・雰囲気で人を集めてみようと想うハードルは低くなると考えています。
というのも、まず"コミニュティを立てる", "IT系イベント"が当たり前に開催される・周知されている雰囲気が岩手は弱いと思ったので、まずはその風土ができてしまえば大量にイベントが発生する東京のようになんでも気軽にイベントを立てやすくなると想うからです。
みんなやってれば真似しやすいし、参入障壁も下がるやろってことですね。
ここまで読めば「俺が風土を作ったから今年はイベントがいっぱい立ったんや」と語っているように聞こえるかも知れませんが、今年立ち上がったコミュニティは別にうちのイベントがなくても、どれも0から1を産み出そうと立ち上がった意欲的なコミュニティだと思っています。(何様のつもりで言ってるんだ)
できれば仲良くしていただいて、"岩手はITコミュニティが活発である"風土にして行けたら幸せだなと思います。土下座。

考えること、お気持ち

「コミュニティを長く続ける」という点では以下の記事がとても参考になります。というかとても心が軽くなります。

強欲

私も、自分のコミュニティは長く続けたいと思っています。
しかしながら、私がコミュニティを立ち上げた動機に「同じ話題で盛り上がれる馴れ合いの場が欲しかった」というのがあり、初期はより多くの人と話したい・交流したいという欲がありました。
そうなると、参加者をいかに増やそうか・・・と考えるわけですが、0から考えるのも難しいし、アイデアだけでどうにかなるわけでもありません。
イベントを立ち上げた当初はとにかく参加者を増やしたいという強欲に囚われ、スーツで盛岡の企業様を周り宣伝したりしました。今思うとやばいやつですね。
夏にセキュリティキャンプに参加したあたりの時期も結構迷走しました。
セキュリティキャンプでのグループワークで「地方でITコミニュティを盛り上げたい人」でワークを行ったのですが、やはり"盛り上がり"という抽象的な目標で頑張るとなるとコミュニティ規模、"参加人数"が指標として上がってしまいます。自分自身も心内では「地域の人たちがみんな集まるようなよりどころになれれば・・・!」という野心は沸きます。話し合っている中でみな”人を集めたい”という気持ちがひしひしと伝わってきて中々体力のいる議論でした。(今思うと自分のコミュニティが成長しなくても触発されて地域全体でポツポツ増えてくれればそれも"盛り上がり"の指標にできる)
そのような気持ちがあってコミュニティを立ち上げる人も多いと思いますが立ち上げた後は一度落ち着いて現状を見る必要があるのかも知れません。しかし周りに参考が少ないと初期段階で 「俺が思ってたんと違う...!もしかしてこれってやばいのか!?」 と私は焦りが出てしまいました。
正直、東京のコミュニティに憧れて、地方に持ってくることのギャップがすごいのでしょう。参加者は自分含め最低2人いれば十分だし、会場が抑えられてWifiが使えるだけでかなり十分だと今は思えるようになりました。
(今思うとたった1年、たった1桁回しかやっていない地方コミュニティイベントとしては、参加者は3~4人いれば結構良い方だし、0人になることもさしておかしい事でもない。)

まだ焦るような時間じゃない

ある時、@tomio2480 さんから「コミュニティを広げるには工夫も必要だが浸透までの時間も必要だ」という趣旨のアドバイスをいただいてからは、確かに焦らずとも長期的に見てゆっくりと拡大して行けばいいかと自分の焦りを抑えることができました。
イベントをよりよくすることは悪いことではないですが、主催者の負担になりコミュニティの運営自体が楽しくなくなってしまっては本末転倒です。
なので、自分が負担にならない程度にちょこちょこアイデアを取り入れ、数年かけての成熟を目標としました。(成熟の基準は私の主観です)
ちょっとした工夫としては、そもそも勉強会に興味があるような意欲的な人は、岩手を出て県外のイベントに積極的に参加している場合があります。自分自身も県外のイベントによく行くのでその場で岩手のコミュニティの宣伝を必ずします。もし岩手の人が偶然にも同じイベントに行った時に、周りの人から「岩手でもこんなイベントやってるらしいね」と話題にしてもらうためです。外堀から埋めていくというこのアイデアも @tomio2480 さんから聞きましたが面白い発想でした。
あとは、毎回必ず参加したことない人 1-2人に誘いをかけるということです。いつも来てくれるメンバーで楽しくやるのもいいですが、やはり新メンバーが入るとより楽しさが増します。入りづらさは感じて欲しくないし、雰囲気の停滞も防ぎたいのです。

盲目故の考えすぎ

と、いうような心づもりで悠々とコミュニティ活動をやっていたのですが、最近また考えが迷走しました。
最近は、「こういうイベントをやってまして・・・」というと「費用対効果」「続ける意義は」「目指すところは」「行って利益になることは」という言葉が社会人・学生問わず飛び交い、「あ、いえ・・・趣味的なもので・・・」と何度もいうことがありました。その度に不思議な顔をされることもしばしばです。
不思議な顔をされるのも当たり前で、まだ私はリアルで集まることの意義をふんわりとしか考えていませんでした。人と語りたければネット通話でも良い時代になぜリアルで集まるのか。
特にも、参加者0人を達成していた時の自分はそこで堂々とプレゼンする自信がありませんでした。

私の大学の学生からは”企業と繋がれるなら行きたい”,”有益な情報が得られるなら行きたい”という意見をいただき、社会人の皆様からは”何が我々の利益になりますか?”, ”実績を作ってから誘いなさい”という意見をいただき正直私は何が何だかわからなくなりました。
純粋な技術好きに出会うことは難しく、まだ"情報を発信する"という習慣、"情報を共有することは有益である"という考えが根付くまでの道は長いのか・・・とも感じてしまいました。
しかし実際は"情報を共有する以上の付加価値"は確かに必要で、それがそのコミュニティの個性ともなり、モチベーションの糧となると今は思います。

もちろん、毎回参加してくれている学生や、協賛いただいている岩館電気様、過去に遥々東京から協賛&登壇していただいたKLab株式会社 様のように本当の技術好きの方々に出会えたこともありました。それは本当に運命の巡り合わせだと思っています。感謝。

ある日、つい酔った勢いで(全然関係者ではない、学生活動を支援している人に)「社会人の方にも来て欲しいと思ってるんですが中々集まらなくて・・・w」と言ってしまった時には「そういうのは実績が出来てから言うもんだぞ、実績がないのに欲を言うな」的なことをグサリと言われてしまい、「長期的に見て最終的に本家IoTLTのようになれれば・・・」という目標さえも「比べる相手が違うんじゃないか?君はまだ学生だろう」と窘められ結構落ち込んだりもしました。
正直、「落ち込むくらいなら気にしなけりゃええやん」と言えばそこまでなのですが。

そんなこんな”お気持ち”が溜まっても、コミュニティが少ない地方では何か悩んだ時に相談したり、コミュニティ運営者としての意見を聞くことは難しいです。
参加者にも相談すべきでしたが、参加者には楽しく参加して、楽しく帰って欲しいと思ってしまい、やんわりとした意見は聞きますがガッツリと相談することはあまりしていませんでした。

結局何が言いたいん?

孤独を癒すために始めたコミュニティ活動なはずなのに結局は考えすぎで自ら孤独になっている錯覚に陥っていました。
しかし、そういう時こそ原点に立ち返りましょう。
「誰になんと言われようとコミュニティは立てただけですごいのです。」
結論、やっぱりのところこれに尽きます。

実際は孤独なんてあり得ないことで、コミュニティは立ち上げた時点で誰かの目につき、何かしら効果を生んでいます。
connpassで自分の地域でイベントを検索して1件ヒットするのとしないのでは大きな差があります。
そういう点ではIoTLT盛岡は人の目につくどころか、すでに多くの人に支えられ、数多の交流が生まれ、大きな効果をもたらしています。それに気づくまで1年かかった。

これから

コミュニティを何年も運営している方はここまで読むのもうんざりするぐらい、典型的な地方コミュニティあるあるな状況であることがわかるでしょう。
なんとなくの雰囲気で手探りでコミュニティを立ち上げましたが、最近は他地域のコミュニティとコラボする機会や、このアドカレなどの刺激もあってコミュニティ運営先駆者の知見を多少なりとも学ぶ良い機会となりました。
それを踏まえた上で私が青かった部分があります。

  • 焦るな!2人でも良いじゃないか!
  • 地域に縛られるな!ネットを使えネットを!
  • リアルで集まる人数をアレコレ言うならリアルで集まる価値を考える
  • コミュニティは仲間なんだから、恐れずちゃんと想いは共有しろ!
  • 続ける理由に自信を持て

多くは考え方と気持ちの問題ですが、来年の課題点になりそうなのは"リアルで集まる価値"です。私はしがない貧乏学生ですのでお金のかからない価値を見出さなければなりません。何かアイデアや事例があればご教授頂ければと思います。

ITコミニュティは人々の高いモチベーションの集合体だと思っています。
まずは自分のモチベーションから、自信を持って、自分が楽しいことができればいいやと高めることは大切です。
これらはずっと言われている事だとは思いますが、改めて、手探りで始めて同じ状況に陥った人たちのために何度でも共有されるべき事項だと思います。
それを忘れないようアドカレ1日目にここに公開する形で文章を残しておきます。

最後に

まさかりは、思いっきり投げたい気持ちはわかりますが、優しく投げてください。
イキりポエムを書くのはこれで最後にしたいと思うので、これ以降Twitterなどで無意識にイキり始めたら「お前、やばいぞ。」と一文だけリプを送ってください。

RaspberryPiのCPUを起動せずGPUだけでLチカ

以下のツイートがTwitterでプチバズったので簡単な解説

先駆者様

あとから知りましたが、RaspiのGPUのみでLチカする試みは私が最初ではないようです。 以下の人たちがGPU Lチカをしています。

github.com

qiita.com

アセンブラが好きな人、環境構築がめんどい人は「Herman H Hermitage」さんのGitリポジトリを見ると良さそうです。(簡単なアセンブラが入ってる)

GCCC言語が好きな人、GPUプログラミング環境を構築したい人は@stkchpさんのQiita記事が良さそうです。

ちなみに私はバイナリエディタ機械語で書きました。

前提知識

"CPUを起動せずに"とはどういうことか理解してもらうためには、BroadcomのBCMチップとRaspberryPiの簡単な理解が必要です。

Raspiの頭脳

Raspiに乗っている頭脳はBroadcomのチップで、ARM CPUとVideocore4(VC4)というGPUが一体となったSoC的なものです。
以下は、Raspiのチップ表です

RaspberryPi SoC's

Raspberry Pi 1系
zero系
Raspberry Pi 2
Model B
Raspberry Pi 3系 Raspberry Pi 4系
チップ BCM2835 BCM2836
BCM283
BCM2837
BCM2837B0
BCM2711
GPU VideoCore IV VideoCore VI
CPU ARM 1176JZF-S (ARM Cortex-A7)
ARM Cortex-A53
ARM Cortex-A53 ARM Cortex-A72

追記:2019/7/8

Raspberry Pi 4のGPUvideocore4だと勘違いしていましたが、正しくはvideocore6のようです。
いろいろ調べているとRaspi4のGPUはvideocore4であると記述している記事も混ざってたりして混乱しました...
どうやらローマ数字のIVVIが似ているので、間違えている人もいるようです。ややこしや。

ちなみにVC4VC6の互換性は微妙そう

Pi 4 - full specification of VideoCore 6 - Raspberry Pi Forums

ブートプロセス

次に、Raspiのブートプロセスについてですが、
RaspberryPiに電源を入れるとBCMチップはGPUから起動します。で、彼が簡単なブートプロセスを行ってからARM CPUを起こすというわけです。これが特徴的です。
簡単なブートプロセスの流れはこの記事がわかりやすいです。

qiita.com

VC4には小さいOSが乗っているようです(VCOS)

GPUブートコード(bootcode.bin)について

SDカードから最初に読み込まれるGPUプログラムですが、ソースコードは公開されていません。バイナリはARM CPUの命令ではなくVC4の命令で記述されています。
このプログラムが実行される段階ではCPUは停止しているので、ここをHackしてLチカしようというのが今回の試みです。

注意点

RaspberryPi 4では「bootcode.bin」は工場出荷時にROMに焼き込まれるようなので今回のような手法は使えません。

RaspberryPiのACT LEDについて

RaspberryPiには表面に「ACT」と書かれたLEDがついてます。
これは、
Pi B rev1, Pi B rev2GPIO16
Pi B+, Pi2 BGPIO47
に割り当てられています。
RaspberryPi 3からはなぜかGPIO割当がなくなったのでLチカできません・・・一応 i2c の信号線とつながっているらしいので i2c をベアメタルでセットアップすればLチカできますが少しめんどくさそうです。

Lチカの解説

個人的に「Herman H Hermitage」さんのGitリポジトリがわかりやすかったので、この中のコードを例にさらっと解説していきます。 リポジトリの「blinker01」が初代Raspi 1でLチカするコードで、「blinker01-beeplus」がRaspi 1 B+ から Raspi zeroRaspi 2 B (ver1.1 ?)でLチカするコードです。
実際やることは「blinker01」の中に書いてあるとおりなのですが、簡単な日本語訳としてここに書き残しておきます。

環境

今回私はRaspi zeroをターゲットにしたので、見るサンプルは「blinker01-beeplus」です。
コンパイルに必要なのはgccmakeですが、コンパイル済みバイナリが入っているのですぐLチカできます。
FAT32でフォーマットされたSDカードも必要です。

コードとバイナリ

GPUのブートルーチンでは最初の0x200 byteは使わないらしいので0埋めしときます。

以下で説明するGPIO制御はBCM2835のリファレンスを読めば大体書いてます。(pp.90あたり) https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf

GPIOの制御レジスタ

equ(GPFSEL4, 0x7e200010); /* input or outputを決める 90p */
equ(GPSET1, 0x7e200020);  /* GPIOに出力(High) 90p */
equ(GPCLR1, 0x7e20002c); /* GPIOをクリア(Low) 90p */

それぞれ32ビットの幅を持ったレジスタです。
使い方としては、

  1. GPFSELの各ピンのI/O設定3bitにinputなら000、outputなら001を書き込む。その他は111(GND)とかにしとく
  2. GPSETで出力したいピンのbitに1を立てる
  3. GPCLRでクリアしたいピンのbitに1を立てる

みたいな使い方をします。

GPIOの設定

  movi(r1, GPFSEL4);
  ld(r0, r1);
  andi(r0, ~(7<<21));
  ori(r0, 1<<21);
  st(r0, r1);

Raspi zero の ACT LEDはGPIO47です。
GPIO47はGPFSEL423-21 bitで設定できます。

よって、r0をANDで11111111 11111111 00011111 11111111にして、設定するbitsだけ一度0にします。
で、ORで21 bit目だけに1を立ててあげれば23-21 bit001になります。

あとはそれをGPFSEL4のアドレスにセットします。

メインループ

メインループで必要な値をレジスタに持ちます。

  movi(r1, GPSET1);
  movi(r2, GPCLR1);
  movi(r3, 1<<15);

GPSET1orGPSCLR1GPIO32~53を扱います。
よって[制御したいGPIO] - 32のビットに1を立てれば出力が制御できます。

st(r3, r1);  // GPSET1(15bit) High
st(r3, r2);  // GPCLR1(15bit) High

ちなみにRaspi zeroとか2のACT LEDは、SET消灯CLR点灯するようです。ほかは検証してません。

適当なディレイ

適当な回数ラベルジャンプループをして点灯・消灯の間隔を空けます。

  movi(r0, 0);
label(delayloop1);
  addi(r0, 1);
  cmpi(r0, 0x100000);
  bne(delayloop1);

メインループ

最後に消灯→点灯の処理をラベルジャンプで囲めばLチカループの完成です。

label(loop);
  ・・・
bra(loop);

実行

makeしてできたblinker01.binbootcode.binにリネームしてSDカードにそのまま放り込めばOKです。

ポエムと宣伝

これがGPGPUと呼べるかはわかりませんが、GPGPUはとても面白い技術だし、AIとか機械学習に関連して注目度も高いです。
私はOSの研究室に所属していながらも、去年からGPGPUの活用研究を行っていて、Raspi GPUの活用で論文出したりしてますので興味ある方は気軽に声をかけてくださると嬉しいです!(色々答えれたり、答えられなかったりすることもありますが)

あと、技術書典7に「RaspberryPiのGPUを使い倒す本(仮)」で申請してるので、もしサークル当選してたらチェックしていただけると嬉しいです!

【macOSも対応】ARMv8 64bit向けのGCCコンパイル環境を構築する

きっかけ

RaspberryPiのベアメタルプログラミングを始めたくなったのでARM向けのバイナリがはけるコンパイラを準備しようと思った。

想定環境

自分が普段使っているメイン機はMacなのでMacコンパイルしたい。
Mac以外のOS(Windows/Linux)の人は以下の公式のビルド済みツールチェーン から読み始めてください。

自分のMac:$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.5
BuildVersion:   18F132

ターゲットはRaspberryPiのARMチップ、Raspi2以降のCortex-Aシリーズ向けのバイナリがはければいい。
ちなみに以下がRaspiのチップとそのアーキテクチャの対応表。

RaspberryPi CPU's

Raspberry Pi 1系
zero系
Raspberry Pi 2
Model B
Raspberry Pi 3系 Raspberry Pi 4系
CPU ARM 1176JZF-S (ARM Cortex-A7)
ARM Cortex-A53
ARM Cortex-A53 ARM Cortex-A72
アーキテクチャ ARMv6 (ARMv7)
ARMv8
ARMv8 ARMv8

せっかくならARMv8以降なら64bitモード(AArch64)で使いたいので64bit向けバイナリをはいて欲しい。

よって、
MacがホストでターゲットはARM(32bit/64bit)のクロスコンパイラを準備する。

公式のビルド済みツールチェーン(Windows/Linuxの人)

Mac以外のOSを使っている人は、以下のARM Developer公式からビルド済みのツールチェーンが公開されているのでそれをダウンロードして使うだけ。(未検証)
GNU Toolchain | GNU-A Downloads – Arm Developer

このサイトのGNU-A(Cortex-Aファミリ用GNUツールチェーン)は過去にLinaroが配布していたものと同じらしいので以下からもダウンロードできる。
Builds & Downloads - Linaro

MacOS】32bit(AArch32)クロスコンパイラ

一応32bit向けクロスコンパイラの話も。
Homebrewでインストールする方法が以下に書かれている。
Homebrew様様。
www.yokoweb.net

使ってみる

$ arm-none-eabi-gcc -c startup.S -o startup.o
$ file startup.o
startup.o: ELF 32-bit LSB relocatable, ARM, EABI5 version 1 (SYSV), not stripped

MacOS】64bit(AArch64)クロスコンパイラ

急いでるの人のために結論から書くとこれもHomebrewで入れられる。

入れ方

$ brew tap SergioBenitez/osxct
$ brew install aarch64-none-elf

使ってみる

$ aarch64-none-elf-gcc -mcpu=cortex-a53 -c startup.S -o startup.o
$ file startup.o
startup.o: ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), not stripped

しかしながら上記の結論にたどり着くまで文字通り3日3晩悩まされた。
調べ方が悪いのかMacOS向けのAArch64コンパイラ情報が見つからない・・・
ソースコードからビルドしなければいけないのは覚悟していたが、「ビルドしてみた」的な報告情報が全く見つからない。
自分でビルドしてみたところ爆時間かかる上に失敗する。ビルドエラーを取り除くためにGCCソースコードを手作業で修正したりもした。
make -j2でビルドしても普通に1日かかったので3日3晩はあっという間だった・・・

今度からはとりあえず[欲しいもの Homebrew]で検索しようと思う。
Homebrewに感謝🙏

競プロの過去問をPython, Go, Rust 使って黙々と解く_Day3

苦行 3日目

「競プロの過去問をPython, Go, Rust 使って黙々と解く」 という、1人アドベントカレンダーの3日目です!(遅刻3回目)🎉
このアドカレを始めた理由や、ルールは1日目の記事をみてください。

問題3 Some Sums

AtCoder Beginner Contest 083」
B - Shift only

今回は新たに条件分岐を使いそうな問題を選びました。

Python

自分の回答

n,a,b = list(map(int, input().split()))
ans = 0

for i in range(1, n+1) :
    digit_sum = sum(map(int, list(str(i))))

    if a <= digit_sum <= b:
        ans+=i

print(ans)

なんか公式の解説とは違う回答だが、こっちの方が簡単に書けた。

digit_sum = sum(map(int, list(str(i))))

数字を文字配列にしてから、mapで一気にint配列に変換。それの合計を求めている。
これで各桁数字の合計が得られる。簡単。

if a <= digit_sum <= b:

a以上、b以下を判定している。条件式の書き方が数学的な書き方に近いのが、初心者にもわかりやすい上に、andで繋ぐより若干速度が速いので嬉しい。

別解

c = []
for i in range(10):
    for j in range(10):
        for k in range(10):
            for l in range(10):
                c.append(i+j+k+l)
c.append(1)
n, a, b = map(int,input().split())
print(sum([i for i in list(filter(lambda i : a <= c[i] <= b, range(n+1)))]))

提出されている中で2番目に実行速度の速いコード(22ms)がこれなんですが・・・

え、なにこれは・・・(困惑)

どうやら104分の[桁の和]を全て格納したリストcを作ったようです。
こうすることでc[hoge]hogeに桁の合計を求めたい数字を渡すだけで検索できるというわけです。
え、なにそれは・・・(困惑)

新たな学びとしては、filter(function, iterable)は、リストから条件に合う特定の要素だけを抽出できるということです。これは便利そう。

Go

自分の回答

package main

import (
    "fmt"
    "strconv"
    "strings"
)

func main() {
    n, a, b, ans := 0, 0, 0, 0

    fmt.Scan(&n, &a, &b)

    for i := 1; i <= n; i++ {
        str := strconv.Itoa(i)
        char_slice := strings.Split(str, "")
        digit_sum := 0

        for _, c := range char_slice {
            digit_val, _ := strconv.Atoi(c)
            digit_sum += digit_val
        }

        if a <= digit_sum && digit_sum <= b {
            ans += i
        }
    }

    fmt.Println(ans)
    // println(ans) //標準エラーに書き込まれる
}

昨日もそうだったが、自分の組み方が悪いだけかもしれないが、他の2つに比べてコードが長くなりがち。
考え方はさっきのpythonの解き方と同じ。
ただ、Goにはmap()が無いため、forで書いている。
ちなみにmap()的なことしたいという問いに対して、Goの開発に関わっているRob Pike氏が「forを使え」と言っているので、多分言語の思想が他2つと違うのだろう。

うっかり間違えたとこが2つ。

1つ目

digit_val, _ := strconv.Atoi(c)

strconv.Atoi()は、それぞれ、valerrorの2つの値を返してくる。
正しく変換できない時の例外処理が簡単にできるようにだろう。多分。 以下のように1つだけ受け取るように書くと動かない。厳しい。

digit_val := strconv.Atoi(c)

2つ目

   fmt.Println(ans)
    // println(ans) //標準エラーに書き込まれる

fmtPrintlnと、ビルドインのprintlnの違い。
どうやらビルドインのprintlnは標準エラーに書き込まれるらしい。
ビルドインのprintlnを使ったコードをAtCoderに提出した時、結果が全部[WA]になって焦りました・・・

別解

package main

import "fmt"

func main() {
    var n, a, b int
    fmt.Scan(&n, &a, &b)

    var ans int
    for i := 1; i <= n; i++ {
        v := i
        s := 0
        for v > 0 {
            s += v % 10
            v /= 10
        }
        if s >= a && s <= b {
            ans += i
        }
    }
    fmt.Println(ans)
}

別解というか、公式の解説ではこの解き方が解答例として載っていた。
数字vに対して、v /= 10を繰り返すことで桁が一つずつ右にシフトし、v % 10でその時の一番下の桁が取れるので、あとはそれを足し合わせていけば各桁の合計を出したことになるわけだ。
やっぱりstrconvとかsplitが遅いのか、こっちのコードの方が自分のコードより5倍速い。(自分:5ms, 別解:1ms)

Rust

自分の回答

use std::io::*;
use std::str::FromStr;

fn read<T: FromStr>() -> T {
    let stdin = stdin();
    let stdin = stdin.lock();
    let token: String = stdin
        .bytes()
        .map(|c| c.expect("failed to read char") as char)
        .skip_while(|c| c.is_whitespace())
        .take_while(|c| !c.is_whitespace())
        .collect();
    token.parse().ok().expect("failed to parse token")
}

fn main() {
    let n: u32 = read();
    let a: u32 = read();
    let b: u32 = read();

    let mut ans: u32 = 0;

    for i in 1..n + 1 {
        let digit_sum: u32 = i.to_string().chars()
                          .map(|c| c.to_digit(10).unwrap()).sum();

        if a <= digit_sum && digit_sum <= b {
            ans += i;
        }
    }
    println!("{}", ans);
}

pythonと全く同じだこれぇ!!

let digit_sum: u32 = i.to_string() //int -> string
                  .chars() //string -> Vec<char>
                  .map(|c| c.to_digit(10).unwrap()) //Vec<char> -> Vec<int>
                  .sum(); //Vec<int> の総和

コメントの通りです。pythonのセクションで開設したので特にいうことはありません・・・
書きやすかったけど、これはRustの特徴を生かし切れているのか・・・?

書くことがなさすぎるので、強いてポイントをあげるとすれば、
string -> intu32::from_str(str).unwrap()ですが、char -> intchar.to_digit(10).unwrap()です。
.to_digit(10)の引数には何進数に変換するか指定します(この場合10進数)
普通に1つのcharを変換するならlet i: u32 = i as u32 - 48とかできます。

別解

fn main() {
    let scan = std::io::stdin();
    
    let mut line = String::new();
 
    let _ = scan.read_line(&mut line);
    let vec: Vec<&str> = line.split_whitespace().collect();
 
    let n: i32 = vec[0].parse().unwrap();
    let a: i32 = vec[1].parse().unwrap();
    let b: i32 = vec[2].parse().unwrap();

    let mut ans = 0;
    for i in 1..n+1 {
        let s = i/10000+(i%10000)/1000+(i%1000)/100+(i%100)/10+(i%10);
        if s >= a && s <= b {
            ans += i
        }
    }
    println!("{}", ans);
 
}

コード自体は愚直に104分、書く桁の割れる回数を足し合わせることで各桁数の合計を出してる。(お金の計算とかでこんなコード書いた気がする)
で、気になったのは自分の書いたコードとこのコードでは、ms単位では実行速度が変わらないということ。(どちらも実行速度は2ms)
こっちのコードの方が速い気もするが・・・なぜだ?🤔
コンパイラが優秀なのかもしれない。

まとめ

今回から実行速度と、メモリ使用量も記録してみました。
自分の回答の情報はそれぞれ以下の通り。

言語 実行速度(ms) メモリ使用量(KB)
Python 33 2940
Go 5 1280
Rust 2 4352

やっぱ同じアルゴリズム書いてもRustが速いっすね。

日に日にスラスラかけるようになってきているのが楽しい今日この頃です💪
だんだんPythonとRustが好きになってきました。Goはまだ掴めない感じ。
3日目で得た教訓は「Goは書き方によって爆速になるかも」ということでした。
以上、また明日!

競プロの過去問をPython, Go, Rust 使って黙々と解く_Day2

苦行 2日目

「競プロの過去問をPython, Go, Rust 使って黙々と解く」 という、1人アドベントカレンダーの2日目です!(遅刻)🎉
このアドカレを始めた理由や、ルールは1日目の記事をみてください。

問題2 Shift only

AtCoder Beginner Contest 081」
B - Shift only

今回は新たに配列的な物ループを意識した問題を選びました。

Python

自分の回答

def how_many_divided(a_val):
    cnt = 0
    while a_val>0 and a_val % 2 == 0:
        a_val = a_val//2
        cnt+=1
    return cnt


n = int(input())
a_lst = list(map(int, input().split() ))

ans = min(map(how_many_divided, a_lst))

print(ans)

map()の使い方を学んでからというもの、map()を活用したコードを書くのが楽しくなってきました。
さて、解き方はそんなに変な解き方はしてないと思います。(後から解説をみましたがだいたい合ってた)
各値が、0奇数になるまで割り続け、割った回数の最小値を答えとする。といった単純明快なコードです。
今回新たにwhile()min()を使いましたが、特に深く考えたところはありません。何も考えず無心で書きました。
ポイントがあるとすればa_val//2のとこで書いてる//ぐらいです。
python/で割り算すると0.0のように実数で答えが返ってくるんですが、//で割ると整数の範囲で答えが返ってきます。

別解

import fractions

N = int(input())
num = list(map(int, input().split()))
ans = num[0]

for i in range(1, N):
    ans = fractions.gcd(ans, num[i])

if ans % 2 == 1:
    print(0)
else:
    cnt = 0
    while ans/2 == int(ans/2):
        ans /= 2
        cnt += 1
    print(cnt)

これが累積GCDの力か・・・! なるほどなと思いました。
ちなみにpython 3.5以上ではgcd()mathに入ってますね。

Go

自分の回答

package main

import "fmt"

func main() {
    n, ans := 0, 0

    fmt.Scan(&n)
    a := make([]int, n)

    for i := range a {
        fmt.Scan(&a[i])
    }

    for i, v := range a {
        cnt := 0
        for v > 0 && v%2 == 0 {
            v = v / 2
            cnt++
        }

        if i == 0 || cnt < ans {
            ans = cnt
        }
    }

    fmt.Println(ans)
}

正直自分ではモヤモヤする仕上がり。スマートなコードではない気がする・・・

入力

今回から入力にfmt.Scan()を使うことにした。すげー使いやすい。

a := make([]int, n)

ループ

ここで、要素数nint型sliceを宣言しています。
goは、配列的なものとして、arraysliceがある。イメージとしてはarrayが固定長配列で、sliceが可変長配列なのかな?
内部的にはarraysliceがラップしている感じらしい。納得。

for i := range a {
        fmt.Scan(&a[i])
    }

このforは、pythonで言うfor inの感じに似ている。と言うかそのまんま。いわゆるforeach ループ
ただし、特徴的なのはfor変数(仮名)に渡される値。以下のように変数1Index変数2Valueが渡される。

for Index, Value := range hoge { ... }

Value用の変数は省略可能なので、値を取り出したくてpythonのようにfor i := range a {}と書くと0 1 2...とIndexしか得られない。
結構個人的にはこの仕様は気に入っています☺️

whileという制御文はgoに存在しない。for 条件式 {}という書き方によってforがwhile的な働きをする。
for{}だけだと無限ループ。なぜwhileを採用しなかったのかはきになるところ🧐

別解

package main

import (
  "fmt"
)

func main() {
  var n, ans int
  fmt.Scan(&n)
  var a = make([]int, n)
  for i := 0; i < n; i++ {
    fmt.Scan(&a[i])
  }

  flag := true
  for flag {
    for i := 0; i < n; i++ {
      if a[i] % 2 == 0 {
        a[i] /= 2
      } else {
        flag = false
      }
    }
    if flag {
      ans++
    }      
  }
  fmt.Println(ans)
}

大体みんな自分と似たり寄ったりな回答でしたが、ちょっと面白かったのがこのコード。
for flag { for hoge { } }の多重forの中でflag = falseすることで多重forを一気に抜けるというもの。賢い。
しかし、Goのbreakはラベル指定でbreakできるので(goto文みたいな感じ)、正直この書き方は好まれないのかも・・・?
結構私は好きですが。

Rust

自分の回答

use std::io::*;
use std::str::FromStr;

fn read<T: FromStr>() -> T {
    let stdin = stdin();
    let stdin = stdin.lock();
    let token: String = stdin
        .bytes()
        .map(|c| c.expect("failed to read char") as char) 
        .skip_while(|c| c.is_whitespace())
        .take_while(|c| !c.is_whitespace())
        .collect();
    token.parse().ok().expect("failed to parse token")
}

fn how_many_divided(mut a_val: usize) -> usize {
    let mut cnt = 0;

    while (a_val > 0) && (a_val % 2 == 0) {
        a_val /= 2;
        cnt+=1;
    }

    return cnt
}

fn main() {
    let n: usize = read();

    // let mut a_vec: Vec<usize> = Vec::with_capacity(n);
    let mut a_vec: Vec<usize> = vec![0; n];

    for i in 0..n {
        a_vec[i] = read();
    }

    let ans = a_vec.into_iter().map(|a| how_many_divided(a)).min().unwrap();

    println!("{}",ans);
}

入力の諸々を関数にまとめました。それだけで昨日の苦労が嘘だったかのように快適にコーディングできました。

vector

// let mut a_vec: Vec<usize> = Vec::with_capacity(n);
let mut a_vec: Vec<usize> = vec![0; n];

素数nで、全ての要素が0で初期化されたベクトルを用意しています。
コメントアウトしている書き方でも同じことができますが、マクロを使った方が個人的に見やすい感がある。

for

for i in 0..n { }

0からnまでのループです。」と、言わなくてもすぐに理解できる。
この書き方は非常に好き(段々ここすきポイントの紹介になってきている)

mapあたり

a_vec.into_iter() //イテレータを生成
    .map(|a| how_many_divided(a)) //割れる回数をカウント
    .min() //最小値を取得
    .unwrap();

コメントの通りです。
pythonと似たような組み方ができたので、昨日の苦労が嘘のようにスラスラと書けました。楽しい。

概念を理解するのはまだまだ難しそうだが、書くことに関してはそこまで苦無くできそうな気がしてきた。(標準入力以外は)

別解

use std::io;

fn read_line() -> String {
    let mut line: String = String::new();
    io::stdin().read_line(&mut line).unwrap();
    return String::from(line.trim());
}

fn main() {
    let n: i64 = read_line().parse().unwrap();
    let ans: u32 = read_line().split_whitespace().map(|s| s.parse::<u32>().unwrap().trailing_zeros()).min().unwrap();
    println!("{}", ans);

}

天 才 か 。

問題の2で割れる回数という条件と、二進数の特徴を使った非常に賢くて、スマートな解き方です。

let ans: u32 = read_line().split_whitespace() //空白区切の数字を読み込み
    .map(|s| s.parse::<u32>().unwrap() //キャスト
        .trailing_zeros()) //2進数で「下のビットから0が続く数」を取得
        .min() //最小値を取得
        .unwrap();

2進数で偶数を表した時、「下のビットから0が続く数」で割れる回数がわかるということですね!
4 -> 100 -> 0は下から2個 -> 2回割れる
しかも奇数がきた場合必ず「下のビットから0が続く数」は0になります。
5 -> 101 -> 0は下から0個

まとめ

1日目に比べると、驚くほどスラスラ書けて全体的に楽しかったです!
もしかしてここで扱っている言語は標準入力についてあまり優しくないのかも・・・
そもそもそんなに必要ないしね。 2日目で得た教訓は「標準入力周りは言語によってはあんまり重要視されてないのかも」ということでした。
以上、また明日!

競プロの過去問をPython, Go, Rust 使って黙々と解く_Day1

苦行

「競プロの過去問をPython, Go, Rust 使って黙々と解く」 という、1人アドベントカレンダーを立てました。
今日はその記念すべき1日目です!(遅刻)🎉

始めた理由

各言語を選定した理由は以下の通り。

  • Python・・・競プロで使いたいから
  • Go・・・面白そうだから
  • Rust・・・OSの研究とかで使いたいから

で、「各言語に慣れるには・・・」と考えたときに競プロの問題を例題に素振りをしようと思ったわけです。
この学習方法が良いのかはわかりませんが、とにかく「物は試し」やっていきます。💪

Qiitaとかに投稿するような内容でも無いので、はてなブログを新たに開設して書いていくことにしました。 ブログ初心者ですので悪しからず。

ルール

一応1日目なので、このアドベントカレンダー(1人)のルールを決めておきます。

  1. 言語は Python, Go, Rust それぞれを毎回使う
  2. 解く問題は「AtCoder Beginner Contest」の過去問、AB問題中心
  3. 「言語に慣れる」「特徴を理解する」が目的なので解く速さは問わない

各回の流れ

最初に過去問から問題を選定し、以下の流れを各言語で行います。

  1. 最初は言語リファレンスをみながら自分で解く努力をする
  2. 他の回答者のコードを見て、より良い書き方や、別解を学ぶ
  3. 知見をまとめる

問題1 Welcome to AtCoder

AtCoder Beginners Selection」
PracticeA - Welcome to AtCoder

まずはどの言語も初回なので、プラクティスをやっていきましょう。

Python

自分の回答

a = int(input())
b,c = map(int, input().split() )
s = input()

print(a+b+c, s)

Pythonはちょっとだけ触れていたので、「これは簡単だろう」と意気込んで書いたコード。

  • 標準入力はinput()
  • intへのキャストはint()
  • 文字列の切り分けはsprit()(デフォルトで空白を区切り文字とする)
  • map()map( 関数, [list] )の構文で、listの各要素に関数を適用

といった感じ。

別解

コード長が短い順で探して一番上位に出てきたコード。 https://atcoder.jp/contests/abs/submissions/4760515

a=input;print(sum(map(int,[a()]+a().split())),a())

こ、これは。。。 短いコードなので美しい・・・のか?
一瞬びっくりしましたが、どうやらinput関数をaに代入しているようです。

python関数をオブジェクトとして変数に格納することが可能なようで、上記のコードで言うa関数オブジェクトと呼ばれるものなんだとか。
私は手続き型言語に慣れているせいもあってか、ちょっと理解しづらいが、関数型言語ライクな考え方・・・? で合ってるのかな。 要勉強。

それから;区切りで1行で複数の処理を記述できるようです。下記が例コードです。
コード

print("hello");print("world")

出力

hello
world

知らなかった。(しかし使うことあるのか・・・?)

Go

自分の回答

package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
)

var sc = bufio.NewScanner(os.Stdin)

func nextLine() string {
    sc.Scan()
    return sc.Text()
}

func toInt(str string) int {
    i, e := strconv.Atoi(str)
    if e != nil {
        panic(e)
    }
    return i
}

func main() {
    sc.Split(bufio.ScanWords)

    a := toInt(nextLine())
    b := toInt(nextLine())
    c := toInt(nextLine())
    s := nextLine()

    ans := a + b + c

    fmt.Println(ans, s)
}

Goフォーマットにより美しく整形されました。
色々とimportして便利に使えるようです。頼もしい!

var sc = bufio.NewScanner(os.Stdin)

入力にはバッファIOパッケージを使いました。
.NewScanner()でファイルを指定して新たなスキャナーを作る。今回は標準入力なのでos.Stdinを指定しました。

sc.Split(bufio.ScanWords)これをつけるとどうやら空白区切で読み込んでくれるらしい。

sc.Scan()で入力トークンをスキャンして、sc.Text()でスキャンしたトークンから文字列を取り出すようです。なぜ二段階に分けたのだろう。。。あとで調べてみます。

整数型へのキャストはstrconv.Atoi()でやりました。こいつはキャストした結果と一緒に標準エラー情報を返してくるようなのでハンドリングしました。

あと、全体的に:=は変数の初期化に使うと学びました。(var hoge = fugaの短縮?)

別解

package main
 
import "fmt"
 
func main() {
    var a, b, c int
    var s string
 
    fmt.Scanf("%d", &a)
    fmt.Scanf("%d %d", &b, &c)
    fmt.Scanf("%s", &s)
    fmt.Printf("%d %s\n", a+b+c, s)
}

実行速度を求めないのであればfmt.Scanf()を使う方が多いようですね。
確かに、C言語チックで書きやすい上にコード量も短くなるかも!
私とほとんど同じようなコードを書いている方も何人かいました。ちょっと安心。

Rust

自分の回答

use std::io;
use std::str::FromStr;

fn main() {
    /* 入力 */
    let mut a = String::new(); //空のStringを作る
    io::stdin().read_line(&mut a).expect("Failed to read line"); //1行読み込み
    let a: u32 = a.trim() //文字列前後の空白を取り除く
        .parse() //キャスト
        .expect("Please type a number!"); //エラーハンドリング

    let mut it = String::new();
    io::stdin().read_line(&mut it).expect("Failed to read line"); //1行読み込み
    let mut it = it.split_whitespace().map(|n| u32::from_str(n).unwrap());
    let (b, c) = (it.next().unwrap(), it.next().unwrap());

    let mut s = String::new();
    io::stdin().read_line(&mut s).expect("Failed to read line"); //1行読み込み

    /* 計算 */
    let ans = a+b+c;

    /* 出力 */
    println!("{} {}",ans,s);
}

一番辛いっす・・・。
今回1つの問題にしかチャレンジできなかったのは、こいつの理解にすごい時間がかかったからです・・・

さて、いったいどこで何をしているのかじっくり見ていきます。 言語の基本的な理解は、公式チュートリアルの日本語翻訳版が非常に役立ちました。

まず標準入力を受け取りたい

入力された文字列を格納する空の文字列を作ります。

let mut a = String::new(); //空のStringを作る

なんとなくインスタンス生成を彷彿とさせるが、公式曰くどうも自分のイメージとは違うらしい。

これは String のインスタンスではなく String 自体に関連付けられているということです。 これを「スタティックメソッド」と呼ぶ言語もあります。

うーん、わからん。 これについては要勉強。

次に1行読み込んでさっきのaに入れます。

io::stdin().read_line(&mut a).expect("Failed to read line"); //1行読み込み

stdin()クラスのread_line()メソッドを呼び出してる感じかな?
しかし公式チュートリアルの日本語訳ではio::stdin()はクラスではなく関数という呼ばれ方をしている。

mutについて

read_line()はその名の通り1行読む。で、&mut aで代入先を指定している。
地味なところではあるが、&mutをつける意味が、わかったようなわかってないような...まだ若干考えが定着しない部分だと思った。
詳しくは公式の ミュータビリティ(mutability) に書いている。

【ここからふわふわ(にわか)説明】
前提知識として、rustはデフォルトで「変数の再代入は許さない」「値の変更は許さない」という特徴があります。
なのでlet hogeで宣言される変数は、いわゆるconst定数となります。
再代入可能な変数にしたい場合mutをつけます。

let mut x = 5;
let y = &mut x;

で、このようなコードを書くと、ybの参照を渡すこととなる。
そして、let yは不変なのでyは参照先を束縛されます。
よって、以下のように参照渡しをもう一度行うことは不可能ということです。

let mut x = 5;
let y = &mut x;

y = &mut z; //これダメ

しかし、&mutをつけているのでyに束縛されているものを変化させることは可能です・・・はい。

let mut x = 5;
let y = &mut x;

*y = 6;

色々長いこと書きましたが、要はコードの以下の部分は、再代入可能な変数itの参照渡しを行なっているということです。多分。(投げやり)

.read_line(&mut it)

さて、ここまで調べたところでアドベントカレンダー初日から記事投稿の遅刻が確定しました😇
しかしまだまだ問題解答には遠いです、初日から気合い入れていきましょう!

さて、mutについてだいぶ時間を割いてしまいましたが、話を標準入力に戻しましょう。
と言っても残っているのはこの部分だけ、

.expect("Failed to read line");

これはエラーハンドリングですね、私は面倒くさがりなので最初この部分を書きませんでしたが、コンパイラに「書け」と怒られました(warning)
これは結構言われるので、従順にしたがっていれば安全なコードが書けそうです。

キャスト

let a: u32 = a.trim() //文字列前後の空白を取り除く
        .parse() //キャスト
        .expect("Please type a number!");

さっき読み込んだデータは文字列として格納されてるので、整数型にキャストしましょう。
読み込んだ文字列には改行とかも含まれているので、trim()で切り落とします。
.parse()がキャスト関数なんですが、何にキャストするかは変数の型から推測するそうです(賢い)
そのためにlet a: u32で明示的に型を指定しています。
aはさっきまでString型でしたが、rustは変数の定義を新しいもので隠すことができる(上書きなのかな?)らしいので、名前を再利用しています。

これで整数が1つ読み込めました・・・! えらい。

複数の値を読み込む

2 3 4

のように、1行から空白区切の複数の値を読み込むのが以下の部分です。

let mut it = it.split_whitespace().map(|n| u32::from_str(n).unwrap());

.split_whitespace()は、名前のまんまの仕事をしています。わかりやすい。
さて、これで切り分けられた複数の文字列が手に入るわけですが、これを全部整数型にキャストしなければなりません。

.map(|n| u32::from_str(n).unwrap());

先ほど、pythonmap()に触れていたので考え方はそんなに難しくなかったです。
要素の1つ1つをnとして、そのnに対してu32::from_str(n)でキャスト変換をします。
.unwrap()from_str()の戻り値がResult<T,Err>という型で返って来るんですが、それのT部分を抜き出す関数です。とことん安全について考えられてますね・・・

別解

別解というか、競プロのテクニックだと思うんですが、やはり入力は真面目に毎回書いてたら手間なので、多くの人が例えば以下のように関数としてまとめているようですね。

fn read<T: FromStr>() -> T {
    let stdin = stdin();
    let stdin = stdin.lock();
    let token: String = stdin
        .bytes()
        .map(|c| c.expect("failed to read char") as char) 
        .skip_while(|c| c.is_whitespace())
        .take_while(|c| !c.is_whitespace())
        .collect();
    token.parse().ok().expect("failed to parse token")
}

確かに・・・1から書いてみると納得。

まとめ

1日目から早速苦行でした・・・・
文字ばっかりでわかりづらいまとめになったので、「図も入れたいな」と思うけどそんな余裕はないかも・・・
しかしながら、今日で入出力部分はしっかり理解できたと思うので、明日からやっと、実際にもっと計算する問題を解いていきましょー!
1日目で得た教訓は「Rustは強いけど最初辛い」ということでした。
以上、また明日!