Week 4 assignment of Coursera's course Kotlin for Java Developers
Kotlin_for_Java_Developers._Week_4
Week 4 assignment of Coursera’s course “Kotlin for Java Developers”.
1. Rational Numbers
Your task is to implement a class Rational
representing rational numbers.
A rational number is a number expressed as a ratio n/d
, where n
(numerator)
and d
(denominator) are integer numbers, except that d
cannot be zero.
If the denominator is zero, you can throw an exception on creating a new
instance of the class, e.g. IllegalArgumentException
.
Examples of rational numbers are 1/2
, 2/3
, 117/1098
, and 2/1
(which
can alternatively be written simply as 2
).
Rational numbers are represented exactly, without rounding or
approximation, which gives them the advantage compared to floating-point numbers.
Your task it to model the behavior of rational numbers, including allowing them
to be added, subtracted, multiplied, divided and compared.
All arithmetic and comparison operations must be available for rationals:
+
, -
, *
, /
, ==
, <
, >=
etc.
Check whether a number belongs to a range should also be possible:
1/2 in 1/3..2/3
should return true.
The Rational
class should contain a numerator and denominator which might be
unlimited numbers, so use java.math.BigInteger
class for storing them.
The rational numbers must be compared to their “normalized” forms:
for example, 1/2
should be equal to 2/4
, or 117/1098
to 13/122
.
The string representation of a rational must be also given in the normalized form.
Note that the denominator 1
must be omitted, so 2/1
must be printed as "2"
.
The denominator must be always positive in the normalized form.
If the negative rational is normalized, then only the numerator can be negative, e.g.
the normalized form of 1/-2
should be -1/2
.
Note that you can use BigInteger.gcd
to find the greatest common divisor
used in the normalized form calculation.
You need to support two ways to create rationals. The first one is to convert
a string representation to a rational directly, like in "1/2".toRational()
.
Converting an integer number should also be possible, and 1
should be used
as denominator by default: "23".toRational()
is the same as "23/1".toRational()
.
The alternative way to create a rational is to use divBy
infix function
like in 1 divBy 2
. The receiver and the argument might be of types Int
,
Long
, or BigInteger.
Examples
All the following equality checks must be evaluated to true
:
val half = 1 divBy 2
val third = 1 divBy 3
val sum: Rational = half + third
5 divBy 6 == sum
val difference: Rational = half - third
1 divBy 6 == difference
val product: Rational = half * third
1 divBy 6 == product
val quotient: Rational = half / third
3 divBy 2 == quotient
val negation: Rational = -half
-1 divBy 2 == negation
(2 divBy 1).toString() == "2"
(-2 divBy 4).toString() == "-1/2"
"117/1098".toRational().toString() == "13/122"
val twoThirds = 2 divBy 3
half < twoThirds
half in third..twoThirds
2000000000L divBy 4000000000L == 1 divBy 2
"912016490186296920119201192141970416029".toBigInteger() divBy
"1824032980372593840238402384283940832058".toBigInteger() == 1 divBy 2
2. Board
Your task is to implement interfaces SquareBoard
and GameBoard
.
SquareBoard
SquareBoard stores the information about the square board and all the cells on it. It allows the retrieval of a cell by its indexes, parts of columns and rows on a board, or a specified neighbor of a cell.
Note that the numbering of cells starts with 1 (not 0).
A board of width two consists of the following cells:
(1, 1) (1, 2)
(2, 1) (2, 2)
For the following examples, we’ll use this board of width 2:
val board = createSquareBoard(2)
If you call board.getCellOrNull(3, 3)
for such a board, you’ll get null
as
the result, because the board doesn’t have a cell with such coordinates.
The function Board.getCell
should throw IllegalArgumentException
for
incorrect values of i
and j
.
You can write board.getRow(1, 1..2)
or board.getRow(1, 2 downTo 1)
,
and you’ll get the lists of cells [(1, 1), (1, 2)]
and [(1, 2), (1, 1)]
accordingly.
Note how using the range 2 downTo 1
returns a row in a reversed order.
You can use any range to get a part of a column or a row.
Note that getRow
and getColumn
should return a list containing only
the cells that belong to the board if the range is larger than the board limits
and ignore other indexes,
thus, board.getRow(1, 1..10)
should return [(1, 1), (1, 2)]
.
The neighbors of a cell (1, 1)
, depending on the direction should be:
Direction.UP - null
Direction.LEFT - null
Direction.DOWN - (2, 1)
Direction.RIGHT - (1, 2)
Create only width * width
cells; all the functions working with cells
should return existing cells instead of creating new ones.
GameBoard
GameBoard allows you to store values in board cells, update them,
and enquire about stored values (like any
,
all
etc.)
Note that GameBoard extends SquareBoard.
Don’t store a value in a Cell
: data class Cell
is intended to be immutable
and only store the coordinates.
You can store values separately, for instance, in a map from Cell
to stored values type.
See TestSquareBoard
and TestGameBoard
for examples.