Kumite (ko͞omiˌtā) is the practice of taking techniques learned from Kata and applying them through the act of freestyle sparring.
You can create a new kumite by providing some initial code and optionally some test cases. From there other warriors can spar with you, by enhancing, refactoring and translating your code. There is no limit to how many warriors you can spar with.
A great use for kumite is to begin an idea for a kata as one. You can collaborate with other code warriors until you have it right, then you can convert it to a kata.
syntax error here
describe "Solution" do
it "should test something" do
1.should eq 1
end
end
This tries to address the problem observed in this Kata which tries to wrap the user function in the tests.
def replicate(times, num):
if times <= 0: return []
return [num] + replicate(times-1, num)
from solution import *
@test.describe('Replicate')
def fixture():
def _ref(times, num):
return [num] * times
@test.it('Basic Tests')
def basic():
data = [(3, 5), (-1, 2), (5, 10), (0, 4)]
for t, n in data:
test.assert_equals(replicate(t, n), _ref(t, n))
def counts(f):
def wrapped(*args, **kwargs):
wrapped.calls += 1
return f(*args, **kwargs)
wrapped.calls = 0
return wrapped
#@test.it('Recursion Tests')
def recurse():
import sys
r = sys.modules['solution'].replicate = counts(sys.modules['solution'].replicate)
data = [(3, 5), (5, 10), (-1, 2)]
for t, n in data:
r.calls = 0
test.assert_equals(r(t, n), _ref(t, n))
if t <= 0: t = 1
test.expect(t <= r.calls <= t + 1)
print(r.calls)
#or...
@test.it('Recursion Tests')
def recurse2():
import solution
r = solution.replicate = counts(solution.replicate)
data = [(3, 5), (5, 10), (-1, 2)]
for t, n in data:
r.calls = 0
test.assert_equals(r(t, n), _ref(t, n))
if t <= 0: t = 1
test.expect(t <= r.calls <= t + 1)
print(r.calls)
Basic idea for the new cw-2.py
's timeout
function to have an extra argument that prevents running the function right away.
pass
def timeout(sec, autorun=True):
def wrapper(func):
from multiprocessing import Process
process = Process(target=func)
process.start()
process.join(sec)
if process.is_alive():
test.fail('Exceeded time limit of {:.3f} seconds'.format(sec))
process.terminate()
process.join()
def wrapper2(func):
def wrapped(*args, **kwargs):
from multiprocessing import Process
process = Process(target=lambda: func(*args, **kwargs))
process.start()
process.join(sec)
if process.is_alive():
test.fail('Exceeded time limit of {:.3f} seconds'.format(sec))
process.terminate()
process.join()
return wrapped
return wrapper if autorun else wrapper2
@timeout(0.1)
def f():
for r in range(10**9): pass
test.pass_()
@timeout(0.1, autorun=False)
def f2(n):
for r in range(n): pass
test.pass_()
f2(10**6)
f2(10**9)
@test.it('test')
@timeout(0.1, autorun=False)
def fx():
for r in range(10**9): pass
test.pass_()
def f3(testname, t, n):
@test.it(testname)
@timeout(t, autorun=False)
def sample_it():
for r in range(n): pass
test.pass_()
f3('test1', 0.01, 10**3)
f3('test2', 0.1, 10**9)
[GNU Fortran] Free-format Hello World Program
Background
https://en.wikipedia.org/wiki/Fortran
Initially conceived in late 1953 and realized in 1957, Fortran (formerly known as FORTRAN, short for "Formula Transformation") is considered to be the oldest high-level programming language in existence. In its early days, FORTRAN was limited to scientific computing (such as calculating trajectories of missiles) and wasn't even Turing-complete due to lack of support for dynamic memory allocation which is a requirement for initializing arrays of varying size during runtime. Nevertheless, FORTRAN gained widespread acceptance in both the science and engineering communities and became the main programming language used in academia for decades. Most notably, the formulation of the first FORTRAN compiler paved the way for modern compiler theory and influenced one of the most successful and widespread programming languages of all time, C.
Modern Fortran standards and implementations (e.g. F90, F95, F2003, F2008, GNU Fortran) are believed to be Turing-complete. In particular, I have successfully demonstrated that GNU Fortran (an extension of the F95 standard) is indeed Turing-complete by implementing a full-fledged BF interpreter (which itself has been proven to be Turing-complete).
This Kumite
In this Kumite I will show you how to print out the text Hello World!
(perhaps with a few leading/trailing whitespace) in GNU Fortran. In Fortran, a program is defined by using the program
keyword, then giving the program a name (such as HelloWorld
). At the end of the program, one should type end program
followed by the program name (which is HelloWorld
in this case). The contents of the program are placed between these two statements:
program HelloWorld
! Program code here. Fortran comments start with an exclamation mark
end program HelloWorld
Note that Fortran is case-insensitve, i.e. HelloWorld
is the same as helloWorld
or helloworld
. In fact, we can start with pROGRAM HelloWorld
and end with END Program hELLOwORLD
and the code will compile/execute just fine.
After we define our program, the first statement inside that program should (almost) always be implicit none
. This tells the compiler that any and all variables declared by the programmer should be done so explicitly with a specified type instead of attempting to type-infer when no such type is provided. It is not an absolute necessity to add this statement but is added to 99% of programs as a best practice.
program HelloWorld
implicit none
end program HelloWorld
Then, we use the print
command to print out our text Hello World!
. The print statement accepts a format specifier as its first "argument" (double-quoted here because print
is not a Fortran procedure) which we'll learn about later but we'll set it to *
in this Kumite which means "use the default settings". Any subsequent "arguments" passed to the print
command are comma-separated. In our case, our only other "argument" is the string "Hello World!"
so our program would appear as follows:
program HelloWorld
implicit none
print *, "Hello World!" ! Print the text "Hello World!" to the screen
! with the default formatting settings
end program HelloWorld
Note that:
- When the default settings
*
are used for the format specifier in theprint
command, Fortran is likely to pad the displayed output with one or more leading and/or trailing whitespace. We will learn how to get rid of them in the next Kumite. - Fortran does not make a distinction between characters and strings - in fact, a string is declared as
character
type and an actual character is just a string with length 1. Due to this, both characters and strings can be wrapped in single quotes''
or double quotes""
, e.g.'Hello World!'
is equally valid as"Hello World!"
and"H"
is considered equal to'H'
.
module Solution
! Please refer to the "Test Cases" for the actual Kumite code example - we'll learn
! about modules in future Kumite ;)
end module Solution
program HelloWorld
implicit none
print *, "Hello World!" ! Print the text "Hello World!" to the screen
! with the default formatting settings
end program HelloWorld
In my previous Kumite, we learned how to print out Hello World!
to the console in Fortran. However, you may have noticed it wasn't perfect - the output has an unnecessary leading whitespace! So, the question is, how to remove it?
When the default settings (denoted by *
) is used for the format specifier for print
in Fortran, Fortran has to guess how much space it should reserve for the output which it usually overestimates, resulting in undesired padding of the output. To rectify this issue, we can provide our customized format specifier for print
.
A customized format specifier for print
is passed as a string and is always enclosed in parentheses ()
. Inside the parentheses, one or more individual format specifiers are separated by a comma ,
which may or may not be prepended/appended with one or more whitespace characters as desired. The individual format specifiers are as follows:
-
An
(n
is a positive integer) - a string of lengthn
. For example,A10
specifies that the output contains a string of length10
. -
I0
- an integer (of any size) containing any number of digits -
In
- an integer containing exactlyn
digits
There are also many other format specifiers for floating point values, logical values (known as booleans in most modern programming languages) and so on but we won't cover them in this Kumite.
Since we know that the string "Hello World!"
contains exactly 12 characters, our format specifier for this string when print
ing it to STDOUT is A12
. Hence, the full format specifier for the entire line of output should be (A12)
:
program HelloWorld
implicit none
print "(A12)", "Hello World!"
end program HelloWorld
This should print the output text Hello World!
to the console without any leading or trailing whitespace as the length of the string (which is 12
) fits the format specifier perfectly (A12
). In the case where the value of n
specified in the format specifier exceeds the length of the string, the output is padded with leading/trailing whitespace as required to make up to the given length n
. Conversely, if the n
in the format specifier is smaller than the size of the output then the output is truncated one way or another. This also applies to integers (and other data types).
module Solution
! See "Fixture" for actual Kumite content; we will explore modules in future Kumite ;)
end module Solution
program HelloWorld
implicit none
print "(A12)", "Hello World!"
! Exercise: try replacing the statement above with the commented statement shown below ;)
! print "(A6, A6)", "Hello ", "World!"
! Then try replacing it with this instead:
! print "(A4, A4, A4)", "Hell", "o Wo", "rld!"
end program HelloWorld
Constants, Variables and Datatypes in Fortran
In Fortran, there are a few intrinsic datatypes (known as primitive datatypes in other programming languages):
-
integer
- An integral value such as23
or-133
. By default, an integer is 4 bytes (32 bits) long. Integers of custom size can be specified using theinteger(kind=n)
syntax wheren
is the number of bytes that the integer occupies in memory an can be either of1
,2
,4
,8
and16
. -
real
- A floating point value such as2.0
or3.14
. By default,real
is single precision (i.e. only 4 bytes long) butreal(kind=8)
is double precision (since it occupies8
bytes in memory) -
complex
- A set of (two) floating point values representing a complex number. By default, both floats are single precision butcomplex(kind=8)
holds two double-precision floats instead. Complex numbers are defined using the intrinsic functioncmplx
with two or three arguments, e.g.cmplx(3, 4)
defines a default complex number (components are single precision floating point values) representing the value3 + 4i
-
logical
- A special type of value that can only ever take two values:.true.
or.false.
. Equivalent to a boolean in most modern programming languages. -
character
- A character (i.e. string with length 1) or character string. By default, it specifies a character such as'C'
or"0"
butcharacter(len=n)
specifies a character string of lengthn
instead.
Declaration of a variable must be done after implicit none
and before any other statements. It takes the following form: <type_name> :: <variable_1>, <variable_2>, ... , <variable_n>
. For example, if we want to declare a variable answer
with type integer, our program would look like this:
program ConstantsVariablesAndDatatypes
implicit none
integer :: answer
end program ConstantsVariablesAndDatatypes
We can also define our variable on the same line as the declaration, like such:
program ConstantsVariablesAndDatatypes
implicit none
integer :: answer = 42 ! `=` is the assignment operator
end program ConstantsVariablesAndDatatypes
After we declare a variable, we can assign/reassign to it, use it in our computations and/or print it out as desired. For example:
program ConstantsVariablesAndDatatypes
implicit none
integer :: answer = 42
print "(I0)", answer ! > 42
answer = 100 ! Reassignment
print "(I0)", answer ! > 100
end program ConstantsVariablesAndDatatypes
What if we don't want to change the value of a "variable" after initialization? We can do that by adding a comma followed by the parameter
keyword to mark a "variable" as a constant. For example:
program ConstantsVariablesAndDatatypes
implicit none
real(kind=8), parameter :: PI = 3.141592653589793
end program ConstantsVariablesAndDatatypes
We can then use it in our computations and/or print it out but not reassign to it:
program ConstantsVariablesAndDatatypes
implicit none
real(kind=8), parameter :: PI = 3.141592653589793 ! Our PI constant
print *, PI ! Prints something like "3.141592653589793", perhaps with leading/trailing whitespace
! The line below causes compilation to fail with an error
! PI = 2.718281828459045
end program ConstantsVariablesAndDatatypes
See "Fixture" of this Kumite for more examples.
module Solution
! Please refer to "Fixture" for examples, we will learn about modules in future Kumite ;)
end module Solution
program ConstantsVariablesAndDatatypes
implicit none
integer :: answer = 42 ! A integer of default size with value 42
! A 16 byte (128-bit) integer. Note that integer literals exceeding 4 bytes
! must be immediately followed by `_n` where `n` is the integer byte size and
! can be either of 1, 2, 4, 8, and 16
integer(kind=16) :: largeN = 170141183460469231731687303715884105727_16
! A double precision floating point constant. The `_8` specifies that the floating
! point literal is 8 bytes in size, preserving precision instead of creating a 4 byte
! (imprecise) literal and then typecasting to double precision
real(kind=8), parameter :: PI = 3.141592653589793_8
complex :: z = cmplx(3, 4) ! z = 3 + 4i
logical :: l1, l2 ! l1 and l2 are declared as logical values but not defined
character :: cProgrammingLanguage = 'C' ! Character string of length 1
character(len=12) :: hello = "Hello World!" ! Character string of length 12
l1 = .true. ! Now we define our first logical value to be true
print "(I0)", answer ! > 42
print "(I0)", largeN ! > 170141183460469231731687303715884105727
answer = 100
print "(I0)", answer ! > 100
answer = answer + 1 ! increment `answer` (note that `+=` and the like don't exist in Fortran)
print "(I0)", answer ! > 101
print *, PI ! prints something to the effect of 3.1415 ... (with or without leading/trailing whitespace)
! Cannot reassign to a `parameter` type (i.e. constant value)
! PI = 2.718281828459045_8
print *, z ! Prints the complex value `z` in coordinate form
print "(L1)", l1
l1 = .false. ! Reassign l1 to be false
l2 = .false. ! Assign l2 to be false
print "(L1)", l1
print "(L1)", l2
end program ConstantsVariablesAndDatatypes
Fortran Modules
Fortran modules allow us to split our code into logical chunks across files, each providing a specific functionality and/or set of functionalities and allows us to reuse certain code files in different projects/programs. To create a module, a separate file should first be created. Then, in that separate file, add a module <module_name>
statement where <module_name>
is a placeholder for the name of our module. In our case, let's name it MyFirstModule
. After that, we end our module by adding end module <module_name>
on the last line and put everything else in between.
Our module now looks like this:
module MyFirstModule
! TODO
end module MyFirstModule
Same as in a program, the first statement inside a module should almost always be implicit none
which disables type inference in the case of a programmer error (e.g. forgetting to declare a variable before defining/using it).
module MyFirstModule
implicit none
! TODO
end module MyFirstModule
Apart from the fact that modules are not executed themselves (they are used in other programs which are then executed), they are almost identical to a program, with one major difference: No action can be performed in a module (at least at the top level). This means that you cannot use print
statements among other things as you would in a program - you can only declare/define variables, constants and procedures (more on procedures in future Kumite). For example:
module MyFirstModule
implicit none
integer, parameter :: answer = 42
real :: x = 0.0, y = 1.0
character(len=12) :: hello = "Hello World!"
end module MyFirstModule
Then, to use a module in our program, we add the statement use <module_name>
before implicit none
:
program MyProgram
use MyFirstModule
implicit none
! TODO
end program MyProgram
Now our program will be able to see all of the variables, constants and procedures that our module has declared/defined. What if we want to hide the two reals x
and y
(because it is an implementation detail and not intended to be used directly in a program, for example) from the main program? To do that, we simply add a declaration private :: <variable_or_procedure_name_1>, <variable_or_procedure_name_2>, ..., <variable_or_procedure_name_n>
to control its visiblity. Our module now looks like this:
module MyFirstModule
implicit none
integer, parameter :: answer = 42
real :: x = 0.0, y = 1.0
character(len=12) :: hello = "Hello World!"
private :: x, y ! x and y are now private to the module itself
! and no longer exposed to the program using it
end module MyFirstModule
Finally, we can use the (exposed) variables/constants/procedures from our module in our main program as if they were defined in our program in the first place:
program MyProgram
use MyFirstModule
implicit none
print "(I0)", answer
print "(A12)", hello
end program MyProgram
See both Kumite "Code" and "Fixture" for the full code example.
module MyFirstModule
implicit none
integer, parameter :: answer = 42
real :: x = 0.0, y = 1.0
character(len=12) :: hello = "Hello World!"
private :: x, y ! x and y are now private to the module itself
! and no longer exposed to the program using it
end module MyFirstModule
program MyProgram
use MyFirstModule
implicit none
print "(I0)", answer
print "(A12)", hello
! This fails because `answer` is a parameter
! answer = 100
! These fail because the main program can't see private `x` and `y`; therefore
! failing with a "no IMPLICIT type" error
! print *, x
! print *, y
end program MyProgram
Just a quick test to see how Preloaded works with GNU Fortran on Codewars ...
module Solution
use Preloaded
implicit none
integer :: n = answer - 42 ! n = 0
end module Solution
program TestCases
use CW2
! Using Preloaded here in the test cases is optional if the user solution also uses Preloaded
! not including this line doesn't cause errors as `use Solution` already implies `use Preloaded`
! (from solution module) but adding it in here too doesn't cause double import error
! use Preloaded
use Solution
implicit none
print "(A9, I0)", "answer = ", answer
print "(A4, I0)", "n = ", n
! Fails as expected:
! answer = 100
n = answer ! Succeeds as expected
print "(A4, I0)", "n = ", n
call assertEquals(.true., .true.) ! Just to prevent publishing with failed tests
end program
Fortran Procedures - Functions, Pass By Reference and Purity
In Fortran it is possible to define reusable sets of instructions that either perform a given action / actions or evaluate to a certain value (or do both) which are called procedures. There are two main types of procedures:
- Functions - These eventually evaluate to a certain value which can then be used by the caller. They can be pure (i.e. do not cause any side effects, more on that later) or impure (causing side effects and/or modifying the state of the program in the process).
- Subroutines - These only perform a given set of actions and do not evaluate to any value. Subroutines may also be pure/impure.
This Kumite demonstrates how to define and use a function.
A function (and in fact any procedure) can be defined in any module/program (it does not matter which, the declaration syntax is identical in both cases) by placing them at the bottom half of the module/procedure. To do that, the given program/module has to be split into exactly two sections using the contains
statement/keyword in between. The top section contains all of the variable declarations and statements of the program/module, while the bottom section contains all of the procedure definitions:
module FunctionExample
implicit none
! Top section - contains all variable declarations/definitions
contains
! Bottom section - contains all function and subroutine
! (i.e. procedure) definitions
end module FunctionExample
Then, under the contains
statement, we declare and define our function using a function declaration of the form function <fn_name>(<var_1>, <var_2>, ..., <var_n>)
. We end our function definition using end function <fn_name>
and our function body goes between these two lines. For example, if we want to declare and define an add
function that adds two integers, our module would look like this:
module FunctionExample
implicit none
! Variable declarations
contains
function add(a, b)
! TODO
end function add
end module FunctionExample
If you've paid any amount of attention to my previous Kumite then it should've occurred to you by now that Fortran is a statically typed language, i.e. each variable has a fixed type that cannot be changed at runtime. However, our function declaration shown above didn't assign any types to the parameters a
and b
(which we want to be integers
). So, how to declare their types? Fortunately, it's very simple and straightforward - just declare them at the top of the function body like you would global variables at the start of a program/module!
module FunctionExample
implicit none
! Variable declarations
contains
function add(a, b)
integer :: a, b
end function add
end module FunctionExample
In our add
function, we would like to compute the sum of the integers a
and b
and return the corresponding integer value to the caller. Unfortunately, there is no return
keyword in Fortran so how is it done? In Fortran we must store the result we want to return to the caller in a variable with an identical name to the function name which is add
in this case. Our result is anticipated to be an integer so we need to declare the type of add
as well:
module FunctionExample
implicit none
! Variable declarations
contains
function add(a, b)
integer :: a, b
integer :: add
end function add
end module FunctionExample
Then, we simply assign the result of adding a
and b
to add
:
module FunctionExample
implicit none
! Variable declarations
contains
function add(a, b)
integer :: a, b
integer :: add
add = a + b
end function add
end module FunctionExample
Now our function declaration/definition is complete and we can use it in our program as desired.
program MyProgram
use FunctionExample
implicit none
print "(I0)", add(3, 5) ! > 8
end program MyProgram
Using a custom variable name for the returned result
What if you don't want to use the function name to store the returned result? For example, instead of add = a + b
, you want to do c = a + b
and have c
store the result to be returned. All you have to do is modify the function declaration to function <fn_name>(<var_1>, <var_2>, ..., <var_n>) result(<result_var>)
and subsequently the affected variable declarations:
module FunctionExample
implicit none
! Variable declarations
contains
function add(a, b) result(c)
integer :: a, b
integer :: c
c = a + b
end function add
end module FunctionExample
Procedure arguments in Fortran are passed by variable reference, not by value or object reference
Unlike many modern programming languages such as C, Java or Python, procedure (and hence function) arguments in Fortran are passed by variable reference. This means that if you reassign the values of arguments within your function, the passed in variable itself will be affected.
module FunctionExample
implicit none
contains
function add(a, b) result(c)
integer :: a, b
integer :: c
a = a + b ! Argument `a` is assigned the value of the result
c = a ! Result variable `c` assigned the new value of `a`
end function add
end module FunctionExample
program MyProgram
use FunctionExample
implicit none
integer :: m = 3, n = 5
! > m = 3, n = 5
print "(A4, I0, A6, I0)", "m = ", m, ", n = ", n
! > add(m, n) = 8
print "(A12, I0)", "add(m, n) = ", add(m, n)
! This segfaults
! print "(I0)", add(3, 5)
! > m = 8, n = 5
print "(A4, I0, A6, I0)", "m = ", m, ", n = ", n
end program MyProgram
Therefore, in Fortran, one must be careful not to assign any new values to existing parameters (unless there is a good reason to do so deliberately).
Pure Functions
A pure function is one that does not mutate its input in any way and does not depend on and/or change the state of the program when it is executed/evaluated. Due to Fortran's pass-by-variable-reference, it is easy to make a mistake and mutate the value of argument variables passed in and therefore violate this rule. Fortunately, Fortran has native syntactical support for these types of functions - simply prepend the function declaration with the pure
keyword: pure function <fn_name>(<v1>, <v2>, ..., <vn>) result(<rv>)
.
By explicitly declaring your function as pure
, Fortran enforces compile-time restrictions on the type declarations of all the parameters to ensure that all parameters are declared in a way that their value cannot be reassigned. This means that we need to modify the parameter declarations by adding a comma, followed by intent(in)
after the type name (and before the ::
) - this tells the Fortran compiler that the values of the parameters are read-only:
module FunctionExample
implicit none
contains
pure function add(a, b) result(c)
integer, intent(in) :: a, b
integer :: c
c = a + b
end function add
end module FunctionExample
Now we can use the add
function with the guarantee that it will never mutate its inputs:
program MyProgram
use FunctionExample
implicit none
integer :: m = 3, n = 5
print "(I0)", add(m, n) ! > 8
print "(I0)", add(3, 5) ! Works - no segfault :)
print "(I0)", m ! > 3
print "(I0)", n ! > 5
end program MyProgram
module FunctionExample
implicit none
contains
pure function add(a, b) result(c)
integer, intent(in) :: a, b
integer :: c
c = a + b
end function add
end module FunctionExample
program TestCases
use CW2
use FunctionExample
implicit none
integer :: m, n, i, k
integer, dimension(8) :: datetime
integer, dimension(:), allocatable :: seed
real :: x
call describe("add")
call it("adds integers")
call assertEquals(2, add(1, 1))
call endContext()
call it("adds more integers")
call assertEquals(8, add(3, 5))
call assertEquals(23, add(11, 12))
call assertEquals(1983, add(834, 1149))
call endContext()
call it("should be pure (i.e. doesn't mutate its input)")
m = 3
n = 5
call assertEquals(8, add(m, n))
call assertEquals(3, m)
call assertEquals(5, n)
call assertNotEquals(8, m)
call assertNotEquals(8, n)
call endContext()
call it("should work for random test cases")
! Seed intrinsic subroutine `random_number` with current time
! Source: https://www.linuxquestions.org/questions/programming-9
! /fortran-90-how-do-i-use-random_number-and-random_seed-913870/#post4525725
call date_and_time(values = datetime)
call random_seed(size = k)
allocate(seed(k))
seed(:) = datetime(8)
call random_seed(put = seed)
! Execute test loop
do i = 1, 100
call random_number(x)
m = 1 + 100 * x
call random_number(x)
n = 1 + 100 * x
print "(A16, I0, A9, I0)", "Testing for m = ", m, " and n = ", n
call assertEquals(solution(m, n), add(m, n))
end do
call endContext()
call endContext()
contains
pure function solution(a, b) result(c)
! Reference solution for random assertions
integer, intent(in) :: a, b
integer :: c
c = a + b
end function solution
end program
Fortran Procedures - Recursive Functions
In my last Kumite you learned how to declare and define a function in Fortran. If you were interested in it, you may have done some experimentation on defining a few of your own. Some of you might even have attempted to define a recursive function in Fortran using the syntax shown in the last Kumite. For example, you might have tried to find the sum of the first n
positive integers recursively:
module Solution
implicit none
contains
function sum1ToN(n) result(sum)
integer :: n, sum
if (n <= 0) then
sum = 0
else
sum = n + sum1ToN(n - 1)
end if
end function sum1ToN
end module Solution
However, if you attempted to compile and execute this module (with an accompanying program), you would've seen an error message similar to the following: Error: Function 'sum1ton' at (1) cannot be called recursively, as it is not RECURSIVE
. In fact, you can define a recursive function in Fortran, but you need to add the recursive
modifier to the beginning of your function declaration in order to do so:
module Solution
implicit none
contains
recursive function sum1ToN(n) result(sum)
integer :: n, sum
if (n <= 0) then
sum = 0
else
sum = n + sum1ToN(n - 1)
end if
end function sum1ToN
end module Solution
Now, when we define our program using this module and test our sum1ToN
function, everything works as expected:
program TestCases
use Solution
implicit none
print "(I0)", sum1ToN(10) ! > 55
end program TestCases
The recursive
keyword can also be used in conjunction with the pure
keyword to specify a pure function that has the capacity to invoke itself recursively.
module Solution
implicit none
contains
pure recursive function sum1ToN(n) result(sum)
integer, intent(in) :: n
integer :: sum
if (n <= 0) then
sum = 0
else
sum = n + sum1ToN(n - 1)
end if
end function sum1ToN
end module Solution
module Solution
implicit none
contains
pure recursive function sum1ToN(n) result(sum)
integer, intent(in) :: n
integer :: sum
if (n <= 0) then
sum = 0
else
sum = n + sum1ToN(n - 1)
end if
end function sum1ToN
end module Solution
program TestCases
use CW2
use Solution
implicit none
integer :: n, size, i
integer, dimension(8) :: values
integer, dimension(:), allocatable :: seed
real :: x
call describe("sum1ToN")
call it("should add the first n natural numbers correctly")
call assertEquals(55, sum1ToN(10))
call assertEquals(5050, sum1ToN(100))
call assertEquals(195001626, sum1ToN(19748))
call endContext()
call it("should be pure")
n = 10
call assertEquals(55, sum1ToN(n))
call assertNotEquals(55, n)
call assertEquals(10, n)
call endContext()
call it("should work for random tests as well")
call date_and_time(values = values)
call random_seed(size = size)
allocate(seed(size))
seed(:) = values(8)
call random_seed(put = seed)
do i = 1, 100
call random_number(x)
n = 1 + 1e3 * x
print "(A12, I0)", "Testing n = ", n
call assertEquals(sol(n), sum1ToN(n))
end do
call endContext()
call endContext()
contains
! Reference solution for random tests
pure recursive function sol(n) result(s)
integer, intent(in) :: n
integer :: s
if (n <= 0) then
s = 0
else
s = n + sol(n - 1)
end if
end function sol
end program