3.9 Julia - Solutions

In this sub-chapter you will find exemplary solutions to the exercises from the previous section.

3.9.1 Solution to Exercise 1

Since I’m eating a surface, and the task description gives me diameters, then I should probably calculate area of a circle. I will use Base.MathConstants.pi in my calculations.

function getCircleArea(radius::Real)::Real
    return pi * radius * radius
end

Now, we can finally get the answer.

# radius = diameter / 2
(getCircleArea(30/2) * 2, getCircleArea(45/2))
(1413.7166941154069, 1590.431280879833)

It seems that I will get more food while ordering this one pizza (45 cm in diameter) and not those two pizzas (each 30 cm in diameter).

Note: Instead of pi * radius * radius I could have used radius^2, where ^ is an exponentiation operator in Julia. If I want to raise 2 to the fourth power I can either type 2^4 or 2*2*2*2 and get 16.

If all the pizzas were cylinders of equal heights (say 2 cm or an inch each) then I would calculate their volumes like so

function getCylinderVolume(radius::Real, height::Real=2)::Real
    # hmm, is cylinder just many circles stacked one on another?
    return getCircleArea(radius) * height
end

and the results

# radius = diameter / 2
(getCylinderVolume(30/2) * 2, getCylinderVolume(45/2))
(2827.4333882308138, 3180.862561759666)

Still, it appears the conclusion is the same.

3.9.2 Solution to Exercise 2

My solution to that problem would look something like

function areApproxEqual(f1::Float64, f2::Float64)::Bool
    return round(f1, digits=16) == round(f2, digits=16)
end

Let’s put it to the test

areApproxEqual(0.1*3, 0.3)

true

Seems to be working fine. Still, you may prefer to use Julia’s built-in isapprox. In general, it is a good idea to use a built in function from the standard library over your own as it should be more robust.

Anyway, let’s test isapprox as well.

isapprox(0.1*3, 0.3)
# compare with
# isapprox(0.11*3, 0.3)
# or to test if the values are not equal
# !isapprox(0.11*3, 0.3)

true

It works just fine.

Lesson to be learned here. If you want to do something you can:

  1. look for a function in the language documentation
  2. look for a function in some library
  3. write a function yourself by using what you already got at your disposal

3.9.3 Solution to Exercise 3

Possible solution

function getMax(vect::Vector{Int}, isSortedDesc::Bool)::Int
    return isSortedDesc ? vect[1] : sort(vect)[end]
end

(getMax([3, 2, 1], true), getMax([2, 3, 1], false))
(3, 3)

or if you read the documentation for sort

function getMax(vect::Vector{Int}, isSortedDesc::Bool)::Int
    return isSortedDesc ? vect[1] : sort(vect, rev=true)[1]
end

(getMax([3, 2, 1], true), getMax([2, 3, 1], false))
(3, 3)

Sorting an array to get the maximum (or minimum) value is not the most effective method (sorting is based on rearranging elements and takes quite some time). Traveling through an array only once should be faster. Therefore probably a better solution (in terms of performance) would be something like

function getMaxUnsorted(unsortedVect::Vector{Int})::Int
    maxVal::Int = unsortedVect[1]
    for elt in unsortedVect[2:end]
        if maxVal < elt
            maxVal = elt
        end
    end
    return maxVal
end

function getMax(vect::Vector{Int}, isSortedDesc::Bool)::Int
    return isSortedDesc ? vect[1] : getMaxUnsorted(vect)
end

(getMax([3, 2, 1], true), getMax([2, 3, 1], false))
(3, 3)

Read it carefully and try to figure out how it works.

Note: Julia already got similar functionality to getMin, getMax that we developed ourselves. See min, max, minimum, and maximum.

3.9.4 Solution to Exercise 4

Perhaps the most direct version of the program would be

function printFizzBuzz()
    for i in 1:30
        # or: if rem(i, 15) == 0
        if rem(i, 3) == 0 && rem(i, 5) == 0
            println("Fizz Buzz")
        elseif rem(i, 3) == 0
            println("Fizz")
        elseif rem(i, 5) == 0
            println("Buzz")
        else
            println(i)
        end
    end
    return nothing
