Quick Review

My plan here is to collect all the Julia code I have written so far with some notes on the techniques used. Comments should explain what each code fragment does, if it isn't obvious.

Almost all of the code on this page was done on a web site called Exercism. This is a great site for learning Julia and lots of other things too. There are some great exercises to do: the code sections below are my solutions to the exercises, often hugely improved by the great mentoring I received on the site. Strongly recommended!

Hello World

It is a tradition to start with a tiny program called 'Hello World.' The simplest way to get started is in the REPL. The code: print("Hello World") is all that is needed to print the message on the screen.

If the code: println("Hello, World!") is in a file called HelloWorld.jl then it can be invoked at the command line using: julia HelloWorld.jl. The function println is used to append a line after printing the message.

The string to print could be passed in as a command line argument. In this case the arguments are stored in a Vector of Strings called ARGS. Vectors are indexed starting at 1. Hence the code in the file could be: println(ARGS[1])

However, this assumes that there will be at least one argument. Making this a little more user-friendly:


                length(ARGS) > 0 ? for s in ARGS println(s) end : println("No arguments supplied")
            

There is more on arrays and vectors here: Arrays

A feature of Julia, called Broadcasting, is closely related to arrays and there is info on it here: Broadcasting. This page also uses an example using anonymous functions. There is more info on that subject here: Anonymous Functions.

Leap Year Test


                """
                is_leap_year(year)
                Return `true` if `year` is a leap year in the gregorian calendar.
                """
                function is_leap_year(year)
                    if year % 100 == 0
                        return year % 400 == 0
                    else
                        return year % 4 == 0
                    end
                end
            

I think that the following - which incorporates using the command line - is simpler:


                function isLeapYear(year)
                    year % 100 == 0 ? year % 400 == 0 : year % 4 == 0
                end
                
                println(isLeapYear(parse(Int64, ARGS[1])) ? "True" : "False")
            

Clock


                #=
                This is my Clock code after getting brilliant feed back from Ian Weaver
                =#
                using Dates, Printf
                
                # Clock
                # Just use minutes internally.
                struct Clock
                    minutes::Int
                    Clock(minutes) = new(mod(minutes, 60 * 24))
                end
                
                # Public interface
                Clock(hour, min) = Clock(hour * 60 + min)
                Base.:+(c::Clock, m::Minute) = Clock(c.minutes + m.value)
                Base.:-(c::Clock, m::Minute) = c + -m
                
                function Base.show(io::IO, c::Clock)
                    (h,m) = divrem(c.minutes, 60)
                    str = @sprintf "%02i:%02i" h m
                    show(io, str)
                end
            

There are some important learning points here. The use of "Base."" is to extend a method in the standard 'Base' library to work on a newly defined type, in this case Clock. So Base.show extends the show method in the Base library to display a nicely formatted clock time.

The public interface also extends the Base definition of + and - to work with the Clock struct. Note the use of : to quote the function names + and -. There are situations where the function name will need to be enclosed in brackets e.g:
Base.:(==)(a::MyType, b::MyType) = ..... There is an example here:
Operator Overloading

Raindrops

Convert a number into its corresponding raindrop sounds. If a given number: is divisible by 3, add "Pling" to the result. is divisible by 5, add "Plang" to the result. is divisible by 7, add "Plong" to the result. is not divisible by 3, 5, or 7, the result should be the number as a string.

                function raindrops(n)
                    d = [(3,"Pling"), (5,"Plang"), (7, "Plong")]
                    s = [v for (k, v) in d if n % k == 0] |> join
                    s == "" ? string(n) : s 
                end
            

Pangram


                """
                ispangram(input)
                This was my mentor's solution (Ian Weaver)
                It is an example of a single line function.
                The alphabetic range is captured in the 'a':'z' part.
                The subset symbol can be typed \subseteq + tab in the REPL.
                The conversion to lowercase is a library function.
                """
                ispangram(input::AbstractString) = 'a':'z' ⊆ lowercase(input)
            

