challenge 除算・余剰を使わずに閏年

ある西暦が閏年か否かを判定するプログラムを書いてください。 ただし、除算・余剰を求める演算子、組み込み関数、ライブラリ関数等を使用してはいけません。 また、閏年は以下のように定義されています。 1. 西暦年が4で割り切れる年は閏年 2. ただし、西暦年が100で割り切れる年は平年 3. ただし、西暦年が400で割り切れる年は閏年

Posted feedbacks - Flatten

Nested Hidden
無限リストつかいます。

実行例:
*Main> :main 1900
False
*Main> :main 2000
True
*Main> :main 2008
True
*Main> :main 2100
False

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
module Main (main) where

import System.Environment

main :: IO ()
main = print . (ys !!) . read . head =<< getArgs

y4s = [True,False,False,False]
y100s = False : tail (take 100 $ cycle y4s)
y400s = True : tail (take 400 $ cycle y100s)

ys = cycle y400s

割り切れる数字の部分だけ真で他は偽の値を持つ循環リストを作成し、 周期4、100、400を重ね合せて判定してみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(defun leap-year-p (y)
  (do ((i y (1- i))
       (c4 (%clist 4) (cdr c4))
       (c100 (%clist 100) (cdr c100))
       (c400 (%clist 400) (cdr c400)))
      ((zerop i) (or (and (car c100) (car c400))
                     (and (not (car c100)) (car c4))))))

(defun %clist (freq)
  (let ((l (make-list freq)))
    (setf (car l) t)
    (rplacd (last l) l)
    l))

正規表現で。
sub judge()はうるう年のとき1、通常年のときは0を返します。

実行結果:
$ ./dk124.pl
1900 0
2000 1
2008 1
2100 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/usr/bin/perl

sub judge($)
{
    $_ = shift;
    return 0 != (/(([13579][26])|([02468][048]))00$/ || (!/00$/ && /(([13579][26])|([02468][048]))$/));
}

for (1900, 2000, 2008, 2100) {
    printf("%d %d\n", $_, judge($_));
}

すいません、紀元前に対応してませんでしたので、修正します…。

