学習雑記 JAVA24

Calendarクラスを使ってみた。

返されるフィールド値の値などが最初は混乱するが、何故0から始まるかなどは単純な計算に都合が良いことだけは理解した。

使いこなすべく、もう少しこれらで遊びたい。

説明
AM_PM HOUR が正午より前であるか後であるか。AM(0) PM(1)
DATE 月の日を示す。
DAY_OF_MONTH 月の日を示す。
DAY_OF_WEEK 曜日を示す。このフィールドの値は1から7で、SUNDAY(1)、MONDAY(2)、TUESDAY(3)、WEDNESDAY(4)、THURSDAY(5)、FRIDAY(6)、SATURDAY(7) になる
DAY_OF_WEEK_IN_MONTH 現在の月の何度目の曜日かを示す。
DAY_OF_YEAR 現在の年の何日目かを示す。
ERA ユリウス暦の AD または BC などの年代を示す。
HOUR 午前または午後の何時かを示す。12 時間制 (0 ~ 11) で,正午および真夜中は、12 ではなく 0 で表される
HOUR_OF_DAY 時刻を示す。
MILLISECOND ミリ秒を示す。
MINUTE 分を示す。
MONTH 月を示すフィールド値。このフィールドの値は0から11で、JANUARY(0), FEBRUARY(1), MARCH(2), APRIL(3), MAY(4), JUNE(5), JULY(6), AUGUST(7), SEPTEMBER(8), OCTOBER(9), NOVEMBER(10), DECEMBER(11)になる点に注意が必要
SECOND 秒を示す。
WEEK_OF_MONTH 現在の月の何週目かを示す。
WEEK_OF_YEAR 現在の年の何週目かを示す。
YEAR 年を示すフィールド値。
ZONE_OFFSET GMT から直接計算したオフセットをミリ秒単位で示す。



public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Calendar cal = Calendar.getInstance();
int y = sc.nextInt();
int m = sc.nextInt() - 1; //月のフィールド値は0-11のため、読み取った実際の月−1を値に
int d = sc.nextInt();
// cal.set(y, m, d);

cal.set(Calendar.YEAR, y);
cal.set(Calendar.MONTH, m);
cal.set(Calendar.DATE, d);
// チェック
       // System.out.println(cal.get(Calendar.YEAR) + " " + cal.get(Calendar.MONTH) + " " + cal.get(Calendar.DATE));

String[] week_days = {"日曜日", "月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日"};
int index = cal.get(Calendar.DAY_OF_WEEK) - 1; //曜日は1-7の値で管理するため
System.out.println(week_days[index]);

// switch(cal.get(Calendar.DAY_OF_WEEK)) {
// case 1:
// System.out.println("日曜日");
// break;
// case 2:
// System.out.println("月曜日");
// break;
// case 3:
// System.out.println("火曜日");
// break;
// case 4:
// System.out.println("水曜日");
// break;
// case 5:
// System.out.println("木曜日");
// break;
// case 6:
// System.out.println("金曜日");
// break;
// case 7:
// System.out.println("土曜日");
// break;
// }
}
}

学習雑記JAVA23

問題集を解きながら出てきた表現を見て、文字列を分割するsplitメソッドで知らなかったことをまとめた。

 

- 引数に正規表現を設定できる

   str.split("[ , : ]");のようにカンマとコロンで区切る指示を出せる

- 分割する上限を指定できる

   第二引数に戻り値の配列要素数上限を設定でき、str.split(" , " , 3);は、strを前から,で区切っていき、要素が3つ以上作らない(= 3つ目のコンマは無視し、区切られない)

 

JAVA22

 回文判定

早時問題なので、考え方としては簡単なのだが、自分が作成したものはやけに(早時も問題の割りに)冗長だったので、解いたあと少し調べるといろいろなやり方があって頭を柔らかくしなければな〜と感じている。

問題自体は、scannerで入力された文字列が回文かを判定してYES/NOで出力するというもの。

