paizaオンラインハッカソン6に参加してみた(Java/C#)

これまで、当ブログではpaizaオンラインハッカソン6のC++及びC言語の回答例を上げてきたが、今回は当方がメインで使っていないJava及びC#で書いてみた。なお、同様のサンプルコードについてはGitHubでも上げているので、そちらも参照してもらいたい。

サンプルコード

緑川つばめミッション

Java

[code lang=”Java”]import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Main {
public static void main(String[] args) throws Exception {
// 文字列を取得し、整数に変換する
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = br.readLine();
int num = Integer.parseInt(line);

// 不正な値なら、例外を投げる
final int minNum = 10;
final int maxNum = 99;
if (num < minNum || maxNum < num) {
throw new RuntimeException("The numbe must be " + minNum + "…" + maxNum + ".");
}

// それぞれの桁の数値を加算させる
// 数字は文字コードとしての値となるため、補正が必要。
final char zero = ‘0’;
for (int idx = 0; idx < line.length(); idx++) {
num += line.charAt(idx) – zero;
}

// 出力する
System.out.println(num);
}
}
[/code]

C Sharp1

[code lang=”csharp”]using System;

public class Tsubame {
public static void Main(){
// 文字列を取得し、整数に変換する
var line = System.Console.ReadLine();
int num = int.Parse(line);

// 不正な値なら、例外を投げる
const int minNum = 10;
const int maxNum = 99;
if (num < minNum || maxNum < num) {
throw new Exception("The number must be " + minNum + "…" + maxNum + ".");
}

// それぞれの桁の数値を加算させる
// 数字は文字コードとしての値となるため、補正が必要。
const char zero = ‘0’;
foreach (var ch in line) {
num += ch – zero;
}

// 出力する
System.Console.WriteLine(num);
}
}
[/code]

六村リオミッション

Java

[code lang=”java”]import java.io.BufferedReader;
import java.io.InputStreamReader;

/// コーヒーを定義する
class Coffee {
/// コーヒー粉
private float _powder = 0.0f;
/// 湯
private float _water = 0.0f;

/// 合計を取得する
/// @return コーヒー粉と湯の合計値
public float getTotal() {
return _powder + _water;
}

/// コーヒー粉を入れる
/// @param quantity コーヒー粉の量
public void addPowder(float quantity) {
_powder += quantity;
}

/// 湯を入れる
/// @param quantity 湯の量
public void addWater(float quantity) {
_water += quantity;
}

/// 味見する
/// @param quantity 味見するコーヒーの量
public void taste(float quantity) {
float total = getTotal();
_powder -= quantity * _powder / total;
_water -= quantity * _water / total;
}

/// 濃度を取得する
/// @param asPercent パーセント形式で取得するかどうか
/// @return 濃度
public float getConsentration(boolean asPercent) {
float consentration = _powder / getTotal();
return consentration * (asPercent ? 100.0f : 1.0f);
}
}

public class Main {
public static void main(String[] args) throws Exception {
// 文字列を取得し、整数に変換する
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = br.readLine();
int lines = Integer.parseInt(line);

final int minLines = 1;
final int maxLines = 10;
if (lines < minLines || maxLines < lines) {
throw new RuntimeException("The num of lines must be " + minLines + "…" + maxLines + ".");
}

Coffee coffee = new Coffee();

// 量の最大値・最小値を設定する
final int minQuantity = 1;
final int maxQuantity = 100;

// カラムのタイプ
final int columnTypeActType = 0;
final int columnTypeQuantity = 1;
final int columnSize = 2;

// 行動のタイプ
final int actTypeAddWater = 1;
final int actTypeAddPowder = 2;
final int actTypeTaste = 3;

// コーヒーの処理を行う
for (int idx = 0; idx < lines; idx++) {
String actString = br.readLine();
String[] actArr = actString.split(" ");
if (actArr.length != columnSize) {
throw new RuntimeException("Invalid format of action inputted.");
}

int actionType = Integer.parseInt(actArr[columnTypeActType]);
int quantity = Integer.parseInt(actArr[columnTypeQuantity]);
if (quantity < minQuantity || maxQuantity < quantity) {
throw new RuntimeException("The quantity must be " + minQuantity + "…" + maxQuantity + ".");
}

switch (actionType) {
case actTypeAddWater:
coffee.addWater(quantity);
break;
case actTypeAddPowder:
coffee.addPowder(quantity);
break;
case actTypeTaste:
coffee.taste(quantity);
break;
default:
throw new RuntimeException("Invalid action type detected. The action type must be 1(add water), 2(add powder), or 3(taste).");
}
}
System.out.println((int)coffee.getConsentration(true));
}
}
[/code]

C Sharp

[code lang=”csharp”]using System;

/// コーヒーを定義する
class Coffee {
/// コーヒー粉
private float Powder = 0.0f;
/// 湯
private float Water = 0.0f;

/// 合計を取得する
/// @return コーヒー粉と湯の合計値
public float GetTotal() {
return Powder + Water;
}

/// コーヒー粉を入れる
/// @param quantity コーヒー粉の量
public void AddPowder(float quantity) {
Powder += quantity;
}

/// 湯を入れる
/// @param quantity 湯の量
public void AddWater(float quantity) {
Water += quantity;
}

/// 味見する
/// @param quantity 味見するコーヒーの量
public void Taste(float quantity) {
float total = GetTotal();
Powder -= quantity * Powder / total;
Water -= quantity * Water / total;
}

/// 濃度を取得する
/// @param asPercent パーセント形式で取得するかどうか
/// @return 濃度
public float GetConsentration(bool asPercent) {
float consentration = Powder / GetTotal();
return consentration * (asPercent ? 100.0f : 1.0f);
}
}

public class Rio {
public static void Main() {
// 処理行数を取得する
var lines = int.Parse(System.Console.ReadLine());
const int minLines = 1;
const int maxLines = 10;
if (lines < minLines || maxLines < lines) {
throw new Exception("The num of lines must be " + minLines + "…" + maxLines + ".");
}

Coffee coffee = new Coffee();

// 量の最大値・最小値を設定する
const int minQuantity = 1;
const int maxQuantity = 100;

// カラムのタイプ
const int columnTypeActType = 0;
const int columnTypeQuantity = 1;
const int columnSize = 2;

// 行動のタイプ
const int actTypeAddWater = 1;
const int actTypeAddPowder = 2;
const int actTypeTaste = 3;

// コーヒーの処理を行う
for (int idx = 0; idx < lines; idx++) {
var actString = System.Console.ReadLine();
var actArr = actString.Split(‘ ‘);
if (actArr.Length != columnSize) {
throw new Exception("Invalid format of action inputted.");
}

int actionType = int.Parse(actArr[columnTypeActType]);
int quantity = int.Parse(actArr[columnTypeQuantity]);
if (quantity < minQuantity || maxQuantity < quantity) {
throw new Exception("The quantity must be " + minQuantity + "…" + maxQuantity + ".");
}

switch (actionType) {
case actTypeAddWater:
coffee.AddWater(quantity);
break;
case actTypeAddPowder:
coffee.AddPowder(quantity);
break;
case actTypeTaste:
coffee.Taste(quantity);
break;
default:
throw new Exception("Invalid action type detected. The action type must be 1(add water), 2(add powder), or 3(taste).");
}
}

// 出力する
System.Console.WriteLine((int)coffee.GetConsentration(true));
}
}
[/code]

霧島京子ミッション

Java

[code lang=”java”]import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;

public class Main {
public static void main(String[] args) throws Exception {
// マスの数を取得する
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
final int numOfCells = Integer.parseInt(br.readLine());
final int goal = numOfCells – 1;
final int minNumOfCells = 2;
final int maxNumOfCells = 100;
if (numOfCells < minNumOfCells || maxNumOfCells < numOfCells) {
throw new RuntimeException("The number of cells must be " + minNumOfCells + "…" + maxNumOfCells + ".");
}

// マスを生成する
String[] cellsString = br.readLine().split(" ");
if (cellsString.length != numOfCells) {
throw new RuntimeException("The value of number of cells and cells length do not match.");
}
int[] cells = new int[numOfCells];
final int minCellNum = -100;
final int maxCellNum = 100;
for (int idx = 0; idx < numOfCells; idx++) {
int cellNum = Integer.parseInt(cellsString[idx]);
if (cellNum < minCellNum || maxCellNum < cellNum) {
throw new RuntimeException("The value of cell must be " + minCellNum + "…" + maxCellNum + ".");
}else if((idx == 0 || idx == numOfCells – 1) && cellNum != 0) {
throw new RuntimeException("The value of first cell and/or last cell must be 0.");
}
cells[idx] = cellNum;
}

// 出目数を出力する
final int acts = Integer.parseInt(br.readLine());
final int minActs = 0;
final int maxActs = 100;
if (acts < minActs || maxActs < acts) {
throw new RuntimeException("The number of acts must be " + minActs + "…" + maxActs + ".");
}

// 出目の最小値と最大値
final int minActNum = 1;
final int maxActNum = 100;

// 出目の処理を行う
for (int act = 0; act < acts; act++) {
// 出目を取得する
int num = Integer.parseInt(br.readLine());
if (num < minActNum || maxActNum < num) {
throw new RuntimeException("The value of act must be " + minActNum + "…" + maxActNum + ".");
}

ArrayList<Integer> moveLog = new ArrayList<Integer>();
boolean finished = false;
boolean dead = false;

while (!finished && !dead) {
if (num == goal) {
// ゴールに到達した場合
finished = true;
}else if(num < 1 || goal < num) {
// スタート地点に戻ったり、ゴールを越えてしまった場合
dead = true;
}else{
// それ以外
int moves = cells[num];
if (moves == 0) {
// これ以上進めない場合
dead = true;
}else{
// それ以外の場合、前に行ったことのあるマスならゲームオーバー
num += moves;
for (int moved : moveLog) {
if (num == moved) {
dead = true;
break;
}
}
if (!dead) {
moveLog.add(num);
}
}
}
}
System.out.println(finished ? "Yes" : "No");
}
}
}
[/code]

C Sharp

[code lang=”csharp”]using System;
using System.Collections.Generic;

public class Kirishima {
public static void Main() {
// マスの数を取得する
int numOfCells = int.Parse(System.Console.ReadLine());
int goal = numOfCells – 1;
const int minNumOfCells = 2;
const int maxNumOfCells = 100;
if (numOfCells < minNumOfCells || maxNumOfCells < numOfCells) {
throw new Exception("The number of cells must be " + minNumOfCells + "…" + maxNumOfCells + ".");
}

// マスを生成する
var cellsString = System.Console.ReadLine().Split(‘ ‘);
if (cellsString.Length != numOfCells) {
throw new Exception("The value of number of cells and cells length do not match.");
}
var cells = new int[numOfCells];
const int minCellNum = -100;
const int maxCellNum = 100;

for (int idx = 0; idx < numOfCells; idx++) {
var cellNum = int.Parse(cellsString[idx]);
if (cellNum < minCellNum || maxCellNum < cellNum) {
throw new Exception("The value of cell must be " + minCellNum + "…" + maxCellNum + ".");
}else if((idx == 0 || idx == numOfCells – 1) && cellNum != 0) {
throw new Exception("The value of first cell and/or last cell must be 0.");
}
cells[idx] = cellNum;
}

// 出目数を出力する
int acts = int.Parse(System.Console.ReadLine());
const int minActs = 0;
const int maxActs = 100;
if (acts < minActs || maxActs < acts) {
throw new Exception("The number of acts must be " + minActs + "…" + maxActs + ".");
}

// 出目の最小値と最大値
const int minActNum = 1;
const int maxActNum = 100;

// 出目の処理を行う
for (int act = 0; act < acts; act++) {
// 出目を取得する
var num = int.Parse(System.Console.ReadLine());
if (num < minActNum || maxActNum < num) {
throw new Exception("The value of act must be " + minActNum + "…" + maxActNum + ".");
}

var moveLog = new List<int>();
bool finished = false;
bool dead = false;

while (!finished && !dead) {
if (num == goal) {
// ゴールに到達した場合
finished = true;
}else if(num < 1 || goal < num) {
// スタート地点に戻ったり、ゴールを越えてしまった場合
dead = true;
}else{
// それ以外
int moves = cells[num];
if (moves == 0) {
// これ以上進めない場合
dead = true;
}else{
// それ以外の場合、前に行ったことのあるマスならゲームオーバー
num += moves;
foreach (var moved in moveLog) {
if (num == moved) {
dead = true;
break;
}
}
if (!dead) {
moveLog.Add(num);
}
}
}
}
System.Console.WriteLine(finished ? "Yes" : "No");
}
}
}
[/code]

解説

いずれのコードもJavaあるいはC#のどちらかの基礎がわかっており、なおかつこれまでのC言語版やC++版の記事を読んでいれば問題なく解けるコードである。

緑川つばめミッションではいずれも文字列の指定のインデックスを取得した場合、char型の文字として扱われることを確認、数値としての0と文字列としての’0’の数値上の値は別物であるため、それを吸収するために、’0’の文字コードの値を減算した上で計算を行った。

六村リオミッションでは処理速度よりもわかりやすさと改修のしやすさを重視し、クラスを生成し、コーヒー粉・湯を入れる処理及び味見をする処理、濃度を取得する処理、合計を取得する処理などをメソッドとして実装した。これについてはC++での実装例とほぼ同様である。

霧島京子ミッションは今回もC++およびC言語同様、シミュレーション形式で計算を行った。なお、break文の使用を抑制するため、while文の判定に使用する変数を2種類使った。なお、整数またはenumを使うことで、変数を一つに減らすことも可能である。

全ミッション共通で、基本的には全テストケースではフォーマット通りの入力が確約されているが、実際に動かす場合はフォーマット通りの書式が指定されるとは限らないことを考えて、蛇足ながら不正なフォーマットあるいは入力がされた時は例外を投げるように処理を加えた。

なお、paizaのテストケースの仕様上、Javaでは任意のクラスを指定することができず、Mainクラスのpublic staticなmainメソッドを呼ばなければならないため、今回もそれに準拠して記述している。この制約はC#にはないため、C#ではそれぞれクラス名を分けている。

最後に

今回は個人的にはあまり使わない言語で書いてみたため、違いの吸収で若干戸惑った。とはいえ、ライブラリー面では似ている上にどちらも便利で強力なライブラリーを持っているため、言語仕様の違いを吸収できればむしろ楽にコーディングできたともいえる。

もし一つの言語に飽きたなら、他の言語にもぜひチャレンジしてみよう。違いに気付いてみるのもまたよいだろう。

ウェブマスター。本ブログでITを中心にいろいろな情報や意見などを提供しております。仕事依頼や相談などについては、Contact Formよりお願いいたします。
  1. 本来の表記は「C#」ですが、なぜか見出しでは正常に表示されないため、「C Sharp」と表記しています。以下も同様です []