Search code examples
c#leap-year

How to count leap years in range [a, b] without loops on C#


Assuming, you have year a and year b, which are the range of years (they are included), how to properly count leap years in this range without using loops? (gregorian calendar)

I wrote this on C#, but i don't think that my code is great. I also used a global variable, but lol, i think there are a solution that is so much better and more elegant than this. I'm just a newbie, so sorry that i'm asking this dumb question. Also, i don't think you should use DateTime here and etc.

Here's my code:

class Program
{
    public static int leap_years = 0;
    static void Main()
    {
        do
        {
            int a, b;
            leap_years = 0;
            do Console.Write("Enter a: ");
            while (!int.TryParse(Console.ReadLine(), out a) || a < 0);
            do Console.Write("Enter b: ");
            while (!int.TryParse(Console.ReadLine(), out b) || b < 0 || a == b || a >= b);
            Console.WriteLine("Leap years: " + countLeapYears(a, b));
        } while (Console.ReadKey().Key != ConsoleKey.Escape);
    }

Where countLeapYears is

    static public int countLeapYears(int a, int b)
    {
        if (a > b)
            return leap_years;
        else
        {
            if (a % 4 == 0)
            {
                if (a % 100 == 0)
                {
                    if (a % 400 == 0)
                    {
                        Console.WriteLine("Year {0} - leap year", a);
                        leap_years++;
                        a += 4;
                        countLeapYears(a, b);
                    }
                    else
                    {
                        Console.WriteLine("Year {0} - not a leap year", a);
                        a++;
                        countLeapYears(a, b);
                    }
                }
                else
                {
                    Console.WriteLine("Year {0} - leap year", a);
                    leap_years++;
                    a += 4;
                    countLeapYears(a, b);
                }
            }
            else
            {
                Console.WriteLine("Year {0} - not a leap year", a);
                a++;
                countLeapYears(a, b);
            }
        }
        return leap_years;
    }
}

Solution

  • Well, we don't have year 0: 1BC is followed by 1AD which spoils the fun. However, if we can work with AD (positive years) only, you can try something like this:

    private static int countLeapYears(int from, int to) =>
      (to / 4 - (from - 1) / 4) -
      (to / 100 - (from - 1) / 100) +
      (to / 400 - (from - 1) / 400);
    

    Let's test it with respect to naive computation:

    private static int naiveCount(int from, int to) {
      int result = 0;
    
      for (int i = from; i <= to; ++i) 
        if (i % 400 == 0 || i % 4 == 0 && i % 100 != 0)
          result += 1;
    
      return result;
    }
    
    ...
    
    Random gen = new Random(123);
    
    var result = Enumerable
      .Range(1, 20)
      .Select(i => {
        int to = gen.Next(1590, 2222);
        int from = gen.Next(1590, 2222);
    
        return (from: Math.Min(from, to), to: Math.Max(from, to));
      })
      .Select(test => $"{test.from} - {test.to} actual: {countLeapYears(test.from, test.to),3} expected: {naiveCount(test.from, test.to),3}");
      
    Console.Write(string.Join(Environment.NewLine, result));
    

    Outcome:

    2163 - 2212 actual:  12 expected:  12
    2059 - 2102 actual:  10 expected:  10
    1620 - 2056 actual: 107 expected: 107
    1600 - 1684 actual:  22 expected:  22
    1713 - 1988 actual:  67 expected:  67
    1902 - 2164 actual:  65 expected:  65
    1709 - 1881 actual:  42 expected:  42
    1639 - 2124 actual: 118 expected: 118
    1751 - 1948 actual:  48 expected:  48
    1594 - 2184 actual: 144 expected: 144
    1605 - 1691 actual:  21 expected:  21
    1591 - 2082 actual: 120 expected: 120
    1993 - 2066 actual:  18 expected:  18
    2022 - 2158 actual:  33 expected:  33
    1678 - 1919 actual:  57 expected:  57
    1966 - 2128 actual:  40 expected:  40
    1635 - 2069 actual: 106 expected: 106
    1649 - 1963 actual:  75 expected:  75
    1719 - 2169 actual: 110 expected: 110
    1847 - 2093 actual:  61 expected:  61