Difference of Squares


                #=
                Find the difference between the square of the sum and the sum of the squares of the first N natural numbers.
                =#
                function square_of_sum(n)
                    square(n) = n * n
                    square(n*(n+1)) ÷ 4
                end

                function sum_of_squares(n)
                    (n*(n+1)*(2n+1)) ÷ 6 
                end

                function difference(n)
                    square_of_sum(n) - sum_of_squares(n)
                end
            

Nucleotide Count


                """
                count_nucleotides(strand)

                The count of each nucleotide within `strand` as a dictionary.

                Invalid strands raise a `DomainError`.

                """
                function count_nucleotides(strand)
                    dict = Dict{Char, Integer}('A' => 0, 'C' => 0, 'G' => 0, 'T' => 0)
                    for k in strand
                        try
                            dict[k] += 1
                        catch e
                            throw(DomainError("Invalid DNA string"))
                        end
                    end
                    return dict
                end
            

Hamming


                #=
                Given two equal length strings return the number of differences.
                Throw error if the strings are unequal.
                =#
                function distance(a,b)
                    if length(a) != length(b)
                        throw(ArgumentError("distance($a, $b)"))
                    end

                    count_not_equal((f,s)) = f != s ? 1 : 0
                    sum(count_not_equal, zip(a, b); init = 0)
                end
            

Rotational Cipher


                #=
                This is the second implementation of the Caesar cipher.
                Next steps are to figure out how to use string literals and meta-programming.
                =#
                function rotate(offset :: Int, key :: Int, c :: Char)
                    convert(Char,(((convert(Int, c) - offset) + key) % 26) + offset)
                end

                function rotate(key :: Int, c :: Char)
                    if c in 'A':'Z'
                        rotate(65, key, c)
                    elseif c in 'a':'z'
                        rotate(97, key, c)
                    else
                        return c
                    end
                end

                function rotate(key :: Int, text :: String)
                    rotateN(c) = rotate(key, c)
                    map(rotateN, text)
                end

                macro R13_str(text)
                    # Encrypt a single character, given offset.
                    function rotate1(offset :: Int, c :: Char)
                        key = 13
                        convert(Char,(((convert(Int, c) - offset) + key) % 26) + offset)
                    end

                    # Encrypt a single character, works for upper and lower case.
                    function rotate2(c :: Char)
                        if c in 'A':'Z'
                            rotate1(65, c)
                        elseif c in 'a':'z'
                            rotate1(97, c)
                        else
                            return c
                        end
                    end

                    # Encrypt a string
                    function rotate3(s :: String)
                        map(rotate2, s)
                    end

                    rotate3(text)
                end
            

Secret Handshake

Instructions Your task is to convert a number between 1 and 31 to a sequence of actions in the secret handshake. The sequence of actions is chosen by looking at the rightmost five digits of the number once it's been converted to binary. Start at the right-most digit and move left. The actions for each number place are: 00001 = wink 00010 = double blink 00100 = close your eyes 01000 = jump 10000 = Reverse the order of the operations in the secret handshake. Let's use the number 9 as an example: 9 in binary is 1001. The digit that is farthest to the right is 1, so the first action is wink. Going left, the next digit is 0, so there is no double-blink. Going left again, the next digit is 0, so you leave your eyes open. Going left again, the next digit is 1, so you jump. That was the last digit, so the final code is: wink, jump Given the number 26, which is 11010 in binary, we get the following actions: double blink jump reverse actions The secret handshake for 26 is therefore: jump, double blink


                function secret_handshake(code)
                    secrets = [(1, "wink"), (2, "double blink"), (4, "close your eyes"), (8, "jump")]
                    r = [v for (k, v) in secrets if k & code == k]
                    code & 16 == 16 ? reverse(r) : r
                end
            