end

Note: Julia applies operators based on precedence and associativity. If you are unsure about the order of their evaluation (e.g. in if rem(i, 3) == 0 && rem(i, 5) == 0) then check the docs or use parenthesis () to enforce the desired order of evaluation (e.g. if (rem(i, 3) == 0) && (rem(i, 5) == 0)).

Go ahead, test it out.

If you like challenges try to follow the execution of the following program.

function getFizzBuzz(num::Int)::String
    return (
        rem(num, 15) == 0 ? "Fizz Buzz" :
        rem(num, 3) == 0 ? "Fizz" :
        rem(num, 5) == 0 ? "Buzz" :
        string(num)
    )
end

function printFizzBuzz()
    foreach(x -> println(getFizzBuzz(x)), 1:30)
    return nothing
end

# you can use it like so: printFizzBuzz()

There are probably other more creative [or more (unnecessarily) convoluted] ways to solve this task. Personally, I would be satisfied if you understand the first version.

3.9.5 Solution to Exercise 5

For more information about the legend see this Wikipedia’s article.

If you want some more detailed mathematical explanation you can read that Wikipedia’s article.

The Wikipedia’s version of the legend differs slightly from mine, but I like mine better.

Anyway let’s jump right into some looping.

function getNumOfGrainsOnField64()::Int
    noOfGrains::Int = 1 # no of grains on field 1
    for _ in 2:64
        noOfGrains *= 2 # *= is update operator similar to +=
    end
    return noOfGrains
end

getNumOfGrainsOnField64()

-9223372036854775808

Hmm, that’s odd, a negative number.

Wait a moment. Now I remember, a computer got finite amount of memory. So in order to work efficiently data is stored in small pre-allocated pieces of it. If the number you put into that small ‘memory drawer’ is greater than the amount of space then you get strange results (imagine that a number sticks out of the drawer but Julia looks only at the part inside the drawer, hence the strange result).

If you are interested in technical stuff then you can read more about it in Julia’s docs (sections Integers and Overflow Behavior).

You can check the minimum and maximum value for Int by typing typemin(Int) and typemax(Int) on my laptop those are -9223372036854775808 and 9223372036854775807, respectively.

The broad range of Int is enough for most calculations, still if you expect a really big number you should use BigInt (BigInt calculations are slower than the ones for Int, but now you should be only limited by the amount of memory on your computer).

So let me correct the code.

function getNumOfGrainsOnField64()::BigInt
    noOfGrains::BigInt = 1 # no of grains on field 1
    for _ in 2:64
        noOfGrains *= 2
    end
    return noOfGrains
end

getNumOfGrainsOnField64()

9223372036854775808

Whoa, that number got like 19 digits. I don’t even know how to name it. It cannot be that big, can it?

OK, quick verification with some mathematical calculation (don’t remember ^? See Section 3.9.1).

BigInt(2)^63 # we multiply 2 by 2 by 2, etc. for fields 2:64

9223372036854775808

Yep, the numbers appear to be the same.

getNumOfGrainsOnField64() == BigInt(2)^63

true

So I guess the aforementioned Wikipedia’s article is right, it takes much more grain than a country (or the world) could produce in a year.

3.9.6 Solution to Exercise 6

A possible solution with generics looks something like that

function getInit(vect::Vector{T})::Vector{T} where T
    return vect[1:(end-1)]
end
getInit (generic function with 1 method)

The parenthesis around end-1 are not necessary. I added them for better clarity of how the last by one index is calculated.

Tests:

getInit([1, 2, 3, 4])
[1, 2, 3]
getInit(["ab", "cd", "ef", "gh"])
["ab", "cd", "ef"]
getInit([3.3])
Float64[]
getInit([])

BTW. Try to remove type declarations and see if the function still works (if you do this right then it should).

OK, that’s it for now. Let’s move to another chapter.



CC BY-NC-SA 4.0 Bartlomiej Lukaszuk