Kotlin Heroesに挑戦してみた

Kotlinを使ったプログラミングコンテストに参加した。たまたまTwitterで流れてきたのを見て、たまたま時間が空いてたので挑戦してみた。

https://codeforces.com/contests

腕試しになるかなぁという程度の軽い気持ちでの挑戦であったが、「俺ってまったくできないじゃん」と凹む結果に終わってしまった。 そんなに深刻になるレベルではないけれど、それなりにできるんじゃないかという妙な自身は見事に砕かれた。 もっと地道に精進しようと反省である。

Read full post gblog_arrow_right

ブログをWordPressからHugoに移行した

前々からブログをWordPressで運用するのをやめたいと思っていた。 すったもんだのすでにようやくHugoで運用するようにできたので、経緯やら苦労したところなどを書き残しておきたい。 まだ作業途中ではあるのだが、いい加減記事の更新もしたいので書いておく。

Read full post gblog_arrow_right

LeetCodeでプログラミング学習

最近LeetCodeというサイトを利用してプログラミングの学習をしている。学習と腕試しと頭の体操を兼ねてというのが正確なところだろうか。 自分の中では頭の体操的な位置づけが大きい。設定された問に対して、どうやったら解けるか自分で考え実装してみる。実装の過程で、ついでにKotlinの勉強にもなったらなぁなんて感覚である。 そう、Kotlinで解いている。つい最近も、groupingByなるメソッドが用意されていることを知ったが、問題を解いている最中にこういった新たな発見に出会えるかもしれないというのが、Kotlinの勉強にもなるかなあと思っている所以である。 競技プログラミングという観点で見れば、日本語でできるAtCoderが有名だろう。こっちのほうが計算量やら書いたプログラムの効率性をちゃんと測れて良いと思う。LeetCodeでも処理時間は出るが、Kotlinで解いた場合、同じコードでもSubmitするたびに100msくらいの誤差が出る場合があって、まったく指標として役に立たない。ついでにKotlinで解いている人は少ないのか、提出されたコードの100%より早いです=自分が初めてKotlinで提出した、なんてパターンがあってさらに指標にならない。 そもそも競技プログラミングとしてやっているというよりは、先にも書いたとおり、私の場合は学習のためという面が大きい。その観点で見ると、LeetCodeは英語であることを除けばやりやすかった。AtCoderだと標準入力から入力を読み取る部分からやらないといけないのに対して、LeetCodeは引数として渡ってくるので純粋に解法に集中できるのが良かった。あとはAtCoderだとKotlinのバージョンが1.0.0で、対してLeetCodeが1.2.50だったというのも理由の1つではある。 英語であることは、障壁というよりは英語の学習にもなっていいかもしれないと考えている。英語の学習とはならなくとも、英語に慣れるのに役に立つだろう。ちなみに問題によっては英語が理解できずにそもそも解く以前の問題で詰まってしまうこともあるけれど、そういう場合は他の理解できる問題をやるようにしている。 LeetCodeのいいなと思ったところは、問題を解く以外にも学習用コンテンツがあることが挙げられる。 例えばIntroduction to Data Structure – Binary Treeは二分木についてのコーナーである。一部は課金しないとアクセスできないが、無料でもできる部分がいくつかある。 各ノードを巡回するにはどうしたらよいかという問いにすら苦労するのだが、だからこそ勉強になる。わかるわかる、再帰使えばいいんでしょと思っても、実際に適用しようとすると手が止まってしまった。 これに限らないのだが、再帰処理をすれば解けそうという場合に、どこからどこまでが再帰処理に必要なのかがすぐに言語化できない。だからなかなかコードに落とせない。LeetCodeをやったからと言ってすぐに言語化できるようになるわけではないのだが、実際に問題に直面して解くという経験を通じて徐々に理解を深めていけたらいいなぁと思っている。 そういえば感覚で理解しているといえば、lambda式も結構感覚で使っている。こう書いたらこうなるんでしょというノリで使っているということか。 こういう問題には自分でアプリを作るだけではなかなか出会えないので、こういうサイトを利用して学習するのもよいのではないかなと思う。 問題の探し方 私はProblems→Difficultyで難易度選択、Listsで「Top 100 Liked Questions」にチェックを入れてフィルタリング、その上でSolutionつきの問題を解くようにしている。 問題の質は玉石混交で「なんやねんこれ」というような問題もあったりする。likeが多い問題はそれだけ勉強になると思った人や面白いと思った人が多いということになる。さらに解説付きであれば解法の勉強にもなるだろうからなおおすすめである。 Solutionがないやつは、ディスカッションで他の人の解法を見ることも可能ではあるが、解説になっているとは限らないのでSolutionつきのものを選んでいる。

