;; open Assert (* In this lab, you will implement a few OCaml functions according to the specification listed in the comments. Don't worry if you don't know the syntax of OCaml yet, the exercises and examples below will teach you what you need to know to solve the problem. Notice that each function is "stubbed" with an error message saying that it is unimplemented. If you try to run the function you will get this error. You should replace this "failwith " with your implementation of the solution. The convention is to put code below the comment that describes it (where the stubs are), not above the comment. We also provide you with some test cases, but they are not exhaustive and you are asked to write additional test cases of your own. INSTRUCTIONS: For each problem below, do the following: 1) Read the comment below that describes the method and consider the relevant concepts and how they relate to one another. 2) Read and understand the interface by looking at the method signature. What are the argument types? What is the return type? (The second step of the design process "Formalize the interface" is done for you in today's lab -- but in future assignments you will design your own headers.) 3) Examine the provided test cases and ensure they match your understanding of the problem. Since the provided tests are not exhaustive, write at least TWO additional test cases for each problem. Your test cases should exercise not just typical inputs, but also corner cases. 4) Run the file and note the first test that fails (it should be one of the test cases for the problem you are working on). 5) Finally, implement the method. Run the file again and make sure the test cases for this problem all pass. Then move on to the next problem and repeat this procedure. Notice that writing the actual code for the method is the LAST thing you do. We emphasize first understanding the problem, then capturing this understanding in a series of test cases, and finally writing the code and verifying that it passes the tests that you earlier wrote. *) (*************************************************************************) (* Example 1 (seconds in days) *******************************************) (* We'll start with a simple example to give you experience with OCaml syntax. There is no code to write here, just read the following to see what it does. *) (* Compute the number of seconds that pass in a given number of days. *) let seconds_in_days (days: int) : int = let seconds_in_one_minute = 60 in let seconds_in_one_hour = 60 * seconds_in_one_minute in let seconds_in_one_day = 24 * seconds_in_one_hour in seconds_in_one_day * days (* A few notes about syntax: the 'let' keyword creates both new top-level and local definitions. Above 'seconds_in_days' is a new function, which takes an argument called 'days' of type 'int' and returns a result of type 'int'. The text after the '=' is the body of the function. The body first defines three local variables ('seconds_in_one_minute', 'seconds_in_one_hour', and 'seconds_in_one_day') and then computes the final result by multiplication. Note that local definitions use the keyword 'in', whereas top-level definitions do not. Note also that OCaml does not use the 'return' keyword. *) (* The test cases specify the behavior of this function. *) let test () : bool = (seconds_in_days 0) = 0 ;; run_test "seconds_in_days 0" test let test () : bool = (seconds_in_days 1) = 86400 ;; run_test "seconds_in_days 1" test let test () : bool = (seconds_in_days 2) = 172800 ;; run_test "seconds_in_days 2" test (* In CIS 120, we design test cases using a very specific form. Each test case starts with the definition of a testing function, called 'test' that takes no arguments and returns a boolean value. For example, the first test case compares the result of the function call 'seconds_in_days' with argument 0 with the expected result 0. Note that OCaml uses '=' in both the function definitions ('test' and 'seconds_in_days' above) and for equality tests. The second part of the test case actually runs the test. Beginning lines with double-semicolons instead of 'let' instructs OCaml to execute the expression instead of creating a new definition. In this case we call 'run_test' with the name of the test and the testing function. If the test passes, 'run_test' won't print anything. If the test fails, then an error message will appear in the output. *) (*************************************************************************) (* Problem 1 (total seconds) *********************************************) (* Given an amount of time in days, hours, minutes and seconds, create a method that calculates the total number of seconds in that time period. *) let total_seconds (days: int) (hours: int) (minutes: int) (seconds: int): int = let minute = 60 in let hour = 60 * minute in let day = 24 * hour in days * day + hours * hour + minutes * minute + seconds (* One example is that we can calculate the total number of seconds in an "average" year (taking into account leap years): 365 days, 5 hours, 49 minutes, and 12 seconds. *) (* Note that this test will fail until you have implemented the total_seconds function. *) let test () : bool = (total_seconds 365 5 49 12) = 31556952 ;; run_test "average seconds per year" test (* Here are two more tests that you'll need to implement on your own. Uncomment these two lines and replace the (failwith "dummy test case") with code that actually implements a test for this function. *) let test () : bool = (total_seconds 0 0 0 0) = 0 ;; run_test "total_seconds 0 0 0 0" test let test () : bool = (total_seconds 2 0 0 0) = seconds_in_days 2 ;; run_test "total_secons 2 0 0 0" test (*************************************************************************) (* Example 2 (booleans and modular arithmetic) ***************************) (* The next example demonstrates the syntax for operators over booleans and and integers. In particular, it uses the following infix binary operators: n = m equal, return true if values n and m are the same n <> m not equal, return true if values n and m are different n > m greater than, compare int n with int m and return boolean result n < m less than, compare int n with int m and return boolean result a && b logical and, return true if boolean values a and b are both true a || b logical or, return true if one of boolean values a and b are true n mod m modulus, calculate the modulus of int m and int n *) (* A natural number is a small prime if it is less than 100 and prime. We can determine if a number is a small prime by making sure that it is not divisible by any prime up to 11. *) let is_small_prime (n : int) : bool = n > 1 && n < 100 && (n = 2 || n mod 2 <> 0) && (n = 3 || n mod 3 <> 0) && (n = 5 || n mod 5 <> 0) && (n = 7 || n mod 7 <> 0) && (n = 11 || n mod 11 <> 0) let test () : bool = (is_small_prime 47) = true ;; run_test "is_small_prime 47" test let test () : bool = (is_small_prime 91) = false ;; run_test "is_small_prime 91" test (*************************************************************************) (* Problem 2 (is leap year) **********************************************) (* Output whether the given year is a leap year. A leap year is one that is evenly divisible by four with the exception: years evenly divisible by 100 are not leap years unless they are also evenly divisible by 400. *) let is_leap_year (year: int): bool = (year mod 4 = 0 && year mod 100 <> 0) || year mod 400 = 0 (* test case *) let test () : bool = (is_leap_year 1901) = false ;; run_test "is_leap_year 1901" test (* Add your own (at least two more) test cases for is_leap_year here. *) let test () : bool = (is_leap_year 2000) = true ;; run_test "is_leap_year 2000" test let test () : bool = (is_leap_year 2004) = true ;; run_test "is_leap_year 2004" test (*************************************************************************) (* Example 3 (days of the month) *****************************************) (* This example demonstrates the syntax of pattern matching, a convenient way to branch in OCaml code. *) (* Given a month (where 1 is Jan, 2 is Feb, etc) return the number of days in that month during nonleap years. *) let days_of_month (month:int) : int = begin match month with | 1 -> 31 (* Jan *) | 2 -> 28 (* Feb, except for leap years *) | 3 -> 31 (* Mar *) | 4 -> 30 (* Apr *) | 5 -> 31 (* May *) | 6 -> 30 (* Jun *) | 7 -> 31 (* Jul *) | 8 -> 31 (* Aug *) | 9 -> 30 (* Sep *) | 10 -> 31 (* Oct *) | 11 -> 30 (* Nov *) | 12 -> 31 (* Dec *) | _ -> failwith "invalid month" end (*************************************************************************) (* Problem 3 (day of the week) *******************************************) (* This is a challenge problem! If you don't finish it, no worries, it's long. If you have time to play around with it on your own and complete it after lab, you can still submit it online. (Though you should submit what you do complete at the end of the lab session.) Try to complete this problem using only what we have covered in lab. In particular, we haven't covered any looping constructs or recursion yet, so try to solve the problem without such features. Later, when we have learned more of the OCaml language you can come back to this problem and rewrite it with a better solution. *) (* Given a year in the inclusive range [2010, 2019] and a valid month and day, determine what day of the week it was. January 1, 2010 was a Friday. Return 0 for Sunday, 1 for Monday, 2 for Tuesday, etc. *) let day_of_week (year: int) (month: int) (day: int): int = let partial_year = let jan, mar, may, jul, aug, oct, dec = 31, 31, 31, 31, 31, 31, 31 in let feb = 28 in let apr, jun, sep, nov = 30, 30, 30, 30 in begin match month with | 1 -> 0 | 2 -> jan | 3 -> jan + feb | 4 -> jan + feb + mar | 5 -> jan + feb + mar + apr | 6 -> jan + feb + mar + apr + may | 7 -> jan + feb + mar + apr + may + jun | 8 -> jan + feb + mar + apr + may + jun + jul | 9 -> jan + feb + mar + apr + may + jun + jul + aug | 10 -> jan + feb + mar + apr + may + jun + jul + aug + sep | 11 -> jan + feb + mar + apr + may + jun + jul + aug + sep + oct | 12 -> jan + feb + mar + apr + may + jun + jul + aug + sep + oct + nov | _ -> failwith "Invalid month" end in let leap_extra = if year > 2016 || (year = 2016 && month > 2) then 2 else if year > 2012 || (year = 2012 && month > 2) then 1 else 0 in let days_elapsed = 365 * (year - 2010) + partial_year + day + leap_extra in (days_elapsed - 1 + 5) mod 7 (* test case *) let test () : bool = (day_of_week 2010 1 1) = 5 ;; run_test "day_of_week 2010 1 1" test (* Add your own (at least two more) test cases for day_of_week here. *) let test () : bool = ((day_of_week 2012 2 29)) = 3 ;; run_test "day_of_week 2012 2 29" test let test () : bool = ((day_of_week 2014 7 19)) = 6 ;; run_test "day_of_week 2014 7 19" test ;; print_endline "leapYear.ml: ran to completion"