1. 最初に作ったコード

下記の通り、とっても冗長です。文字列が素材として出た時点でsubstringしか頭になく、そのまま素直に書いた。この時点で微妙に感じていたのは、centerが偶数と奇数で判定があるため、toCharArray()で配列に入れて値の両端から比較する方法も考えたが素直に書ききった。あとは特に問題はないがStringにcharを足していくこと、StringBuilderを使うべきか、どっちがいいのか、あまり変わらないのか調べたいと思っている。

public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String line = sc.nextLine();
int center = line.length() / 2;

String half = "";
String another_half = line.substring(center);
if(line.length()%2==0){
half = line.substring(0, center);
} else {
half = line.substring(0, center+1);
}

String reverse = "";
for(int i=half.length()-1; i>=0; i--){
reverse += half.charAt(i);
}
if(reverse.equals(another_half)) {
System.out.println("YES");
} else {
System.out.println("NO");
}
}
}

2. その他の解法1 

charを一つ一つチェックするメソッドを呼び出してboolean値で返す。一回でも異なる場合、falseが返る。値に準じた出力を設定すればOK。1でちょっと考えたようなcharの配列を作らずともよく、charAt()で簡単に比較できる。スマート。

public static boolean checkCharOneByOne(String line) {
int min_Index = 0;
int max_Index = line.length() - 1;

while(min_Index < max_Index) {
if(line.charAt(min_Index++) != line.charAt(max_Index--)) {
return false;
}
}
return true;
}

3.  その他の解放2

StringBuilderにはreverseメソッドが存在する。

重要なのはtoStringでString型の比較にしてあげること。String型とStringBuilder型をequalメソッドで比較する場合、参照先の比較となるため100% falseになる。sb型の反転された文字列をtoStringメソッドでString型に直して比較することで純然たる文字列同士の比較になる。

public static boolean checkWholeSentenceAtOneTime(String line) {
StringBuilder sb =
new StringBuilder(line);
return line.equals(sb.reverse().toString());
}

復習

朝に直面したエラー問題点

ifの条件式で、startIndex<0 && stopIndex>=0 である必要があった。

桁数が4で小分けにできない場合の処理を作成しておいたが、桁数が4で小分けにできる時、startIndex = 負の数、stopIndex=0の状態になるため、s.substring(0, 0)となり、文字列が空のものを数字に変換しようとしていたことに因る。

 

この点を受けて、次回より気をつけて行きたいこと。

エラーになっている直接の状況をしっかり確認する。要素や変数の値を出力してみて予期していない動きになっているかをチェックすることで気づけた可能性がある。

慣れなんだろうな。。。

public class Main {
public static void main(String[] args) {
//Sは4〜最大1000桁(数字の)文字列
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
int t = Integer.parseInt(sc.nextLine());

int len = s.length();
int startIndex = len-4;
int stopIndex = len;
ArrayList<Integer> array = new ArrayList<>();
while(startIndex>=0) {
String num = s.substring(startIndex, stopIndex);
int x = Integer.parseInt(num);
//System.out.println(num);
array.add(x);
stopIndex = startIndex;
startIndex = startIndex - 4;
if(startIndex<0 && stopIndex>0) {
//System.out.println(s.substring(0, stopIndex));
String last = s.substring(0, stopIndex);
int y = Integer.parseInt(last);
array.add(y);
}
}
int up = 0;
ArrayList<Integer> array2 = new ArrayList<>();
for(int i=0; i<array.size(); i++){
int x = array.get(i);
int y = x * t;
//System.out.print(y);
//System.out.print(":");
int left = y % 10000;
//System.out.print(left + ":");
int sub = left + up;
//System.out.println(sub);
array2.add(sub);
up = y / 10000;
}
String result = "";
for(int i=array2.size()-1; i>=0; i--) {
result += array2.get(i);
}
System.out.println(result);
}
}

