java-beginner.com ブログ

プログラミングを学習するブログ(Javaをメインに)

単位付きの数値データの数値と単位を分割する練習

投稿日:

最終更新日:2020年07月25日

アイキャッチ

こんにちは。「Javaを復習する初心者」です。

入力されたデータが「1g」、「2kg」など単位付きのデータである場合、数値部分と単位部分を切り分けるプログラムについて考えてみました。簡単のため、重さのデータが入力されるとします。また、使われている単位は「g」と「kg」とします。

Listにデータが格納されていると仮定

以下のようにArrayListにデータが格納されているとします。(ファイルからデータを読み取るなど、入力にあたる部分の代わりです。)

ソース

	List<String> list = new ArrayList<>();
	list.add("1g");
	list.add("2kg");
	list.add("12g");
	list.add("123g");

変数listには以下のデータがString型で格納されています。

  1. 1g
  2. 2kg
  3. 12g
  4. 123g

今回作るプログラムの目的は上記のデータについて、数値部分と単位部分を分割することです。例えば、「1g」を「1」と「g」に分割するという処理です。

結果を格納するためのクラスを定義

結果を格納するためのクラスを以下のように作りました。

ソース

public class Result {

	private int num;
	private String str;

	public Result(int num, String str) {
		super();
		this.num = num;
		this.str = str;
	}

	public int getNum() {
		return num;
	}

	public String getStr() {
		return str;
	}

}

コンストラクタで、メンバ変数numには分割後の数値部分、メンバ変数strには分割後の単位部分を格納するという使い方です。実際には次のように、Resultオブジェクトを要素に持つArrayListを使うことにします。

ソース

	List<Result> results = new ArrayList<>();

変数listの1番目のデータを分割した結果を変数resultsの1番目に格納するのが目的となります。

重さの単位を定義したクラスを作成

データに使われている単位は「g」と「kg」と仮定しました。今回はこれらを以下のように列挙型クラスで定義しました。(重さの単位は英語で「Measure」らしいので、それをクラス名としました。)

ソース

public enum Measure {

	MEASURE1("g"), MEASURE2("kg");

	private Measure(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}

	private final String name;

}

上記は列挙型でコンストラクタを使っています。

今回は「g」と「kg」が使われている想定なので、この2つを定義しています。列挙型の定数自体は「MEASURE1」など連番で定義することにしました。具体的な単位の文字列はメンバ変数nameに格納することにしました。

コンストラクタを呼び出す記述にしているため、メンバ変数nameに具体的な文字列が格納されます。getName()メソッドを呼び出すことで、メンバ変数nameの内容を取得できます。

検索用にPatternクラスを使う

「1kg」という文字列に「g」が含まれているのか「kg」が含まれているのか。これをプログラムで判定ために、Patternクラスを使うことを考えました。

以下のようにMeasureクラスから正規表現を作りました。

ソース

	// 定義された単位
	Measure[] measures = Measure.values();

	// 検索用の正規表現
	List<Pattern> patterns = new ArrayList<>();
	for(int i = 0; i < measures.length; i++) {
		patterns.add(Pattern.compile("^\\d+" + measures[i].getName() + "$"));
	}

列挙型クラスではvalues()というメソッドを使うことが出来ます。定義されている列挙型定数をすべて取得することが出来ます。

List型変数patternsにPatternオブジェクトを格納しています。Patternオブジェクトそれぞれに、以下の正規表現が設定されます。

  1. ^\d+g$
  2. ^\d+kg$

1番目は「行頭」、「数字が1回以上」、「g」、「行末」という正規表現です。例えば「1g」はこの正規表現を満たします。また、「1kg」はこの正規表現を満たしません。

参考

String型にはendsWith()メソッドがありますが、これでは期待通りの結果は得られませんでした。

ソース

		System.out.println("kg".endsWith("g"));

上記の結果は「true」となります。「1kg」の単位部分を正確に判定できないため、今回の目的には使えませんでした。

各データを正規表現で検索

正規表現をPatternオブジェクトに設定した後は、各データを検索して、ヒットすれば、その単位が使われていることになります。以下に、今回作ったプログラム全体を載せます。

ソース

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

public class Test {

	public static void main(String[] args) {
		List<String> list = new ArrayList<>();
		list.add("1g");
		list.add("2kg");
		list.add("12g");
		list.add("123g");

		List<Result> results = new ArrayList<>();

		// 定義された単位
		Measure[] measures = Measure.values();

		// 検索用の正規表現
		List<Pattern> patterns = new ArrayList<>();
		for(int i = 0; i < measures.length; i++) {
			patterns.add(Pattern.compile("^\\d+" + measures[i].getName() + "$"));
		}

		// 各データを正規表現で検索
		for(String s: list) {
			Measure measure = null;
			for(int i = 0; i < measures.length; i++) {
				if(patterns.get(i).matcher(s).matches()) {
					measure = measures[i];
					break;
				}
			}

			int num = Integer.parseInt(s.substring(0, s.length() - measure.getName().length()));
			results.add(new Result(num, measure.getName()));
		}

		// 結果出力
		for(Result r: results) {
			System.out.printf("%3d, %2s", r.getNum(), r.getStr());
			System.out.println();
		}
	}
}

結果は以下のように出力されました。

結果

  1,  g
  2, kg
 12,  g
123,  g

データの数値部分を取り出すために、Stringクラスのsubstring()メソッドを使っています。第一引数を0にすることで、先頭から文字列を取り出すことが出来ます。どこまで取り出すかは、データの文字列長と使用されている単位の文字列長を使って計算できます。

以上、参考になれば幸いです。