KotlinのgroupingByについて調べてみた

KotlinにgroupingByなる関数があることを知った。 https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/grouping-by.html Groupingソースなるものを作成するための拡張関数で、listとか配列で使うことができる。 https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-grouping/index.html 例えば”I have a pen”で各アルファベットが何回出現しているかを調べるのに使える。 val result = "I have a pen".groupingBy { it }.eachCount() println(result) // {i=1, =3, h=1, a=2, v=1, e=2, p=1, n=1} groupingBy自体は続く関数オブジェクトで求められるkeyを元にしたMapへ集計できるようにするためのインターフェースで、これ自体呼び出しても何も起こらない。上記の例でいうとeachCount()を呼び出して初めて集計が行われる。 groupByとの違い keySelectorを引数に取るのはgroupByもgroupingByも同じ。 groupByだと指定したkeyごとの要素をListにもつMapを返す。イメージ的にはmapに近い処理。 groupingByはそれ自体は何もしない。keyを元に集計処理を行うインターフェースを用意するだけのメソッドなので、その後に別途集計処理が必要になる。forEachを拡張したものと考えるといいかもしれない。 両者の使い分けは、keyを元にした要素のリストがほしいのか、それともその要素を何らかの処理をして集計した結果だけが欲しいのかで使い分けることになるだろう。集計した結果のみが必要なのであれば、その中間形態であるkeyごとの要素リストは不要なので、groupingByを使ったほうが効率的である。 集計処理 groupingByだけでは集計処理は行われないので、その後に以下のいずれかを利用して集計を行う。 aggregate fold reduce eachCount それぞれ別にxxxToという処理も用意されていて、違いは集計先のMapが指定できるかどうか。Toがついている方は、既存のMapが集計先に利用されるので、前の集計結果にさらに付け足すのに使える。Toがつかない方は空のMapが集計先として利用される。 groupingBy自体は要素のグルーピングを行うわけではなく、aggregateなどを呼び出すことで初めて要素のグルーピングと集計が行われる。 fold / reduce / eachCount の使い分け よほど特殊な事情がない限り、aggregateを直接使うことはないと思われる。大体のケースでfoldを使ったほうが便利だろう。 というのも、aggregateは要素がkeyによるグルーピングを行った最初の要素かどうかを判定したりするのも全て自分で書く必要があるからだ。要素が初出の場合に初期値を用意し、そうでない場合に集計処理をするというのがfoldなので、大抵のケースでfoldで事足りるはず。 最終的にはどれを使ってもList<何らかの型>がMap<指定したキー, 集計後の結果>に集計される。(元のデータがListとは限らないけれど、最終的にMapに集約されるのは変わらない) eachCount keyごとの要素の個数が欲しい場合にこれを使う。引数もいらないので最もシンプル。 foldとreduce 要素の集計にロジックが必要な場合にこちらを利用する。オブジェクトの特定のフィールドだけが集計対象であるときなどに利用することが考えられるだろうか。 どちらも集計処理を行う関数オブジェクトを引数にとるのは同じ。 違いはfoldは集計値の初期値を設定する必要があるが、reduceは初期値すら省略できるというところ。reduceはkeyごとに出現した最初の要素が初期値に使われるからである。 集計処理を行う関数オブジェクトは、集計後の値を返すような関数オブジェクトにすればいい。この関数オブジェクトの戻り値が、次の要素のaccumulatorの値になる。 foldとreduceの違い 集計結果が要素と同じ型になるのかどうかが使い分けの分岐点となる。集計結果が元の要素と同じ型ならreduceを使ったほうが便利(初期値の指定がいらないので)。 というのもreduceの初期値はkeyごとに最初に出現した要素になるからである。だから型の変換が行えない。 data class SalesInfo(val id: String, val date: Date, val sales: Int) val dailySales = listOf(...) // 日々の売上データ // 商品IDごとに売上を集計 val sum = dailySales.groupingBy { it.id } .fold(0) { _, acc, element -> acc + element.sales } println(sum["hoge"]) // 商品ID"hoge"の1ヶ月分の売上を表示 上記の例で言えばSalesInfo型の要素をInt型に集計している。このケースではreduceは使えない。
Read full post gblog_arrow_right