1
2
3
4
5
--- 5333.txt    2008-01-16 07:51:10.085978672 +0900
+++ 5333.txt.fix        2008-01-16 07:51:54.569311280 +0900
@@ -2 +2 @@
-  (do ((i y (1- i))
+  (do ((i (abs y) (1- i))

Squeak Smalltalk で。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
| 閏年か? |

閏年か? := [:int |
    ((0 to: Float infinity by: 4) includes: int)
        and: [((0 to: Float infinity by: 100) includes: int) not
            or: [(0 to: Float infinity by: 400) includes: int]]].

閏年か? value: 1900.   "=> false "
閏年か? value: 2000.   "=> true "
閏年か? value: 2008.   "=> true "
閏年か? value: 2100.   "=> false "

とても素朴な書き方。剰余の計算を禁止しても引き算の繰り返しでできてしまいますね。一応、大きい順に引くことで繰り返しの回数を減らしてあります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
>>> def leap(x, d=400):
    if x < 0:
        return leap(-x, d)

    if x == 0:
        if d == 400:
            return True
        if d == 100:
            return False
        if d == 4:
            return True

    if x < d:
        if d == 400:
            return leap(x, 100)
        if d == 100:
            return leap(x, 4)
        if d == 4:
            return False

    return leap(x - d, d)

>>> leap(1900)
False
>>> leap(2000)
True
>>> leap(2008)
True
>>> leap(2100)
False

一応、条件は満たしているかと思います。 こんな感じでどうでしょうか?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class Sample124 {
    public static boolean isLeepYear(int year) {
        if (!isDividedBy4(year)) return false;
        String sYear = String.valueOf(year);
        if (sYear.endsWith("00")) {
            return isDividedBy4(Integer.parseInt(sYear.substring(0, sYear.length() - 2)));
        }
        return true;
    }
    private static boolean isDividedBy4(int num) {
        return (num & 3) == 0;
    }

    public static void main(String[] args) {
        System.out.println("1900:" + isLeepYear(1900));
        System.out.println("2000:" + isLeepYear(2000));
        System.out.println("2008:" + isLeepYear(2008));
        System.out.println("2100:" + isLeepYear(2100));
    }
}

考えたら、べつに無限にせずともこれで十分ですね(^_^;)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
| 閏年か? |

閏年か? := [:int |
    ((0 to: int by: 4) includes: int)
        and: [((0 to: int by: 100) includes: int) not
            or: [(0 to: int by: 400) includes: int]]].

閏年か? value: 1900.   "=> false "
閏年か? value: 2000.   "=> true "
閏年か? value: 2008.   "=> true "
閏年か? value: 2100.   "=> false "

こういうことかな?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
/*うるう年なら1、平年なら0を返す*/
int IsUruuDoshi(int year){
    int uruu=0;
    int uruu_check[]={800,400,100,40,4,0};    //800,40はループ数短縮用
    int count=0;
    while(uruu_check[count]){
        if(uruu==year){
            if(uruu_check[count]==100) return 0;
            else return 1;
        }
        if(uruu+uruu_check[count]>year){
            count++;
        }else{
            uruu+=uruu_check[count];
        }
    }
    return 0;
}

1
2
3
leap(N) :- N < 0, N1 is N + 400, !, leap(N1).
leap(N) :- N >= 400, N1 is N - 400, !, leap(N1).
leap(N) :- N /\ 0b11 =:= 0b00, N \= 100, N \= 200, N \= 300.

ついにゅっとなってやってしまった。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def leap(x, d=400):
    return (
        not d and [d]
        or
        x == 0 and [[1, 0, 1][d >> 6 & 3]]
        or
        x < d and [leap(x, [0, 4, 100][d >> 6 & 3])]
        or
        [leap(x - d, d)]
    )[0]

にしおさんのを見て引き算のがらくだと思ったので修正。

(失敗投稿を削除しておきましたby管理者)

投稿失敗しました・・・・

まあ、かけ算でも出来るわけでして。
#なんか揚げ足取りばっかしているように思われ
#ちゃうだろうなぁ。マイナス評価でもしょうがないか。


$ pl -qs 124.pl
?- uru(1900).

No
?- uru(2000).

Yes
?- uru(2008).

Yes
?- uru(2100).

No
?-
1
2
3
4
5
6
7
uru(X):- divideby400(X).
uru(X):- divideby4(X), not(divideby100(X)).

divideby(X, Y, Z) :- 0 =:= X - integer(X * Y) * Z.
divideby4(X) :- divideby(X, 0.25, 4).
divideby100(X) :- divideby(X, 0.01, 100).
divideby400(X) :- divideby(X, 0.0025, 400).

引き算に修正
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
int IsUruuDoshi(int year){
    int uruu_check[]={800,400,100,40,4,0};    //800,40はループ数短縮用
    int count=0;
    while(uruu_check[count]){
        if(year){
            if(uruu_check[count]>year){
                count++;
            }else{
                uruu-=uruu_check[count];
            }
        }else{
            if(uruu_check[count]==100) return 0;
            else return 1;
        }
    }
    return 0;
}

4の倍数チェック以外、減算で剰余を求めてるだけなんですが。何かアクロバティックな方法でもあるんでしょか?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
bool isLeapYear(int y) {
/*    return ((y % 4) == 0) && (((y % 400) == 0) || ((y % 100) != 0)); */
    if (y < 0)
        y *= -1;                /* y = abs(y); */
    if ((y & 0x03) != 0x00)     /* (y != 4×n) */
        return false;
    while (y >= 400) { y -= 400; }
    if (y == 0)
        return true;
    while (y >= 100) { y -= 100; }
    return (y != 0);
}

1
2
3
4
5
6
7
N = 2000
checker = Hash['4', 0, '100', 0, '400', 0]
1.upto(N){|i|
  checker.each_key{|k| checker[k] += 1 and k.to_i == checker[k] and checker[k] = 0}
}
evaluate = checker['400'] == 0 || checker['100'] != 0 && checker['4'] == 0
puts "#{N} is a " +  (evaluate ? 'bissextile' : 'normal') + ' year.'

それぞれの条件を無限リストにした。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
isLeapYear x = d !! x
    where a = cycle $ True  : replicate 3   False
          b = cycle $ False : replicate 99  True
          c = cycle $ True  : replicate 399 False
          d = zipWith3 (\a b c -> (a && b) || c) a b c

main = do putStr ">> "
          l <- getLine
          print $ isLeapYear $ read l
          main

subが成功したかどうかで処理が分岐します

1
2
3
def leap?(n)
  0 == (3 & n.abs.to_s.sub(/00$/,'').to_i)
end

str使っていいのかなぁと。
strで100で割るを実現しています。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def div4(n):
    return (n >> 2 , n & 3)

def div100(n):
    m = str(n)
    return (int(m[:-2]), int(m[-2:]))

def uruudoshi(year):
    n = div4(year)
    if n[1] == 0:
        m = div100(year)
        if m[1] == 0:
            if div4(m[0])[1] == 0:
                return 1
            else:
                return 0
        return 1
    else:
        return 0
    
if __name__=="__main__":
    print uruudoshi(1900)
    print uruudoshi(2000)
    print uruudoshi(2008)
    print uruudoshi(2100)

正規表現。
1
2
3
4
5
6
def leap?( year )
    return false unless /((^|[02468])[048]|[13579][26])$/.match( year.to_s )
    return true unless /00$/.match( year.to_s )
    return false unless /((^|[02468])[048]|[13579][26])00$/.match( year.to_s )
    return true
end

正弦関数の周期性を利用しました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import std.stdio;
import std.math;

bool isLeapYear(int year){
    return abs(sin(year * PI * 0.25)) < 0.0001 &&
           abs(sin(year * PI * 0.01)) > 0.0001 ||
           abs(sin(year * PI * 0.0025)) < 0.0001;
}

void main(){
    writefln(isLeapYear(1900));  // false
    writefln(isLeapYear(1978));  // false
    writefln(isLeapYear(1988));  // true
    writefln(isLeapYear(2000));  // true
    writefln(isLeapYear(2001));  // false
    writefln(isLeapYear(2008));  // true
}

10進で下4桁を分解できればループする必要すらないのか....なるほどね。

sprintf使わない方法が思いつかんのでダメっぽい。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

int isLeapYear2(int y) {
    char sn[20];
    int n, low, high;

    if (y < 0) { y *= -1; }

    n = sprintf(buff, "%d", y);

    low = 0;    /* 10進数 1~2桁目 */
    if (n > 0) { low += (sn[n-1] - '0'); }
    if (n > 1) { low += (sn[n-2] - '0') * 10; }
    if ((low & 0x03) != 0x00)
        return false;

    high = 0;    /* 10進数 3~4桁目 */
    if (n > 2) { high += (sn[n-3] - '0'); }
    if (n > 3) { high += (sn[n-4] - '0') * 10; }
    return (low != 0) || ((high & 0x03) == 0x00);
}

素直にやってみました。

実行結果
-------
A.D. 1900: false
A.D. 2000: true
A.D. 2008: true
A.D. 2009: false
A.D. 2100: false
-------
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class LeapYear {
    public static void main(String[] args) {
        System.out.println("A.D. 1900: " + isLeapYear(1900));
        System.out.println("A.D. 2000: " + isLeapYear(2000));
        System.out.println("A.D. 2008: " + isLeapYear(2008));
        System.out.println("A.D. 2009: " + isLeapYear(2009));
        System.out.println("A.D. 2100: " + isLeapYear(2100));
    }

    public static boolean isLeapYear(int aYear) {
        return isDivisible(aYear, 4) ? (isDivisible(aYear, 100) ? (isDivisible(aYear, 400) ? true : false) : true) : false;
    }

    public static boolean isDivisible(int aLeft, int aRight) {
        while (aLeft >= aRight) {
            aLeft -= aRight;
        }
        return aLeft == 0;
    }
}

余剰を自前で作ってみました。

参考

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
year=2000
もし,yearが閏年ならば
    "{year}年は閏年です"を表示
違えば
    "{year}年は閏年ではありません"を表示

●閏年(yが)
    もし((MOD(y,4)=0) && (MOD(y,100)<>0||MOD(y,400)=0))ならば
        1で戻る
    違えば
        0で戻る

●MOD(a,b)
  Nとは整数。Xとは整数。Yとは整数
  もし(a=b)ならば,0で戻る
  もし(a<b)ならば,aで戻る
  (2^N<b)の間
    N=N+1
  X=2^N-b
  Y=2^N-1
  (a>=2*b)の間
    a=X*(a>>N)+AND(a,Y) 
  もし(a>=b)ならば,a=a-b
  aで戻る

なんか、この解答にはワクワクさせるものがあります。


入力が離散的だから、こういう答えもありなのかー。

まあ、こういうのもあるということで。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
| 閏年か? |

閏年か? := [:int |
    (int isDivisibleBy: 4)
        and: [(int isDivisibleBy: 100) not or: [int isDivisibleBy: 400]]].

閏年か? value: 1900.   "=> false "
閏年か? value: 2000.   "=> true "
閏年か? value: 2008.   "=> true "
閏年か? value: 2100.   "=> false "

閏年なら1を、平年なら0を返します☆
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include<stdio.h>
int isleapyear(int y){
  int i = y * 0.01;
  return !(y == i * 100 ? i & 3 : y & 3);
}
int main(void){
  printf("%d\n", isleapyear(1900));
  printf("%d\n", isleapyear(2000));
  printf("%d\n", isleapyear(2008));
  printf("%d\n", isleapyear(2100));
  return 0;
}

組み込み「メソッド」は使ってもいいのかな。
(でないとRuby/Smalltalkあたりは解答のしようが無いし)
1
2
3
4
5
6
7
function doukaku124a(y){
  return new Date(y, 1, 29).getMonth() < 2;
}

function doukaku124b(y){
  return !((y +'').replace(/00$/, '') & 3);
}

その手がありましたか!って、もはや悪のりでしかありませんね(^_^;)。

1
2
3
4
5
6
7
8
| 閏年か? |

閏年か? := [:int | int asYear daysInYear = 366].

閏年か? value: 1900.   "=> false "
閏年か? value: 2000.   "=> true "
閏年か? value: 2008.   "=> true "
閏年か? value: 2100.   "=> false "

結果:
1900年 は 平年
1901年 は 平年
1902年 は 平年
1903年 は 平年
1904年 は 閏年
... ...
2000年 は 閏年
2001年 は 平年
2002年 は 平年
2003年 は 平年
2004年 は 閏年
2005年 は 平年
2006年 は 平年
2007年 は 平年
2008年 は 閏年
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<html>
<head>
<title>除算・余剰を使わずに閏年</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript">
<!--
//閏年の判断
function isLeap(year) {
    var nNum = parseInt((year + '').substr(-4));
    while(nNum >= 400) {
        nNum -= 400;
    }
    if(nNum == 0) {
        return true;
    } else {
        while(nNum >= 100) {
            nNum -= 100;
        }
        if(nNum == 0) {
            return false;
        } else {
            while(nNum >= 4) {
                nNum -= 4;
            }
            if(nNum == 0) {
                return true;
            }
        }
    }
    return false;
}

//getElementByIdの短縮
function $(id) {
    return document.getElementById(id);
}
//テスト
window.onload = function() {
    var sResult = '';
    for(var y=1900; y<2009; y++) {
        sResult += y + '年 は ' + (isLeap(y) ? '閏年':'平年') + '<br/>';
    }
    $('result').innerHTML = sResult;
}
//-->
</script>
</head>
<body>
<input type="text" id="year" value="2008" size="6" />年は
<input type="button" value="閏年?" onclick="alert($('year').value + '年 は ' + (isLeap($('year').value) ? '閏年':'平年'));" />
<br/>
<br/>
結果:<br/>
<div id="result">&nbsp;</div>
</body>
</html>

perlらしく。

Dan the Perl Monger

1
2
3
4
5
#!/usr/local/bin/perl
use strict;
use warnings;
sub is_leap{!($_[0]&0b11)&&($_[0]!~/00$/||($_[0]>>2)=~/00$/)}
print is_leap(shift), "\n";

文字列にしてやってみました。うるう年のときに1を
それ以外のとき0を返します。

1800 0 
2000 1 
2007 0 
2008 1 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
FUNCTION leap(n)
   LET  y$ = STR$(n)
   LET  a$ = RIGHT$(y$,2)
   IF a$ = "00" THEN  LET  a$ = LEFT$(y$,LEN(y$)-2)
   IF RIGHT$(BSTR$(VAL(a$),2),2) = "00" THEN
      LET  leap = 1
   ELSE
      LET  leap = 0
   END IF  
END FUNCTION

PRINT "1800";leap(1800)
PRINT "2000";leap(2000)
PRINT "2007";leap(2007)
PRINT "2008";leap(2008)
END

SQLで書いてみました。 十分な行数をもつテーブルT_DUMMYがあるものとします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
SELECT CASE WHEN TB_FORE.Y IS NULL
           THEN '平年'
            ELSE CASE WHEN TB_FOREHANDRED.Y IS NOT NULL
                      THEN '閏年'
                      WHEN TB_HANDRED.Y IS NOT NULL
                      THEN '平年'
                 ELSE '閏年'
            END
       END AS RESULT 
FROM (SELECT ABS(:ARG) AS Y FROM DUAL) TB_ARG
LEFT JOIN (SELECT ROWNUM *   4 AS Y FROM T_DUMMY ) TB_FORE 
     ON TB_ARG.Y = TB_FORE.Y
LEFT JOIN (SELECT ROWNUM * 100 AS Y FROM T_DUMMY ) TB_HANDRED
     ON TB_ARG.Y = TB_HANDRED.Y
LEFT JOIN (SELECT ROWNUM * 400 AS Y FROM T_DUMMY ) TB_FOREHANDRED
     ON TB_ARG.Y = TB_FOREHANDRED.Y

PostScript で。我ながら汚ないコード。
紀元前はマイナス符号で与えてやって下さい。
# 紀元前対応をやってみたら無駄に複雑に...
# 動作はあってますよね?

AD: 2008: true
AD: 2007: false
AD: 2000: true
AD: 1900: false
AD: 1500: true
AD: 100: true
AD: 8: true
AD: 4: false
BC: 4: false
BC: 8: true
BC: 9: false
BC: 10: false
BC: 11: true
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
%!PS

/IsLeapYear {
    dup 1582 ge { % Gregorian Calendar AD 1582-
        10 string cvs dup
        0 2 getinterval cvi
        exch 2 2 getinterval cvi
        dup 0 eq dup not % mod 100
        3 -1 roll 3 and 0 eq % mod 4
        and
        exch 3 -1 roll 3 and 0 eq and or % mod 400
    } { % Julian Calendar BC 45 - AD 1582