朝活 アウトプット

入力される1~最大1000桁の数字の文字列を計算する問題で何も考えずにint や longを使って、エラーになった話。

型の範囲を超える計算なんて想定していなかったが、教科書なんかではこうした型の範囲に関して初期段階から出てくる。

int (4byte)  -2147483648 ~ 2147483647 

long(64byte)  -9223372036854775808 ~ 9223372036854775807 

 

この型の範囲を超えている数字を代入できないので、1000桁の数字の文字列として受け取って、とりあえず4桁ずつ区切る(10000進法の数字で)ことでまず考えてみたが、、、

10000で割った数字を1の位、次を10の位と計算して行けば良いのですが、問題は割る元となる数字が代入できないので、計算ができないことがそもそも問題だった。。。

 

ですので、.length()で桁数を確認した上で、.substring()のstartIndexをlength()-4, stopIndexをlength()として得た数字を配列に格納し、startIndexを-4、stopIndexに前のstartIndexを代入するstartIndex>=0の範囲で数字を取得する方法をまず考えた。

String s = sc.nextLine();
int t = Integer.parseInt(sc.nextLine());

int len = s.length();
int startIndex = len-4;
int stopIndex = len;
ArrayList<String> array = new ArrayList<>();
while(startIndex>=0) {
String num = s.substring(startIndex, stopIndex);
//System.out.println(num);
array.add(num);
stopIndex = startIndex;
startIndex = startIndex - 4;
if(startIndex<0) {
//System.out.println(s.substring(0, stopIndex));
array.add(s.substring(0, stopIndex));
}
}
for(int i=0; i<array.size(); i++){
int x = Integer.parseInt(array.get(i));
    //parseIntがここでは何故か働かない。
}

課題が一つ見つかりましたが、まだ分かっていない。ここは要チェック必要です。

ArrayListの方をIntegerにして、代入する時にparseIntしてみた時はwhileで回す分は問題なく動きますが、if文の中のparseIntではエラーになる??

明示的な変換をしては行けないのだろうか?

 

ただ、もっと調べてみると便利なものがありますね、、math.BigIntegerクラスが使えるみたいで、グッと楽に計算できます。大きな数字の文字列の四則演算を可能にします。

import java.math.BigInteger;

public class Main {
public static void main(String[] args) {
//Sは最大1000桁(数字の)文字列
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
BigInteger S = new BigInteger(s);
//BigInteger S = new BigInteger("123456789987654321123456789987654321123456789987654321123456789987654321");
//Tは1桁(数字の)文字列
String t = sc.nextLine();
BigInteger T = new BigInteger(t);
//BigInteger T = new BigInteger("5");
S = S.multiply(T);
System.out.println(S);
}
}

 

 

学習雑記 JAVA21

ラムダ式と関数型インターフェース

ラムダ式 : メソッド定義を式として扱える機能で、主に関数型インターフェースを実装する時に使う。下記構文のように引数と処理式をアロー演算子( -> )で結ぶ

構文 Interface名 Object名 = (argument1, argument2, ...) -> {return Processing};

@ 引数の型はコンパイラが判断するので、記述する必要はない。(記述しても良い)

@ 引数を持たないメソッドでも使える()->{return Process};

@ 引数が1つだけの時、()を省略可能

@ 実行したい処理式が1つだけの時、{}を省略可能

{}を省略している時、戻り値がある場合、returnを省略する

@ 処理内容で変数を参照する場合は注意が必要

 

ラムダ式内にある変数のアクセスとスコープ

ラムダ式の変数スコープはそれを囲むブロックと同じ変数スコープを持つことになる。変数が重複するとコンパイルエラーになる。そのため、ラムダ式内でメソッドのローカル変数にアクセスできない。ただし、finalと修飾子がついている変数は利用できる。修飾されていなくとも実質的に変更がない場合でも許容はされる。

1. ラムダ式内で定義した変数を使うことはできる