テストを実行した跡にレポートを自動的に開く

gradle経由でテストタスクを実行すると、テスト結果のレポートがbuild/reportsディレクトリに出力される。このレポートファイルを確認するのに、毎回ProjectウィンドウをProject表示に切り替えて、ディレクトリを掘ってファイルをブラウザで開くようにするのは手間である。そこでテスト実行後に自動的に開くようにした。 もっとスマートにやる方法があるのではないかと思うのだが、Gradleがよくわからなくて私には無理だった。以下のタスクをapp/build.gradleに追加した。 task openReportJvmTest(type: Exec) { workingDir "build/reports/tests/testProdDebugUnitTest" commandLine 'open' args "index.html" } task openReportInstrumentedTest(type: Exec) { workingDir "build/reports/androidTests/connected/flavors/MOCK" commandLine 'open' args "index.html" } openReportJvmTest.onlyIf { !ciBuild && !travisBuild } openReportInstrumentedTest.onlyIf { !ciBuild && !travisBuild } tasks.withType(AbstractTestTask) { task -> if (task.name == "testProdDebugUnitTest") { task.finalizedBy openReportJvmTest } } tasks.withType(com.android.build.gradle.internal.tasks.AndroidTestTask) { task -> if (task.name == "connectedMockDebugAndroidTest") { task.finalizedBy openReportInstrumentedTest } } openReportXXXというのがテストレポートをブラウザで開くタスク。開くディレクトリを直書きしているが、がんばればタスクによって開くディレクトリを変えることはできるだろう。openコマンドを使っているが、Windowsだときっと動かない。 既存のテストタスクのFinalizerタスクとして実行するよう指定しているが、CI上ではopenコマンドが認識できないので失敗してしまう。そこでCI上では実行しないようにonlyIfで指定している。 pluginで定義されているテストタスクの後に実行させたいのだが、tasks.withTypeを使うことで各タスクの参照を得ている。直接タスクを指定すると「そんなプロパティはない」と言われたり、nullだったりしてうまくいかなくてこういう形に行き着いた。 取得したテストタスクそれぞれでfinalizedByを使ってレポートを開くタスクを指定している。dependsOnを使うとテストが全てパスしていればレポートが開くが、1つでもテストが失敗しているとopenReportタスクが実行されないので、finalizedByを使っている。 http://gradle.monochromeroad.com/docs/userguide/more_about_tasks.html 余談 そもそもRun configurationでテストを実行できるようにすればいいのではないかと思うかもしれない。というか私も最初はその方法をとった。 しかしAndroid Instrumented Testに関してはできたが、Android JUnitではできなかった。 個別のテストをクラスを指定して実行する分には問題ないが(テストコードを表示して実行するやり方)、プロジェクトに存在するテストコードをまとめて実行する方法が取れなかった。Javaで書かれたテストコードは実行されても、Kotlinで書かれたテストが漏れてしまうのである(このプロジェクトはJavaで書かれたテストコードとKotlinで書かれたテストコードが混在している)。 しょうがないのでCIでも実行するgradleのテストタスクを走らせる方法をとることにしたのだ。

Floobitsを試してみた

