制御フロー · 07
繰り返しをもっと
一つずつ取り出す、逆順に回る、終わりの印まで読む、必ず1回は回る do-while。
「繰り返し」の単元で学んだ for と while に、四つの形を足します。
下の例は 0 が出るまで正の整数を読んで集め、値を一つずつ取り出して合計を求め、後ろから逆順に表示し、最後に合計が何桁の数かを数えます。
nums = []x = int(input())while x != 0: # 0이 끝 표시예요. 0 뒤의 줄은 읽지 않아요 nums.append(x) x = int(input()) total = 0for v in nums: # 번호 없이 값을 하나씩 total += vprint(total) for i in range(len(nums) - 1, -1, -1): # 마지막 번호부터 0번까지 print(nums[i]) digits = 0while True: # do-while이 없어서: 먼저 한 번 하고 끝에서 확인해요 digits += 1 total //= 10 if total == 0: breakprint(digits)const lines = require("fs").readFileSync(0, "utf8").split("\n");let at = 0;const nums = [];let x = Number(lines[at++]);while (x !== 0) { // 0이 끝 표시예요. 0 뒤의 줄은 읽지 않아요 nums.push(x); x = Number(lines[at++]);} let total = 0;for (const v of nums) { // 번호 없이 값을 하나씩 total += v;}console.log(total); for (let i = nums.length - 1; i >= 0; i--) { // 마지막 번호부터 0번까지 console.log(nums[i]);} let digits = 0;do { // 먼저 한 번 하고 끝에서 확인해요 digits++; total = Math.trunc(total / 10);} while (total !== 0);console.log(digits);import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");let at = 0;const nums: number[] = [];let x: number = Number(lines[at++]);while (x !== 0) { // 0이 끝 표시예요. 0 뒤의 줄은 읽지 않아요 nums.push(x); x = Number(lines[at++]);} let total: number = 0;for (const v of nums) { // 번호 없이 값을 하나씩 total += v;}console.log(total); for (let i = nums.length - 1; i >= 0; i--) { // 마지막 번호부터 0번까지 console.log(nums[i]);} let digits: number = 0;do { // 먼저 한 번 하고 끝에서 확인해요 digits++; total = Math.trunc(total / 10);} while (total !== 0);console.log(digits);import java.util.ArrayList;import java.util.List;import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); List<Integer> nums = new ArrayList<>(); int x = sc.nextInt(); while (x != 0) { // 0이 끝 표시예요. 0 뒤의 줄은 읽지 않아요 nums.add(x); x = sc.nextInt(); } int total = 0; for (int v : nums) { // 번호 없이 값을 하나씩 total += v; } System.out.println(total); for (int i = nums.size() - 1; i >= 0; i--) { // 마지막 번호부터 0번까지 System.out.println(nums.get(i)); } int digits = 0; do { // 먼저 한 번 하고 끝에서 확인해요 digits++; total /= 10; } while (total != 0); System.out.println(digits); }}using System;using System.Collections.Generic; class Program{ static void Main() { List<int> nums = new List<int>(); int x = int.Parse(Console.ReadLine()); while (x != 0) // 0이 끝 표시예요. 0 뒤의 줄은 읽지 않아요 { nums.Add(x); x = int.Parse(Console.ReadLine()); } int total = 0; foreach (int v in nums) // 번호 없이 값을 하나씩 { total += v; } Console.WriteLine(total); for (int i = nums.Count - 1; i >= 0; i--) // 마지막 번호부터 0번까지 { Console.WriteLine(nums[i]); } int digits = 0; do // 먼저 한 번 하고 끝에서 확인해요 { digits++; total /= 10; } while (total != 0); Console.WriteLine(digits); }}#include <iostream>#include <vector> int main() { std::vector<int> nums; int x; std::cin >> x; while (x != 0) { // 0이 끝 표시예요. 0 뒤의 줄은 읽지 않아요 nums.push_back(x); std::cin >> x; } int total = 0; for (int v : nums) { // 번호 없이 값을 하나씩 total += v; } std::cout << total << '\n'; for (int i = (int)nums.size() - 1; i >= 0; i--) { // 마지막 번호부터 0번까지 std::cout << nums[i] << '\n'; } int digits = 0; do { // 먼저 한 번 하고 끝에서 확인해요 digits++; total /= 10; } while (total != 0); std::cout << digits << '\n';}#include <stdio.h> int main(void) { int nums[1000], n = 0, x; scanf("%d", &x); while (x != 0) { // 0이 끝 표시예요. 0 뒤의 줄은 읽지 않아요 nums[n++] = x; scanf("%d", &x); } int total = 0; for (int i = 0; i < n; i++) { // 값을 하나씩 꺼내는 꼴이 없어서 번호로 돌아요 total += nums[i]; } printf("%d\n", total); for (int i = n - 1; i >= 0; i--) { // 마지막 번호부터 0번까지 printf("%d\n", nums[i]); } int digits = 0; do { // 먼저 한 번 하고 끝에서 확인해요 digits++; total /= 10; } while (total != 0); printf("%d\n", digits); return 0;}nums = [];x = コンソール.行読み().数に();// 0이 끝 표시예요. 0 뒤의 줄은 읽지 않아요間 (x != 0){ nums.追加(x); x = コンソール.行読み().数に();} total = 0;// 번호 없이 값을 하나씩それぞれ (v の中 nums){ total += v;}コンソール.書く(total); // 마지막 번호부터 0번까지繰り返す (i = nums.個数 - 1; i >= 0; i--){ コンソール.書く(nums[i]);} digits = 0;// do-while이 없어서: 먼저 한 번 하고 끝에서 확인해요間 (真){ digits++; total = 数学.商(total, 10); もし (total == 0) { 抜ける; }}コンソール.書く(digits);12 7 30 0 99
49 30 7 12 2
ポイント
- 一つずつの繰り返し(for-each ループ)は、番号なしで値をそのまま取り出します。
番号を間違えることがないので、最初から最後まで全部見るときにいちばん安全です。
何番目かが必要なときや逆順に進むときは番号で回します。 - 逆順の繰り返し(逆順ループ)は、始まり・条件・変わる量をすべて反対にします。
最後の番号個数 - 1から始めて、i >= 0のあいだ、i--で減らします。 - 終わりの印までの繰り返し(番兵ループ)は、個数を先に教えてもらえず、「0 が出たら終わり」のように決めた値で終わりを知らせるときに使います。
先に一つ読み、印でないあいだ処理して、周の終わりで次の値を読みます。
印そのものは処理しません。 - 印もなければ入力が終わるまで読みます。
「入力を受け取る」の単元で見た形です。
終わりの印まで読む繰り返しと形は同じで、条件だけが「読めたか」に変わります。 - do-while は本体を先に1回実行し、条件を最後に見ます。
だから必ず1回は回ります。
桁数を数えるのがそういう仕事です。
0 も1桁の数ですが、while (total != 0)と書くと1回も回らず、0桁と答えてしまいます。 - Python と Craykon には do-while がありません。
無限ループの中で先に仕事をして、最後に条件を見て抜ければ、同じ動きになります。
言語ごとの書き方
| Python | for v in nums:、逆順は for v in reversed(nums): か番号で range(len(nums) - 1, -1, -1)。終わりの印は while x != 0:、入力の終わりまでは for line in sys.stdin:。do-while はないので while True: の中で if 条件: break |
|---|---|
| JavaScript | for (const v of nums)、逆順は for (let i = nums.length - 1; i >= 0; i--)。nums.reverse() は元のリスト自体を逆にしてしまいます。do { } while (条件); は最後に ; を付けます。入力はまとめて読んで行のリストに分けておき、番号で取り出します。 入力の終わりまでは、 .trim().split("\n") で分けた行のリストを for (const line of lines) で最後まで回します |
| TypeScript | JavaScript と同じです |
| Java | for (int v : nums)、逆順は for (int i = nums.size() - 1; i >= 0; i--)(配列なら nums.length)。do { } while (条件);。入力の終わりまでは while (sc.hasNextInt()) |
| C# | foreach (int v in nums)、逆順は for (int i = nums.Count - 1; i >= 0; i--)。do { } while (条件);。入力の終わりまでは while ((line = Console.ReadLine()) != null) |
| C++ | for (int v : nums)、逆順は for (int i = (int)nums.size() - 1; i >= 0; i--) — size() は符号なしの数なので int に変えて使います。do { } while (条件);。入力の終わりまでは while (std::cin >> x) |
| C | 値を一つずつ取り出す形がないので for (int i = 0; i < n; i++)、逆順は for (int i = n - 1; i >= 0; i--)。do { } while (条件);。入力の終わりまでは while (scanf("%d", &x) == 1) |
| Craykon | それぞれ (v の中 nums)、逆順は 繰り返す (i = nums.個数 - 1; i >= 0; i--)。終わりの印は 間 (x != 0)、入力の終わりまでは 行読み() が なし を返すまで(間 (line != なし))。do-while はないので 間 (真) { … もし (条件) { 抜ける; } } |
よくある間違い
一つずつ取り出した変数を変えれば、リストも変わると思ったPython · JavaScript · TypeScript · Java · C# · C++ · Craykon
nums = [1, 2, 3]
for v in nums:
v = v * 2
print(nums)→ [1, 2, 3] — そのままです。v は取り出した値を入れた変数にすぎないので、v を変えてもリストのマスは変わりません。JavaScript(for (let v of nums))、Java、C++(for (int v : nums))、Craykon も同じくそのままです。C# は foreach の変数に代入するところでコンパイルエラー(CS1656)になります
マスを変えるには番号で回して nums[i] = nums[i] * 2 と書きます。
C++ は for (int& v : nums) で取り出せば元のリストが変わります。
回している最中にリストから値を取り除くミスは「リストの編集」の単元で扱います。
do-while の本体の中で作った変数を条件に使ったJava · C# · C · C++ · JavaScript · TypeScript
int total = 0;
do {
int x = sc.nextInt();
total += x;
} while (x != 0);→ コンパイルエラーです(Java は cannot find symbol)。{ } の中で作った x は閉じかっこで消えるので、そのあとの条件からは見えません。TypeScript もコンパイルエラー(Cannot find name 'x')で、JavaScript は実行中に ReferenceError: x is not defined で止まります
条件で使う変数は do の前で作ります:int x; のあとに do { x = sc.nextInt(); … } while (x != 0);。
符号なしの番号で逆順に回したC · C++
std::vector<int> nums = {12, 7, 30};
for (size_t i = nums.size() - 1; i >= 0; i--) {
std::cout << nums[i] << '\n';
}→ 30、7、12 を表示しても止まりません。符号なしの i は 0 から 1 を引くと -1 ではなく 18446744073709551615 になるので、i >= 0 がいつも真です。そのあとはリストの外を読みますが、これは未定義動作で、何が起きるかは決まっていません。ジャッジで動かしたときは、でたらめな値を次々に表示してから Segmentation fault で落ちました
int i = (int)nums.size() - 1 のように符号つきの整数で回します。
符号なしのまま回すなら for (size_t i = nums.size(); i-- > 0; ) と書きます。
選び方は簡単です。
最初から最後まで全部見るなら一つずつ、番号が必要か逆順なら番号で、何回回るかわからないなら
while、必ず1回はやるなら do-while です。終わりの印まで読むときは「読む → 確かめる → 処理する → また読む」の順を守ります。
確かめるより先に処理すると、印まで足してしまいます。