
目次
サンプル・プログラム
4年に一度のうるう年
4年に一度のうるう年――入力した西暦年がうるう年かどうかを判定するプログラムが "isLeapYear1.py" である。ユーザー定義関数の部分を抜粋して説明する。
なお、calendarモジュールの isleap関数を使えば、うるう年の判定は容易にできるのだが、今回は if文 の教材として、関数を手作りする。
なお、calendarモジュールの isleap関数を使えば、うるう年の判定は容易にできるのだが、今回は if文 の教材として、関数を手作りする。
def isLeapYear1(year):
"""うるう年かどうかを判定する(不完全版)
Args:
year(int): 西暦年
Returns:
これまで何度か出てきた if文を使って、「4年に一度」の判定をプログラムに実装した。
if文は if (式) のように用い、式が True のとき、それに続くブロック (インデントのある部分)を実行する。式が False のときは、else に続くブロック を実行する。
if文は if (式) のように用い、式が True のとき、それに続くブロック (インデントのある部分)を実行する。式が False のときは、else に続くブロック を実行する。
正確なうるう年判定
「4年に一度、うるう年」というのは、じつは不正確である。
現在の西暦であるグレゴリオ暦で、うるう年は次のように定義されている。
現在の西暦であるグレゴリオ暦で、うるう年は次のように定義されている。
- 4で割り切れる年はうるう年である。
- ただし、100で割り切れる年は平年である。
- ただし、400で割り切れる年はうるう年である。
def isLeapYear2(year):
"""うるう年かどうかを判定する(完全版)
Args:
year(int): 西暦年
Returns:
あらたに elif が登場した。一般的に、if文 は次のような構造をとる。
まず、グレゴリオ暦の定義を次のように並べ替える必要がある。
if (式1):
ブロック1
elif (式2):
ブロック2
else:
ブロック3
- もし式1がTrueならブロック1を実行する。
- 式1がFalseであり、式2がTrueならブロック2を実行する
- 式1,式2がすべてFalseなら、ブロック3を実行する。
- if文は必須である。
- elif句は2つ以上書くことができる。必要がなければ省略できる。
- else句は省略できる。
まず、グレゴリオ暦の定義を次のように並べ替える必要がある。
- もし西暦年を400で割り切れれば、うるう年である。
- そうでなければ、西暦年を100で割り切れれば、平年である。
- そうでなければ、西暦年を4で割り切れれば、うるう年である。
- そうでなければ、平年である。

うるう年の判定
早期リターン
"isLeapYear2.py" では、どんな西暦年に対しても3回の剰余算を行う。
一方、「3.7 数値型の誤差と範囲」で紹介したように、Python の処理速度はそれほど早い方ではない。
少しでも早く結果を得るには、算術演算は少ないに越したことはない。
そこで、プログラム "isLeapYear3.py" をご覧いただきたい
一方、「3.7 数値型の誤差と範囲」で紹介したように、Python の処理速度はそれほど早い方ではない。
少しでも早く結果を得るには、算術演算は少ないに越したことはない。
そこで、プログラム "isLeapYear3.py" をご覧いただきたい
def isLeapYear3(year):
"""うるう年かどうかを判定する(早期リターン版)
Args:
year(int): 西暦年
Returns:
bool: True うるう年 / False 平年
"""
if (year % 400 == 0):
return True
elif (year % 100 == 0):
return False
elif (year % 4 == 0):
return True
else:
return False
# メイン・プログラム =======================================================
400で割り切れたら、その時点で return してしまうのである。100, 4についても同様だ。
これを早期リターンと呼ぶ。Python では1つの関数に複数の return を書くことが可能という仕様を利用した。
これを早期リターンと呼ぶ。Python では1つの関数に複数の return を書くことが可能という仕様を利用した。
入れ子になったif文
20世紀以降(1901年~)の西暦年月日を元号に変換するプログラムを作ってみる。
まず、20世紀以降で元号の切り替わった年月日を整理しておこう。
まず、20世紀以降で元号の切り替わった年月日を整理しておこう。
1912年7月30日 | 大正 |
1926年12月25日 | 昭和 |
1989年1月8日 | 平成 |
2019年5月1日 | 令和 |