Floobitsというのはリアルタイムコラボレーションを実現するためのサービス。 VSCodeのVisual Studio Live Shareみたいなやつで、リモート経由でのペアプログラミングとかを実現するためのサービスである。 リモート経由でペアプロしたいが、Android開発でそんなことできるのかというのが出発点だった。 Android StudioのベースとなっているIntelliJ IDEAでもそういう機能があるのか調べたところ、今の所ないというのが結論。 要望自体ははるか昔からあるみたいだが、実現するにはコラボレーション用のサーバ用意したりとハードルがあまりに高いだろうことは想像に難くない。 公式には用意されていないが、見つけたのがこのFloobitsというサービスである。 別にIntelliJに限らず、他のプラットフォームでもプラグインが用意されている。 https://floobits.com/help/plugins Emacs, Sublime Texxt, Neovim, IntelliJ, Atomと多数のエディタに対応している。 Free planであれば5つのWorkSpaceを持てる。 Android Stuioでいえば1つのプロジェクト=WorkSpaceになるだろう。 Free planではprivateなWorkSpaceを持つことはできないので、ソースコードは誰でも見れる状態になる。 Edit権限を与えなければ勝手に編集されることはない。 ちなみにWorkSpaceはActive Workspaceで確認できる。 どんな感じなのか気になったので、パソコン2台使ってとりあえず試してみた。 試した環境が同一LAN内にあるPCではあったことが関係している可能性はあるけれど、遅延はほぼないと思っていいだろう。 Summonなる機能があって、これを使えば他のコラボレーターを自分が編集しているファイルに瞬時に招集することが可能。 「このファイルがさー」「どれだよ?」 なんてときに活躍しそう。 とりあえず試したのはコード編集だけではあるが、他にもチャットができたりコラボレーションのための便利な機能が盛り込まれている。 ただ、ペアプロに便利とはいうものの、Floobitsはあくまでリアルタイムでのペアプロ目的に使うにとどめた方がいいだろう。 Floobitsにアップロードしたプロジェクトを、Floobitsに接続しない状態で更新→FloobitsのWorkSpaceに接続という流れになると、リモートのファイルを上書きするか、リモートのファイルでローカルを上書きするかの2択になってしまう。 Gitでバージョン管理していても、FloobitsのWorkSpace自体はGitで管理されているわけではないので、下手するとGitの履歴自体が上書きされてなかったことにされる恐れもありそう。 プロジェクトはGitで別途管理して、サブ的にFloobitsを使うという感じがいいのかもしれない。 ゼロからいきなりFloobitsのWorkSpaceにジョインして開発を進めるとおかしなことになりそう。 gitと併用してFloobitsを使う場合のFAQがあった。 https://floobits.com/help/faq 運用でカバーする必要があるっぽい。 リモート経由でペアプロするのには非常に便利だと思う。 またFloobitsはAndroidに限らず使えるというのは便利な点だろう。 セキュリティ的にどうなんだというところがクリアできるなら、普通に便利な気がする。

TouchDelegateを使ってタッチ可能領域を拡張する

Buttonのクリックに反応する範囲を拡張したい時がある。 今回は下図のようなカスタムViewを作りたかった。 ImageButtonとTextViewを内包したViewである。 親レイアウトのViewGroupをタッチする=ImageButtonをタッチするという扱いにしたかった。 ImageButtonにしているのは、他のViewとの兼ね合いである。 他のViewはImageButtonにstyle="@style/Base.Widget.AppCompat.ActionButton"を適用して、アイコン画像の周りにだけリップルエフェクトがかかるようになっていて、このカスタムViewもそれに合わせたかったのである。 これはTouchDelegateを使うと実現できる。 TouchDelegate – Android Developers 使い方 TouchDelegateは親のViewGroupに設定する。 TouchDelegateのインスタンスを作成するには、タッチエリアを表すRectと委譲先のView(今回でいうとImageButton)への参照が必要。 Rectはローカル座標系(ディスプレイ上での絶対座標ではなく、ViewGroupの左上を0とした相対座標)を使う。 今回はViewGroupのgetDrawingRect()を使って取得したRectを使った。 ぐぐるとgetHitRect()を使った例がみつかったが、これだとうまく動かなかった。絶対座標になっているからだと思われる。 コード例 class CommentStatusView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr) { private lateinit var binding: ViewCommentStatusBinding init { binding = ViewCommentStatusBinding.inflate(LayoutInflater.from(context), this, true) doOnPreDraw { val rect = Rect() binding.viewGroup.getDrawingRect(rect) binding.viewGroup.touchDelegate = TouchDelegate(rect, binding.imageButton) } } override fun setOnClickListener(l: OnClickListener?) { binding.imageButton.setOnClickListener(l) } } Kotlinで、Android-ktxとDataBindingを使っている。 doOnPreDrawがAndroid-ktxを使っている部分で、ViewTreeObserverを利用してViewの大きさが決まった後で中のブロックの処理(TouchDelegateの設定)を行っているだけである。 後はこのカスタムViewに対するsetOnClicklistenerをImageButtonに対して設定するようにしている。 ポイントは 親のViewGroupに対してTouchDelegateを設定する rectはTouchDelegateを指定するViewGroupからみて、どの位置のタッチイベントを委譲先に渡すのかをローカル座標系で指定する ドキュメントに書いてあるとおりなのだが、英語力がなくていまいちわからず、ググったコードを参考にしながらやってもうまくいかずでちょっとハマった。 実際の動きはこんな感じになった。 リップルエフェクトもクリックリスナもViewGroupに対するものがImageButtonを押している扱いになっている。