Algorithm algorithm = (name) -> {
int x = 5;
while(x>0) {
System.out.println("Hello, " + name);
x--;
}
};

2. 外で定義した変数をラムダ式内で使う場合はコンパイルエラー

int x = 5; //外で定義した変数をラムダ式内で使用するとコンパイルエラー
Algorithm algorithm = (name) -> {
while(x>0) {
System.out.println("Hello, " + name);
x--;
}
};

3. ラムダ式内で使う変数を外で使用した場合もコンパイルエラー

Algorithm algorithm = (name) -> {
int x = 5;
while(x>0) {
System.out.println("Hello, " + name);
x--;
}
};
int y = x + 5; // x をラムダ式の外で使うとコンパイルエラー

 

付け替え可能なアルゴリズムを実現するGoFのStrategy(下記)

共通の方で使えるインターフェースを用意し、アルゴリズムを処理の流れを担当するクラスと、具体的なアルゴリズムを担当するクラスに分けて作成する。この時ラムダ式を使えば、実装が必要なメソッドをを1つだけ持つインターフェース型変数に実行したいこコードを代入することができるようになる。(このようなインターフェースを関数型インターフェースとよぶ)

interface Algorithm {
void abstractMethod(String name);
}
class Service {
private Algorithm logic;
public void setLogic(Algorithm logic) {
this.logic = logic;
}
public void doProcess(String name) {
this.logic.abstractMethod(name);
}
}
public class Main {
public static void main(String[] args) {
Algorithm algorithm = (name) -> {
System.out.println("Hello, " + name);
};
Service s = new Service();
s.setLogic(algorithm);
s.doProcess("LAMBDA");
}
}

学習雑記JAVA20

複数の言語を交互に学習しているとビルトインメソッドが「あれっ何故動かない??」ということがおき、しかも気づけないことがある。

名前も似ていて、エラー表示には、「メソッドが無い」と出るよりは「型が違う」かまたは「型に互換性が無い」と表示されることもドツボにハマる原因だ。

 

今日は、ポーカーゲームのペアの判定部分を作成しようとして、pythonに出てくるarray.count(特定要素)をJavaで使用を試みて、無限ループに陥った。

 

仮説=>実行=>不具合からの検証がまだまだ視点の切り替えが遅いんだな、、と感じた。

 

振り返り:

pokerゲームはルールを一つ一つメソッド化する上で非常に勉強になる。コードを書いていく上で非常に挑戦的だった。(まだ出来上がっていない)

現状、下記の3種のクラスとタイプに分けて構築中。Handクラスのロジックが多いので、判定する部位などを切り分けるか検討。ストレートの判定は簡単に思えたが1:10:J:Q:Kのように一見連なっていない部分のループをどう考えるかが問題かなと思っている。

 

カードの種類と強さをランク付けるCardクラス

1ゲームごとのカードのパターンを決めるDeckクラス

プレイヤーに渡すカードや手札の判定を行うHandクラス

class Card{
short rank;
short mark;
String[] marks = {"Spade", "Heart", "Club", "Diamond"};
static String[] ranks = {"A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"};

public static String getRank(int r) {
return ranks[r];
}
Card(short mark, short rank) {
this.rank = rank;
this.mark = mark;
}
@Override
public String toString() {
return ranks[rank] + " の " + marks[mark];
}
public short getRank() {
return rank;
}
public short getMark() {
return mark;
}
}
class Deck{
// デッキは4種*13の52枚のカード
ArrayList<Card> cards;
Deck() {
cards = new ArrayList<>();
for(short a =0; a<= 3; a++) {
for(short b=0; b<= 12; b++){
cards.add(new Card(a, b));
}
}
}
// 配列の順序はビルトインでシャッフル
public void shuffle() {
Collections.shuffle(cards);
}
// 配ったカードは重複しないようにデッキのサイズを調整
public Card drawCard() {
return cards.remove(cards.size()-1);
}
}