In everyday life people have to make decisions and so do computer programs. This is the job for if ... elseif ... else
constructs.
To demonstrate decision making in action let’s say I want to write a function that accepts an integer as an argument and returns its textual representation. Here we go.
function turnInt2string(num::Int)::String
if num <= 0
return "zero or less"
elseif num == 1
return "one"
elseif num == 2
return "two"
else
return "three or above"
end
end
(turnInt2string(2), turnInt2string(5)) # a tuple with results
("two", "three or above")
The general structure of the construct goes like this:
# pseudocode, don't run this snippet
if (condition_that_returns_Bool)
what_to_do
elseif (another_condition_that_returns_Bool)
what_to_do
elseif (another_condition_that_returns_Bool)
what_to_do
else
what_to_do
end
As mentioned in Section 3.3.4 Bool
type can take one of two values true
or false
. The code inside if
/elseif
clause runs only when the condition is true
. You can have any number of elseif
clauses. Only the code for the first true
clause runs. If none of the previous conditions matches (each and every one is false
) the code in the else
block is executed. Only if
and end
keywords are obligatory, the rest is not, so you may use
# pseudocode, don't run this snippet
if (condition_that_returns_Bool)
what_to_do
end
or
# pseudocode, don't run this snippet
if (condition_that_returns_Bool)
what_to_do
else
what_to_do
end
or
# pseudocode, don't run this snippet
if (condition_that_returns_Bool)
what_to_do
elseif (condition_that_returns_Bool)
what_to_do
else
what_to_do
end
or
# pseudocode, don't run this snippet
if (condition_that_returns_Bool)
what_to_do
elseif (condition_that_returns_Bool)
what_to_do
elseif (condition_that_returns_Bool)
what_to_do
else
what_to_do
end
or …, never mind, I think you got the point.
Below I place another example of a function using if/elseif/else
construct (in order to remember it better).
# works fine for non-empty vectors
function getMin(vect::Vector{Int}, isSortedAsc::Bool)::Int
if isSortedAsc
return vect[1]
else
sortedVect::Vector{Int} = sort(vect)
return sortedVect[1]
end
end
x = [1, 2, 3, 4]
y = [3, 4, 1, 2]
(getMin(x, true), getMin(y, false))
(1, 1)
Here I wrote a function that finds the minimal value in a vector of integers. If the vector is sorted in the ascending order it returns the first element. If it is not, it sorts the vector using the built in sort function and returns its first element (this may not be the most efficient method but it works). Note that the else
block contains two lines of code (it could contain more if necessary, and so could if
block). I did this for demonstrative purposes. Alternatively instead those two lines (in the else
block) one could write return sort(vect)[1]
and it would work just fine.
If you need only a single if ... else
in your code, then you may prefer to replace it with ternary operator. Its general form is condition_or_Bool ? result_if_true : result_if_false
.
Let me rewrite getMin
from Section 3.5.1 using ternary expression.
function getMin(vect::Vector{Int}, isSortedAsc::Bool)::Int
return isSortedAsc ? vect[1] : sort(vect)[1]
end
x = [1, 2, 3, 4]
y = [3, 4, 1, 2]
(getMin(x, true), getMin(y, false))
(1, 1)
Much less code, works the same. Still, I would not overuse it. For more than a single condition it is usually harder to write, read, and process in your head than the good old if/elseif/else
block.
Dictionaries in Julia are a sort of mapping. Just like an ordinary dictionary is a mapping between a word and its definition. Here, we say that the mapping is between key
and value
. For instance let’s say I want to define an English-Polish dictionary.
engPolDict::Dict{String, String} = Dict("one" => "jeden", "two" => "dwa")
engPolDict # the key order is not preserved on different computers
Dict{String, String} with 2 entries:
"two" => "dwa"
"one" => "jeden"
Here I defined a dictionary of type Dict{String, String}
, so, both key
and value
are of textual type (String
). The order of the keys is not preserved (this data structure cares more about lookup performance and not about the order of the keys). Therefore, you may see a different order of items after executing the code on your computer.
If we want to now how to say “two” in Polish I type aDict[key]
(if the key is not there you will get an error), e.g.
engPolDict["two"]
dwa
To add a new value to a dictionary (or to update the existing value) write aDict[key] = newVal
. Right now the key “three” does not exist in engPolDict
so I would get an error (check it out), but if I type:
engPolDict["three"] = "trzy"
trzy
Then I create (or update if it was already there) a key-value mapping.
Now, to avoid getting errors due to non-existing keys I can use the built in get function. You use it in the form get(collection, key, default)
, e.g. right now the word “four” (key) is not in a dictionary so I should get an error (check it out). But wait, there is get
.
get(engPolDict, "four", "not found")
not found
OK, what anything of it got to do with if/elseif/else
and decision making. The thing is that if you got a lot of decisions to make then probably you will be better off with a dictionary. Compare
function translEng2polVer1(engWord::String)::String
if engWord == "one"
return "jeden"
elseif engWord == "two"
return "dwa"
elseif engWord == "three"
return "trzy"
elseif engWord == "four"
return "cztery"
else
return "not found"
end
end
(translEng2polVer1("three"), translEng2polVer1("ten"))
("trzy", "not found")
with
function translEng2polVer2(engWord::String,
aDict::Dict{String, String} = engPolDict)::String
return get(aDict, engWord, "not found")
end
(translEng2polVer2("three"), translEng2polVer2("twelve"))
("trzy", "not found")
Note: Dictionaries like Arrays (see Section 3.3.7) are passed by references
In translEng2polVer2
I used a so called optional argument for aDict
(aDict::Dict{String, String} = engPolDict
). This means that if the function is provided without the second argument then engPolDict
will be used as its second argument. If I defined the function as translEng2polVer2(engWord::String, aDict::Dict{String, String})
then while running the function I would have to write (translEng2polVer2("three", engPolDict), translEng2polVer2("twelve", engPolDict))
. Of course, I may prefer to use some other English-Polish dictionary (perhaps the one found on the internet) like so translEng2polVer2("three", betterEngPolDict)
instead of using the default engPolDict
we got here.
In general, the more if ... elseif ... else
comparisons you got to do the better off you are when you use dictionaries (especially that they could be written by someone else, you just use them). Still, in the rest of the book we will probably use dictionaries for data storage and a quick lookup.
OK, enough of that. If you want to know more about conditional evaluation check this part of Julia’s docs.