34
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

8つの言語で「うるう年」を数えてみる (JavaScript,Ruby,C,Java,VBA,AppleScript,シェルスクリプト,バッチファイル)

Last updated at Posted at 2014-02-27

#そもそも、うるう年ってなんだっけ

グレゴリオ暦では、次の規則に従って400年間に(100回ではなく)97回の閏年を設ける。

  • 西暦年が4で割り切れる年は閏年
  • ただし、西暦年が100で割り切れる年は平年
  • ただし、西暦年が400で割り切れる年は閏年

By Wikipedia(閏年)

説明不要だが、うるう年の際は2月に29日目が降臨する。
1年で6時間ぐらい余るから4年ごとに1日として消費しましょうということらしい。
単純に1年365日で計算してると、腹立つことが稀によくある。

#この記事について
今までに齧ったことのある言語で書いてみて、違いがちょっと面白かったのでまとめただけ。
関数の宣言、真偽の定義、繰り返しの構文、戻り値の返し方の違いについて、少し頭の整理ができた。
コードに面白味は無いと思います。

  • うるう年の判定と数え上げの2つの関数(メソッド)を作り、なるべく同じ構成にする
  • 判定は基本的にyear % 400 == 0 || (year % 100 != 0 && year % 4 == 0)とする
  • マイナスの年はifではじく。4未満の年もついでにはじく
  • うるう年を1年から2000年まで数え上げ(485になる)、標準出力またはそれに準ずる場所に出力する
  • コメントの違いも確認する為、コメントを入れる

(各言語について、少し自分の中の認識を書きました。
間違っていたら教えていただければ有難いです。)

#JavaScript

function isLeapYear(year){ //判定
  if (year < 4) return false;

  return year % 400 == 0 || (year % 100 != 0 && year % 4 == 0);
}

function countLeapYear(fromYear, toYear){ //数え上げ
  var count = 0;

  for (var i = fromYear; i <= toYear; i++)
    if (isLeapYear(i)) count++;

  return count;
}

console.log(countLeapYear(1, 2000)) //出力

オブジェクト思考言語の中でも、クラスベースではなくプロトタイプベースとかいうけっこう珍妙な存在。

条件判断は、 [ false, null, undefined, 0, NaN, 空文字列 ]が偽。それ以外の全てが真(多分)。
0はfalseなので、isLeapYearのyear % 100 != 0year % 100とすることもできる。
(「&&」は最後、「||」は最初に評価されたtrueの式の結果を返す。なければfalse)

returnを省略した場合、戻り値は返されない。
ローカル変数にいちいち「var」をつけないといけないのがちょっとめんどい。
スコープに少しクセがある。

けっこう変なとこあるけど、自由度が高くて、気軽に使えて楽しい言語。
ブラウザさえあればどこでも遊べるところもいい。
ブラウザ以外でも使えるとこ多い。WSHとかGASとか。

#Ruby

def isLeapYear(year) #判定
  return false if year < 4

  year % 400 == 0 || (year % 100 != 0 && year % 4 == 0) 
end

def countLeapYear(fromYear, toYear) #数え上げ
  (fromYear..toYear).count{|i| isLeapYear i}
end

puts countLeapYear 1, 2000 #出力

存在するもの全てがオブジェクトな、徹底したオブジェクト指向の言語。
他の言語はだいたい数値とかがオブジェクトじゃなかったりする。
Rubyは数値もオブジェクトなので、数値からそのままメソッドを呼べたりする。

条件判断は、 [ false, nil ]が偽、それ以外の全てが真。
nilはnullの別名みたいなもの。(Objective-Cでもそう呼ぶ)
数値の0も真。0をぼっちにさせない。

returnを省略した場合、最後に評価された式の結果が戻り値になる。
意味が明確であれば、引数の括弧は省略可能。

繰り返しの方法はやたらたくさんある。
ここでは数値2つの範囲から各数値を取り出して式を実行し、戻り値が真の要素だけ数えるメソッドを使っている。

#C言語

#include <stdio.h>

int isLeapYear(int year){ //判定
  if (year < 4) return 0;
  
  return year % 400 == 0 || (year % 100 != 0 && year % 4 == 0);
}

int countLeapYear(int fromYear, int toYear){ //数え上げ
  int count = 0;
  
  for (int i = fromYear; i <= toYear; i++)
    if (isLeapYear(i)) count++;
  
  return count;
}

int main (void){ //出力
  printf("%d", countLeapYear(1, 2000));

  return 0;
}

条件判断は、 0が偽、それ以外の全てが真
値を返す関数の場合、returnは省略不可。

初めて学んだとき、真偽値の型が存在しないことにビビった。
そういう漢らしさが随所に滲み出てて惚れる。
(できれば使いたくないけど。)

#Java

class YearUtils {
  private YearUtils() {}

