{ }codelper

学ぶ › Java

さらに先へ · 24

数える・累計・グループ化

一度たどりながら、条件に合うものを数え、ここまでの値を持ち歩き、キーごとにまとめます。

数える、累計、グループ化は、データを一度たどりながら答えを作っていく三つの基本の形です。
下の例は点数を一つずつ見ながら、ここまでの合計と最高点を表示し、80 点以上がいくつあるかを数え、点数を 60 点台・70 点台のように十の位でまとめて表示します。

import java.util.*; public class Main {    public static void main(String[] args) {        Scanner in = new Scanner(System.in);        List<Integer> scores = new ArrayList<>();        while (in.hasNextInt()) scores.add(in.nextInt());        int count = 0;               // 80점 이상인 점수의 개수        int total = 0;               // 지금까지의 합        int best = scores.get(0);    // 지금까지의 최고 점수        Map<Integer, List<Integer>> groups = new TreeMap<>();   // 십의 자리 → 그 묶음의 점수들(열쇠 순)        for (int s : scores) {            if (s >= 80) count++;            total += s;            if (s > best) best = s;            System.out.println(total + " " + best);            int key = s / 10 * 10;            if (!groups.containsKey(key)) groups.put(key, new ArrayList<>());   // 처음 보는 열쇠면 빈 목록부터            groups.get(key).add(s);        }        System.out.println(count);        for (int key : groups.keySet()) {            StringBuilder line = new StringBuilder(key + ":");            for (int s : groups.get(key)) line.append(" ").append(s);            System.out.println(line);        }    }}
stdin
72 85 90 68 85 77 93
stdout
72 72
157 85
247 90
315 90
400 90
477 90
570 93
4
60: 68
70: 72 77
80: 85 85
90: 90 93

ポイント

  • 数えるときは 0 から始めて、条件に合うときだけ 1 を足します。
    合計と違って、値ではなく 1 を足します。
  • 累計の値は、一つ見るたびに書き換える変数です。
    ここまでの合計(total)、ここまでの最高(best)のように「ここまでの答え」をいつも持っているので、途中の結果をその場で表示できます。
    繰り返しの単元で見た total += i がまさにこの形です。
  • 累計の変数は繰り返しの前で作ります。
    個数と合計は 0 から、最高・最低は最初の値から始めます。
  • グループ化は、値ごとにキーを決めて(例では 68 → 60、72 → 70)、辞書の中のそのキーのリストに値を入れます。
    数える辞書(辞書の単元)は値が数でしたが、まとめる辞書は値がリストです。
  • 初めて見るキーには、まだリストがありません。
    先に空のリストを入れてから、値を追加します。
  • 三つを別々にたどる必要はありません。
    例は一つの繰り返しの中で、数える・累計・グループ化をまとめて行います。

言語ごとの書き方

Python数えるのは count += 1(1行なら sum(1 for s in scores if s >= 80))、累計は total += s。
グループ化は例のように if key not in groups: groups[key] = [] のあとで groups[key].append(s)、短く書くなら groups.setdefault(key, []).append(s) や collections.defaultdict(list)
JavaScript数えるのは count++(1行なら scores.filter((s) => s >= 80).length)、累計は total += s。
グループ化は Map に if (!groups.has(key)) groups.set(key, []); のあとで groups.get(key).push(s)
TypeScriptJavaScript と同じで、new Map<number, number[]>() のように型を書きます。
groups.get(key) は型の上では undefined かもしれないので、そのまま .push すると型エラー(TS2532)です。
例のように取り出した値を変数に受けて、undefined かどうか確かめます
Java数えるのは count++(1行なら scores.stream().filter(s -> s >= 80).count())、累計は total += s。
グループ化は Map<Integer, List<Integer>> に containsKey で確かめて put(key, new ArrayList<>())、短く書くなら groups.computeIfAbsent(key, k -> new ArrayList<>()).add(s)。
TreeMap ならキー順にたどれます
C#数えるのは count++(LINQ なら scores.Count(s => s >= 80))、累計は total += s。
グループ化は Dictionary<int, List<int>> に if (!groups.ContainsKey(key)) groups[key] = new List<int>(); のあとで groups[key].Add(s)。
SortedDictionary ならキー順にたどれます
C++数えるのは count++(<algorithm> の std::count_if もあります)、累計は total += s。
グループ化は std::map<int, std::vector<int>> に groups[key].push_back(s) の1行です。
ないキーなら [] が空の vector を作ってくれます
C数えるのと累計はほかの言語と同じです(count++、total += s)。
辞書がないので、グループ化は手で行います。
キーが小さい整数なら、例のように2次元配列に groups[key][sizes[key]++] = s で入れ、グループごとの個数(sizes)を別に数えます
Craykon数えるのは count++、累計は total += s。
グループ化は 辞書() に もし (!groups.含む(key)) { groups[key] = []; } のあとで groups[key].追加(s)。
キー順に表示するには groups.キー一覧 を 並べ替え() してからたどります

よくある間違い

+= を =+ と書いた

int count = 0;
int scores[] = {72, 85, 90};
for (int i = 0; i < 3; i++)
    if (scores[i] >= 80) count =+ 1;
printf("%d\n", count);

→ 1 — エラーなしで動きますが、2 になりません。count =+ 1 は count = +1、つまり「1 を入れる」です

count += 1 と書きます。
個数がいつも 1 になるときは、まずこの2文字の順番を確かめます。

累計の変数を繰り返しの中で 0 にした

const scores = [72, 85, 90];
for (const s of scores) {
  let total = 0;
  total += s;
  console.log(total);
}

→ 72、85、90 — 1周ごとに 0 に戻るので、合計がたまりません

total の最初の値(0)は繰り返しの前で一度だけ入れ、繰り返しの中では足すだけにします。
let や int のような宣言の言葉がない Python と Craykon でも、total = 0 が繰り返しの中にあれば同じく 72、85、90 になります。

空のリストを作らずにグループへ入れたJava

Map<Integer, List<Integer>> groups = new TreeMap<>();
int s = 68;
int key = s / 10 * 10;
groups.get(key).add(s);

→ NullPointerException — ないキーなので get が null を返しました

先に if (!groups.containsKey(key)) groups.put(key, new ArrayList<>()); とするか、groups.computeIfAbsent(key, k -> new ArrayList<>()).add(s) と書きます。

言語ごとの違い
グループごとにリストを別々に作る必要があります。
Python で groups = dict.fromkeys([60, 70], []) とキーを先に用意すると、すべてのキーが同じ一つのリストを共有するので、68 と 72 を別のキーに入れても {60: [68, 72], 70: [68, 72]} になります。
例のように、初めて見るキーごとに新しい [] を作りましょう。

練習問題