明治と大正の切り替わり時期
令和を除けば、月の途中で切り替わっているため、条件分岐が複雑になる。
たとえば、1911年以前であれば「明治」で確定だが、1912年は――月が7以下で、日が29以下の時は「明治」、日が30以上なら「大正」。月が8以上なら「大正」――というように、元号の切り替わりの年は、月と日を加えた3段階の条件分岐が必要になる。
これを Python のプログラムにすると、次のように if文が入れ子になる。
たとえば、1911年以前であれば「明治」で確定だが、1912年は――月が7以下で、日が29以下の時は「明治」、日が30以上なら「大正」。月が8以上なら「大正」――というように、元号の切り替わりの年は、月と日を加えた3段階の条件分岐が必要になる。
これを Python のプログラムにすると、次のように if文が入れ子になる。
def gregorian2gengo1(year, month, day):
"""西暦年を元号に変換する(20世紀以降)
Args:
year(int): 西暦年
month(int): 月
day(int): 日
Returns:
プログラムをよく見てもらうと、year == 2019、つまり、平成から令和への切り替わりのところだけは、入れ子が2段で済んでいる。前述の通り、5月1日に元号が切り替わったため、条件式で年と月の2つを見ておけば切り替わるタイミングが判断できるのだ。また、5月1日であれば、4月の年度初めともぶつからないし、ゴールデンウィーク中なので基幹業務系システムを変更する影響も小さい。このように、社会の隅々にまで普及していたシステムへの影響を少しでも減らそうと、5月1日に改元が行われたという背景がある。
Python は他言語と同じように、if文の入れ子を幾らでも深くすることができる。しかし、いかにも読みにくい。
フロー図で表そうとすると、プログラムの流れが曲がりくねり、まるでスパゲッティの麺が絡んでいるように見えることから、スパゲッティ・プログラムと忌み嫌われる。スパゲッティ・プログラムは、あとで読んだときに理解に時間がかかることも問題なのだが、入れ子になっている分だけテストケースを増やさなくてはならず、余分な工数(コスト)がかかるし、テストケース漏れがあった場合には不具合が見つからないまま本番稼動を始めてしまう危険性がある。

どうしたら麺が絡まないようにすることができるか、よく考えてみよう――入れ子の原因は、年・月・日を独立した変数として見ていることにある。
ここで、年4桁+月2桁+日2桁という8桁の整数にまとてみよう。そうすれば、1つの変数で条件判定できる。つまり、1次元の数直線上で切り替わるタイミングを判定することができるようになる。この変数には yyyymmdd という名前を付けることが多い。もっとも、19262345(1926年23月45日)のような値は存在しないので、数直線といっても連続しているわけではない。
フロー図で表そうとすると、プログラムの流れが曲がりくねり、まるでスパゲッティの麺が絡んでいるように見えることから、スパゲッティ・プログラムと忌み嫌われる。スパゲッティ・プログラムは、あとで読んだときに理解に時間がかかることも問題なのだが、入れ子になっている分だけテストケースを増やさなくてはならず、余分な工数(コスト)がかかるし、テストケース漏れがあった場合には不具合が見つからないまま本番稼動を始めてしまう危険性がある。

どうしたら麺が絡まないようにすることができるか、よく考えてみよう――入れ子の原因は、年・月・日を独立した変数として見ていることにある。
ここで、年4桁+月2桁+日2桁という8桁の整数にまとてみよう。そうすれば、1つの変数で条件判定できる。つまり、1次元の数直線上で切り替わるタイミングを判定することができるようになる。この変数には yyyymmdd という名前を付けることが多い。もっとも、19262345(1926年23月45日)のような値は存在しないので、数直線といっても連続しているわけではない。

明治と大正の切り替わり時期(yyyymmdd)
def gregorian2gengo2(year, month, day):
"""西暦年を元号に変換する(20世紀以降)
Args:
year(int): 西暦年
month(int): 月
day(int): 日
Returns:
これをプログラムにしたものが "gengo2.py" である。if文の入れ子がなくなり、流れが上から下へ直線的に落ちるようになった。
もし、プログラムを作っていて if文を入れ子にしなければならない場面に出会ったら、いったん立ち止まって、条件式に使っている変数を一元化できるか工夫してみてほしい。
もし、プログラムを作っていて if文を入れ子にしなければならない場面に出会ったら、いったん立ち止まって、条件式に使っている変数を一元化できるか工夫してみてほしい。
練習問題
次回予告
Python には、条件分岐として、他言語にある switch文が存在しない。if文で代用できるというのがその理由なのだが、代わりに Python 3.10では match文が実装された。
次回は、switch文とはひと味違う match文の書き方について学ぶ。
次回は、switch文とはひと味違う match文の書き方について学ぶ。
コラム:フロー図
制御の流れを表すのに、よくフロー図(流れ図、フローチャート)を使う。本文中でも紹介した。
「コラム:プログラム電卓から人工知能へ」で紹介したプログラミング電卓付属の「プログラム・ライブラリ」にもフロー図が掲載されており、とても参考になった。
プログラミングに慣れるまでは、手間を惜しまず、自力でフロー図を書くことをお勧めする。スパゲッティ・プログラムになっていないか点検する上でも役に立つからだ。