  public static boolean isLeapYear(int year) { //判定
    if (year < 4) return false;

    return year % 400 == 0 || (year % 100 != 0 && year % 4 == 0);
  }

  public static int countLeapYear(int fromYear, int toYear) { //数え上げ
    int count = 0;

    for (int i = fromYear; i <= toYear; i++)
      if (isLeapYear(i)) count++;

    return count;
  }
}

public class Main { //ユーティリティクラスとして作ったので一応mainだけクラスを分けた
  public static void main(String[] args) { //出力
    System.out.println(YearUtils.countLeapYear(1, 2000));
  }
}

条件判断はbooleanの true/false のみ。
Boolean(booleanのラッパクラス)も使えるけど、自動で変換(オートボクシングと呼ぶ)されてるだけ。
boolean以外をifとかの条件に入れると、そもそも実行すら出来ない。

値を返す関数の場合、returnは省略不可。
クラスを作らないと実行すらできない等、決まり事が多く、Eclipse等のIDEを使わないとちょっと不便。

#VBA

Function isLeapYear(ByVal year As Integer) '判定
  If year < 4 Then
    isLeapYear = False
    Exit Function
  End If

  isLeapYear = year Mod 400 = 0 Or (year Mod 100 <> 0 And year Mod 4 = 0)
End Function

Function countLeapYear(ByVal fromYear As Integer, ByVal toYear As Integer) '数え上げ
  Dim count As Integer
  Dim i As Integer
   
  For i = fromYear To toYear
    If (isLeapYear(i)) Then count = count + 1
  Next i
   
  countLeapYear = count
End Function

Sub Exec() '出力
  Debug.Print countLeapYear(1, 2000)
End Sub

条件判断は、 [ False, 0 ]が偽。[ True, 0以外の数値 ]は真。 それ以外は実行時エラー(多分)。
左の「=」は代入演算子で、それより右にある「=」は等号。
ifの条件式の中に「=」がある場合とかも等号。等号否定は「<>」。

return文は存在しない。処理を中断したい場合はEXITを使う。
関数の戻り値を返す場合、関数と同じ名前の変数に値を代入する。 何これ・・
なので、4未満をはじく動作に4行使っている。

論理演算子「And」「Or」はショートサーキットに対応していない。
結果がTrueでもFalseでもその後の式を全て実行する。(他言語の「&&」「||」ではなく「&」「|」)
処理効率を重視したい場合、isLeapYearは以下のようになる。

Function isLeapYear(ByVal year As Integer)
  If year < 4 Then
    isLeapYear = False
    Exit Function
  End If

  If year Mod 400 = 0 Then
    isLeapYear = True
  Else
    If year Mod 100 <> 0 Then
      If year Mod 4 = 0 Then
        isLeapYear = True
      Else
        isLeapYear = False
      End If
    Else
      isLeapYear = False
    End If
  End If
End Function

こんなコードを見続けていると寿命が縮む為、ここでは人命を優先した。
VB.NETには「AndAlso」と「OrElse」というものがあるらしいが、VBAには無い。

Integer型は2バイトしかなくて、最大値が3万ちょいなのですぐオーバーフローする。
Long型は4バイトで、やっと他言語のintぐらいにはなる。

このコードをExcelのVBエディタに標準モジュールとしてコピペすると、関数として使用可能になる。
Excelはどこに行ってもだいたいあるし、何だかんだでちょっと便利。
(セキュリティの警告が表示されるので敬遠されることも多い)

#AppleScript

on isLeapYear(aYear) --判定
  if (aYear < 4) then return false

  aYear mod 400 = 0 or (aYear mod 100  0 and aYear mod 4 = 0)
end isLeapYear

on countLeapYear(fromYear, toYear) --数え上げ
  set counter to 0
	
  repeat with aYear from fromYear to toYear
    if isLeapYear(aYear) then set counter to counter + 1
  end repeat

  counter
end countLeapYear

log countLeapYear(1, 2000) --出力

条件判断は true/false のみ。他の値が入ってると実行時エラー。
等号は「=」。等号否定は「/=」で、入力すると「≠」になる。
Appleらしいと言えばAppleらしいが...。

英語に似た表現を使うことが可能で、等号は「is」、等号否定は「is not」みたいにできる。

代入演算子「=」は存在しない。変数に代入するには「set 変数 to 値」といちいち書く必要がある。
「foo = 1」とかやってもエラーは出ないが、代入出来ていないことにすぐ気づく。
その式は、fooと1を比較し、その場で真偽値が返されるだけで終わる。何も起こらない。

returnを省略した場合、最後に評価された式の結果が戻り値になる。
繰り返しにrepeat以外の文は無い。
「and」「or」はショートサーキットに対応している。

予約語や独自の文法が多く、慣れるまで大変かも。
「year」「count」も予約語である為、「aYear」「counter」にした。

日本語の情報源がほぼ無く、参考書は10年以上前に発売されたものが最新、というのもツライ。