TextViewに設定したテキスト内のURLに遷移する

TextViewに設定したテキスト内にURLがあった場合に、そのリンクをクリックできるようにしたい。 クリックしたらブラウザが開いて該当ページに移動できるようにしたい。 手っ取り早くこの要望を満たそうと思ったら、TextViewには便利な機能が用意されている。 TextViewにandroid:autoLink="web"を追加すればよいだけである。 これでテキスト内のURLをクリックしたらブラウザが開いてくれる。 めでたしめでたし。 といくなら楽でよかったのだが、この機能によるURLの処理はあまり正確ではない。 autoLinkの問題点 URLが半角スペースで区切られていたり、2バイト文字以外で区切られていたりしたら正しくリンクとして拾ってもらえる。 例えばあいうえお https://android.gcreate.jp/ かきくけこというテキストであればURLの部分のみがURLとして識別される。 しかしあいうえおhttps://android.gcreate.jp/だとリンクを拾ってくれない。 またあいうえお https://android.gcreate.jp/がリンクになってほしいとした場合、「がリンクになってほしい」という部分までURLとして拾われてしまう。 2バイト文字でない半角カッコで囲ってかっこで(https://android.gcreate.jp/)とした場合、閉じカッコもURLに含まれてしまう。 URLの抽出がうまくいかない場合があるのが最大の問題であるが、URLのハンドリングをカスタマイズできないのもちょっと不便である。 例えばChromeカスタムタブでリンクを開きたい場合に、autoLinkでは対応できない。 autoLinkの場合、リンクをクリックするとACTION_VIEWの暗黙的インテントが発行される。 自前で処理する 以上の問題点を回避するには、自分でテキストにClickableSpanを設定してやると良い。 テキストをSpannableStringに変換する テキストから正規表現を利用してURLを抽出する 抽出したURLを用いてSpannableStringにClickableSpanを設定する TextViewにSpannableStringをsetTextで設定する TextViewにsetMovementMethodを設定する 以上の手順で独自のClickableSpanを設定することができる。 コード的にはこんな感じ(Kotlinとandroid-ktxを利用している)。 val text = "あいうえおhttps://android.gcreate.jp/かきくけこ" val spannable = text.toSpannable() val matcher = Pattern.compile("(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]").matcher(text) while (matcher.find()) { val url = matcher.group() val start = matcher.start() val end = matcher.end() spannable.setSpan(MyUrlSpan(url), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } textView.text = spannable textView.movementMethod = LinkMovementMethod.getInstance() MyUrlSpanは自分で定義する。 といっても大したことはやっていない。 class MyUrlSpan(val url: String) : ClickableSpan() { override fun onClick(view: View) { Snackbar.make(view, "$url clicked", Snackbar.LENGTH_SHORT).show() } } SnackbarでURLを表示しているだけ。 リンクをクリックした際の挙動はこのonClickでカスタマイズできる。 もう少し詳しく このあたりの挙動が知りたければ、TextViewのソースコードを確認するのが手っ取り早い。 mAutoLinkMaskをキーに見ていくとだいたい分かると思う。 ClickableSpanを設定する際に必要なのは、文字列中のどこからどこまでにClickableSpanを適用するかが必要になる。 これは正規表現を使ってマッチさせれば該当する文字列、その文字列の開始位置・終了位置が分かるのでそれを使えば良い。 正規表現はこちらの記事を参考にさせていただいた。 AndroidのTextViewのautolink=webが冗長になる setSpan()する際の第4引数はflagsである。 https://developer.android.com/reference/android/text/Spanned これはEditTextにSpanを設定しないのであれば、おそらく何を設定しても影響はないと思う。 (Spanを設定したテキストの内容が動的に変化する場合に、変更前に設定したClickableSpanの開始位置・終了位置がどう変動するかを指定するフラグだと思うので) TextViewのTextをSpannableにして、ClickableSpanを設定しただけではリンクをクリックすることはできない。 ClickableSpanのonClickが呼び出されるためには、setMovementMethodで何らかのMovementMethodがTextViewに設定されていなければならない。 autoLinkを使った場合のLinkをタップしたときの動きはLinkMovementMethodが使われているのでここはそのまま流用する。 ClickableSpanのonClickを呼び出すかどうかは、TextViewのonTouchメソッド内の処理を確認すれば分かるが、mMovementがnullではないことが条件になっている。 だからTextView.
Read full post gblog_arrow_right