Killer Sudoku Helper


                # ===================================================================================
                # Killer sudoko
                # combinations_in_cage(sum, num_cells)
                # Inputs: sum is the total to arrive at, num_cells is the number of cells in the cage
                # Optional paramter: an array of digits which must not be in the final output.
                # Return: array of arrays
                # Structure of code at present is that an array implements a switch.
                # Then each size of cage creates the list for a given sum.
                # ============================================================================

                # List the cases to deal with:
                #   cases[number of cells] = function to compute possible ways of getting sum.
                # Then evaluate the function using the parameters
                function combinations_in_cage(sum, num_cells)
                    cases = [f1, f2, f3, f4, f5, f6, f7, f8, f9]
                    sort(cases[num_cells](sum))
                end 

                # Exclude the arrays containing any of the unwanted numbers
                function combinations_in_cage(sum, num_cells, unwanted)
                    exclude(vals) = !(any(in(unwanted), vals))
                    filter(exclude, combinations_in_cage(sum, num_cells))
                end

                # These are the cases used in the function
                # combinations_in_cage(sum, cells)

                # 1-digit cages, output has to be the same as the sum
                f1(sum) = [[sum]]

                # cages done using list comprehension
                f2(sum) = [[a,b] for a in 1:8, b in 2:9 if a<b && sum == (a+b) ]
                f3(sum) = [[a,b,c] for a in 1:7, b in 2:8, c in 3:9 if a<b<c && sum == (a+b+c) ]
                f4(sum) = [[a,b,c,d] for a in 1:6, b in 2:7, c in 3:8, d in 4:9 if a<b<c<d && sum == (a+b+c+d) ]
                f5(sum) = [[a,b,c,d,e] for a in 1:5, b in 2:6, c in 3:7, d in 4:8, e in 5:9 if a<b<c<d<e && sum == (a+b+c+d+e) ]

                # Same thing but differently laid out to keep lines from getting too long.
                function f6(sum) 
                    [[a,b,c,d,e,f] for a in 1:4, b in 2:5, c in 3:6, d in 4:7, e in 5:8, f in 6:9
                    if a<b<c<d<e<f && sum == (a+b+c+d+e+f)]
                end

                function f7(sum) 
                    [[a,b,c,d,e,f,g] for a in 1:3, b in 2:4, c in 3:5, d in 4:6, e in 5:7, f in 6:8, g in 7:9
                    if a<b<c<d<e<f<g && sum == (a+b+c+d+e+f+g)]
                end

                function f8(sum) 
                    [[a,b,c,d,e,f,g, h] for a in 1:2, b in 2:3, c in 3:4, d in 4:5, e in 5:6, f in 6:7, g in 7:8, h in 8:9
                    if a<b<c<d<e<f<g<h && sum == (a+b+c+d+e+f+g+h)]
                end

                # f9 only has one useful output
                f9(sum) = sum == 45 ? [[1,2,3,4,5,6,7,8,9]] : [[]]

            

Multi-dimensional matrices

In the last example we saw how a two dimensional matrix can be constructed. A simple example would be:


                tables = [a * b for a in 1:12, b in 1:12]
            

Which gives the output:


                12x12 Matrix{Int64}:
  1   2   3   4   5   6   7   8    9   10   11   12
  2   4   6   8  10  12  14  16   18   20   22   24
  3   6   9  12  15  18  21  24   27   30   33   36
  4   8  12  16  20  24  28  32   36   40   44   48
  5  10  15  20  25  30  35  40   45   50   55   60
  6  12  18  24  30  36  42  48   54   60   66   72
  7  14  21  28  35  42  49  56   63   70   77   84
  8  16  24  32  40  48  56  64   72   80   88   96
  9  18  27  36  45  54  63  72   81   90   99  108
 10  20  30  40  50  60  70  80   90  100  110  120
 11  22  33  44  55  66  77  88   99  110  121  132
 12  24  36  48  60  72  84  96  108  120  132  144