#シェルスクリプト (bash)

#!/bin/bash

isLeapYear(){ #判定
  if [ $1 -lt 4 ]; then
    return 1
  fi

  return $((!($1 % 400 == 0 || ($1 % 100 != 0 && $1 % 4 == 0))))
}

countLeapYear(){ #数え上げ
  local toYear=$2

  local count=0
  local i=$1

  while [ $i -le $toYear ]; do
    if isLeapYear $i; then
      count=$(($count+1))
    fi
    i=$(($i + 1))
  done

  echo $count
}

echo `countLeapYear 1 2000` #出力

Unix, Linux等のシェルのコマンドを順番に置くことによってプログラミングする。ここでは全てが単なる文字列。

変数から値を取り出すには「$変数」とする。
引数の文字列は空白で分割され、「$1」や「$2」として取り出せる。

「expr」コマンドで文字列を数式として評価できる。bashでは「$(())」も使える。
数値の計算をexprで作ったら計算終了まで何十秒もかかったので、全部$(())にした。

条件判断は、 条件式のコマンドの「終了ステータス」が0の時だけ真、それ以外は偽。
「return」は戻り値を返すものじゃなくて「終了ステータス」を返す。
0は正常終了、それ以外はエラー。なので条件判断も0の時だけ真となっている。

「[」は括弧に見えるけどコマンドで、条件が真ならば終了ステータス0を返し、偽ならば1を返す。
関数で戻り値を返したければ「echo」等で出力し、呼び出し元で「``」等で受け取ったりする。

シェルスクリプトってどういう作り方が正解なのかイマイチ分からない。
尚、$(())等のドルがmarkdownによって数式として解釈されるのを避ける為、この説明文ではコードのインラインで表示している。

#バッチファイル

@echo off

call :showResult 1 2000
pause
exit /b

rem 判定
:isLeapYear
  setlocal
  if %1% LSS 4 exit /b 0

  set /a "mod400=%1 %% 400"
  if %mod400% == 0 exit /b 1

  set /a "mod100=%1 %% 100"
  set /a "mod4=%1 %% 4"
  if not %mod100% == 0 if %mod4% == 0 exit /b 1
exit /b 0

rem 数え上げ
:countLeapYear
  setlocal
  set fromYear=%1%
  set toYear=%2%

  set count=0

  for /l %%i in (%fromYear%,1,%toYear%) do (
    call :isLeapYear %%i
    if errorlevel 1 set /a "count+=1"
  )
exit /b %count%

rem 出力
:showResult
  call :countLeapYear %1 %2
  echo %ERRORLEVEL%
exit /b

注: 以前から興味があり、私にとってこれが初めてのバッチファイルです。拙いコードでごめんなさい。

Windowsのコマンドプロンプトのコマンドを順番に置くことによってプログラミングする。ここでは全てが単なる文字列。
シェルスクリプトに似ているが、機能はかなり少ない。
コメントまでコマンド(rem)であり、行途中のコメントが不可能。

変数から値を取り出すには「%変数%」とする。
引数の文字列は空白で分割され、「%1%」や「%2%」として取り出せる。

変数に値を代入する「set」コマンドに「/a」を付けると、引数の文字列を数式として評価してくれる。
その文字列の中で変数を使うことができ、値を代入することも可能。%で囲まなくても値として評価される。
割り算の余りを計算する「%」は「%%」とエスケープする必要がある

条件判断は、よく分かんない。
「if」は条件の中で式を実行させるわけじゃないから、真偽とかそういう問題じゃない。
「if」に複数の条件を与えることは出来ない。andとかorとかいう概念が存在しない。

関数が存在しない。
処理は先頭から順番に実行され、ファイルの最後か「exit」コマンドに到達した時点で終了する。

行を「:」で始めることによってその行にラベルを貼り、「goto」や「call」でその行に移動できる。
「call」には引数を渡すことができ、「exit /b」によって呼び出し行まで戻れる。
これによって一応は関数のように使えなくはない。(サブルーチン)

ただし戻り値は返せないし、「exit /b」を忘れるとラベルとか無視してそのまま次の処理がどんどん実行される。

戻り値ではないが、「exit /b」の後ろに数値を渡すことによってエラーレベルを返せる。
そんなことはスルーして、ここでは戻り値としてありがたく利用させていただいている。

苦労した結果、他の言語よりも格段に処理が遅い。(作りが下手くそ?)
そもそも、こういう使い方をすべきではない。

しかし、 上の処理をほんの数行で終わらせる、画期的な方法がある。

@echo off
set fromYear=1
set toYear=2000

ruby -e "puts [*%fromYear%..%toYear%].count{|i| i >= 4 && (i %% 400 == 0 || (i %% 100 != 0 && i %% 4 == 0))}"
pause

お粗末さまでした。

参考にさせていただきました: 開発に役立つ,BATファイルの書き方・パターン集

34
33
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?