- 投稿日:2020-01-19T22:47:11+09:00
javaでAtCoder Beginner Contest 152を解く
AtCoder Beginner Contest 152お疲れ様でした!
公式ページ今回の自分の書いたコードはこちら
結果はA-DまでAC、EがWAでした。以下簡単に解説します。
問題A
引数のNとMを比較する問題。
同じ場合はYes
、違う場合はNo
を出力すればOK問題B
aをb回繰り返した文字列とbをa回繰り返した文字列のどちらが辞書順ではやいかを出力する問題。
辞書順なので、aとbのうち小さいほうが先です。aとbの小さい方をaとbの大きい方ぶん繰り返した文字列を出力できたらACです。
問題C
説明文はちょっと難しく書いてありますが、結局数列で最小値が何回更新されたかを答えればOKです。
4, 5, 2, 3, 1
上記だと太字になっている数字の数3が正解になります。問題D
N以下の数字の中で、
- Aの先頭の数字 = Bの末尾の数字
- Bの先頭の数字 = Aの末尾の数字
のどちらも満たす数字の組み合わせが何通りあるかを答える問題です。
Nの数字の数的に二重ループしていると間に合わないと思ったので、
- 最初にループして先頭の数字と末尾の数字のMapをつくる
- 二回目のループで条件にマッチする数をとってきて足し合わせる
としました。計算量的に間に合わないから方針変えなきゃって気づけたのは個人的にうれしいポイントでした。
問題E
どのような i,j についても AiBi=AjBjが成り立つ。
つまり、最小公倍数を求めてAiで割ってBiを求める問題。答えはさらにそれを足し合わせて出力しなさいとのこと。
方針を決める部分までは分かり、最近勉強した逆元使えばAiで割れるやん!というところまではよかったが、
大きい数の場合にうまく動かず。原因まで特定もできました。最小公倍数を計算する際、大きい数になり1000000007で一度割ってしまうと最小公倍数を正しく求められなくなるというものでした。
色々考えた結果解消できず。。解説にもある最小公倍数を素因数分解した形で保持するというのも考えたのですが、実力不足でコードにできませんでした。
(ちょっとまだどうすればいいかイメージも出来ていないので、平日仕事終わりにまた考えてみます。。)問題F
問題Eがもうちょっとでできる気がしたので手を付けれていないです。。
レーティングは897→956。最高更新です!
D問題までは25分とサクサクできて、成長も感じれたのですが
E問題の逆元使うところまでは思いつけたのに、あと一歩及ばずでとても悔しいです。。次こそは学びをきちんと形にしたいです・・・
- 投稿日:2020-01-19T19:00:36+09:00
Javaで実行しているディレクトリの絶対パスを取得する方法
はじめに
Qiita初投稿です。
環境
java1.8.0_221
やりたいこと
C:\Users\Miyayu\IDEA\example\example.jar
を実行しているとする。
C:\Users\Miyayu\IDEA\example
(String型)を取り出したい。コード
FileSystems.getDefault().getPath("").toAbsolutePath().toString()これだけ!かんたんでしょ?
指定したファイルの絶対パスを知りたいときは
.\expdir\hoge.json
の絶対パスを知りたいFileSystems.getDefault().getPath("expdir","hoge.json").toAbsolutePath().toString()
- 投稿日:2020-01-19T17:23:28+09:00
【Java】多重forループを一重にする方法
ネタです
はじめに
Javaに限らず,プログラムを書いていると何重ものforループを書かなければならない時が発生します.
例. int型三次元配列の全ての要素を1にしたい.
final int n = 10; final int[][][] cube = new int[n][n][n]; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { for (int k = 0; k < n; k++) { cube[i][j][k] = 1; } } }しかし,このようなループはネストが深くなったり行数が無駄に増えるため,プログラムの保守性が悪化する原因となります.
そこで,多重forループを一重にする方法を考えます.forループの構文
問題を解決するために,まずはforループの構文を確認して行きます.
Eclipse JDTによると,for文は以下のような構成になっていますfor (
[ ForInit ];
[ Expression ] ;
[ ForUpdate ] )
Statementまた,ForInitとForUpdateは以下のように定義されています.
ForInit:
Expression { , Expression }
ForUpdate:
Expression { , Expression }
ForInit
とForUpdate
は複数の式を指定できるので,ForInit
に多重forループで必要となるそれぞれの変数の宣言をForUpdate
で変数の更新を書くことができそうです.やってみた
それでは実際に一重のforループにしていきます.
初期化式
先ほどの例だと,必要な変数は
i,j,k
の三つなので,初期化式はint i = 0, j = 0, k = 0で大丈夫そうです.
条件式
次に(終了)条件式ですが,今回の場合は
j,k
の値に関係なく,一番外側のループを制御するi
についてi < nと書くことができます.
更新式
最後に更新式について考えていきます.
ループの内側を制御する変数から順番(k -> j -> i
)に考えていきます.
まずk
についてですが,ループの度にインクリメントし,なおかつkの値がnになるタイミングで0になればよいのでk = (k + 1) % nと書けます.
次に
j
についてですが,j
の更新のタイミングはk
が0になったタイミングであり,それ以外では値は変わらないためj = k == 0 ? (j + 1) % n : jとなります.
最後に
i
ですが,更新のタイミングはk == 0 && j == 0
なのでi = j == 0 && k == 0 ? i + 1 : iです.
結果
これまでの結果を統合すると以下のように書き換えられます.
final int n = 10; final int[][][] cube = new int[n][n][n]; for (int i = 0, j = 0, k = 0; i < n; k = (k + 1) % n, j = k == 0 ? (j + 1) % n : j, i = j == 0 && k == 0 ? i + 1 : i) { cube[i][j][k] = 1; }最初の三重ループのコードに比べるとネストが減り,すっきりした印象を受けたのではないでしょうか?
おわりに
今回は多重forループを一重ループに変換する方法を紹介しました.
ぜひこれを参考にしてコードの可読性を高めてください!
- 投稿日:2020-01-19T11:44:35+09:00
Javaのインライン展開についてちょっと実験してみる
JavaではJITコンパイラがインライン展開を行っているということは聞いたことがあるのですが、それによってどれだけの恩恵を受けているのか、ちょっと実験してみたくなりました。
そもそもインライン展開とは
詳細はWikipediaを参照してください。
要はメソッドの呼び出しにはオーバーヘッドがあるため、メソッド内の処理をメソッドの呼び出し側に埋め込んでしまえば、メソッド呼び出しのコストを抑えることができるということですね。JITコンパイラが必要に応じてこれを行っています。実験
今回試してみるコードは以下の通りです。StringBuilderに文字列をappendするだけのメソッドを用意して、繰り返し呼び出してみました。
同じメソッドを何度も呼び出していますので、インライン展開される/されないによってパフォーマンスに差が出るのではないかと仮説を立ててみました。InlineSample.javapublic class InlineSample { public static void main(String[] args) { long start = System.currentTimeMillis(); StringBuilder builder = new StringBuilder(); for (int i = 0; i < 10_000_000; i++) { appendHoge(builder); } System.out.println(builder.length()); long end = System.currentTimeMillis(); System.out.println("elapsed " + (end - start) + " ms"); } private static void appendHoge(StringBuilder builder) { builder.append("hoge"); } }このコードをインライン化を有効/無効の両方のパターンで実行して性能を検証しました。
インライン化はデフォルトで有効になっているため、以下のVMパラメータを追加することによって無効化しました。-XX:-Inlineなお、Windows10、Java15-eaの環境で試しています。
結果
5回試行して平均をとったところ、以下のような結果になりました。
インライン展開ON: 平均 125ms インライン展開OFF: 平均 342ms結構な差ですね。
ただ、JITコンパイルはプログラム実行中に行われますので、プログラムの実行開始時からインライン展開されたいたわけではありません(-XX:+PrintInlining オプションによりインライン展開のログを確認しました)。この点ではあまり公平な比較にはなっていません。
ベンチマークツールのJMHを使ったほうがよかったかなと思いますが、ともあれ、これだけの差がつくとは驚きです。なお、インライン展開の対象となるメソッドのサイズは
-XX:MaxInlineSize=sizeにより、指定することができます。デフォルトは35バイトです。
インライン展開を意識したコードを書くことによって、よりJITコンパイラの恩恵を受けることができるかもしれませんね。参考
https://docs.oracle.com/javase/jp/8/docs/technotes/tools/unix/java.html
- 投稿日:2020-01-19T07:38:06+09:00
macOS での Java のバージョン管理
ちょっと真の本題で書いたことを調べようと思って、いろいろ調べた結果の備忘録です。
でも、真の本題の方は、いまいち解決していないという。。Java のバージョンについて
Java については、まず、言語そのものと、その動作環境である JVM があり、それらについて、公式の仕様の部分と、公式 (Oracle) やその他さまざまなところが出している実装の部分があります。
Oracle が出している Java の仕様は Java SE (Standard Edition) と呼ばれます。
アプリケーション実装に踏み込んだ仕様である Java EE (Enterprise Edition) (現在は Jakarta EE として移管) や組み込み用途での Java ME (Micro Edition) というのがあった影響で、 Java SE と名付けられていますが、完全に歴史的経緯ですね。Oracle による Java 実装は、通称 Oracle JDK と呼ばれています。
また、準公式的な位置づけのオープンソース Java 実装として OpenJDK というのもあります。Oracle 買収前の Sun Microsystems が始めたもののようです。
そして、昨今 Java を取り巻く環境が大きく変わってきています。
他のプログラミング言語の進歩に取り残される形になった Java が変化に対応しようとする中での動きなのでしょう。まず、短いスパンで、細かい機能修正を加えたバージョンアップが行われるようになりました。
2017-09 リリースの Java 9 以降、現在のところ、半年に1回、メジャーバージョンが更新されています。そして Java 11 以降ですが、 Oracle のライセンス形態が変わり、公式版のいわゆる Oracle JDK 自体は利用が有償化されるようになりました。
それに変わって、今まで Oracle JDK に含まれていた機能で OpenJDK に入っていなかった部分も OpenJDK に移管され、オープンソース化されました。
OpenJDK 自体は無償で利用できますし、この流れを受けて、 OpenJDK を独自にビルドしたディストリビューションも各社・コミュニティが公開するようになりました。つまり、今まで Java といえば、だいたい Java 6-8 あたりの Oracle JDK だけ使っていればよかったのが、各ベンダ別のさまざまなバージョンの "Java" が普及するようになっています。
References
- Oracle JDK Releases for Java 11 and Later | Oracle Java Platform Group, Product Management Blog
- 「Java8からJava11」で何が起きたのか、どう環境構築すればいいのか - Qiita
- JDK、Oracle JDK、OpenJDK、Java SEってなに? - Qiita
- オープンソースのOracle JDKとOpenJDKを比較
macOS での Java のバージョン管理
本題です。主には SDKMAN! を使った方法と
/usr/libexec/java_home
を使った方法があります。SDKMAN! を使った方法
SDKMAN! は、 JVM 系のバージョン管理ツールで、 Java だけではなくて、 JVM 系の言語 (Groovy, Scala など) やミドルウェアのバージョン管理までやってくれます。
もともとは GVM (Groovy enVironment Manager) だったらしい。各バージョンの検索・インストールから、各 scope でのバージョン切り替えまでをやってくれます。詳細はここでは書きません。
内部的に
JAVA_HOME
を切り替えているようです。
/usr/libexec/java_home
を使った方法こちらの方法では、インストーラや Homebrew を使ってインストールした Java のバージョンの切り替えを行えます。
例えば Homebrew で以下のように AdoptOpenJDK 最新版をインストールします。
macOS Catalina では、以下のように--no-quarantine
option が必要です。$ brew cask install adoptopenjdk --no-quarantineインストールした Java の一覧は、以下のコマンドで確認できます。
/Library/Java/JavaVirtualMachines/
配下にあるものだけが表示されるようです。
ちなみに、このディレクトリはシステムベースのもので、 SDKMAN! はユーザベースでモジュールを管理することから、 SDKMAN! でインストールした Java はこのディレクトリには配置されず、したがってこのコマンドの結果には出てきません。
SDKMAN! のドキュメントにも、/usr/libexec/java_home
を使った方法との互換性はないと書かれています。$ /usr/libexec/java_home -V特定のバージョンの Java のパスを得たい場合は、小文字の
-v
オプションを利用する。$ /usr/libexec/java_home -v 13.0オプション無しで実行すれば、デフォルト (たぶん最新バージョン) の Java のパスが取得されます。
$ /usr/libexec/java_home
特定のバージョンに
JAVA_HOME
を切り替える場合は、以下のようにします。$ export JAVA_HOME=`/usr/libexec/java_home -v 13.0`一時的にデフォルト以外の Java バージョンを使って、実行することも可能です。
$ /usr/libexec/java_home -v 13.0 --exec java -versionお察しのとおりですが、
/usr/libexec/java_home
は Java のバージョンを見るだけで、そのビルドを発行しているベンダまでは見てくれないので、複数ベンダの Java を入れた場合はうまく対処してくれないみたいです。
/Library/Java/JavaVirtualMachines/<JAVA>/Contents/Info.plist
に書いてあるJVMVersion
のバージョンで比較をしているようですね。ベンダも含めて管理したい場合は、 jEnv を使うのが良さそうです。
References
- sdkmanでJavaのインストール - mike-neckのブログ
- java_home and JAVA_HOME on macOS - Notes for Geeks - Medium
- Working with multiple Java versions in MacOS - Bruno Frascino - Medium
- HomebrewでインストールできるJDKまとめ(2019年11月時点) - Qiita
- macos - Why doesn't /usr/libexec/java_home recognize JDK 1.8? - Ask Different
- Unsigned error when using this cask on Catalina 10.15.1 · Issue #267 · AdoptOpenJDK/homebrew-openjdk
- macos - How can I change Mac OS's default Java VM returned from /usr/libexec/java_home - Stack Overflow
- FAQ · sdkman/sdkman-cli Wiki
- Installed JDK in Mac, But can show it with system command
/usr/libexec/java_home
· Issue #638 · sdkman/sdkman-cliHomebrew で
Java 1.8+ is required to install this formula
みたいなエラーが出る場合真の本題です。
SDKMAN! で入れた Java しか無い状態で、 Homebrew で bundletool をインストールしようとしたところ、以下のエラーが発生しました。
$ brew install bundletool bundletool: Java 1.8+ is required to install this formula. Install AdoptOpenJDK with Homebrew Cask: brew cask install adoptopenjdk Error: An unsatisfied requirement failed this build.bundletool の formula を見たところ、
depends_on :java => "1.8+"
の指定があり、これを満たせていないようです。
SDKMAN! で入れた場合にもJAVA_HOME
は入っているし、Homebrew のソースコード をちらっと見るには、JAVA_HOME
に適切な Java が入っていれば問題なさそうなんですが...
あまりじっくり見ていないので、何がダメなのかちょっと良くわかりませんでした。誰か分かる人教えてください><とりあえず言われるがままに、 Homebrew で AdoptOpenJDK をインストールして、特に
JAVA_HOME
の切り替えなどはせずに再度brew install bundletool
を実行すると、うまくインストールできました。$ brew cask install adoptopenjdk --no-quarantineHomebrew でのインストール時に必要なだけで、後は SDKMAN! でインストールした Java でも動作しそうなので、すぐにアンインストールしても問題なさそうです。
$ brew cask uninstall adoptopenjdk
References