また、プログラミングに慣れた方でも、プログラムをフロー図にして見直すことで、条件式を正規化することなどして、制御の流れをシンプルにすることができる場合がある。
短くシンプルなプログラムほどバグが少ない。一度、自作プログラムを見直してみてはいかがだろうか。
「コラム:プログラム電卓から人工知能へ」で紹介したプログラミング電卓付属の「プログラム・ライブラリ」にもフロー図が掲載されており、とても参考になった。
プログラミングに慣れるまでは、手間を惜しまず、自力でフロー図を書くことをお勧めする。スパゲッティ・プログラムになっていないか点検する上でも役に立つからだ。

また、プログラミングに慣れた方でも、プログラムをフロー図にして見直すことで、条件式を正規化することなどして、制御の流れをシンプルにすることができる場合がある。
短くシンプルなプログラムほどバグが少ない。一度、自作プログラムを見直してみてはいかがだろうか。
コラム:スパゲッティプログラム

空飛ぶスパゲッティ・モンスター教
FORTRANやBASICでプログラムを組んでいた時代、GOTO文やグローバル変数を多用することがスパゲッティプログラムの代表例とされた。
Pythonや JavaScriptといったモダンな手続き型言語では、GOTO文がなくなり、クラスのプロパティ(メンバシップ変数)を活用することでグローバル変数も使わずに済むようになっている。

だがしかし、安易な継承を繰り返せばスパゲッティプログラムになってしまうし、関数型を目指して不用意にコールバック関数を使えば、やはりスパゲッティプログラムになってしまう。さらに、処理速度を上げるために何でもマルチスレッドにすると、それもスパゲッティプログラムになってしまう。

新しいプログラミング言語を使えば、スパゲッティプログラムを防止できるというわけではない。
複雑なロジックが予想されるモジュールでは、初心にかえり、設計段階でフロー図を描いた方がいいだろう。マルチスレッドにするならタイミングチャートも必要だ。フロー図やタイミングチャートを眺めて、流れが直線的になるよう設計段階で配慮しなければいけない。
Pythonや JavaScriptといったモダンな手続き型言語では、GOTO文がなくなり、クラスのプロパティ(メンバシップ変数)を活用することでグローバル変数も使わずに済むようになっている。

だがしかし、安易な継承を繰り返せばスパゲッティプログラムになってしまうし、関数型を目指して不用意にコールバック関数を使えば、やはりスパゲッティプログラムになってしまう。さらに、処理速度を上げるために何でもマルチスレッドにすると、それもスパゲッティプログラムになってしまう。

新しいプログラミング言語を使えば、スパゲッティプログラムを防止できるというわけではない。
複雑なロジックが予想されるモジュールでは、初心にかえり、設計段階でフロー図を描いた方がいいだろう。マルチスレッドにするならタイミングチャートも必要だ。フロー図やタイミングチャートを眺めて、流れが直線的になるよう設計段階で配慮しなければいけない。
コラム:ExcelのIF関数、他言語の制御文

