- 投稿日:2020-03-28T23:09:15+09:00
javaでAtCoder Beginner Contest 160を解く
AtCoder Beginner Contest 160お疲れ様でした!
公式ページ今回の自分の書いたコードはこちら
結果はA-EまでACできました。久しぶりの5問ACでテンション上がります^^以下簡単に解説します。
問題A
引数にStringで6文字のものが渡されてくるので、
String.charAt()
で指定文字のcharacterを取り出して、比較すればOKです。問題B
金額が与えられて、それを適切に500円硬貨と5円硬貨に両替する問題です。
答えの出力が「嬉しさ」になっていて、500円硬貨をもっていると1000の嬉しさが手に入るので、与えられた金額を2倍しないように注意。
また、5円硬貨についても5の嬉しさが入るからといって、単に余った金額を足せばよいってもんではないので注意です。long happy = (x / 500) * 1000; x = x % 500; happy = happy + (x / 5) * 5;みたいな感じでACできました。
問題C
池の周りに家が建っていて、とある地点からの距離が与えられます。
全ての家をまわるのに最小の距離を求める問題です。今回の家は池の周りに建っているので、実際に通る経路としては視力検査のアイツのような形になるはずです。
円を少し切り取ったような形。
なので、家と家の間が一番遠い距離を求めることができれば、池の外周からその距離を引くことで最小距離が算出できます。基本的には前の家の距離を覚えておいて、入力のたびに計算でOKです。
最初の家と最後の家も隣り合っているので、忘れずに比較しましょう。問題D
N個の頂点を持つ無向グラフで、距離がkのものはいくつあるかを出力する問題です。
無向グラフとか難しい言葉でてきてびびりますが、今回のグラフは単純です。せいぜい直接向かうか、X-Yの間の道を通るかの2通りしかありません。
TLE起こしそうだなぁ・・・と思っても、以下を見て安心します。
3 ≤ N ≤ 2 × 10^3コードで表現すると以下のようになります。
二重forループで、直接行くのとX-Yの間通るのとどちらが近いかを計算して、答えの配列に追加しています。for (int i = 1; i <= n; i++) { for (int j = i; j <= n; j++) { int direct = j - i; int shortCut = dist(i, x) + 1 + dist(j, y); int dist = Math.min(direct, shortCut); answer[dist]++; } }distっていうメソッド以下のような感じです。
public static int dist(int p, int q) { return Math.max(q - p, p - q); }問題E
X個の赤いリンゴとY個の緑色のリンゴを食べます。
赤いリンゴのおいしさと緑色のリンゴのおいしさを与えられて、おいしさが最大になるように食べます。
無色のリンゴもあり、こいつは着色できます。自分は以下のようなアルゴリズムで解きました。
- 赤いリンゴと緑色のリンゴをおいしい順に並べる
- 赤いリンゴをおいしい順にX個、緑色のリンゴをおいしい順にY個取得して配列APPLEに加える
- 配列APPLEに無色のリンゴを全て加える
- 配列APPLEをおいしい順に並べる
- 配列APPLEからX+Y個所得して足し算していく
で、無事ACできました。
無色のリンゴも配列APPLEに足す発想ができたのが勝因ですかね、、よかったぁ。個人的にはE問題の中では難易度は低めかな・・・?
問題F
5分考えて、無理だなってなったので残りの20分はぼーっとしていました笑
感想
久しぶりの水パフォ(1411)出せまして、
レーティングは936→1003と初めての1000突破です!!E問題がちょっと簡単めなのもありましたが、なんとか解けてよかった・・・
A-EのACにはこだわりをもって安定させていきたい。これできてればそのうち水ネームなれるはず!最後まで読んでくださり、ありがとうございます!
- 投稿日:2020-03-28T20:22:18+09:00
【XML】気象庁から地震情報を取得する①
気象庁XMLを取得して解析までの道のり
① XMLのダウンロード(今回)
② XMLファイルの解析(次回)気象庁からXMLファイルを取得する
ダウンロードを実行するクラスを作成する。
XMLDownloader.javaimport java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; public class EarthquakeXMLDownloader implements Runnable { // 気象庁のリンク(http://www.data.jma.go.jp/developer/xml/feed/eqvol.xml) String link; // ダウンロードする場所 File out; public EarthquakeXMLDownloader (String link, File out) { this.link = link; this.out = out; } @Override public void run() { try { URL url = new URL(link); HttpURLConnection http = (HttpURLConnection) url.openConnection(); double fileSize = (double)http.getContentLengthLong(); BufferedInputStream in = new BufferedInputStream(http.getInputStream()); FileOutputStream fos = new FileOutputStream(this.out); BufferedOutputStream bout = new BufferedOutputStream(fos, 1024); byte[] buffer = new byte[1024]; double downloaded = 0.00; int read = 0; double percentDownloaded = 0.00; while((read = in.read(buffer, 0, 1024))>= 0) { bout.write(buffer, 0, read); downloaded += read; percentDownloaded = (downloaded*100)/fileSize; String percent = String.format("%.4f", percentDownloaded); System.out.println("Downloaded " + percent + "of a file."); } bout.close(); in.close(); System.out.println("Download complete."); }catch (IOException ex) { ex.printStackTrace(); } } }↑のファイルを実行する。
Main.javaimport java.io.File; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class Main { public static void main(String[] args) { // アウトプットするファイルパスを作成 // ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ LocalDateTime now = LocalDateTime.now(); DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm"); String fileName = "earthquake_" + now.format(dateTimeFormatter); String link = "http://www.data.jma.go.jp/developer/xml/feed/eqvol.xml"; File out = new File("C:\\pleiades\\workspace\\SAX\\resource\\" + fileName + ".xml"); // ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ // 実行する。 new Thread(new EarthquakeXMLDownloader(link,out)).start(); } }
- 投稿日:2020-03-28T12:56:31+09:00
java 標準関数型インターフェース
関数型インターフェースとは、抽象メソッドを1つだけもつインターフェースで、ラムダ式やメソッド参照の代入先として使われるやつです
標準関数型インターフェースについてまとめてみました標準関数型インターフェース
標準関数型インターフェースとして、典型的なメソッドの型が
java.util.function
パッケージで定義されている基本的な標準関数型インターフェース
インターフェース メソッド 説明 Function <T, R> R apply (T t) T型の引数を受け取って、R型の結果を返す Consumer <T> void accept (T t) T型の引数を受け取って、結果は返さない Predicate <T> boolean test(T t) T型の引数を受け取って、boolean値の結果を返す Supplier <T> T get() 引数無し、T型の結果を返す サンプルコード
Sample.java// Function<T, R> // 例:引数の文字列長を返す Function<String, Integer> function = String::length; System.out.println(function.apply("12345")); // 5 // Consumer<T> // 例:引数の文字列長を出力 Consumer<String> consumer = str -> System.out.println(str.length()); consumer.accept("12345"); // 5 // Predicate<T> // 例:引数の文字列に"foo"が含まれているか判定 Predicate<String> predicate = str -> Objects.nonNull(str) && str.contains("foo"); System.out.println(predicate.test("foo bar")); // true // Supplier<T> // 例:"Sample"を返す Supplier<String> supplier = () -> "Sample"; System.out.println(supplier.get()); // Sample関数の合成
標準関数型インターフェースを合成して新しい関数型インターフェースを作成できる
compose
、andThen
を使って合成する(compose
とandThen
の違いは合成の順序)
Predicate系はand
、or
、negate
を使って合成できるサンプルコード
Sample.java// Functionの関数合成 Function<String, Integer> function1 = String::length; Function<Integer, Integer> function2 = i -> i * 100; // composeを使う場合 Function<String, Integer> newFunction1 = function2.compose(function1); // andThenを使う場合 Function<String, Integer> newFunction2 = function1.andThen(function2); System.out.println(newFunction1.apply("12345")); // 500 System.out.println(newFunction2.apply("12345")); // 500 // Predicateの関数合成 Predicate<Integer> predicate1 = i -> i % 2 == 0; // 偶数かどうか判定 Predicate<Integer> predicate2 = i -> i >= 100; // 100以上どうか判定 // 偶数かつ100以上かを判定 Predicate<Integer> newPredicate1 = predicate1.and(predicate2); // 奇数かつ100未満かを判定(negate()は論理否定) Predicate<Integer> newPredicate2 = predicate1.negate().and(predicate2.negate()); System.out.println(newPredicate1.test(100)); // true System.out.println(newPredicate2.test(99)); // true
- 投稿日:2020-03-28T09:31:58+09:00
アプリケーション間でRequestIdを送りあってみる
やりたいこと
サービスが複数またがってている場合、運用時にサービス間を跨いだログを見たくなる時があるので、その方法をかく。
順番は
1. Spring BootでRequestIdを生成して、ログファイルに出しておく
2. 他アプリケーション(今回はGin)に対して、リクエストを投げる
3. Gin側でRequestIdを受け取るソース
Spring Boot
Gin環境
- Java
- Spring Boot
- Go
- Gin
Spring Boot側にRequestIdの設定を入れる
ログファイルにRequestIdが表示されるようにする
MDCを使用して、リクエスト毎にUUIDを生成して、ログに仕込めるようにしておく
SampleFilter.javaimport org.slf4j.MDC; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.UUID; public class SampleFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String requestIdKey = "X-REQUEST-ID"; String uuid = UUID.randomUUID().toString(); // どこからでもリクエストIDが取得できるように設定しておく request.setAttribute(requestIdKey, uuid); try { // ログに出力できるように設定 MDC.put(requestIdKey, uuid); filterChain.doFilter(request, response); } finally { MDC.remove(requestIdKey); } } }Filterを使えるようにBeanに登録する
/api
配下にフィルターがかかるように設定するSampleConfiguration.javaimport org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class SampleConfiguration { @Bean public FilterRegistrationBean<SampleFilter> hogeFilter() { FilterRegistrationBean<SampleFilter> bean = new FilterRegistrationBean<>(); bean.setFilter(new SampleFilter()); bean.addUrlPatterns("/api/*"); bean.setOrder(Integer.MIN_VALUE); return bean; } }Logbackの設定
SampleFilter.java
で定義したX-REQUEST-ID
を<pattern>
にも設定するlogback-spring.xml<?xml version="1.0" encoding="UTF-8"?> <configuration> <include resource="org/springframework/boot/logging/logback/defaults.xml" /> <include resource="org/springframework/boot/logging/logback/console-appender.xml" /> <include resource="org/springframework/boot/logging/logback/file-appender.xml" /> <springProperty name="loggingFilePath" source="logging.file.path" /> <appender name="FILE" class="ch.qos.logback.core.FileAppender"> <file>${loggingFilePath}/spring.log</file> <encoder> <pattern>[%-5level] [%X{X-REQUEST-ID}] [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%logger{36}] - %msg%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="CONSOLE" /> <appender-ref ref="FILE" /> </root> </configuration>今回はINFO以上のレベルしか出さないようにする
application.propertieslogging.file.path=logs/ logging.level.org.springframework=INFOリクエストを投げてログを確認
http://localhost:8080/api/
にGETのエンドポイントを作っておくSampleController.javaimport org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("api") public class SampleController { private static final Logger logger = LoggerFactory.getLogger(SampleController.class); @GetMapping public String get() { logger.info("info!"); return "get"; } }リクエストの結果のログファイル
spring.log[INFO ] [2389c607-f5ed-4789-95a3-efd78be1e8d9] [2020-XX-XX 23:26:25.536] [c.e.log.logdemo.SampleController] - info!
2389c607-f5ed-4789-95a3-efd78be1e8d9
が生成したUUID
ちゃんと出てるっぽいねRestTemplateで他サービスにリクエストする時にRequestIdを付与する
RestTemplateを使えるようにBeanに登録しておく
(特にここで定義する必要はなかったですが楽だったので)LogDemoApplication.java@SpringBootApplication public class LogDemoApplication { public static void main(String[] args) { SpringApplication.run(LogDemoApplication.class, args); } @Bean public RestTemplate setRestTemplate(){ return new RestTemplate(); } }他アプリケーションに投げるためのエンドポイントを用意しておく
SampleController.javaimport org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; @RestController @RequestMapping("api") public class SampleController { private static final Logger logger = LoggerFactory.getLogger(SampleController.class); // 追記 RestTemplate restTemplate; // 追記 public SampleController(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @GetMapping public String get() { logger.info("info!"); return "get"; } // 追記 @GetMapping("to-other-app") public String toOtherApp() { String requestId = (String) RequestContextHolder .getRequestAttributes() .getAttribute("X-REQUEST-ID", RequestAttributes.SCOPE_REQUEST); logger.info("他のアプリケーションにリクエスト投げます!"); HttpHeaders headers = new HttpHeaders(); headers.set("X-REQUEST-ID", requestId); HttpEntity<String> entity = new HttpEntity<>("headers", headers); ResponseEntity<OtherAppResponse> response = restTemplate.exchange("http://localhost/api", HttpMethod.GET, entity, OtherAppResponse.class); return response.getBody().getValue(); } }これでSpring Boot側の設定は完了です!
Gin側のアプリケーションを作成する
別で書いたの記事のものを流用します
docker-compose up -d
を叩くだけなので、簡単
http://localhost:80/api
にアクセスすると、{"key": "value"}
が返ってくるだけのAPIがありますお互いのログを確認
Spring Bootは
http://localhost:8080/
Giはhttp://localhost:80/
で起動しますSpring Boot側でリクエスト
http://localhost:8080/api/to-other-app
にアクセスspring.log[INFO ] [549ef61a-44c9-4fe4-9c0f-3d923976d32f] [2020-XX-XX 00:19:00.274] [c.e.log.logdemo.SampleController] - 他のアプリケーションにリクエスト投げます!ログが見えました
リクエストIDは549ef61a-44c9-4fe4-9c0f-3d923976d32f
Gin側のログファイルを確認
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode) [GIN-debug] GET /api --> main.main.func1 (4 handlers) [GIN-debug] Listening and serving HTTP on :3000 [GIN] 2020/XX/XX - 15:16:44 | 200 | 1.005ms | 172.19.0.3 | GET "/api" {"time":"2020-XX-XXT15:19:00.3482677Z","level":"-","prefix":"-","file":"main.go","line":"30","message":"RequestId=549ef61a-44c9-4fe4-9c0f-3d923976d32f"}送られてきたリクエストIDは
549ef61a-44c9-4fe4-9c0f-3d923976d32f
どうやら一致したようですね(日付はぼかしてます)まとめ
さらっとやりましたが、Spring BootでリクエストIDをどこでも使えるようにするだとか、Ginでアプリケーション作るだとかに時間がかかりました。Spring Boot側の
RequestContextHolder
とか、「これでええんかな...」感はありますが、他にやり方が浮かばなかったので、もし知っていたら教えていただけるととても嬉しいです。Gin側でSpring Bootに返すのはHeader設定するだけで簡単っぽいので割愛しました。なんでGinなのか、ギョームではSpring Boot + NodeJSで実現してますが、最近Goに興味が湧いたので(Go楽しい)。
分かり辛いところ、もっと効率のいいやり方、アドバイスあればぜひコメントください
- 投稿日:2020-03-28T08:39:04+09:00
新人研修でやった三角形の面積を求める実装を今やってみる
プログラミングを始めて2年が経った
新卒で入社した会社のJava研修で初めてプログラミングをしてからもうすぐ2年が経ちます。最近は情報系の学生さんとも話す機会が増え、すごい人がたくさんいるんだなーと実感することが多いです。
自分が新人研修でやっていたことなんて大したことなかったなーと振り返りつつ疑問が…あの時俺がやっていたのはオブジェクト指向?
研修の課題の一つに
三角形の面積を求めなさい
というものがありました。
趣旨としては手続き型的に書くのではなく、クラスを使って実装しようというものでした。
そこで、私が考えた答えは⬇️こんな感じだったと思います。class Triangle { int bottom; int height; public Triangle(int arg1, int arg2) { this.bottom = arg1; this.height = arg2; } public int calc() { return this.bottom * this.height / 2; } }確か模範解答もこんな感んじだった気がします。
たぶん申し分ない。(申し分ある)ちょっと待てよ このクラスが表現している三角形ってこんな感じか?
うーん、、三角形ではない、、、、
けどいいんです!!!その時の我々が底辺と高さの組み合わせを三角形と呼びます
と定義していればきっといいんです!たぶんだがしかし、その時のテキストにはしっかり三角形の図が載っていたのできっと作成者側も△こういうのを考えているはず。
とういうことで、あの時の課題をオブジェクト指向で再挑戦してみる
オブジェクト指向はかじってる最中です。不足、誤り等あればご指摘ください。
三角形を定義する
課題として与えられる情報は底辺 と高さだけなので、この2つを辺として
三角形は直角三角形
であるとします。(させて下さい)実装してみる
public class RightTriangle { private Set<Side> sideSet; public RightTriangle(Side bottom, Side height) { assert !bottom.isHypotenuse(); assert !height.isHypotenuse(); sideSet = Set.of(bottom, height, calcHypotenuse(bottom, height)); } private Side calcHypotenuse(Side bottom, Side height) { return Side.createHypotenuse(Math.hypot(bottom.length(), height.length())); } public double area() { return sideSet.stream() .filter(s -> !s.isHypotenuse()) .map(Side::length) .reduce(1.0, (s1, s2) -> s1 * s2) / 2; } } public class Side { private boolean isHypotenuse; private double value; private Side(double value, boolean isHypotenuse) { assert value > 0; this.value = value; this.isHypotenuse = isHypotenuse; } public static Side createSide(double value) { return new Side(value, false); } public static Side createHypotenuse(double value) { return new Side(value, true); } public boolean isHypotenuse() { return this.isHypotenuse; } public double length() { return this.value; } }三角形クラスを初期化時に2辺から斜辺を求めるようにしてみた。これでイメージする三角形になると思う。
辺は値オブジェクトっぽく表現して、斜辺の状態を持たせてみた。
三角形クラスに面積を問い合わせると、斜辺じゃない2辺から計算して返してくれるようにした。辺の長さはLengthクラスとして切り出してもよかったかもしれない。
三角形クラス、辺クラスの初期化処理はイケてないかもしれない。
そもそもいろいろとイケてないかもしれない。
これが今の精一杯…また2年後挑戦しようご指摘あればお願いしますオブジェクト指向を理解したい