ExcelにもIF関数が備わっている。
うるう年の判定は、=IF(MOD(A1,400)=0,"うるう年",IF(MOD(A1,100)=0,"平年",IF(MOD(A1,4)=0,"うるう年","平年"))) とすることで実現はできるが、かなり読みにくくなってしまう。JavaScriptのように複数行にわたって書くことができるブロック文を制御する方が、読みやすい。
うるう年の判定は、=IF(MOD(A1,400)=0,"うるう年",IF(MOD(A1,100)=0,"平年",IF(MOD(A1,4)=0,"うるう年","平年"))) とすることで実現はできるが、かなり読みにくくなってしまう。JavaScriptのように複数行にわたって書くことができるブロック文を制御する方が、読みやすい。
まずは、1954年にIBM 704にリリースされた世界最初の高水準プログラミング言語「FORTRAN」で書いてみよう。初期の FORTRAN は大文字と小文字の区別がなく、行頭から5文字目まではコメントや行番号用に空けておく。
! うるう年の判定:FORTRAN SUBROUTINE IS_LEAP_YEAR(YEAR, RESULT) INTEGER YEAR, RESULT IF (MOD(YEAR, 4) .EQ. 0) THEN IF (MOD(YEAR, 100) .NE. 0 .OR. MOD(YEAR, 400) .EQ. 0) THEN RESULT = 1 ELSE RESULT = 0 ENDIF ELSE RESULT = 0 ENDIF RETURN END PROGRAM MAIN INTEGER YEAR, RESULT YEAR = 2023 CALL IS_LEAP_YEAR(YEAR, RESULT) IF (RESULT .EQ. 1) THEN WRITE(*,*) YEAR, '年はうるう年である.' ELSE WRITE(*,*) YEAR, '年はうるう年ではない.' ENDIF STOP END
つづいて、1959年に文系事務員でもプログラミングできる言語としてアメリカ政府主導で開発されたプログラミング言語「COBOL」で書いてみた。
*> うるう年の判定:COBOL IDENTIFICATION DIVISION. PROGRAM-ID. LeapYearProgram. DATA DIVISION. WORKING-STORAGE SECTION. 01 YEAR PIC 9(4). 01 RESULT PIC 9(4) VALUE 0. 01 DIV PIC 9(4) VALUE 0. 01 MOD PIC 9(3) VALUE 0. PROCEDURE DIVISION. MOVE 2024 TO YEAR PERFORM IS-LEAP-YEAR. IF RESULT = 1 DISPLAY YEAR "年はうるう年である." ELSE DISPLAY YEAR "年はうるう年でない." END-IF. STOP RUN. IS-LEAP-YEAR. MOVE 0 TO RESULT DIVIDE YEAR BY 4 GIVING DIV REMAINDER MOD. IF MOD = 0 ADD 1 TO RESULT DIVIDE YEAR BY 100 GIVING DIV REMAINDER MOD IF MOD = 0 MOVE 0 TO RESULT DIVIDE YEAR BY 400 GIVING DIV REMAINDER MOD IF MOD = 0 ADD 1 TO RESULT END-IF END-IF END-IF.
1970年に、プログラミング教育を意識した言語として誕生した「Pascal」で書いてみた。FORTRAN や COBOL とは違い、ブロックの概念が導入された。
{ うるう年の判定:Pascal } program LeapYearProgram; uses SysUtils; function isLeapYear(year: integer): boolean; begin if year mod 400 = 0 then isLeapYear := true else if year mod 100 = 0 then isLeapYear := false else if year mod 4 = 0 then isLeapYear := true; end; var year: integer; begin year := 2024; if isLeapYear(year) then writeln(year, '年は うるう年である.') else writeln(year, '年は うるう年でない.'); end.
Pascal より1年早く誕生したのが C言語である。データ型が明確という違いがあるものの、JavaScriptに似ている。
/* うるう年判定:C */ #include <stdbool.h> #include <stdio.h> bool isLeapYear(int year) { bool result = false; if (year % 400 == 0) { result = true; } else if (year % 100 == 0) { result = false; } else if (year % 4 == 0) { result = true; } return result; } int main(void) { int year = 2023; if (isLeapYear(year)) { printf("%d年は うるう年である.", year); } else { printf("%d年は うるう年ではない.", year); } }
1983年に C言語の改良版として登場した C++言語で書いてみる。三項演算子や文字列が扱えるようになり、JavaScriptに近い書き方ができる。
//うるう年判定:C++ #include <iostream> bool isLeapYear(int year) { bool result = false; if (year % 400 == 0) { result = true; } else if (year % 100 == 0) { result = false; } else if (year % 4 == 0) { result = true; } return result; } int main(void) { int year = 2023; std::cout << year << "年は うるう年" << (isLeapYear(year) ? "である." : "ではない.") << std::endl; }
CGIとして古くからWebアプリケーションに使われてきた Perlで書くとこうなる。
#うるう年判定:Perl sub isLeapYear { my ($year) = @_; if ($year % 400 == 0) { $result = 1; } elsif ($year % 100 == 0) { $result = 0; } elsif ($year % 4 == 0) { $result = 1; } else { $result = 0; } return $result; } my $year = 2023; if (isLeapYear($year) == 1) { print "$year 年はうるう年である."; } else { print "$year 年はうるう年ではない."; }
Rubyは Perlに似ている。
#うるう年判定:Ruby def isLeapYear?(year) if (year % 400 === 0) result = true elsif (year % 100 === 0) result = false; elsif (year % 4 === 0) result = true else result = false end return result end year = 2023 result = isLeapYear?(year) ? 'である.' : 'ではない.' puts "#{year}年は うるう年#{result}"
サーバサイド・プログラムとして人気のある PHPで書くとこうなる。変数名の頭に $ が付くのが特徴的だ。動的型付けなので、データ型まで含めて厳密に等価であることを計算するのに厳密等価演算子 === が用意されている。
<?php //うるう年判定:PHP function isLeapYear($year) { if ($year % 400 === 0) $result = true; else if ($year % 100 === 0) $result = false; else if ($year % 4 === 0) $result = true; else $result = false; return $result; } $year = 2023; $result = isLeapYear($year) ? 'である.' : 'ではない.'; print $year . '年は うるう年' . $result; ?>
サーバサイド・プログラムとして歴史の長い Javaで書くとこうなる。JavaScript と似た名前だが、別物であることが分かる。
//うるう年判定:Java import java.util.*; public class Main { //うるう年判定:Java public static boolean isLeapYear(int year) { boolean result = false; if (year % 400 == 0) result = true; else if (year % 100 == 0) result = false; else if (year % 4 == 0) result = true; return result; } public static void main(String[] args) throws Exception { int year = 2023; String result = Main.isLeapYear(year) ? "である." : "ではない."; System.out.println(year + "年は うるう年" + result); } }
JavaScriptをサーバサイドでも利用できるようにした TypeScriptで書くとこうなる。JavaScript と比べると、データ型が明示されている。
//うるう年の判定:TypeScript function isLeapYear(year: number):boolean { let ret: boolean; //戻り値 if (year % 400 === 0) { ret = true; } else if (year % 100 === 0) { ret = false; } else if (year % 4 === 0) { ret = true; } else { ret = false; } return ret; } const year: number = 2100; const isLeap: boolean = isLeapYear(year); if (isLeap) { console.log(`${year}年はうるう年である.`); } else { console.log(`${year}年はうるう年ではない.`); }
2009年にGoogleが開発したプログラミング言語 Goで書くとこうなる。C++ や Java に似ているが、より簡明な書き方ができる。
//うるう年判定:Go package main import ("fmt") func isLeapYear(year int) bool { var result bool if (year % 400 == 0) { result = true } else if (year % 100 == 0) { result = false } else if (year % 4 == 0) { result = true } else { result = false } return result } func main() { var year int year = 2023 if isLeapYear(year) { fmt.Printf("%d年はうるう年です。\n", year) } else { fmt.Printf("%d年はうるう年ではありません。\n", year) } }
WWWを動的に表示するためのスクリプト言語として、1995年に登場した JavaScript で書くとこうなる。
//うるう年判定:JavaScript function isLeapYear(year) { if (year % 400 == 0) { result = true; } else if (year % 100 == 0) { result = false; } else if (year % 4 == 0) { result = true; } else { result = false; } return result; } let year = 2023; if (isLeapYear(year) == true) { result = "である."; } else { result = "ではない."; } console.log(year, "年は うるう年", result);
C言語に Smalltalk型のオブジェクトし高機能を持たせたプログラミング言語 Objecctive-C は1984年に発表され、NeXTやmacOSの開発言語となった。
//うるう年判定:Objective-C #importBOOL isLeapYear(NSInteger year) { BOOL ret = NO; if (year % 400 == 0) { ret = YES; } else if (year % 100 == 0) { ret = NO; } else if (year % 4 == 0) { ret = YES; } else { ret = NO; } return ret; } int main(int argc, const char * argv[]) { @autoreleasepool { NSInteger year = 2024; if (isLeapYear(year)) { NSLog(@"%ld年はうるう年である.", (long)year); } else { NSLog(@"%ld年はうるう年でない.", (long)year); } } return 0; }
Objective-C の後継として、2014年にAppleがiOSやMac向けのネイティブアプリケーションを開発するために発表したオープンソースのプログラミング言語 Swiftで書くとこうなる。書き方がだいぶシンプルになった。
//うるう年判定:Swift func isLeapYear(_ year: Int) -> Bool { var ret: Bool if year % 400 == 0 { ret = true } else if year % 100 == 0 { ret = false } else if year % 4 == 0 { ret = true } else { ret = false } return ret } let year = 2023 if isLeapYear(year) { print("\(year)年はうるう年でらる.") } else { print("\(year)年はうるう年ではない.") }
関数型プログラミング言語の祖先 LISPで書くとこうなる。
;うるう年判定:Lisp (defun isLeapYear (year) (if (zerop (mod year 400)) t (if (zerop (mod year 100)) nil (if (zerop (mod year 4)) t nil))) ) (setq year 2023) (if (isLeapYear year) (format t "~A 年はうるう年である.~%" year) (format t "~A 年はうるう年ではない.~%" year))
同じく関数型プログラミング言語 Haskellで書くとこうなる。
--うるう年判定:Haskell isLeapYear :: Int -> Bool isLeapYear year | (year `mod` 4 == 0) && (year `mod` 100 /= 0) = True | year `mod` 400 == 0 = True | otherwise = False main = do let year = 2023 ::Int if isLeapYear year then putStrLn $ show year ++ "年はうるう年です。" else putStrLn $ show year ++ "年はうるう年ではありません。"
人工知能言語として一世を風靡した論理型プログラミング言語の Prologでは条件分岐の概念がなくなり、こんなプログラムになる。
%うるう年判定:Prolog isLeapYear(Year) :- 0 is Year mod 400, !. isLeapYear(Year) :- 0 is Year mod 100, !, fail. isLeapYear(Year) :- 0 is Year mod 4, !. main(Year) :- (isLeapYear(Year) -> format('~w 年はうるう年です。', [Year]); format('~w 年はうるう年ではありません。', [Year]) ). :- main(2023).
SQL はデータベースを処理するためのスクリプトだが、PROCEDUREを備えているRDBMSでは、プログラミング言語のようなことができる。下記は MySQL で実行できる。
--うるう年判定:MySQL DELIMITER // CREATE PROCEDURE isLeapYear(IN year INT, OUT message VARCHAR(50)) BEGIN DECLARE result BOOLEAN DEFAULT FALSE; IF (year % 400 = 0) THEN SET result = TRUE; ELSEIF (year % 100 = 0) THEN SET result = FALSE; ELSEIF (year % 4 = 0) THEN SET result = TRUE; END IF; IF result THEN SET message = CONCAT(year, '年はうるう年です'); ELSE SET message = CONCAT(year, '年はうるう年ではありません'); END IF; END // DELIMITER ; SET @year = 2024; SET @message = ''; CALL IsLeapYear(@year, @message); SELECT @message;
参考サイト
- オリンピック・イヤーは閏年(うるう年)か?:ぱふぅ家のホームページ
- PHPで閏年(うるう年)かどうか判定する:ぱふぅ家のホームページ
(この項おわり)
制御を活用することで、Excel関数だけでは実現できなかった処理が可能になり、プログラムの威力が目に見えてくる。
今回は、条件によって制御を切り替える if文の使い方を学ぶ。Python のブロックについて説明する。プログラムの内容も、少しずつ実用的なものにステップアップしてゆく。