Introducing Julia/print

From Wikibooks, open books for an open world
< Introducing Julia
Jump to: navigation, search
Introducing Julia

Contents

Introduction[edit]

Julia is a recent arrival to the world of programming languages, and has yet to accumulate a profusion of introductory texts. This will surely change. Soon we might see:

  • Julia in 24 hours
  • Learn Julia the Hard Way
  • Julia for Dummies
  • Julia for Fun and Profit

and many others. But until then, here’s a collection of notes and introductory paragraphs that comprise a gentle introduction to the Julia programming language, in the form of a wikibook.

The advantage and disadvantage of Wikibooks, apart from being free and open, is that anyone can edit anything at any time. In theory, a wikibook can only get better as more people add improvements and corrections. In practice, a wikibook may lose focus and consistency as it gains accuracy and coverage. But, because the Julia community has established a good ethos of encouraging participation in the development of the language, it's right that this wikibook is freely editable by everyone.

The official Julia documentation is excellent, although aimed more at the early adopters, developers, and more experienced programmers. Refer to it as often as possible.

Much of the text in this wikibook should work with the current version of Julia, which is, as of September 2014, version 0.3.

« Introducing Julia
Getting Started
»
Contents The REPL

Getting started[edit]

To install Julia on your computer, visit http://julialang.org/downloads/ and follow instructions.

You can use Julia online, in a browser. JuliaBox provides online IJulia notebooks, which let you run Julia on a remote machine, using Jupyter (formerly IPython) interactive notebooks. Jupyter is the interactive notebook technology that lets you run code in Julia, Python, and R in a browser window. You'll need to log in using your Google or GitHub identities.

Alternatively, you can install a local version of IJulia by installing IJulia.

On Mac OS X[edit]

On a Mac, you download the Julia DMG, double-click to open it, and drag the icon to the Applications folder. To run Julia, you can double-click the icon of the Julia package in the /Applications folder. This opens the terminal application, and starts a new window. This is the REPL, introduced in the next section:

$ julia
               _
   _       _ _(_)_     |  A fresh approach to technical computing
  (_)     | (_) (_)    |  Documentation: http://docs.julialang.org
   _ _   _| |_  __ _   |  Type "?help" for help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 0.6.0-pre.alpha.0 (2017-02-28 13:15 UTC)
 _/ |\__'_|_|_|\__'_|  |  Official http://julialang.org/ release
|__/                   |  x86_64-apple-darwin13.4.0

julia>

Alternatively, you can type, in a terminal, something like this:

$ /Applications/Julia-0.6.app/Contents/Resources/julia/bin/julia

— here you’re specifying the path name of the Julia binary executable that lives inside the Julia application bundle. The exact version name might be different — check it using the command:

$ ls /Applications/Julia*/Contents/Resources/julia/bin/julia
/Applications/Julia-0.4.5.app/Contents/Resources/julia/bin/julia
/Applications/Julia-0.4.7.app/Contents/Resources/julia/bin/julia
/Applications/Julia-0.5.app/Contents/Resources/julia/bin/julia
/Applications/Julia-0.6.app/Contents/Resources/julia/bin/julia

Running directly from terminal[edit]

Typically, Julia is installed in /Applications, which isn't included in your PATH, and so the shell won't find it when you type julia on the command line.

But there are clever things you can do with paths and profiles, so that you can log in to a terminal and type julia with immediate success.

For example, after you find out the location of the Julia binary executable file (see above), you can define the following alias:

alias julia="/Applications/Julia-0.6.app/Contents/Resources/julia/bin/julia"

Obviously this will have to be updated every time the version number changes.

As an alternative, you could add the /Applications/Julia... path to the PATH variable:

PATH="/Applications/Julia-0.6.app/Contents/Resources/julia/bin/:${PATH}"
export PATH

A different approach is to create a link to the executable and put it into the /usr/local/bin directory (which should already be in your path), so that typing julia is the exact equivalent of typing /Applications/Julia/.../julia. This command does that:

ln -fs "/Applications/Julia-0.6.app/Contents/Resources/julia/bin/julia" /usr/local/bin/julia

Whichever method you choose, you can add the relevant command to your ~/.bash_profile file to run every time you create a new shell.

You can use the 'shebang' line at the top of a text file ('script') so that the shell can find Julia and execute the file:

#!/usr/bin/env julia

This also works in a lot of text editors, so that you can choose Run to run the file. This works if the editor reads the user's environment variables before running the file. (But not all do!)

Running a Julia program[edit]

If you have a text file containing Julia code, you can run it from the command-line:

$ julia hello-world.jl

or from within the Julia REPL:

$ julia
julia> include("hello-world.jl")

If the first line specifies a Julia interpreter:

#!/Applications/Julia-0.5.app/Contents/Resources/julia/bin/julia

or

#!/usr/bin/env julia

you can run the file like this:

$ ./hello-world.jl

Running a script with Julia[edit]

If you want to write Julia code in an editor and run it, in true scripting-language fashion, you can. At the top of the script file, add a line like the following:

#!/Applications/Julia-0.5.app/Contents/Resources/julia/bin/julia

where the pathname points to the right place on your system, somewhere inside the relevant Julia application bundle, or:

#!/usr/bin/env julia

This is the shebang line.

Now you can run the script from inside the editor in the same way that you'd run a Perl script.

On Windows[edit]

On a Windows machine, you download the Julia Self-Extracting Archive (.exe) 32-bit or 64-bit. Double-click to start the installation process.

By default, it will install to your AppData folder. You may keep the default or choose your own directory (eg. C:\Julia).

After the installation has finished, you should create a System Environment variable called JULIA_HOME and set its value to the \bin directory under the folder where you installed Julia.

It is important to point JULIA_HOME to the /bin directory instead of the JULIA directory.

Then you can append ;%JULIA_HOME% to your PATH System Environment variable, so you can run scripts from any directory. Make sure that the registry key HKEY_CURRENT_USER\Environment\Path is of type REG_EXPAND_SZ, so %JULUA_HOME% gets expanded properly.

On FreeBSD[edit]

To install Julia on FreeBSD (including TrueOS) or DragonFly BSD you can use either a binary package or using the ports system.

Installing from package[edit]

Installing the Julia package is straight forward. Open up a terminal and type:

$ pkg install julia

To remove the package again you can use:

$ pkg remove julia

Installing from ports[edit]

If you have the ports collection installed on your system (you can do so using running the command portsnap auto) the following is the canonical way to compile and install Julia onto your system:

$ cd /usr/ports/lang/julia/ && make install clean

On Linux[edit]

Installing from package[edit]

This is the easiest way to install Julia if you're using Linux distributions based on RedHat, Fedora, Debian or Ubuntu. To install, download the respective package from the website, and install using your favorite way (double-clicking on the package file usually works). After doing this, Julia will be availabe from command line. On a terminal you can do:

$ julia
               _
   _       _ _(_)_     |  A fresh approach to technical computing
  (_)     | (_) (_)    |  Documentation: http://docs.julialang.org
   _ _   _| |_  __ _   |  Type "help()" to list help topics
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version xxxxxxxxxxx
 _/ |\__'_|_|_|\__'_|  |  Commit xxxxxx
|__/                   |  

julia>

Installing from PPA (Ubuntu and derivatives)[edit]

On Ubuntu (and other distributions based on Ubuntu, like elementary or Linux Mint) you can install Julia even easier. You just have to add a repository and install julia from the command line terminal:

$ sudo add-apt-repository ppa:staticfloat/juliareleases
$ sudo add-apt-repository ppa:staticfloat/julia-deps
$ sudo apt-get update
$ sudo apt-get install julia

With this, Julia will be updated together with the other software on your machine. Notice that if you are logged as root, you don't have to use sudo on the commands.

To remove the package, run:

$ sudo apt-get purge julia

And to remove the repository, run:

$ sudo add-apt-repository --remove ppa:staticfloat/juliareleases

Make sure you remove the package before removing the repository.

Arch Linux[edit]

On Arch Linux, Julia is available from community repository, and can be installed running:

$ sudo pacman -S julia

To remove Julia package and it's dependencies (if not used by any other software on your system), you can run:

$ sudo pacman -Rsn julia

Fedora[edit]

On Fedora distributions, Julia is available from the updates repository (a default repository) and can be installed running:

$ sudo dnf install julia

To remove Julia package and it's dependencies (again, if not used by other software on your system), you can run:

$ sudo dnf remove julia

Please note, this applies only to Fedora, downstream distributions such as RHEL or CentOS must check their own repositories to see if Julia is available.

Using Binaries[edit]

You can use Julia direct from the binaries, without installing it on your machine. This is usefull if you have old Linux distributions or if you don't have administrator's access to the machine. Just download the binaries from the website, extract to a directory. While in this directory, enter the bin folder and run:

$ ./julia

If the program doesn't have permission to run, use the following command to give this permission:

$ chmod +x julia 

In principle, this method could be used on any Linux distribution.

Running a script with Julia[edit]

To tell your operating system that it should run the script using Julia, you can use what is called the shebang syntax. To do this, just use the following line on the very top of your script:

#!/usr/bin/env julia

With this as the first line of the script, the OS will search for "julia" on the path, and use it to run the script.

The REPL[edit]

« Introducing Julia
The REPL
»
Getting started Array and tuples

The REPL[edit]

The julia program starts the interactive REPL, the Read/Evaluate/Print/Loop, by default. It lets you type expressions in Julia code and see the results of the evaluation printed on the screen immediately. It:

  • Reads what you type
  • Evaluates it
  • Prints out the return value, then
  • Loops back and does it all over again

The REPL is a great place to start experimenting with the language. But it's not the best environment to do serious programming work of any scale in – for that, a text editor, or interactive notebook environment (e.g. IJulia/Jupyter) is a better choice. But there are advantages to using the REPL: it's simple, and should work without any installation or configuration. There's a built-in help system, too.

Using the REPL[edit]

You type some Julia code and then press Return/Enter. Julia evaluates what you typed and returns the result:

julia> 42 <Return/Enter>
42

julia>

If you're using the Jupyter (IPython) notebook, you probably have to type Control-Enter, or Shift-Enter.

If you don't want to see the result of the expression printed, use a semicolon at the end of the expression:

julia> 42;

julia>

Also, if you want to access the value of the last expression you typed on the REPL, it's stored within the variable ans:

julia> ans
42

If you don't complete the expression on the first line, continue typing until you finish. For example:

julia> 2 +  <Return/Enter>

now Julia waits patiently until you finish the expression:

2  <Return/Enter>

and then you'll see the answer:

4

julia>


Help and searching for help[edit]

Type a question mark ?

julia> ?

and you'll immediately switch to Help mode, and the prompt changes to yellow (in the terminal):

help?>

Now you can type the name of something (function names should be written without parentheses):

   help?> quit
   search: quit QuickSort PartialQuickSort quantile quantile!
   
     quit()
    
     Quit the program indicating that the processes completed successfully. This function calls exit(0) (see exit).

   julia>

Notice that the help system has tried to find all the words that match the letters you typed, and shows you what it found.

If you want to search the documentation, you can use apropos and a search string:

julia> apropos("determinant")
Base.LinAlg.logabsdet
Base.LinAlg.det
Base.LinAlg.logdet

You'll see a list of functions whose names or descriptions contain the string.

julia> apropos("Pearson")
Base.cor
help?> cor
search: cor Core xcorr VecOrMat factor Vector vecnorm factorize factorial Factorization DenseVecOrMat @vectorize_2arg

 cor(x)
 
 Return the number one.
 
 cor(X[, vardim=1])
 
 Compute the Pearson correlation matrix of the matrix X along the dimension vardim.
 
 cor(x, y)
 
 Compute the Pearson correlation between the vectors x and y.
 
 cor(X, Y[, vardim=1])
 
 Compute the Pearson correlation between the vectors or matrices X and Y along the dimension
 vardim.

Shell mode[edit]

If you type a semicolon

julia> ;

you immediately switch to shell mode:

shell>

(And the prompt changes to red). In shell mode you can type any shell (ie non-Julia) command and see the result:

shell> ls
file.txt   executable.exe   directory file2.txt

julia>

then the prompt switches back to julia>, so you have to type a semicolon every time you want to give a shell command. The commands available within this mode are the ones used by your system's shell.

Orientation[edit]

Here are some other useful interactive functions and macros available at the REPL-prompt:

  • whos() – prints information about the current global symbols
julia> whos()
                         Base  20691 KB     Module : Base
                       Compat     39 KB     Module : Compat
                         Core   2735 KB     Module : Core
               DataStructures    215 KB     Module : DataStructures
                       IJulia    133 KB     Module : IJulia
               IPythonDisplay     18 KB     Module : IPythonDisplay 
                         JSON    111 KB     Module : JSON
                         Main  24321 KB     Module : Main
                       Nettle    147 KB     Module : Nettle
                          ZMQ     62 KB     Module : ZMQ
  • @which – tells you which method will be called for a function and particular arguments:
julia> @which sin(3)
sin(x::Real) in Base.Math at math.jl:418
  • versioninfo() – gets Julia version and platform information:
julia> versioninfo()

Julia Version 0.6.0-pre.alpha.0
Commit 29bc2ac (2017-02-28 13:15 UTC)
Platform Info:
  OS: macOS (x86_64-apple-darwin13.4.0)
  CPU: Intel(R) Core(TM) i5-2500S CPU @ 2.70GHz
  WORD_SIZE: 64
  BLAS: libopenblas (USE64BITINT DYNAMIC_ARCH NO_AFFINITY Sandybridge)
  LAPACK: libopenblas64_
  LIBM: libopenlibm
  LLVM: libLLVM-3.9.1 (ORCJIT, sandybridge)

There's also a quick way to find out the version:

julia> VERSION
v"0.6.0-pre.alpha.0"
  • edit("pathname") – edits the file pathname
  • @edit rand() – edits the definition of the built-in function rand() in your default editor
  • less("filename-in-current-directory") – displays a file
  • clipboard("stuff") – copies "stuff" to the system clipboard
  • clipboard() – pastes the contents of the keyboard into the current REPL line
  • dump(x) – displays information about a Julia object x on the screen
  • names(x) – gets an array of the names exported by the module x
  • fieldnames(x) – gets an array of the data fields that belong to the symbol x
  • workspace() – replaces the top-level module (Main) with a new one, with a clean workspace

The <TAB> key: autocompletion[edit]

The TAB key is usually able to complete – or suggest a completion for – something whose name you start typing. For example, if I type w and then press the TAB key (press twice when there are multiple options), all the functions that are currently available beginning with 'w' are listed:

julia> w <TAB>
wait       warn        which       whos        widen       workers     write       writedlm
walkdir    watch_file  while       widemul     withenv     workspace   writecsv

This works both for Julia entities and in shell mode. Here, for example, is how I can navigate to a directory from inside Julia:

shell> cd ~
/Users/me

shell> cd Doc <TAB>
shell> cd Documents/

shell> ls
...

Remember you can get help about functions using ? and typing in its full name (or using TAB-completion).

TAB-completion also works for Unicode-symbols, e.g. type \alp and press TAB to get \alpha and then press TAB again to get α.

History[edit]

You can look back through a record of your previous commands using the Up and Down arrow keys (and you can quit and restart without erasing that history). So you don't have to type a long multi-line expression again, because you can just recall it from history. And if you've typed loads of expressions, you can search through them, both back and forwards, by pressing Ctrl-R and Ctrl-S.

Scope and performance[edit]

One warning about the REPL. The REPL operates at the global scope level of Julia. Usually, when writing longer code, you would put your code inside a function, and organise functions into modules and packages. Julia's compiler works much more effectively when your code is organized into functions, and your code will run much faster as a result. There are also some things that you can't do at the top level – such as specify types for the values of variables.

Changing the prompt[edit]

You can change the prompt from julia> to something else, such as >. The easiest way to do this is to check out the Julia package OhMyREPL.jl on GitHub which lets you customize the REPL's appearance and behaviour.

If you just want to set the prompt every time you start a Julia session, you can add this to your '.juliarc.jl start-up file.

function myrepl(repl)
    repl.interface = REPL.setup_interface(repl)
    repl.interface.modes[1].prompt = "julia-$(VERSION.major).$(VERSION.minor)> "
    return
end

atreplinit(myrepl)

This sets the current REPL prompt to show the Julia version number that you're using.

Julia and mathematics[edit]

You can use Julia as a powerful calculator, using the REPL. It's good practice, too. (This is a tradition in introductions to interactive programming languages, and it's a good way to meet the language.)

Typing long numbers[edit]

Half the world uses a comma (,) to divide long numbers into groups of three, the other half uses a period (.). (And the rest of us use scientific notation...) In Julia you can use an underscore (_) to separate groups:

julia> 1_000_000 - 2_015
997985

although you won't see one in the response.

To use scientific notation, just type "e" and remember to avoid spaces:

julia> planck_length = 1.61619997e-34

How fast is my computer in gigaflops?

julia> peakflops() / 1e9
48.778354495441356
 
julia> peakflops() / 1e9
54.20509453559899

(Notice how the second time is faster!)

Operators as functions[edit]

julia> 2 + 2
4

julia> 2 + 3 + 4
9

An equivalent form for adding numbers is:

julia> +(2, 2)
4

The operators that you usually use between values are ordinary Julia functions, and can be used in the same way as other functions. Similarly:

julia> 2 + 3 + 4
9

can be written as

julia> +(2, 3, 4)
9

and

julia> 2 * 3 * 4
24

can be written as

julia> *(2,3,4)
24

Some maths constants are provided:

julia> pi
π = 3.1415926535897...

julia> golden
φ = 1.6180339887498...

julia> e
e = 2.7182818284590...

All the usual operators are available:

julia> 2 + 3 - 4 * 5 / 6 % 7
1.6666666666666665

Notice the precedence of the operators. In this case it's:

((2 + 3) - ((4 * 5) / 6) % 7)

Multiplication is usually written *, but this can be omitted when multiplying a variable by a number literal:

julia> x = 2
2

julia> 2x + 1
5

This makes equations much easier to write.

You'll sometimes need parentheses to control the evaluation order:

julia> (1 + sqrt(5)) / 2
1.618033988749895

Some others to watch out for include:

  • ^ power
  • % remainder/modulo

To make rational numbers, use two slashes (//):

julia> x = 666//999
2//3

There's also reverse division "\", so that x/y = y\x.

The standard arithmetic operators also have special updating versions, which you can use to update variables quickly:

  • +=
  • -=
  • *=
  • /=
  • \=
  • %=
  • ^=

For example, after defining a variable x:

julia> x = 5
5

you can add 2 to it like this:

julia> x += 2
7

multiply it by 100 like this:

julia> x *= 100
700

and reduce it to its modulus 11 value:

julia> x %= 11
7

There are element-wise operators which work on arrays. This means that you can multiply two arrays element by element:

julia> [2,4] .* [10, 20]
2-element Array{Int64,1}:
 20
 80

Arrays are fundamental to Julia, and so have their own chapter in this book.

If you divide two integers using /, the answer is always a floating-point number. If you're familiar with Python version 2, this is a difference to be aware of:

Python 2.7.10 (default, Aug 22 2015, 20:33:39)
[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.0.59.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 3/2
1

Here Python returns an integer result, whereas Julia returns a float:

julia> 3/2
1.5

julia> 8/2
4.0

(Python 3 also returns a float here.)

Julia offers an integer division operator ÷ (type \div TAB, or use the function version div(). This should be used when you want an integer result rather than the floating-point returned by /.

julia> 3 ÷ 2
1

julia> div(3, 2)
1

Integer overflow[edit]

If you think your calculations are going to burst out of the 64-bit restriction, choose Big Integers by applying the big function to store the operands as big numbers:

julia> 2^64 # oops 0

julia> big(2)^64 # better 18446744073709551616

julia> 2^big(64) # equally better 18446744073709551616

To get the fastest execution speeds for your Julia programs, you should be aware of how your data and variables can be stored without introducing 'type instability'.

Number bases[edit]

These handy utility functions might come in useful when using the REPL as a calculator.

The bits() function shows the literal binary representation of a number, as stored:

julia> bits(20.0)
"0100000000110100000000000000000000000000000000000000000000000000"

julia> bits(20)
"0000000000000000000000000000000000000000000000000000000000010100"

Notice that the floating point 'version' is stored differently.

To go from a binary string back to decimal, you can use parse(), which accepts a target type and number base:

julia> parse(Int, "0000011", 2)
3 
julia> parse(Int, "defaced", 16)
233811181

For working in bases other than the default 10, try hex() and oct():

julia> hex(65535)
"ffff"
julia> oct(64)
"100"

The function base(base, number) converts a number to a string in the given base:

julia> base(16,255)
"ff"

Whereas digits(number, base) returns an array of the digits of number in the given base:

julia> digits(255, 16)
2-element Array{Int64,1}:
 15
 15

Here's a good place to mention num2hex() and hex2num(), functions used to convert between a hexadecimal string and the binary representation of the equivalent floating-point number.

Variables[edit]

In this expression:

julia> x = 3

x is a variable, a named storage location for a data object. In Julia, variables can be named pretty much how you like, although don't start variable names with numbers or punctuation. You can use Unicode characters, if you want.

To assign a value, use a single equals sign.

julia> a = 1
1

julia> b = 2
2

julia> c = 3
3

To test equality, you should use the == operator or isequal() function.

You should avoid using names that Julia has already taken. For example, words like mean and median are used for imported function names, but Julia doesn't stop you redefining them – although you might see a message warning you of possible problems ahead.

julia> mean = 0
Warning: imported binding for mean overwritten in module Main
0

If you make this mistake, you can restore the original meaning like this, if mean is a function in the Base module:

julia> mean = Base.mean
mean (generic function with 6 methods)

In Julia, you can also assign multiple variables at the same time:

julia> a, b = 5, 3
(5,3)

Notice that the return value of this expression is a parenthesis-bounded comma-separated ordered list of elements: tuple for short.

julia> a
5

julia> b
3
Multiplying numbers and variables[edit]

It's worth repeating that you can preface a variable name with a number to multiply them, without having to use an asterisk (*). For example:

julia> x = 42
42

julia> 2x
84

julia> .5x
21.0

julia> 2pi
6.283185307179586

You can use \cdot (see the next section) to provide the "dot" multiplication operator:

julia> 4 ⋅ 5
20

(\times produces the cross-product operator.)

Special characters[edit]

The Julia REPL provides easy access to special characters, such as Greek alphabetic characters, subscripts, and special maths symbols. If you type a backslash, you can then type a string (usually the equivalent LATEX string) to insert the corresponding character. For example, if you type:

julia> \sqrt <TAB>

Julia replaces the \sqrt with a square root symbol:

julia> 

Some other examples:

\Gamma Γ
\mercury
\degree °
\cdot
\in

There's a full list in the Julia source code, which you can find at julia/latex_symbols.jl. As a general principle, in Julia you're encouraged to look at the source code, so there are useful built-in functions for looking at Julia source files. For example, on macOS the Julia source files are stored in /Applications/Julia-0.6.app/Contents/Resources/julia/share/julia/base. So:

julia> less("/Applications/Julia-0.6.app/Contents/Resources/julia/share/julia/base/latex_symbols.jl") # macOS only

runs the file through a pager (ie the less command in Unix). If you're brave, try using edit() rather than less(). This launches an editor and opens the file.

It's also possible to use emoji and other Unicode characters in the REPL.

For emoji, type the Emoji character name, between colons, after the backslash, then press <TAB>:

julia> \:id: <TAB>

which changes to:

julia> 🆔

You can find a list at https://docs.julialang.org/en/latest/manual/unicode-input.html#Unicode-Input-1.

Entering Unicode symbols that aren't in this list is possible but more OS-dependent: on macOS you 'hold down' the Ctrl/Alt key while typing the Unicode hex digits; on Windows it's Ctrl+Shift+u followed by the hex digits.)

julia> ✎ = 3
3

julia> 
3

Maths functions[edit]

Because Julia is particularly suited for scientific and technical computing, there are many mathematical functions that you can use immediately, and you don't have to import them or use prefixes – they're already available in the Base module.

The trigonometry functions expect values in radians:

julia> sin(pi / 2)
1.0

but there are degree-based versions too: sind(90) finds the sine of 90 degrees. Use deg2rad() and rad2deg() to convert between degrees and radians.

There are also lots of log functions:

julia> log(12)
2.4849066497880004

and the accurate hypot() function:

julia> hypot(3,4)
5.0

The norm() function returns the "p"-norm of a vector or the operator norm of a matrix. Here's divrem():

julia> divrem(13,3) # returns the division and the remainder
(4,1)

There are dozens of others. Special functions include erf(), dawson(), eta(), zeta(), a full collection of Bessel functions, and so on.

There's a system-wide variable called ans that remembers the most recent result, so that you can use it in the next expression.

julia> 1 * 2 * 3 * 4 * 5
120

julia> ans/10
12.0

Statistics functions[edit]

In the statistics realm, all the basic statistics functions are built-in. For example:

julia> mean([1, 2, 3, 4, 5, 6, 7, 8])
4.5

You'll also find:

cor() find the Pearson correlation
cov() find the covariance
mean() find the mean of an array
std() find the standard deviation of an array
var() find the variance
median() find the median

among many others, and there are many more available in the StatsBase.jl package.

Exercise[edit]

Guess, then find out using the help system, what mod2pi() and isapprox() do.

Descriptions of all the functions provided as standard with Julia are described here: [1]

Random numbers[edit]

rand() – gets one random Float64 between 0 and 1

julia> rand()
0.11258244478647295

rand(2, 2) – an array of Float64s with dimensions 2, 2

rand(type, 2, 2) – an array of values of this type with dims 2, 2

rand(range, dims) – array of numbers in a range (including both ends) with specified dimensions:

julia> rand(0:10, 6)
6-element Array{Int64,1}:
 6
 7
 9
 6
 3
 10

(See the Arrays chapter for more about range objects.)

The rand() function can generate a true or false value if you tell it to, by passing the Bool keyword:

julia> rand(Bool)
false

or a bunch of trues and falses:

julia> rand(Bool, 20)
20-element Array{Bool,1}:
 false
 true
 false
 false
 false
 true
 true
 false
 false
 false
 false
 false
 false
 false
 true
 true
 false
 true
 true
 false

(Check out randperm() and shuffle() for more randomness.)

Random numbers in a distribution[edit]

randn() gives you one random number in a normal distribution with mean 0 and standard deviation 1. randn(n) gives you n such numbers:

julia> randn()
0.8060073309441075

julia> randn(10),
([1.31598,1.55126,-1.14605,-0.562148,0.69725,0.468769,-1.58275,0.238471,2.72857,1.11561],)

(the comma after randn(10) is just intended for line visualization)

If you've installed the Plots plotting package, you can plot this:

julia> using Plots; gr()
julia> histogram(randn(10000), nbins=100)

histogram plot created in Julia using Plots

Seeding the random number generator[edit]

Before you use random numbers, you can seed the random number generator with a specific value. This ensures that subsequent random numbers will follow the same sequence, if they start from the same seed. You can seed the generator using the srand() or MersenneTwister() functions.

julia> srand(10);

julia> rand(0:10, 6)
6-element Array{Int64,1}:
 6
 5
 9
 1
 1
 0
julia> rand(0:10, 6)
6-element Array{Int64,1}:
 10
 3
 6
 8
 0
 1

After restarting Julia, the same seed guarantees the same random numbers:


julia> exit()
$ julia
               _
   _       _ _(_)_     |  A fresh approach to technical computing
  (_)     | (_) (_)    |  Documentation: http://docs.julialang.org
   _ _   _| |_  __ _   |  Type "?help" for help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version xxxxxxxxx
 _/ |\__'_|_|_|\__'_|  |  Commit  xxxxxxxxx
|__/                   |  x86_64-apple-darwin13.4.0
 
julia> srand(10);

julia> rand(0:10, 6)
6-element Array{Int64,1}:
 6
 5
 9
 1
 1
 0

julia> rand(0:10, 6)
6-element Array{Int64,1}:
 10
 3
 6
 8
 0
 1

Arrays and Tuples[edit]

« Introducing Julia
Arrays and tuples
»
The REPL Types

Storage: Arrays and Tuples[edit]

In Julia, groups of related items are usually stored in arrays, tuples, or dictionaries. Arrays can be used for storing vectors and matrices. This section concentrates on arrays and tuples; for more on dictionaries, see Dictionaries and Sets.

Arrays[edit]

An array is an ordered collection of elements. It's often indicated with square brackets and comma-separated items. You can create arrays that are full or empty, and arrays that hold values of different types or restricted to values of a specific type.

In Julia, arrays are used for lists, vectors, tables, and matrices.

A one-dimensional array acts as a vector or list. A 2-D array can be used as a table or matrix. And 3-D and more-D arrays are similarly thought of as multi-dimensional matrices.

Creating arrays[edit]

Creating simple arrays[edit]

Here's how to create a simple one-dimensional array:

julia> a = [1, 2, 3, 4, 5]
5-element Array{Int64,1}:
1
2
3
4
5

Julia informs you ("5-element Array{Int64,1}") that you've created a 1-dimensional array with 5 elements, each of which is a 64-bit integer, and bound the variable a to it. Notice that intelligence is applied to the process: if one of the elements looks like a floating-point number, for example, you'll get an array of Float64s:

julia> a1 = [1, 2, 3.0, 4, 5]
5-element Array{Float64,1}:
1.0
2.0
3.0
4.0
5.0

Similarly for strings:

julia> s = ["this", "is", "an", "array", "of", "strings"]
6-element Array{String,1}:
"this"
"is"
"an"
"array"
"of"
"strings"

returns an array of strings, and:

julia> trigfuns = [sin, cos, tan]
3-element Array{Function,1}:
sin
cos
tan

returns an array of Julia functions.

You can specify the type and the dimensions of an array with the Array() function (notice that upper-case "A"), putting the type in curly braces:

julia> array = Array{Int64}(5)
5-element Array{Int64,1}:
  4516561312
  4516561376
  4516561440
 408021893238
  4294967362
julia> array3 = Array{Int64}(2,2,2)
2x2x2 Array{Int64,3}:
[:, :, 1] =
 0  0
 0  0
          
[:, :, 2] =
 0  0
 0  0

Sometimes you'll see the new array filled with random-looking numbers:

julia> array = Array{Int64}(5)
5-element Array{Int64,1}:
 2322280078457970723
 3256719550777995314
 3904966330563964977
  744304806301413434
                   0

julia> array[1]
2322280078457970723

which is a reminder that you've created an uninitialized array but haven't filled it with any sensible information.

It's possible to create arrays with elements of different types:

julia> [1, "2", 3.0, sin, pi]
5-element Array{Any, 1}:
 1
  "2"
 3.0
  sin
π = 3.1415926535897...

Here, the array has five elements, but they're an odd mixture: numbers, strings, functions, constants — so Julia creates an array of type Any:

julia> typeof(ans)
Array{Any,1}

To create an array of a specific type, you can use the type definition and square brackets:

julia> Int64[1, 2, 3, 4]
4-element Array{Int64,1}:
1
2
3
4

If you think you can fool Julia by sneaking in a value of the wrong type while declaring a typed array, you'll be caught out:

julia> Int64[1, 2, 3, 4, 5, 6, 7, 8,  9, 10.1]
ERROR: InexactError()

You can create empty arrays this way too:

julia> b = Int64[]
0-element Array{Int64,1}
julia> b = String[]
0-element Array{String,1}
julia> b = Float64[]
0-element Array{Float64,1}

Creating 2D arrays and matrices[edit]

If you leave out the commas when defining an array, you can create 2D arrays quickly. Here's a single row, multi-column array:

julia> [1 2 3 4]
1x4 Array{Int64,2}:
1  2  3  4

Notice the ,2} in the first row of the response.

You can use a semicolon to add another row:

julia> [1 2 3 4 ; 5 6 7 8]
2x4 Array{Int64,2}:
1  2  3  4
5  6  7  8

Row and column vectors[edit]

Compare these two: [1,2,3,4,5] and [1 2 3 4 5].

With the commas, this array could be called a "column vector", with 5 rows and 1 column:

julia> [1, 2, 3, 4, 5]
5-element Array{Int64,1}:
1
2
3
4
5

But with the spaces, this array could be called a "row vector", with 1 row and 5 columns:

julia> [1 2 3 4 5]
1x5 Array{Int64,2}:
1  2  3  4  5

- notice the {Int64,2} here, which tells you that this is a 2D array of Int64s (with 1 row and 5 columns). In both cases, they're standard Julia arrays.

Arrays created like this can be used as matrices:

julia> [1 2 3; 4 5 6]
2x3 Array{Int64,2}:
1  2  3
4  5  6

And of course you can create arrays/matrices with 3 or more dimensions.

There are a number of functions which let you create and fill an array in one go. See Creating and filling an array.

Notice how Julia distinguishes between Array{Float64,1} and Array{Float64,2}:

julia> x = rand(5)
5-element Array{Float64,1}:
 0.183247
 0.480208
 0.623144
 0.342792
 0.0400695
julia> x = rand(5, 1)
5x1 Array{Float64,2}:
 0.553224
 0.0061835
 0.762059
 0.643796
 0.655135

Julia provides the Vector and Matrix constructor functions, but these are simply aliases for one and two dimensional arrays:

julia> Vector(5)
5-element Array{Any,1}:
 #undef
 #undef
 #undef
 #undef
 #undef
julia> Matrix(5, 5)
5x5 Array{Any,2}:
 #undef  #undef  #undef  #undef  #undef
 #undef  #undef  #undef  #undef  #undef
 #undef  #undef  #undef  #undef  #undef
 #undef  #undef  #undef  #undef  #undef
 #undef  #undef  #undef  #undef  #undef

Creating arrays using range objects[edit]

In Julia, the colon (:) has a number of uses. One use is to define ranges and sequences of numbers. You can create a range object by typing it directly:

julia> 1:20
1:20

Or you can use the range() function to make a range object:

julia> range(1, 10)
1:10

It may not look very useful in that form, but it provides the raw material for any job in Julia that needs a range or sequence of numbers.

You can use it in a loop expression:

julia> for n in 1:10 print(n) end
12345678910

Or you can use collect() to build an array consisting of those numbers:

julia> collect(1:10)
10-element Array{Int64,1}:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10

You don't have to start and finish on an integer either:

julia> collect(3.5:9.5)
7-element Array{Float64,1}:
3.5
4.5
5.5
6.5
7.5
8.5
9.5

There's also a three-piece version of a range object, start:step:stop, which lets you specify a step size other than 1. For example, this builds an array with elements that go from 0 to 100 in steps of 10:

julia> collect(0:10:100)
11-element Array{Int64,1}:
  0
 10
 20
 30
 40
 50
 60
 70
 80
 90
100

To go down instead of up, you have to use a negative step value:

julia> collect(4:-1:1)
4-element Array{Int64,1}:
 4
 3
 2
 1

Instead of using collect() to create an array from the range, you could use the ellipsis (...) operator (three periods) after the last element:

julia> [1:6...]
6-element Array{Int64,1}:
 1
 2
 3
 4
 5
 6

(The ... ellipsis is sometimes called the splat operator. It represents a sequence of arguments.)

However, collect() is faster and the recommended method of converting ranges to arrays. But you can use range objects in many situations in Julia, and you don't always need to expand them into arrays.

More range objects[edit]

Another useful function is linspace(), which constructs a range object that goes from a start value to an end value taking a specific number of steps. You don't have to calculate the increment, because Julia calculates the step size for you. For example, to go from 1 to 100 in exactly 12 steps:

julia> linspace(1, 100, 12)
1.0:9.0:100.0

You can use this range object to build an array:

julia> collect(linspace(1, 100, 12))
12-element Array{Float64,1}:
  1.0
 10.0
 19.0
 28.0
 37.0
 46.0
 55.0
 64.0
 73.0
 82.0
 91.0
100.0

Notice that it provided you with a Float64 array, rather than an Integer array, even though the values could have been integers.

There's a logarithmic version called logspace(), here going from 101 to 102 in five steps:

julia> logspace(1, 2, 5)
5-element Array{Float64,1}:
 10.0
 17.7828
 31.6228
 56.2341
100.0

Use step() on a range object to find out what the step size is:

julia> step(linspace(1, 100, 12))
9.0

You can also use range() if you know the start and step, but not the end, and you know how many elements you want:

julia> collect(range(1, 3, 20))
20-element Array{Int64,1}:
  1
  4
  7
 10
 13
 16
 19
 22
 25
 28
 31
 34
 37
 40
 43
 46
 49
 52
 55
 58

Collecting up the values in a range[edit]

As you've seen, if you're not using your range object in a for loop, you can use collect() to obtain all the values from a range object directly:

julia> collect(0:5:100)
21-element Array{Int64,1}:
  0
  5
 10
 15
 20
 25
 30
 35
 40
 45
 50
 55
 60
 65
 70
 75
 80
 85
 90
 95
100

It's worth remembering that you often won't need to convert ranges to arrays before working on them — you can usually iterate over things directly. For example, you don't have to write this:

julia> for i in collect(1:6)
           println(i)
       end
1
2
3
4
5
6

because it works just as well without the collect():

julia> for i in 1:6
           println(i)
       end
1
2
3
4
5
6

Using comprehensions and generators to create arrays[edit]

A useful way to create arrays where each element can be produced using a small computation is to use comprehensions (described in Comprehensions).

For example, to create an array of 5 numbers:

julia> [n^2 for n in 1:5]
5-element Array{Int64,1}:
 1
 4
 9
16
25

With two iterators, you can easily create a 2D array or matrix:

julia> [r * c for r in 1:5, c in 1:5]
5x5 Array{Int64,2}:
1   2   3   4   5
2   4   6   8  10
3   6   9  12  15
4   8  12  16  20
5  10  15  20  25

You can add an if test at the end to filter (keep) values that pass a test:

julia> [i^2 for i=1:10  if i != 5]
9-element Array{Int64,1}:
   1
   4
   9
  16
  36
  49
  64
  81
 100

Generator expressions are similar, and can be used in a similar way:

julia> collect(x^2 for x in 1:10)
10-element Array{Int64,1}:
   1
   4
   9
  16
  25
  36
  49
  64
  81
 100
julia> collect(x^2 for x in 1:10 if x != 1)
9-element Array{Int64,1}:
   4
   9
  16
  25
  36
  49
  64
  81
 100

The advantage of generator expressions is that they generate values when needed, rather than build an array to hold them first.

Creating and filling an array[edit]

There are a number of functions that let you create arrays with specific contents. These can be very useful when you're using 2D arrays as matrices:

- zeros(m, n) creates an array/matrix of zeros

julia> zeros(2,3)
2x3 Array{Float64,2}:
0.0  0.0  0.0
0.0  0.0  0.0

- ones(m, n) creates a array/matrix of ones

julia> ones(2,3)
2x3 Array{Float64,2}:
 1.0  1.0  1.0
 1.0  1.0  1.0

- eye(n) creates an identity matrix I (called "eye", to avoid confusion with imaginary numbers):

julia> eye(3, 3)
3x3 Array{Float64,2}:
 1.0  0.0  0.0
 0.0  1.0  0.0
 0.0  0.0  1.0

eye(3) does the same job as eye(3, 3).

- diagm(x) uses the values in an array to create a matrix with that diagonal. For example, to put the first six integers into the diagonal of a 6 by 6 matrix:

julia> diagm(1:6)
6x6 Array{Int64,2}:
 1  0  0  0  0  0
 0  2  0  0  0  0
 0  0  3  0  0  0
 0  0  0  4  0  0
 0  0  0  0  5  0
 0  0  0  0  0  6

(You can specify a different diagonal by providing a second argument.)

- rand(m, n) creates an m-row by n-column matrix full of random numbers:

julia> rand(2, 3)
2×3 Array{Float64,2}:
 0.488552   0.657078   0.895564
 0.0190633  0.0120305  0.772106

- rand(range, m, n) creates a matrix full of numbers in the supplied range:

julia> rand(1:6, 3, 3)
3x3 Array{Int64,2}:
 4  4  1
 3  2  3
 6  3  3

- randn(m, n) creates an m-row by n-column matrix full of normally-distributed random numbers with mean 0 and standard deviation 1.

As well as the zeros(), ones() functions, there are trues(), falses(), fill(), and fill!() functions as well.

The trues() and falses() functions fill arrays with the Boolean values true or false:

julia> trues(3, 4)
3x4 BitArray{2}:
true  true  true  true
true  true  true  true
true  true  true  true

You can use fill() to create an array with a specific value, i.e. an array of repeating duplicates:

julia> fill(42, 9),
9-element Array{Int64,1}:
42
42
42
42
42
42
42
42
42

julia> fill("hi", 2, 2)
2x2 Array{String,2}:
"hi"  "hi"
"hi"  "hi"

With fill!(), the exclamation mark (!) or "bang" is to warn you that you're about to change the contents of an existing array (a useful indication that's adopted throughout Julia).

julia> a = zeros(10)
10-element Array{Float64,1}:
 0.0
 0.0
 0.0
 0.0
 0.0
 0.0
 0.0
 0.0
 0.0
 0.0

julia> fill!(a, 42)
10-element Array{Float64,1}:
 42.0
 42.0
 42.0
 42.0
 42.0
 42.0
 42.0
 42.0
 42.0
 42.0 

Let's change an array of falses to trues:

julia> trueArray = falses(3,3)
3x3 BitArray{2}:
false  false  false
false  false  false
false  false  false
julia> fill!(trueArray, true)
3x3 BitArray{2}:
true  true  true
true  true  true
true  true  true
julia> trueArray
3x3 BitArray{2}:
true  true  true
true  true  true
true  true  true

You can use linspace() and logspace() functions to create vector-like arrays, followed by reshape() to change them into 2D arrays:

julia> a = reshape(linspace(0, 100, 30), 10, 3)
10×3 Base.ReshapedArray{Float64, 2, StepRangeLen{Float64, Base.TwicePrecision{Float64}, 
Base.TwicePrecision{Float64}}, Tuple{}}:
 0.0      34.4828   68.9655
 3.44828  37.931    72.4138
 6.89655  41.3793   75.8621
10.3448   44.8276   79.3103
13.7931   48.2759   82.7586
17.2414   51.7241   86.2069
20.6897   55.1724   89.6552
24.1379   58.6207   93.1034
27.5862   62.069    96.5517
31.0345   65.5172  100.0   

The result is a 10 by 3 array featuring evenly-spaced numbers between 0 and 100.

Repeating elements to fill arrays[edit]

Two useful functions for creating arrays by repeating smaller ones are repmat() and repeat().

The syntax for the first is repmat(A, n, m), the source array is repeated by n times in the first dimension (rows), and m times in the second (columns).

You don't have to supply the second dimension, just supply how many rows you want:

julia> repmat([1, 2, 3], 2)
6-element Array{Int64,1}:
 1
 2
 3
 1
 2
 3

julia> repmat([1 2 3], 2)
2x3 Array{Int64,2}:
 1  2  3
 1  2  3

The second option specifies the extra columns:

julia> repmat([1, 2, 3], 2, 3)
6x3 Array{Int64,2}:
 1  1  1
 2  2  2
 3  3  3
 1  1  1
 2  2  2
 3  3  3

julia> repmat([1 2 3], 2, 3)
2x9 Array{Int64,2}:
 1  2  3  1  2  3  1  2  3
 1  2  3  1  2  3  1  2  3

The repeat() function lets you create arrays by duplicating rows and columns of a source array. The inner and outer options determine whether rows and/or columns are repeated. For example, inner = [2, 3] makes an array with two copies of each row and three copies of each column:

julia> repeat([1, 2], inner = [2, 3])
4x3 Array{Int64,2}:
 1  1  1
 1  1  1
 2  2  2
 2  2  2 

By contrast, here's outer = [2,3]:

julia> repeat([1, 2], outer = [2, 3])
4x3 Array{Int64,2}:
 1  1  1
 2  2  2
 1  1  1
 2  2  2

Array constructor[edit]

The Array() function builds arrays of a specific type for you:

julia> Array{Int64}(6)
6-element Array{Int64,1}:
 4547140064
 4545812016
 4545807696
 4545807696
 4545807888
          0

This is uninitialized; the odd-looking numbers are simply the old contents of the memory before it was assigned to hold the new array.

julia> Array{Int64}(2, 2)
2x2 Array{Int64,2}:
140237854510080  140237854535680
140237850941552  140237950729104

The Array constructor can construct an array of arrays:

julia> Array[1:3, 4:6]
 2-element Array{Array,1}:
 [1,2,3]
 [4,5,6]

With the reshape() function, you could of course just create a simple array and then change its shape:

julia> reshape([1, 2, 3, 4, 5, 6, 7, 8], 2, 4)
2x4 Array{Int64,2}:
1  3  5  7
2  4  6  8

The same techniques can be used to create 3D arrays. Here's a 3D array of strings:

julia> Array{String}(2, 3, 4)
2x3x4 Array{String,3}:
[:, :, 1] =
#undef  #undef  #undef
#undef  #undef  #undef
[:, :, 2] =
#undef  #undef  #undef
#undef  #undef  #undef
[:, :, 3] =
#undef  #undef  #undef
#undef  #undef  #undef
[:, :, 4] =
#undef  #undef  #undef
#undef  #undef  #undef

Each element is set to 'undefined' — #undef.

Arrays of arrays[edit]

It's easy to create an array of arrays. Sometimes you want to specify the original contents:

   julia> a = Array[[1, 2], [3,4]]
   2-element Array{Array,1}:
    [1, 2]
    [3, 4]
   julia> push!(a, rand(1:100, 5))
   3-element Array{Array,1}:
    [1, 2]
    [3, 4]
    [4, 71, 82, 60, 48]
   julia> push!(a, rand(1:100, 5))
   4-element Array{Array,1}:
    [1,2]
    [3,4]
    [4, 71, 82, 60, 48]
    [4, 22, 52, 5, 14]

or you might want to create them empty:

   julia> a = Array{Int}[]
   0-element Array{Array{Int64,N} where N,1}
   julia> push!(a, [1, 2, 3])
   1-element Array{Array{Int64,N} where N,1}:
   [1, 2, 3]
   julia> push!(a, [4, 5, 6])
   2-element Array{Array{Int64,N} where N,1}:
   [1, 2, 3]
   [4, 5, 6]

You can use Vector as an alias for Array:

   julia> a = Vector{Int}[[1, 2], [3, 4]]
   2-element Array{Array{Int64,1},1}:
    [1, 2]
    [3, 4]
   julia> push!(a,  rand(1:100, 5))
   3-element Array{Array{Int64, 1},1}:
    [1, 2]
    [3, 4]
    [12, 65, 53, 1, 82]
   
   julia> a[2]
   2-element Array{Int64,1}:
    3
    4
   
   julia> a[2][1]
    3

Copying arrays[edit]

If you have an existing array and want to create another array having the same dimensions, you can use the similar() function:

julia> a = collect(1:10);
julia> b = similar(a)
10-element Array{Int64,1}:
0
0
0
0
0
0
0
0
0
0

Notice that the array dimensions are copied, but the values aren't. You can, though, change the type and dimensions anyway, so they don't have to be that similar:

julia> c = similar(b, String, (2, 2))
2x2 Array{String,2}:
#undef  #undef
#undef  #undef

And in any case there's a copy() function.

Matrix operations: using arrays as matrices[edit]

In Julia, a 2-D array can be used as a matrix. All the functions available for working on arrays can be used (if the dimensions and contents permit) as matrices.

A quick way of typing a matrix is to separate the elements using spaces (to make rows) and to use semicolons to separate the rows. So:

julia> [1 0 ; 0 1]
  2x2 Array{Int64,2}:
  1  0
  0  1

You could also do this:

julia> id  = reshape([1, 2, 3, 4], 2, 2)
2×2 Array{Int64,2}:
 1  3
 2  4

which takes a standard array and reshapes it to run in two rows and two columns. Notice that the matrix is filled column by column.

If you don't use commas or semicolons:

 julia> [1 2 3 4]

you'll create a single row array/matrix:

1x4 Array{Int64,2}:
1  2  3  4

In each case, notice the 2 in the braces following the type value. This indicates a 2-dimensional array.

You can create an array of arrays by sticking two arrays next to each other, like this:

julia> [[1, 2, 3], [4, 5, 6]]
 2-element Array{Array{Int64,1},1}:
 [1, 2, 3]
 [4, 5, 6]

When you omit the comma, you're placing columns next to each and you'll get this:

julia> [[1, 2, 3] [4, 5, 6]]
3×2 Array{Int64,2}:
 1  4
 2  5
 3  6

Accessing the contents[edit]

To access the elements of an array or matrix, follow the name of the array by the element number in square brackets. Here's a 1D array:

julia> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

Here's the fifth element:

julia> a[5]
50

The first element is index number 1. Julia is one of the languages that starts indexing elements in lists and arrays starting at 1, rather than 0. (And thus it's in the elite company of Matlab, Mathematica, Fortran, Lua, and Smalltalk, while most of the other programming languages are firmly in the opposite camp of 0-based indexers.)

The last element is referred to as end (not -1, as in some other languages):

julia> a[end]
100

Similarly, you can access the second to last element with

julia> a[end-1]
90

(with similar syntax for the third to last element and so on).

You can provide a bunch of index numbers, enclosed in a pair of brackets:

julia> a[[3,6,2]]
3-element Array{Int64,1}:
 30
 60
 20
julia> a[2:2:end]
5-element Array{Int64,1}:
  20
  40
  60
  80
 100

Here's a 2D array, with the rows separated by semicolons:

julia> a2 = [1 2 3; 4 5 6; 7 8 9]
3x3 Array{Int64,2}:
1  2  3
4  5  6
7  8  9

julia> a2[1]
1

If you just ask for one element of a 2D array, you'll receive it as if the array is unwound column by column, i.e. down first, then across. In this case you'll get 4, not 2:

julia> a2[2]
4

Asking for row then column works as you expect:

julia> a2[1, 2]
2

which is row 1, column 2. Here's row 1, column 3:

julia> a2[1, 3]
3

but don't get the row/column indices wrong:

julia> a2[1, 4]
ERROR: BoundsError: attempt to access 3×3 Array{Int64,2} at index [1, 4]
Stacktrace:
 [1] getindex(::Array{Int64,2}, ::Int64, ::Int64) at ./array.jl:498

By the way, there's an alternative way of obtaining elements from arrays: the getindex() function:

julia> getindex(a2, 1, 3)
3
 
julia> getindex(a2, 1, 4)
ERROR: BoundsError: attempt to access 3×3 Array{Int64,2} at index [1, 4]
Stacktrace:
 [1] getindex(::Array{Int64,2}, ::Int64, ::Int64) at ./array.jl:498

Use the colon to indicate every row or column. For example, here's "every row, second column":

julia> a2[:, 2]
3-element Array{Int64,1}:
2
5
8

and here's "second row, every column":

julia> a2[2, :]
3-element Array{Int64,1}:
 4
 5
 6

Elementwise and vectorized operations[edit]

Many Julia functions and operators are designed specifically to work with arrays. This means that you don't always have to work through each element of an array and process it individually.

A simple example is the use of the basic arithmetic operators. These can be used directly on an array if the other argument is a single value:

julia> a = collect(1:10);
julia> a * 2
10-element Array{Int64,1}:
  2
  4
  6
  8
 10
 12
 14
 16
 18
 20

and every element is multiplied by 2.

julia> a / 100
10-element Array{Float64,1}:
0.01
0.02
0.03
0.04
0.05
0.06
0.07
0.08
0.09
0.1

and every element is divided by 100.

These operations are described as operating elementwise. Many functions can operate on an array in this elementwise or vectorised way. For example, functions like sin() and cos() can be used elementwise on an array:

julia> sin(0:pi/12:2pi)
25-element Array{Float64,1}:
 0.0
 0.258819
 0.5
 0.707107
 0.866025
 0.965926
 1.0
 0.965926
 0.866025
... 
-0.5
-0.258819
-2.44929e-16

Some operators have their own elementwise version, which starts with a dot. These versions are the same as their non-dotted versions, and work on the arrays element by element. For example, the counterpart of the multiply function (*) has an elementwise version (.*). This lets you multiply arrays or ranges together element by element:

julia> n1 = 1:6;
julia> n2 = 100:100:600;
julia> n1 .* n2
6-element Array{Int64,1}:
 100
 400
 900
1600
2500
3600

and the first element of the result is what you get by multiplying the first elements of the two arrays.

As well as the arithmetic operators, some of the comparison operators also have elementwise versions. For example, instead of using == in a loop to compare two arrays, use .==. Here are two arrays of ten numbers, one sequential, the other disordered, and an elementwise comparison to see how many elements of array b happened to end up in the same place as array a:

julia> a = 1:10; b=rand(1:10, 10); a .== b
10-element BitArray{1}:
 true
false
 true
false
false
false
false
false
false
false

Dot syntax for vectorizing functions[edit]

Functions can be applied in vectorized/elementwise way to arrays with the dot syntax. Follow the function name with a dot/period before the opening parenthesis, and supply an array or range as an argument. For example, here's a simple function which multiplies two numbers together:

julia> f(a, b) = a * b
f (generic function with 1 method)

julia> f(2, 3)
6 

To apply this function to an array, use the dot syntax:

julia> f.([1, 4, 2, 8, 7], 10)
5-element Array{Int64,1}:
 10
 40
 20
 80
 70
julia> f.(100, 1:10)
10-element Array{Int64,1}:
  100
  200
  300
  400
  500
  600
  700
  800
  900
 1000

min() and max()[edit]

Watch out for max() and min(). You might think that max() can be used on an array, like this, to find the largest element:

julia> r = rand(0:10, 10)
10-element Array{Int64,1}:
 3
 8
 4
 3
 2
 5
 7
 3
10
10 

but no…

julia> max(r)
LoadError: MethodError: no method matching max(::Array{Int64,1})
...

The max function returns the largest of its arguments. To find the largest element in an array, you can use the related function maximum():

julia> maximum(r)
10

You can use max() on two or more arrays to carry out an elementwise examination, returning another array containing the maximum values:

julia> r = rand(0:10, 10); s = rand(0:10, 10); t = rand(0:10,10);
julia> max(r, s, t)
10-element Array{Int64,1}:
 8
 9
 7
 5
 8
 9
 6
10
 9
 9

min() and minimum() behave in a similar way.

You can test each value of an array and change it in a single operation, using element-wise operators. Here's an array of random integers from 0 to 10:

julia> a = rand(0:10,10, 10)
10x10 Array{Int64,2}:
10   5   3   4  7   9  5   8  10   2
 6  10   3   4  6   1  2   2   5  10
 7   0   3   4  1  10  7   7   0   2
 4   9   5   2  4   2  1   6   1   9
 0   0   6   4  1   4  8  10   1   4
10   4   0   5  1   0  4   4   9   2
 9   4  10   9  6   9  4   5   1   1
 1   9  10  10  1   9  3   2   3  10
 4   6   3   2  7   7  5   4   6   8
 3   8   0   7  1   0  1   9   7   5

Now you can test each value for being equal to 0, then set only those elements to 11, like this:

julia> a[a .== 0] = 11;
julia> a
10x10 Array{Int64,2}:
10   5   3   4  7   9  5   8  10   2
 6  10   3   4  6   1  2   2   5  10
 7  11   3   4  1  10  7   7  11   2
 4   9   5   2  4   2  1   6   1   9
11  11   6   4  1   4  8  10   1   4
10   4  11   5  1  11  4   4   9   2
 9   4  10   9  6   9  4   5   1   1
 1   9  10  10  1   9  3   2   3  10
 4   6   3   2  7   7  5   4   6   8
 3   8  11   7  1  11  1   9   7   5

If you're doing arithmetic on 2D matrices, you might want to read more about matrix arithmetic: Matrix arithmetic

Rows and Columns[edit]

With a 2D array, you use brackets, colons, and commas to extract individual rows and columns or ranges of rows and columns.

With this table:

julia> table = [r * c for r in 1:5, c in 1:5]
5x5 Array{Int64,2}:
1   2   3   4   5
2   4   6   8  10
3   6   9  12  15
4   8  12  16  20
5  10  15  20  25

you can find a single row using the following (notice the comma):

julia> table[1,:]
1x5 Array{Int64,2}:
5-element Array{Int64,1}:
 1
 2
 3
 4
 5

and you can get a range of rows with a range followed by a comma and a colon:

julia> table[2:3,:]
2x5 Array{Int64,2}:
2  4  6   8  10
3  6  9  12  15

For columns, start with a colon followed by a comma:

julia> table[:, 2]
5-element Array{Int64,1}:
 2
 4
 6
 8
10

On its own, the colon accesses the entire array:

julia> table[:]
25-element Array{Int64,1}:
 1
 2
 3
 4
 5
 2
 4
 6
 8
10
 3
 6
 9
12
15
 4
 8
12
16
20
 5
10
15
20
25 

To extract a range of columns:

julia> table[:, 2:3]
5x2 Array{Int64,2}:
 2   3
 4   6
 6   9
 8  12
10  15

Finding items in arrays[edit]

If you want to know whether an array contains an item, use the in() function, which can be called in two ways:

julia> a = 1:10
julia> 3 in a
true

Or phrased as a function call:

julia> in(3, a)
true

There's a set of functions starting with find — such as find(), findfirst(), and findnext() — that you can use to get the index or indices of array cells that match a specific value, or pass a test. Each of these has two or more more forms.

Here's an array of small primes (including 1, because it looks better):

julia> smallprimes = [1, 2, 3, 5, 7, 11, 13, 17, 19, 23];

To find the first occurrence of a number, and obtain its index, you can use the following method of the findfirst() function:

julia> findfirst(smallprimes, 13)
7

so the first occurrence of 13 in the array is in the seventh cell:

julia> smallprimes[7]
13

There's another method of findfirst() that lets you use a function that tests each element; it returns the index of the first one that passes the test. The two arguments are the function and the array.

julia> findfirst(x -> x == 13, smallprimes)
7

The find() function returns an array of indices, pointing to every element where the function returns true when applied to the value:

julia> find(isinteger, smallprimes)
10-element Array{Int64,1}:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
julia> find(iseven, smallprimes)
1-element Array{Int64,1}:
2

Remember that these are arrays of index numbers, not the actual cell values. The indices can be used to extract the corresponding values using the standard square bracket syntax:

julia> smallprimes[find(isodd, smallprimes)]
9-element Array{Int64,1}:
 1
 3
 5
 7
11
13
17
19
23

The findfirst() version returns a single number — the index of the matching cell:

julia> findfirst(iseven, smallprimes)
2
julia> smallprimes[findfirst(iseven, smallprimes)]
2

The findnext() function is very similar to the find() and findfirst() functions, but accepts an additional number that tells the functions to start the search from somewhere in the middle of the array, rather than from the beginning. For example, if findfirst(smallprimes,13) finds the index of the first occurrence of the number 13 in the array, we can continue the search from there by using this value in findnext():

julia> findnext(isodd, smallprimes, 1 + findfirst(smallprimes,13))
8
julia> smallprimes[ans]
17

The findin(A, B) function returns the indices of the elements in array A where the elements of array B can be found:

julia> findin(smallprimes, [11, 5])
2-element Array{Int64,1}:
4
6
julia> smallprimes[4]
5
julia> smallprimes[6]
11

Notice the order in which the indices are returned.

Finding out about an array[edit]

With our 2D array:

julia> a2 = [1 2 3; 4 5 6; 7 8 9]
3x3 Array{Int64,2}:
1  2  3
4  5  6
7  8  9

we can find out more about it using the following functions:

  • ndims()
  • size()
  • length()
  • countnz()

ndims() returns the number of dimensions, i.e. 1 for a vector, 2 for a table, and so on:

julia> ndims(a2)
2

size() returns the row and column count of the array, in the form of a tuple:

julia> size(a2)
(3,3)

length() tells you how many elements the array contains:

julia> length(a2)
9

countnz() tells you how many non-zero elements there are:

julia> countnz(a2)
9

For finding the inverse, determinant and other aspects of an array/matrix, see Manipulating matrices.

There are two related functions for converting between row/column numbers and array index numbers, ind2sub() and sub2ind(). Row 1, Column 1 is easy, of course - it's element 1, But Row 3, Column 7 is harder to work out. ind2sub() takes an array, or the dimensions of an array, and a element index. For example, ind2sub(a2, 5) returns the row and column for the fifth element of array a2, in the form of a tuple:

julia> ind2sub(a2, 5)
(2,2)

With a loop, you could look at the row/column numbers of every element in an array:

julia> for i in 1:length(a2)
         println(ind2sub(a2, i), " ", a2[i])
       end
(1,1)    1
(2,1)   4
(3,1)    7
(1,2)    2
(2,2)    5
(3,2)    8
(1,3)    3
(2,3)    6
(3,3)    9

To go in the reverse direction, use sub2ind() and specify the dimensions of the array.

julia> a2[sub2ind((3, 3), 2, 1)]
4

finds you the element at row 2, column 1, for an array with dimensions (3,3).

Comparing arrays[edit]

union() builds a new array that's the union or combination of two or more arrays. The operation removes duplicates, and the result contains a single version of each element:

julia> odds = collect(1:2:10)
5-element Array{Int64,1}:
1
3
5
7
9
julia> evens = collect(2:2:10)
5-element Array{Int64,1}:
 2
 4
 6
 8
10
julia> union(odds, evens)
10-element Array{Int64,1}:
 1
 3
 5
 7
 9
 2
 4
 6
 8
10

Notice that the ordering of the elements reflects the original order:

julia> union(1:5, 1:10, 5:-1:-5)
16-element Array{Int64,1}:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 0
-1
-2
-3
-4
-5

intersect() returns a new array that's the intersection of two or more arrays. The result contains one occurrence of each element, but only if it occurs in every array:

julia>  intersect(1:10, 5:15)
5:10
julia> intersect(5:20, 1:15, 3:12)
5:12

setdiff() finds the difference between two arrays, i.e. the elements that are in the first array but not the second:

julia> setdiff(1:15, 5:20)
4-element Array{Int64,1}:
1
2
3
4
julia> setdiff(5:20, 1:15)
5-element Array{Int64,1}:
16
17
18
19
20

Filtering[edit]

There's a set of related functions that let you work on an array's elements.

filter() finds and keeps elements if they pass a test. Here, we're using the isodd() function (passing it as a named function without parentheses, rather than a function call with parentheses) to filter (keep) everything in the array that's odd.

julia> filter(isodd, 1:10)
5-element Array{Int64,1}:
 1
 3
 5
 7
 9

Like many Julia functions, there's a version which changes the array. So filter() returns a copy of the original, but filter!() changes the array.

The count() function is like filter(), but just counts the number of elements that satisfy the condition:

julia> count(isodd, 1:100)
50

Also, the any() function just tells you whether any of the elements satisfy the condition:

julia> any(isodd, 1:100)
true

and the all() function tells you if all of the elements satisfy the condition. Here, all() checks to see whether filter() did the job properly.

julia> all(isodd, filter(isodd, 1:100))
true

Random element of an array[edit]

To choose a random element from an array:

julia> a = collect(1:100);
julia> a[rand(1:end)]
14

Other functions[edit]

Because arrays are fundamental to Julia, there are dozens of array-handling functions that can't be described here. But here are a few selections:

Find the extreme values of an array:

julia> a = rand(100:110, 10)

10-element Array{Int64,1}:
 109
 102
 104
 108
 103
 110
 100
 108
 101
 101
julia> extrema(a)
(100,110)

findmax() finds the maximum element and returns it and its index in a tuple:

julia> findmax(a)
(110,6)

Functions such as sum(), prod(), mean(), middle(), do what you would expect:

julia> sum(a)
1046
julia> prod(a)
8302379321573251072
julia> mean(a)
104.6
julia> middle(a)
105.0

sum(), mean(), and prod() also let you supply functions: the function is applied to each element and the results are then summed/mean-ed/prod-ded:

julia> sum(sqrt, 1:10)  # the sum of the square roots of the first 10 integers
22.4682781862041

julia> mean(sqrt, 1:10) # the mean of the square roots of the first 10 integers 
2.24682781862041

There are functions in the Combinatorics.jl package that let you find combinations and permutations of arrays. combinations() finds all the possible combinations of elements in an array: you can specify how many elements in each combination:

julia> Pkg.add("Combinatorics") # (do this just once)
julia> using Combinatorics
julia> collect(combinations(a, 3))
120-element Array{Array{Int64,1},1}:
 [109,102,104]
 [109,102,108]
 [109,102,103]
 [109,102,110]
 [109,102,100]
 [109,102,108]
 [109,102,101]
 [109,102,101]
 [109,104,108]
 [109,104,103]
 [109,104,110]
 [109,104,100]
 [109,104,108]
 ⋮            
 [103,108,101]
 [103,101,101]
 [110,100,108]
 [110,100,101]
 [110,100,101]
 [110,108,101]
 [110,108,101]
 [110,101,101]
 [100,108,101]
 [100,108,101]
 [100,101,101]
 [108,101,101]

and permutations() generates all permutations. There are a lot — in practice you probably don't need to use collect() to collect the items into an array:

julia> collect(permutations(a))
3628800-element Array{Array{Int64,1},1}:
 [109,102,104,108,103,110,100,108,101,101]
 [109,102,104,108,103,110,100,108,101,101]
 [109,102,104,108,103,110,100,101,108,101]
 [109,102,104,108,103,110,100,101,101,108]
 [109,102,104,108,103,110,100,101,108,101]
 [109,102,104,108,103,110,100,101,101,108]
 [109,102,104,108,103,110,108,100,101,101]
 [109,102,104,108,103,110,108,100,101,101]
 [109,102,104,108,103,110,108,101,100,101]
 [109,102,104,108,103,110,108,101,101,100]
 [109,102,104,108,103,110,108,101,100,101]
 [109,102,104,108,103,110,108,101,101,100]
 [109,102,104,108,103,110,101,100,108,101]
 ⋮                                        
 [101,101,108,100,110,103,104,109,102,108]
 [101,101,108,100,110,103,104,109,108,102]
 [101,101,108,100,110,103,104,102,109,108]
 [101,101,108,100,110,103,104,102,108,109]
 [101,101,108,100,110,103,104,108,109,102]
 [101,101,108,100,110,103,104,108,102,109]
 [101,101,108,100,110,103,108,109,102,104]
 [101,101,108,100,110,103,108,109,104,102]
 [101,101,108,100,110,103,108,102,109,104]
 [101,101,108,100,110,103,108,102,104,109]
 [101,101,108,100,110,103,108,104,109,102]
 [101,101,108,100,110,103,108,104,102,109]

Modifying array contents: adding and removing elements[edit]

To add an item at the end of an array, use push!():

julia>  a = collect(1:10); push!(a, 20)
11-element Array{Int64,1}:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
20

As usual, the exclamation mark reminds you that this function changes the array.

To add an item at the front, use the oddly-named unshift!():

julia> unshift!(a, 0)
12-element Array{Int64,1}:
 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
20

To insert an element into an array at a given index, use the splice!() function. For example, here's a list of numbers with an obvious omission:

julia> a = [1, 2, 3, 5, 6, 7, 8, 9]
8-element Array{Int64,1}:
1
2
3
5
6
7
8
9

Use splice!() to insert a sequence at a specific index. Julia returns the values that were replaced. The array grows larger to accommodate the new elements, and elements after the inserted sequence are pushed down. Let's insert the numbers 4:6 at index position 4, currently occupied by the number 5:

julia> splice!(a, 4:5, 4:6)
2-element Array{Int64,1}:
 5
 6

You'll be tempted to check that the new values were inserted correctly:

julia> a
9-element Array{Int64,1}:
 1
 2
 3
 4
 5
 6
 7
 8
 9

Removing elements[edit]

If you don't supply a replacement, splice!() can remove elements and move the rest of them along.

julia> a = collect(1:10); 
julia> splice!(a,5);
julia> a
9-element Array{Int64,1}:
 1
 2
 3
 4
 6
 7
 8
 9
10

To remove the last item:

julia> pop!(a)
10

and the first:

julia> shift!(a)
1

More aggressive modification of arrays (and similar data structures) can be made with functions such as deleteat!() and splice!(). You can find out the indices of elements in various ways. Once you know the indices, you can use deleteat!() to delete an element, given its index number:

julia> a
7-element Array{Int64,1}:
 2
 3
 4
 6
 7
 8
 9
julia> findfirst(a, 6)
4
julia> deleteat!(a, findfirst(a, 6))
6-element Array{Int64,1}:
 2
 3
 4
 7
 8
 9

deleteat!() accepts an iterator to specify the indices, so you can do this:

julia> deleteat!(a, 2:6)
1-element Array{Int64,1}:
 2

Remember that you can always remove a group of elements using a filter: see Filtering.

Other functions[edit]

If you want to do something to an array, there's probably a function to do it, and sometimes with an exclamation mark to remind you of the potential consequences. Here are a few more of these array-modifying functions:

  • resize!()
  • append!()
  • prepend!()
  • empty!(a)
  • rotr90(a) to rotate an array 90 degrees clockwise:
julia> rotr90([1 2 3 ; 4 5 6])
3x2 Array{Int64,2}:
4  1
5  2
6  3
  • circshift(a) to moves the elements around in a circle by a number of steps:
julia> circshift(1:6, 1)
6-element Array{Int64,1}:
 6
 1
 2
 3
 4
 5

This function can also do circular shifts on 2D arrays too. For example, here's a table:

julia> table = collect(r*c for r in 1:5, c in 1:5)
5×5 Array{Int64,2}:
 1   2   3   4   5
 2   4   6   8  10
 3   6   9  12  15
 4   8  12  16  20
 5  10  15  20  25

By supplying a tuple you can move rows and columns. For example: moving the columns by 0 and the rows by 1 moves the first dimension by 0 and the second by 1. The first dimension is downwards, the second rightwards:

julia> circshift(table, (0, 1))
5×5 Array{Int64,2}:
  5  1   2   3   4
 10  2   4   6   8
 15  3   6   9  12
 20  4   8  12  16
 25  5  10  15  20

Setting the contents of arrays[edit]

To set the contents of an array, specify the indices on the left-hand side of an assignment expression:

julia> a = collect(1:10);

julia> a[9]= -9
-9

To check that the array has really changed:

julia> print(a)
[1,2,3,4,5,6,7,8,-9,10]

You can set a bunch of elements at the same time:

julia> a[3:6] = -5
-5
julia> print(a)
[1,2,-5,-5,-5,-5,7,8,-9,10]

And you can set a sequence of elements to a suitable sequence of values:

julia> a[3:9] = collect(9:-1:3)
7-element Array{Int64,1}:
9
8
7
6
5
4
3

Notice here that, although Julia shows the 7 element slice as the return value, in fact the whole array has been modified:

julia> a
10-element Array{Int64,1}:
 1
 2
 9
 8
 7
 6
 5
 4
 3
10

You can set ranges to a single value in one operation:

julia> a[1:5] = 0
0
julia> a
10-element Array{Int64,1}:
  0
  0
  0
  0
  0
  6
  7
  8
  9
 10
julia> a[1:10] = -1
-1
julia> print(a)
[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1]

As an alternative to the square bracket notation, there's a function call version that does the same job of setting array contents, setindex!():

julia> setindex!(a, 1:10, 10:-1:1)
10-element Array{Int64,1}:
10
 9
 8
 7
 6
 5
 4
 3
 2
 1

You can refer to the entire contents of an array using the colon separator without start and end index numbers, i.e. [:]. For example, after creating the array a:

julia> a = collect(1:10);

we can refer to the contents of this array a using a[:]:

julia> b = a[:]
10-element Array{Int64,1}:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10

julia> b[3:6]
4-element Array{Int64,1}:
3
4
5
6

Passing arrays to functions[edit]

A function can't modify a variable passed to it as an argument, but it can change the contents of a container passed to it.

Consider the following function, that changes its argument to 5:

julia> function set_to_5(x)
        x = 5
        end
set_to_5 (generic function with 1 method)
julia> x = 3
3
julia> set_to_5(x)
5
julia> x
3

Although the x inside the function is changed, the x outside the function isn't. Variable names in functions are local to the function.

But, you can modify the contents of a container, such as an array. The next function uses the [:] syntax to access the contents of the container x, rather than change the value of the variable x:

julia> function fill_with_5(x)
         x[:] = 5
       end
fill_with_5 (generic function with 1 method)
julia> x = collect(1:10)
10-element Array{Int64,1}:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
julia> fill_with_5(x)
5
julia> x
10-element Array{Int64,1}:
5
5
5
5
5
5
5
5
5
5

If, instead of accessing the container variable's contents, you try to change the variable itself, it won't work. For example, the following function definition creates an array of 5s in temp and then attempts to change the argument x to be temp.

julia> function fail_to_fill_with_5(x)
         temp = similar(x)
         for i in 1:length(x)
            temp[i] = 5
         end
         x = temp
       end
fail_to_fill_with_5 (generic function with 1 method)
julia> x = collect(1:10)
10-element Array{Int64,1}:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
julia> fail_to_fill_with_5(x)
10-element Array{Int64,1}:
5
5
5
5
5
5
5
5
5
5

It returns an array which looks promising, but:

julia> x
10-element Array{Int64,1}:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10

You can change elements of the array, but you can't change the variable so that it points to a different array. In other words, your function isn't allowed to change the binding between the argument and the array that was passed to it.

Julia's way of handling function arguments is described as “pass-by-sharing”. An array isn't copied when you pass it to a function (that would be very inefficient for large arrays).

Matrix arithmetic[edit]

For matrix-on-matrix arithmetic action, you can:

- add (+) and subtract (-):

julia> A = reshape(1:12, 3, 4)
  3x4 Array{Int64,2}:
  1  4  7  10
  2  5  8  11
  3  6  9  12

julia> B = ones(3,4)
 3x4 Array{Float64,2}:
 1.0  1.0  1.0  1.0
 1.0  1.0  1.0  1.0
 1.0  1.0  1.0  1.0

julia> A + B
 3x4 Array{Float64,2}:
 2.0  5.0   8.0  11.0
 3.0  6.0   9.0  12.0
 4.0  7.0  10.0  13.0


julia> A - B
 3x4 Array{Float64,2}:
  0.0  3.0  6.0   9.0
  1.0  4.0  7.0  10.0
  2.0  5.0  8.0  11.0


- multiply (*), assuming the dimensions are compatible, so m1 * m2 is possible if last(size(m1)) == first(size(m2)). Note the difference between matrix multiplication and elementwise matrix multiplication. Here's a matrix A:

julia> A = [1 2 ; 3 4]
  2x2 Array{Int64,2}:
  1  2
  3  4

and here's matrix B:

julia> B = [10 11 ; 12 13]
  2x2 Array{Int64,2}:
  10  11
  12  13

The .* operator multiplies them elementwise:

julia> A .* B
  2x2 Array{Int64,2}:
  10  22
  36  52

Compare this with matrix multiplication, A * B:

julia> A * B
 2x2 Array{Int64,2}:
  34  37
  78  85

which is:

julia> [1 * 10 + 2 * 12        1 * 11 + 2 * 13  ;      3 * 10 + 4 * 12     3 * 11 + 4 * 13]
 2x2 Array{Int64,2}:
  34  37
  78  85

- division of two matrices. You can use the backslash (\) for left division:

julia> A = rand(1:9, 3, 3)
 3x3 Array{Int64,2}:
  5  4  3
  8  7  7
  9  3  7
 julia> B = rand(1:9, 3, 3)
 3x3 Array{Int64,2}:
  6  5  5
  6  7  5
  7  2  7
 julia> A \ B
 3x3 Array{Float64,2}:
2.01961    0.411765   1.84314
0.254902   1.35294   -0.0392157
  -1.70588   -0.823529  -1.35294

and the forward slash (/) right or slash division:

julia> A / B
3x3 Array{Float64,2}:
4.0       -2.0       -1.0     
0.285714   0.714286   0.285714
5.07143   -3.07143   -0.428571

With a matrix and a scalar, you can add, subtract, multiply, and divide:

julia> A + 1
3x3 Array{Int64,2}:
  6  5  4
  9  8  8
 10  4  8
julia> [1 2 3 4 5] * 2
1x5 Array{Int64,2}:
 2  4  6  8  10
julia> A - 1
3x3 Array{Int64,2}:
 4  3  2
 7  6  6
 8  2  6
julia> A * 2
3x3 Array{Int64,2}:
 10   8   6
 16  14  14
 18   6  14
julia>  A / 2
3x3 Array{Float64,2}:
 2.5  2.0  1.5
 4.0  3.5  3.5
 4.5  1.5  3.5

and more besides:

julia> A // 2
3x4 Array{Rational{Int64},2}:
 1//2  2//1  7//2   5//1
 1//1  5//2  4//1  11//2
 3//2  3//1  9//2   6//1
julia> A .< 6
3x3 BitArray{2}:
  true   true   true
 false  false  false
 false   true  false

You can multiply matrix and a vector (the matrix-vector product), if the arrays have compatible shapes. Here's the matrix A:

julia> A = reshape(1:12, 3, 4)
  3x4 Array{Int64,2}:
   1  4  7  10
   2  5  8  11
   3  6  9  12

and here's a vector V:

julia> V = collect(1:4)
  4-element Array{Int64,1}:
   1
   2
   3
   4

The * operator multiplies them:

julia> A * V
  3-element Array{Int64,1}:
   70
   80
   90

The dot or inner product (aTb) can be found using the dot() function:


julia> dot([1:3...], [21:23...])
  134

julia> (1 * 21) + (2 * 22) +  (3 * 23)
134

The two arguments must have the same length. You can also use the dot operator, which you can obtain in the REPL by typing "\cdot" followed by a tab:

julia> [1:3] ⋅ [21:23]
134

Joining arrays and matrices[edit]

You can use hcat() and vcat() to join matrices together, if their dimensions permit.

hcat() keeps the first dimension and extends (joins) in the second, vcat() keeps the second dimension and extends the first.

Here are two 3 by 4 matrices:

julia> A = reshape(1:12, 3, 4)
3x4 Array{Int64,2}:
 1  4  7  10
 2  5  8  11
 3  6  9  12
julia> B = reshape(100:100:1200, 3, 4)
3x4 Array{Int64,2}:
 100  400  700  1000
 200  500  800  1100
 300  600  900  1200

hcat(A, B) makes an array that still has 3 rows, but extends/joins the columns:

julia> hcat(A, B)
3x8 Array{Int64,2}:
 1  4  7  10  100  400  700  1000
 2  5  8  11  200  500  800  1100
 3  6  9  12  300  600  900  1200

vcat(A, B) keeps the 4 columns, but extends to 6 rows:

julia> vcat(A, B)
6x4 Array{Int64,2}:
   1    4    7    10
   2    5    8    11
   3    6    9    12
 100  400  700  1000
 200  500  800  1100
 300  600  900  1200

You'll probably find the short cuts useful:

  • [A ; B ] is vcat(A, B)
  • [A B ] is hcat(A, B)

vec() flattens a matrix into a vector, turning it into a (or what some call a 'column') vector:

 julia> vec(ones(3, 4))
12-element Array{Float64,1}:
 1.0
 1.0
 1.0
 1.0
 1.0
 1.0
 1.0
 1.0
 1.0
 1.0
 1.0
 1.0

There's also an hvcat() function ([A B; C D;]) that does both.

You can use hcat() to convert an array to a matrix (using the hcat-splat):

julia> a = Array[[1, 2], [3, 4], [5, 6]]
3-element Array{Array{T,N},1}:
 [1, 2]
 [3, 4]
 [5, 6]

julia> hcat(a...)
2x3 Array{Int64,2}:
 1  3  5
 2  4  6

Julia arrays are 'column-major'. This means that you read down the columns:

 1  3
 2  4

whereas 'row-major' arrays are to be read across, like this:

 1  2
 3  4

Column-major order is used in Fortran, R, Matlab, GNU Octave, and by the BLAS and LAPACK engines (the "bread and butter of high-performance numerical computation"). Row-major order is used in C/C++, Mathematica, Pascal, Python, C#/CLI/.Net and others.

Growing or extending arrays[edit]

Often you want to create an array and then add more to it, or 'grow' it. You can do this with vcat() and hcat(), but be aware that both these operations create new temporary arrays and copy elements, so they don't always produce the fastest code. A better way is to use push!. This is an efficient operation that extends the array. You can reshape the array later:

julia> a = []
julia> for i = 1:80
    push!(a, i)
end

julia> a
80-element Array{Any,1}:
  1
  2
  3
  4
  5
  6
  7
  8
  9
  ⋮
 75
 76
 77
 78
 79
 80

reshape() lets you change the dimensions of an array. You can supply the dimensions or use a colon (:) to ask Julia to calculate valid dimensions:

julia> reshape(a, 10, :)
10x8 Array{Any,2}:
  1  11  21  31  41  51  61  71
  2  12  22  32  42  52  62  72
  3  13  23  33  43  53  63  73
  4  14  24  34  44  54  64  74
  5  15  25  35  45  55  65  75
  6  16  26  36  46  56  66  76
  7  17  27  37  47  57  67  77
  8  18  28  38  48  58  68  78
  9  19  29  39  49  59  69  79
 10  20  30  40  50  60  70  80

reshape(a, (10, div(length(a), 10))) would have the same effect.

push!() doesn't let you push new rows to a 2D array or matrix. The best way to do the job is to work on a 1D array, as above, adding more elements at the end, and then use reshape() to convert it to two dimensions. If necessary, use transpose() to flip the matrix.

Manipulating matrices[edit]

To transpose an array or matrix, there's an equivalent ' operator for the transpose() function, to swap rows and columns:

julia> M = reshape(1:12, 3, 4)

3×4 Base.ReshapedArray{Int64,2,UnitRange{Int64},Tuple{}}:

 1  4  7  10
 2  5  8  11
 3  6  9  12
julia> transpose(M)
4x3 Array{Int64,2}:
  1   2   3
  4   5   6
  7   8   9
 10  11  12
julia> M'
4x3 Array{Int64,2}:
  1   2   3
  4   5   6
  7   8   9
 10  11  12

To find the determinant of a square matrix, use det():

julia> A = rand(2:10, 3, 3)
3x3 Array{Int64,2}:
 8  8   2
 6  9   6
 9  2  10
julia> det(A)
438.00000000000006

inv() finds the inverse of a square matrix, if it has one. (If the determinant of the matrix is zero, it won't have an inverse.)

julia> inv(A)
3x3 Array{Float64,2}:
  0.178082   -0.173516   0.0684932
 -0.0136986   0.141553  -0.0821918
 -0.157534    0.127854   0.0547945

rank() finds the rank of the matrix, and nullspace() finds the basis for the nullspace.

julia> A
3x4 Array{Int64,2}:
 1  4  7  10
 2  5  8  11
 3  6  9  12
julia> rank(A)
2
julia> nullspace(A)
4x2 Array{Float64,2}:
 -0.475185  -0.272395
  0.430549   0.717376
  0.564458  -0.617566
 -0.519821   0.172585

trace() sums the diagonal of a square matrix:

julia> s = reshape(1:9, 3, 3)
3x3 Array{Int64,2}:
 1  4  7
 2  5  8
 3  6  9
julia> trace(s)
15

Applying functions to matrices[edit]

There are a number of functions that can be applied to a matrix:

- sum() adds every element:

julia> A = reshape(1:9, 3, 3)
3×3 Base.ReshapedArray{Int64,2,UnitRange{Int64},Tuple{}}:
 1  4  7
 2  5  8
 3  6  9
julia> sum(A)
45

You can specify a dimension if you want to sum just columns or rows. So to sum columns, specify dimension 1:

julia> sum(A, 1)
1x3 Array{Int64,2}:
 6  15  24

To sum rows, specify dimension 2:

julia> sum(A, 2)
3x1 Array{Int64,2}:
 12
 15
 18

- mean() finds the mean of the values in the matrix:

julia> mean(A)
5.0

As with sum(), you can specify a dimension, so that you can find the mean of columns (use dimension 1) or rows (use dimension 2):

julia> mean(A, 1)
1x3 Array{Float64,2}:
 2.0  5.0  8.0
julia> mean(A, 2)
3x1 Array{Float64,2}:
 4.0
 5.0
 6.0

- the min(A, B) and max(A, B) functions compare two (or more) arrays element by element, returning a new array with the largest (or smallest) values from each:

julia> A = rand(-1:2:1, 3, 3)
3x3 Array{Int64,2}:
 -1  -1  -1
 -1   1   1
  1  -1   1
julia> B = rand(-2:4:2, 3, 3)
3x3 Array{Int64,2}:
 2   2  2
 2  -2  2
 2   2  2
julia> max(A, B)
3x3 Array{Int64,2}:
 2  2  2
 2  1  2
 2  2  2
julia> min(A,B)
3x3 Array{Int64,2}:
 -1  -1  -1
 -1  -2   1
  1  -1   1

prod() multiplies a matrix's elements together:

julia> A = reshape(collect(BigInt(1):25), 5, 5)
5×5 Array{BigInt,2}:
 1   6  11  16  21
 2   7  12  17  22
 3   8  13  18  23
 4   9  14  19  24
 5  10  15  20  25

julia> prod(A)
15511210043330985984000000

You can specify a dimension if you want to multiply just columns or rows. To multiply the elements of columns together, specify dimension 1; for rows, use dimension 2:

julia> prod(A, 1)
1x5 Array{Int64,2}:
 120  30240  360360  1860480  6375600
julia> prod(A, 2)
5x1 Array{Int64,2}:
  22176
  62832
 129168
 229824
 375000

Matrix norms[edit]

Vector norms[edit]

The Euclidean norm, , is found by norm(x):

julia> X = [2, 4, -5]
3-element Array{Int64,1}:
  2
  4
 -5
 
julia> norm(X) # Euclidean norm
6.708203932499369

julia> norm(x, 1) # 1-norm of the vector, the sum of element magnitudes
11.0

If X is a 'row' vector:

julia> X = [2 4 -5]
1x3 Array{Int64,2}:
 2  4  -5

julia> norm(X)
6.708203932499369

julia> norm(X, 1)
5.0

The Euclidean distance between vectors and , given by , is found by norm(x - y):

julia> norm([1 2 3] - [2 4 6])
3.741657386773941

julia> norm([1, 2, 3] - [2, 4, 6])
3.741657386773941

The angle between two vectors and is :

acos(dot(a,b)/(norm(a)*norm(b)))

Matrix norms[edit]

Here's the 1-norm of a matrix (the maximum absolute column sum):

julia> B = [5 -4 2 ; -1 2 3; -2 1 0]
3x3 Array{Int64,2}:
  5  -4  2
 -1   2  3
 -2   1  0
julia> norm(B, 1)
8.0

And here's the infinity norm (the maximum absolute row sum):

julia> norm(B, Inf)
11.0

The Euclidean norm() is the default:

julia> norm([2 3 ; 4 6]), sqrt(2^2 + 3^2 + 4^2 + 6^2)
(8.062257748298547,8.06225774829855)

Scaling and rotating matrices[edit]

- scale(A, n) scales every element of the matrix by a scale factor n:

julia> A = reshape(1:12, 3, 4)
3x4 Array{Int64,2}:
1  4  7  10
2  5  8  11
3  6  9  12
julia> scale(A, 2)
3x4 Array{Int64,2}:
2   8  14  20
4  10  16  22
6  12  18  24

There are rotation and circular-shifting functions too:

julia> rot180(A)
3x4 Array{Int64,2}:
12  9  6  3
11  8  5  2
10  7  4  1
julia> circshift(A, (1, 1))
3×4 Array{Int64,2}:
 12  3  6  9
 10  1  4  7
 11  2  5  8

Here's flipdim()

julia> flipdim(A, 1)
3x4 Array{Int64,2}:
 3  6  9  12
 2  5  8  11
 1  4  7  10
julia> flipdim(A, 2)
3x4 Array{Int64,2}:
 10  7  4  1
 11  8  5  2
 12  9  6  3

squeeze() and reshape() can be used to change the dimensions of a matrix. For example, this is how you can use squeeze() to collapse a row vector (1 by 4) into a 4 by 1 array:

julia> a = [1 2 3 4]
1x4 Array{Int64,2}:
1  2  3  4
julia> ndims(a)
2
julia> b = squeeze(a, 1)
4-element Array{Int64,1}:
1
2
3
4
julia> ndims(b)
1

Sorting arrays[edit]

Julia has a flexible sort() function that returns a sorted copy of an array, and a companion sort!() version that changes the array so that it's sorted.

You can usually use sort() without options and obtain the results you'd hoped for:

julia> rp = randperm(10)
10-element Array{Int64,1}:
 6
 4
 7
 3
10
 5
 8
 1
 9
 2
julia> sort(rp)
10-element Array{Int64,1}:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10

You could even sort 2D arrays:

julia> a = reshape(rand(1:20, 20), 4, 5)
4x5 Array{Int64,2}:
19  13   4  10  10
 6  20  19  18  12
17   7  15  14   9
 1  16   8   7  13
julia> sort(a, 1) # columns, dimension 1
4x5 Array{Int64,2}:
 1   7   4   7   9
 6  13   8  10  10
17  16  15  14  12
19  20  19  18  13
julia> sort(a, 2) # rows, dimension 2
4x5 Array{Int64,2}:
4  10  10  13  19
6  12  18  19  20
7   9  14  15  17
1   7   8  13  16

although there are more powerful alternatives in sortrows() and sortcolumns() — see below for details.

The sortperm() function is similar to sort(), but it doesn't return a sorted copy of the collection. Instead it returns a list of indices that could be applied to the collection to produce a sorted version:

julia> r = rand(100:110, 10)
10-element Array{Int64,1}:
 103
 102
 110
 108
 108
 108
 104
 109
 106
 106
julia> sortperm(r)
10-element Array{Int64,1}:
  2
  1
  7
  9
 10
  4
  5
  6
  8
  3
julia> r[sortperm(r)]
10-element Array{Int64,1}:
 102
 103
 104
 106
 106
 108
 108
 108
 109
 110

Sort by and comparisons[edit]

If you need more than the default sort offers, use the by and lt keywords and provide your own functions for processing and comparing elements during the sort.

sort by[edit]

The by function processes each element before comparison and provides the 'key' for the sort. A typical example is the task of sorting a list of numbers in string form. Here's the list:

julia> r = ["1E10", "150", "25", "3", "1.5", "1E-10", "0.5", ".999"];

If you use the default sort, the numbers appear in the order in which the characters appear in Unicode/ASCII:

julia> sort(r)
8-element Array{ASCIIString,1}:
 ".999"
 "0.5"
 "1.5"
 "150"
 "1E-10"
 "1E10"
 "25"
 "3"

To sort the numbers by their value, pass the parse() function to by:

julia> sort(r, by = x -> parse(x))
8-element Array{ASCIIString,1}:
 "1E-10"
 "0.5"
 ".999"
 "1.5"
 "3"
 "25"
 "150"
 "1E10"

The strings are sorted 'by' their value. Notice that the by function you supply produces the sort key, but the original elements appear in the final result.

Anonymous functions can be useful when sorting arrays. Here's a 10 rows by 2 columns array of tuples:

julia> table = collect(enumerate(rand(1:100, 10)))
10-element Array{(Int64,Int64),1}:
(1,86) 
(2,25) 
(3,3)  
(4,97) 
(5,89) 
(6,58) 
(7,27) 
(8,93) 
(9,98) 
(10,12)

You can sort this by the second element of each tuple, not the first, by supplying an anonymous function to by that points to the second element of each, The anonymous function says, given an object x to sort, sort by the second element of x:

julia> sort(table, by = x -> x[2])
10-element Array{(Int64,Int64),1}:
(3,3)  
(10,12)
(2,25) 
(7,27) 
(6,58) 
(1,86) 
(5,89) 
(8,93) 
(4,97) 
(9,98)
Sorting by multiple columns[edit]

You can supply a tuple of "column" identifiers in the by function, if you want to sort by more than one column.

julia>  a = [[2, 2, 2, 1],
             [1, 1, 1, 8],
             [2, 1, 2, 2],
             [1, 2, 2, 5],
             [2, 1, 1, 4],
             [1, 1, 2, 7],
             [1, 2, 1, 6],
             [2, 2, 1, 3]] ;
julia> sort(a, by = col -> (col[1], col[2], col[3]))
8-element Array{Array{Int64,1},1}:
 [1,1,1,8]
 [1,1,2,7]
 [1,2,1,6]
 [1,2,2,5]
 [2,1,1,4]
 [2,1,2,2]
 [2,2,1,3]
 [2,2,2,1]

This sorts the array first by column 1, then by column 2, then by column 3.

Redefining 'less than'[edit]

By default, sorting uses the built-in isless() function when comparing elements. In a sorted array, the first element is less than the second.

You can change this behaviour by passing a different function to the lt keyword. This function should compare two elements and return true if they're sorted, i.e. if the first element is 'less than' the second, using some definition of 'less than'. The sorting process compares pairs of elements repeatedly until every element of the array is in the right place.

For example, suppose you want to sort an array of words according to the number of vowels in each word; i.e. the more vowels a word has, the earlier in the sorted results it occurs. For example, the word "orange" will be considered to be "less than" the word "lemon", because it has more vowels.

First we'll need a function that counts vowels:

vowelcount(string) = count(c -> (c in "aeiou"), lowercase(string))

Now you can pass an anonymous function to sort() that compares the vowel count of two elements using this function and then returns the element with a higher count in each case:

sentence = split("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.");
sort(sentence, lt = (x,y) -> vowelcount(x) > vowelcount(y))

The result is that the word with the most vowels appears first:

19-element Array{SubString{ASCIIString},1}:
"adipisicing"
"consectetur"
"eiusmod"    
"incididunt" 
"aliqua."    
"labore"     
"dolore"     
"Lorem"      
"ipsum"      
"dolor"      
"amet,"      
"elit,"      
"tempor"     
"magna"      
"sit"        
"sed"        
"do"         
"ut"         
"et"         

The sort() function also lets you specify a reverse sort - after the by and lt functions (if used) have done their work, a true value passed to rev reverses the result.

Sorting 2-D arrays[edit]

To sort arrays with two or more dimensions, including matrices, you should use sortrows() and sortcolumns().

Here's a 10 by 10 array.

julia> table = rand(1:4, 10, 10)
10x10 Array{Int64,2}:
1  1  4  1  3  1  3  2  2  2
2  3  2  3  3  4  3  4  1  4
1  1  1  2  2  3  3  2  1  3
2  2  3  1  4  2  1  1  3  2
3  4  2  1  4  4  1  1  3  3
4  1  2  1  4  3  4  1  4  1
1  1  3  2  4  3  4  3  3  1
2  2  3  3  1  4  3  4  3  3
3  3  2  4  3  2  2  2  3  4
4  4  3  1  2  3  3  3  3  3

By default, sortrows() just sorts the array by the first element in each row:

julia> sortrows(table)
10x10 Array{Int64,2}:
1  1  4  1  3  1  3  2  2  2
1  1  1  2  2  3  3  2  1  3
1  1  3  2  4  3  4  3  3  1
2  3  2  3  3  4  3  4  1  4
2  2  3  1  4  2  1  1  3  2
2  2  3  3  1  4  3  4  3  3
3  4  2  1  4  4  1  1  3  3
3  3  2  4  3  2  2  2  3  4
4  1  2  1  4  3  4  1  4  1
4  4  3  1  2  3  3  3  3  3

— notice that 1 1 4 comes before 1 1 1. But, as with sort(), sortrows() lets you specify the keys, and you can provide a tuple of column indicators, such as (x[1], x[2], x[3]), which sorts the array first by the element in column 1, then by the element in column 2, then by column 3.

julia> sortrows(table, by = x -> (x[1], x[2], x[3]))
10x10 Array{Int64,2}:
1  1  1  2  2  3  3  2  1  3
1  1  3  2  4  3  4  3  3  1
1  1  4  1  3  1  3  2  2  2
2  2  3  1  4  2  1  1  3  2
2  2  3  3  1  4  3  4  3  3
2  3  2  3  3  4  3  4  1  4
3  3  2  4  3  2  2  2  3  4
3  4  2  1  4  4  1  1  3  3
4  1  2  1  4  3  4  1  4  1
4  4  3  1  2  3  3  3  3  3

The sortcols() function does a similar job, sorting by column rather than row.

Tuples[edit]

A tuple is an ordered sequence of elements, like an array. A tuple is represented by parentheses and commas, rather than the square brackets used by arrays. Tuples are mostly good for small fixed-length collections — they're used everywhere in Julia, for example, as argument lists and for returning multiple values from functions.

The important difference between arrays and tuples is that tuples are immutable. Other than that, tuples work in much the same way as arrays, and many array functions can be used on tuples too:

julia> t = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
(1,2,3,4,5,6,7,8,9,10)
julia> t
(1,2,3,4,5,6,7,8,9,10)
julia> t[6:end]
(6,7,8,9,10)

You can have two-dimensional tuples:

julia> t = ((1, 2), (3, 4))
((1,2),(3,4))
julia> t[1]
(1,2)
julia> t[1][2]
2

But you can't change a tuple:

julia> t[1] = 0
LoadError: MethodError: no method matching set index!...

And, because you can't modify tuples, you can't use any of the functions like push!() that you use with arrays:

julia> a = [1,2,3];
julia> push!(a,4)
4-element Array{Int64,1}:
1
2
3
4
julia> t = (1,2,3);
julia> push!(t,4)
LoadError: MethodError: no method matching push!

Types[edit]

« Introducing Julia
Types
»
Arrays and tuples Controlling the flow

Types[edit]

This section, on types, and the next section, on functions and methods, should ideally be read at the same time, because the two topics are so closely connected.

Types of type[edit]

Data elements come in different shapes and sizes, which are called types.

Consider the following numeric values: a floating point number, a rational number, and an integer:

0.5  1//2  1

It's easy for us humans to add these numbers without much thought, but a computer won't be able to use a simple addition routine to add all three values, because the types are different. Code for adding rational numbers has to consider numerators and denominators, whereas code for adding integers won't. The computer will probably have to convert two of these values to be the same type as the third—typically the integer and the rational will first be converted to floating-point—then the three floating-point numbers will be added together.

This business of converting types obviously takes time. So, to write really fast code, you want to make sure that you don't make the computer waste time by continually converting values from one type to another. When Julia compiles your source code (which happens every time you evaluate a function for the first time), any type indications you've provided allow the compiler to produce more efficient executable code.

Another issue with converting types is that in some cases you'll be losing precision—converting a rational number to a floating-point number is likely to lose some precision.

The official word from the designers of Julia is that types are optional. In other words, if you don't want to worry about types (and if you don't mind your code running slower than it might), then you can ignore them. But you'll encounter them in error messages and the documentation, so you will eventually have to tackle them…

A compromise is to write your top-level code without worrying about types, but, when you want to speed up your code, find out the bottlenecks where your program spends the most time, and clean up the types in that area.

The type system[edit]

There's a lot to know about Julia's type system, so the official documentation is really the place to go. But here's a brief overview.

Type hierarchy[edit]

In Julia types are organized in an hierarchical way, and this hierarchy has a tree structure. (Note, however, hierarchical relationships between types are explicitly declared, rather than implied by compatible structure.) At the tree's root, we have a special type called Any, and all other types are connected to it directly or indirectly. Informally, we can say that the type Any has children. Its children are called Any's subtypes. Alternatively, we say that a child's supertype is Any.

An example of that is the type Number, a direct child of Any. To see what Number's supertype is, we can use the supertype() function (super() for Julia < v0.5):

julia> supertype(Number)
 Any

But if we were curious, we could also try to find Number's subtypes (Number's children, therefore Any's grandchildren). To do this, we can use the function subtypes():

julia> subtypes(Number)
2-element Array{Union{DataType, UnionAll},1}:
 Complex
 Real   

Despite the syntactical junk, we can observe that we have two subtypes of Number: Complex and Real. If we knew maths, we could check that, for mathematicians, real and complex are both, indeed, numbers. Here is a general rule: Julia type's hierarchy reflect the real world's hierarchy.

type hierarchy for julia numbers

As another example, if both Jaguar and Lion were Julia types, it would natural if their supertype were Feline. We would have:

julia> abstract type Feline end
julia> type Jaguar <: Feline end
julia> type Lion <: Feline end
julia> subtypes(Feline)
2-element Array{Any,1}:
 Jaguar
 Lion  

Concrete and abstract types[edit]

Each object in Julia (informally, this mean everything you can put into a variable in Julia) has a type. But not all types can have a respective object (instances of that type). The only ones that can have instances are called concrete types. These types cannot have any subtypes. The types that can have subtypes (e.g. Any, Number) are called abstract types. Therefore we cannot have a object of type Number, since it's an abstract type. In other words, only the leaves of the type tree are concrete types and can be instantiated.

If we can't create objects of abstract types, why are they useful? With them, we can write code that generalizes for any of its subtypes. For instance, suppose we write a function that expects a variable of the type Number:

#this function gets a number, and returns the same number plus one
function plus_one(n::Number)
 return n + 1
end

In this example, the function expects a variable n. The type of n must be subtype of Number (directly or indirectly) as indicated with the :: syntax (but don't worry about the syntax yet). What does this mean? No matter if n's type is Int (Integer number) or Float64 (floating-point number), the function plus_one() will work correctly. Furthermore, plus_one() will not work with any types that are not subtypes of Number (e.g. text strings, arrays).

We can divide concrete types into two categories: primitive (or basic), and complex (or composite). Primitive types are the building blocks, usually hardcoded into Julia's heart, whereas composite types group many other types to represent higher-level data structures.

You'll probably see the following primitive types:

  • the basic integer and float types (signed and unsigned): Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128, Float16, Float32, and Float64
  • more advanced numeric types: BigFloat, BigInt
  • Boolean and character types: Bool and Char
  • Text string types: String

A simple example of a composite type is Rational, used to represent fractions. It is composed of two pieces, a numerator and a denominator, both integers (of type Int).

Investigating types[edit]

Julia provides two functions for navigating the type hierarchy: subtypes() and supertype().

julia> subtypes(Integer)
4-element Array{Union{DataType, UnionAll},1}:
 BigInt  
 Bool    
 Signed  
 Unsigned

julia> supertype(Float64)
AbstractFloat

The sizeof() function tells you how many bytes an item of this type occupies:

julia> sizeof(BigFloat)
 32

julia> sizeof(Char)
 4

If you want to know how big a number you can fit into a particular type, these two functions are useful:

julia> typemax(Int64)
 9223372036854775807

julia> typemin(Int32)
 -2147483648

There are over 340 types in the base Julia system. You can investigate the type hierarchy with the following function:

function showtypetree(T, level=0)
    println("\t" ^ level, T)
    for t in subtypes(T)
        if t != Any
            showtypetree(t, level+1)
        end
   end
end

showtypetree(Number)

It produces something like this for the different Number types:

julia> showtypetree(Number)
Number
        Complex
        Real
                AbstractFloat
                        BigFloat
                        Float16
                        Float32
                        Float64
                Integer
                        BigInt
                        Bool
                        Signed
                                Int128
                                Int16
                                Int32
                                Int64
                                Int8
                        Unsigned
                                UInt128
                                UInt16
                                UInt32
                                UInt64
                                UInt8
                Irrational
                Rational

This shows, for example, the four main subtypes of Real number: AbstractFloat, Integer, Rational, and Irrational.

Specifying the type of variables[edit]

We've already seen that Julia likes to guess at the types of things you put in your code, if you don't specify them:

julia> collect(1:10)
10-element Array{Int64,1}:
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
julia> collect(1.0:10)
10-element Array{Float64,1}:
  1.0
  2.0
  3.0
  4.0
  5.0
  6.0
  7.0
  8.0
  9.0
 10.0

And we've also seen that you can specify the type for a new array:

julia> fill!(Array{String}(3), "Julia")
3-element Array{String,1}:
 "Julia"
 "Julia"
 "Julia"

For variables, you can specify the type that its value must have. For technical reasons, you can't do this at the top level, in the REPL—you can only do it inside a definition. The syntax uses the :: syntax, which means "is of type". So:

function f(x::Int64)

means that the function f has a method that accepts an argument x which is expected to be an Int64. See Functions.

Type stability[edit]

Here's an example of how the performance of Julia code is affected by the choice of type for variables. Can you spot the difference between these two functions:

julia> function t1(n)
           s  = 0
           for i in 1:n
               s += s/i
           end
       end
t1 (generic function with 1 method)

julia> function t2(n)
           s  = 0.0
           for i in 1:n
               s += s/i
           end
       end
t2 (generic function with 1 method)

These two function definitions are almost identical, except for the type of s at the start of each one. After running them both a few times, the timing results are noteworthy:

julia> @time t1(10000000)
  0.679342 seconds (30.00 M allocations: 457.756 MB, 4.99% gc time)

julia>  @time t2(10000000)
  0.000002 seconds (4 allocations: 160 bytes)

The performance of t1() is significantly worse than that of t2(); it's slower and uses much more memory, and additional time is spent cleaning up ("gc" is the garbage collector). The reason is that s starts out as an integer (s = 0 means that Julia guessed that s was an integer), but inside the loop it's assigned to hold the result of s/i, which is a floating-point value: it has to be converted from integer to floating-point to match. So the function isn't 'type stable—the Julia compiler is unable to make assumptions about its contents, so it can't produce pure integer code or pure floating-point code. As a result, the code it ends up producing isn't as fast as it could be. (Run the commands @code_llvm t1(100) and @code_llvm t2(100) to see how much more work you've made the Julia compiler do...)

Creating types[edit]

In Julia, it's very easy for the programmer to create new types, benefiting from the same performance and language-wise integration that the native types (those made by Julia's creators) have.

Abstract types[edit]

Suppose we want to create an abstract type. To do this, we use Julia's keyword abstract followed by the name of the type you want to create:

abstract type MyAbstractType end

By default, the type you create is a direct subtype of Any:

julia> supertype(MyAbstractType)
 Any

You can change this using the <: operator. If you want your new abstract type to be a subtype of Number, for example, you can declare:

abstract type MyAbstractType2 <: Number end

Now, we get:

julia> supertype(MyAbstractType2)
 Number

Notice that in the same Julia session (without exiting the REPL or ending the script) it's impossible to redefine a type. That's why we had to create a type called MyAbstractType2. Another workaround is to restart Julia workspace using the workspace() function. This will start fresh the REPL section, but you will lose all libraries you imported and variables you stored.

Concrete types and composite[edit]

You can create new composite types. To do this, use the struct keyword, which has the same syntax as declaring the supertype. The new type can contain multiple fields, where the object stores values. As an example, let's define a concrete type that is a subtype of MyAbstractType:

struct MyType <: MyAbstractType
   foo
   bar::Int
end

We just created a composite struct type called MyType, a subtype of MyAbstractType, with two fields: foo that can be of any type, and bar, that is of type Int.

How do we create an object of MyType? By default, Julia automatically creates a constructor, a function that returns an object of that type. The function has the same name of the type, and each argument of the function correspond to each field. In this example, we can create a new object by typing:

julia> x = MyType("Hello World!", 10)
 MyType("Hello World!", 10)

This creates a MyType object, assigning Hello World! to the foo field and 10 to the bar field. We can access x's fields by using the dot notation:

julia> x.foo
 "Hello World!"

julia> x.bar
 10

Also, we can change the field's values of the object easily:

julia> x.foo = 3.0
 3.0

julia> x.foo
 3.0

Notice that, since we didn't specify foo's type when we created the type definition, we can change its type at any time. This is different when we try to change the type of the x.bar field (which we specified as being an Int according to MyType's definition):

julia> x.bar = "Hello World!"
LoadError: MethodError: Cannot `convert` an object of type String to an object of type Int64
This may have arisen from a call to the constructor Int64(...),
since type constructors fall back to convert methods.

The error message tells us that Julia couldn't change x.bar's type. This ensures type-stable code, and can provide better performance when programming. As a performance tip, specifying a field's type when possible is usually good practice.

The default constructor is used for simple cases, where you type something like typename(field1, field2) to produce a new instance of the type. But sometimes you want to do more when you construct a new instance, such as checking the incoming values. For this you can use an inner constructor, a function inside the type definition. The next section shows a practical example.

Example: British currency[edit]

Here's an example of how you can create a simple composite type that can handle the old-fashioned British currency. Before Britain saw the light and introduced a decimal currency, the monetary system used pounds, shillings, and pence, where a pound consisted of 20 shillings, and a shilling consisted of 12 pence. This was called the £sd or LSD system (Latin for Librae, Solidii, Denarii, because the system originated in the Roman empire).

To define a suitable type, start a new composite type declaration:

struct LSD

To contain a price in pounds, shillings, and pence, this new type should contain three fields: pounds, shillings, and pence:

   pounds::Int 
   shillings::Int
   pence::Int

The important task is to create a constructor function. This has the same name as the type, and accepts three values as arguments. After a few checks for invalid values, the special new() function creates a new object with the passed-in values. Remember we're still inside the type definition—this is an inner constructor.

  function LSD(a,b,c)
    if a < 0 || b < 0 || c < 0
      error("no negative numbers")
    end
    if c > 12 || b > 20
      error("too many pence or shillings")
    end
    new(a, b, c) 
  end   

Now we can finish the type definition:

end

Here's the complete type definition again:

struct LSD
   pounds::Int 
   shillings::Int
   pence::Int
   
   function LSD(a, b, c)
    if a < 0 || b < 0 
      error("no negative numbers")
    end
    if c > 12 || b > 20
      error("too many pence or shillings")
    end
    new(a, b, c) 
   end   
end

It's now possible to create new objects that store old-fashioned British prices. You create a new object of this type by using its name (which calls the constructor function):

julia>price1 = LSD(5, 10, 6)
LSD(5, 10, 6)

julia>price2 = LSD(1, 6, 8)
LSD(1, 6, 8)

And you can't create bad prices, because of the simple checks added to the constructor function:

julia> price = LSD(1,0,13)
ERROR: too many pence or shillings
Stacktrace:
 [1] LSD(::Int64, ::Int64, ::Int64)

If you inspect the fields of one of the price 'objects' we've created:

julia> fieldnames(price1)
3-element Array{Symbol,1}:
 :pounds   
 :shillings
 :pence    

you can see the three fields, and these are storing the values:

julia> price1.pounds
5
julia> price1.shillings
10
julia> price1.pence
6

The next task is to make this new type behave in the same way as other Julia objects. For example, we can't add two prices:

julia> price1 + price2
ERROR: MethodError: no method matching +(::LSD, ::LSD)
Closest candidates are:
  +(::Any, ::Any, ::Any, ::Any...) at operators.jl:420

and the output could definitely be improved:

julia> price2
LSD(5, 10, 6)

Julia has the addition function (+) with methods defined for many types of object. The following code adds another method that can handle two LSD objects:

  importall Base.Operators
  function Base.:+(a::LSD, b::LSD)
  newpence = a.pence + b.pence
  newshillings = a.shillings + b.shillings
  newpounds = a.pounds + b.pounds
  subtotal = newpence + newshillings * 12 + newpounds * 240
  (pounds, balance) = divrem(subtotal, 240)
  (shillings, pence) = divrem(balance, 12)
  LSD(pounds, shillings, pence)
end

This definition teaches Julia how to handle the new LSD objects, and adds a new method to the + function, one that accepts two LSD objects, adds them together, and produces a new LSD object containing the sum.

Now you can add two prices:

julia> price1 + price2
LSD(6,17,2)

which is indeed the result of adding LSD(5,10,6) and LSD(1,6,8).

The next problem to address is the unattractive presentation of LSD objects. This is fixed in exactly the same way, by adding a new method, but this time to the show() function, which belongs to the Base environment:

import Base.show
function Base.show(io::IO, money::LSD)
    print(io, "£$(money.pounds).$(money.shillings)s.$(money.pence)d")
end

Here, the io is the output channel currently used by all show() methods. We've added a simple expression that displays the field values with appropriate punctuation and separators.

julia> println(price1 + price2)
£6.17s.2d

julia> show(price1 + price2 + LSD(0,19,11) + LSD(19,19,6))
£27.16s.7d

You can add one or more aliases, which are alternative names for a particular type. Since Price is a better way of saying LSD, we'll create an valid alternative:

julia> const Price LSD
LSD

julia> show(Price(1, 19, 11))
£1.19s.11d

So far, so good, but these LSD objects are still not yet fully developed. If you want to do subtraction, multiplication, and division, you have to define additional methods for these functions for handling LSDs. Subtraction is easy enough, just requiring some fiddling with shillings and pence, so we'll leave that for now, but what about multiplication? Multiplying a price by a number involves two types of object, one a Price/LSD object, the other - well, any positive real number should be possible:

import Base.*
function Base.:*(a::LSD, b::Real)
    if b < 0
        error("Cannot multiply by a negative number")
    end

    totalpence = b * (a.pence + a.shillings * 12 + a.pounds * 240)
    (pounds, balance) = divrem(totalpence, 240)
    (shillings, pence) = divrem(balance, 12)
    LSD(pounds, shillings, pence)
end

Like the + method, this new * method is defined specifically to multiply a price by a number. It works surprisingly well for a first attempt:

julia> price1 * 2
£11.1s.0d
julia> price1 * 3
£16.11s.6d
julia> price1 * 10
£55.5s.0d
julia> price1 * 1.5
£8.5s.9d
julia> price3 = Price(0,6,5)
£0.6s.5d
julia> price3 * 1//7
£0.0s.11d

However, some failures are to be expected,. We didn't allow for the really old-fashioned fractions of a penny: the halfpenny, and the farthing:

julia> price1 * 0.25
ERROR: InexactError()
Stacktrace:
 [1] convert(::Type{Int64}, ::Float64) at ./float.jl:675
 [2] LSD(::Float64, ::Float64, ::Float64) at ./REPL[36]:40
 [3] *(::LSD, ::Float64) at ./REPL[55]:10

(The answer should be £1.7s.7½d. Unfortunately our LSD type doesn't allow fractions of a penny.)

But there's another, more pressing, problem. At the moment you have to give the price followed by the multiplier; the other way round fails:

julia> 2 * price1
ERROR: MethodError: no method matching *(::Int64, ::LSD)
Closest candidates are:
  *(::Any, ::Any, ::Any, ::Any...) at operators.jl:420
  *(::Number, ::Bool) at bool.jl:106
 ...

This is because, although Julia can find a method that matches (a::LSD, b::Number), it can't find it the other way round: (a::Number, b::LSD). But adding it isn't too difficult:

function Base.:*(a::Number, b::LSD)
  b * a
end

which adds yet another method to the * function.

julia> price1 * 2
£11.1s.0d

julia> 2 * price1 
£11.1s.0d

julia> for i in 1:10
           println(price1 * i)
       end
£5.10s.6d
£11.1s.0d
£16.11s.6d
£22.2s.0d
£27.12s.6d
£33.3s.0d
£38.13s.6d
£44.4s.0d
£49.14s.6d
£55.5s.0d

The prices are now looking like an old British shop from the 19th century, forsooth!

If you want to see how many methods you've added for working with this old British pounds type so far, use the methodswith() function:

julia> methodswith(LSD)
4-element Array{Method,1}:
*(a::LSD, b::Real) at In[20]:4
*(a::Number, b::LSD) at In[34]:2
+(a::LSD, b::LSD) at In[13]:2
show(io::IO, money::LSD) at In[15]:2

Just four so far.... And you can continue to add methods to make the type more generally useful—it would depend on how you envisage yourself or others using it. For example, you probably want to add division and modulo methods, and to act intelligently about negative monetary values.

Importing from Base[edit]

You might see a warning when you add your own custom methods to built-in functions from the Base module, such as + and show():

   ERROR: error in method definition: function ... must be explicitly imported to be extended

To prevent this, you can either use importall Base.Operators before you add methods, or if you're just extending one method, define it using the Base. syntax:

function Base.:*(a::Number, b::LSD)
  b * a
end

See the chapter on Modules and Packages for more about importing functions from other modules.

Controlling the Flow[edit]

« Introducing Julia
Controlling the flow
»
Types Functions

Different ways to control the flow[edit]

Typically each line of a Julia program is evaluated in turn. There are various ways to control and modify the flow of evaluation. These correspond with the constructs used in other languages:

  • ternary and compound expressions
  • Boolean switching expressions
  • if elseif else end - conditional evaluation
  • for end - iterative evaluation
  • while end - iterative conditional evaluation
  • try catch error throw exception handling
  • do blocks

Ternary expressions[edit]

Often you'll want to do job A (or call function A) if some condition is true, or job B (function B) if it isn't. The quickest way to write this is using the ternary operator ("?" and ":"):

julia> x = 1
1
julia> x > 3 ? "yes" : "no"
"no"
julia> x = 5
5
julia> x > 3 ? "yes" : "no"
"yes"

Here's another example:

julia> x = 0.3
0.3
julia> x < 0.5 ? sin(x) : cos(x)
0.29552020666133955

and Julia returned the value of sin(x), because x was less than 0.5. cos(x) wasn't evaluated at all.

Boolean switching expressions[edit]

Boolean operators let you evaluate an expression if a condition is true. You can combine the condition and expression using && or ||. && means "and", and || means "or". Since Julia evaluates expressions one by one, you can easily arrange for an expression to be evaluated only if a previous condition is true or false.

The following example uses a Julia function that returns true or false depending on whether the number is odd: isodd(n).

With &&, both parts have to be true, so we can write this:

julia> isodd(1000003) && warn("That's odd!")
WARNING: That's odd!

julia> isodd(1000004) && warn("That's odd!")
false

If the first condition (number is odd) is true, the second expression is evaluated. If the first isn't true, the expression isn't evaluated, and just the condition is returned.

With the || operator, on the other hand:

julia> isodd(1000003) || warn("That's odd!")
true

julia> isodd(1000004) || warn("That's odd!")
WARNING: That's odd!

If the first condition is true, there's no need to evaluate the second expression, since we already have the one truth value we need for "or", and it returns the value true. If the first condition is false, the second expression is evaluated, because that one might turn out to be true.

This type of evaluation is also called "short-circuit evaluation".

If and Else[edit]

For a more general — and traditional — approach to conditional execution, you can use if, elseif, and else. If you're used to other languages, don't worry about white space, braces, indentation, brackets, semicolons, or anything like that, but remember to finish the conditional construction with end.

julia> name = "Brinkley"
"Brinkley"
julia> if name == "Jeeves"
           println("Very Good Jeeves")
       elseif name == "Brinkley"
           println("Thank you, Brinkley")
           println("and shut the door behind you")
       else
           println("Fine, just ignore me")
       end

Thank you, Brinkley
and shut the door behind you

The elseif and else parts are optional too:

julia> name = "Jeeves"
julia> if name == "Jeeves"
          println("Very Good Jeeves")
       end
Very Good Jeeves

Just don't forget the end!

How about 'switch' and 'case' statements? Well, you don't have to learn the syntax for those, because they don't exist!

ifelse[edit]

There's an ifelse function, too. It looks like this in action:

 julia> s = ifelse(false, "hello", "goodbye") * " world"

ifelse is an ordinary function, which evaluates all the arguments, and returns the second or third, depending on the value of the first. With the conditional if or ? ... :, only the expressions in the chosen route are evaluated.

For loops and iteration[edit]

Working through a list or a set of values or from a start value to a finish value are all examples of iteration, and the for ... end construction can let you iterate through a number of different types of object, including ranges, arrays, sets, dictionaries, and strings.

Here's the standard syntax for a simple iteration through a range of values:

julia> for i in 0:10:100
            println(i)
       end
0
10
20
30
40
50
60
70
80
90
100

The variable i takes the value of each element in the array (which is built from a range object) in turn — here stepping from 0 to 100 in steps of 10.

julia> for color in ["red", "green", "blue"] # an array
           print(color, " ")
       end
red green blue
julia> for letter in "julia" # a string
           print(letter, " ")
       end
j u l i a
julia> for element in (1, 2, 4, 8, 16, 32) # a tuple
           print(element, " ")
       end
1 2 4 8 16 32
julia> for i in Dict("A"=>1, "B"=>2) # a dictionary
       println(i)
       end
"B"=>2
"A"=>1
julia> for i in Set(["a", "e", "a", "e", "i", "o", "i", "o", "u"])
       println(i)
       end
e
o
u
a
i

We haven't yet met sets and dictionaries, but iterating through them is exactly the same.

You can iterate through a 2D array, stepping "down" through column 1 from top to bottom, then through column 2, and so on:

julia> a = reshape(1:100, (10, 10))
10x10 Array{Int64,2}:
  1  11  21  31  41  51  61  71  81   91
  2  12  22  32  42  52  62  72  82   92
  3  13  23  33  43  53  63  73  83   93
  4  14  24  34  44  54  64  74  84   94
  5  15  25  35  45  55  65  75  85   95
  6  16  26  36  46  56  66  76  86   96
  7  17  27  37  47  57  67  77  87   97
  8  18  28  38  48  58  68  78  88   98
  9  19  29  39  49  59  69  79  89   99
 10  20  30  40  50  60  70  80  90  100

julia> for n in a
       print(n, " ")
       end
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100

You can use = instead of in.

Iterating over an array and updating it[edit]

When you're iterating over an array, the array is checked each time through the loop, in case it's changed. A mistake you should avoid making is to use push! to make an array grow in the middle of a loop. Run the following text carefully, and be ready to Ctrl-C when you've seen enough (otherwise your computer will eventually crash):

 julia> c = [1]
 1-element Array{Int64,1}:
  1
 
 julia> for i in c
           push!(c, i)
           @show c
           sleep(1)
       end
 c = [1,1]
 c = [1,1,1]
 c = [1,1,1,1]
 ...

Loop variables and scope[edit]

The variable that steps through each item — the 'loop variable' — can be either a variable that already exists, in which case it stays around after the loop finishes, or a variable that doesn't currently exist (in the current scope), in which case it disappears as soon as the loop finishes.

Here's a comparison:

julia> v = 42
42
julia> for v in 1:5 # v is already defined, so it's just re-used for looping
       println(v)
       end
1
2
3
4
5
julia> v
5

v existed before the loop, and exists after the loop — its value was changed by the iteration process. However:

julia> w
ERROR: w not defined
julia> for w in 1:5 # w is now defined
       println(w)
       end
1
2
3
4
5

julia> w
ERROR: w not defined

Here, w didn't exist before the loop, and doesn't exist after the loop.

The difference between the two is worth noting. The advantage of the first approach is that you can find out the final value of the loop variable after the loop's finished.

Fine tuning the loop: Continue[edit]

Sometimes on a particular iteration you might want to skip to the next value. You can use continue to skip the rest of the code inside the loop and start the loop again with the next value.

julia>for i in 1:10
    if i % 3 == 0
       continue
    end
    println(i) # this and subsequent lines are
               # skipped if i is a multiple of 3
end

1
2
4
5
7
8
10

Comprehensions[edit]

This oddly-named concept is simply a way of generating and collecting items. In mathematical circles you say something like:

"Let S be the set of all elements n where n is greater than or equal to 1 and less than or equal to 10". 

In Julia, you can write this as:

 julia> S = Set([n for n in 1:10])
Set([7,4,9,10,2,3,5,8,6,1])

and the [n for n in 1:10] construction is called array comprehension or list comprehension ('comprehension' in the sense of 'getting everything' rather than 'understanding'). The outer brackets collect together the elements generated by evaluating the expression placed before the for iteration. Instead of end, use a square bracket to finish.

julia> [i^2 for i in 1:10]
10-element Array{Int64,1}:
   1
   4
   9
  16
  25
  36
  49
  64
  81
 100

The type of elements can be specified:

julia> Complex128[i^2 for i in 1:10]
10-element Array{Complex{Float64},1}:
   1.0+0.0im
   4.0+0.0im
   9.0+0.0im
  16.0+0.0im
  25.0+0.0im
  36.0+0.0im
  49.0+0.0im
  64.0+0.0im
  81.0+0.0im
 100.0+0.0im

But Julia can work out the types of the results you're producing:

julia> [(i, sqrt(i)) for i in 1:10]
10-element Array{Tuple{Int64,Float64},1}:
 (1,1.0)
 (2,1.41421)
 (3,1.73205)
 (4,2.0)
 (5,2.23607)
 (6,2.44949)
 (7,2.64575)
 (8,2.82843)
 (9,3.0)
 (10,3.16228)

Here's how to make a dictionary via comprehension:

julia> Dict(string(Char(i + 64)) => i for i in 1:26)
Dict{String,Int64} with 26 entries:
  "Z" => 26
  "Q" => 17
  "W" => 23
  "T" => 20
  "C" => 3
  "P" => 16
  "V" => 22
  "L" => 12
  "O" => 15
  "B" => 2
  "M" => 13
  "N" => 14
  "H" => 8
  "A" => 1
  "X" => 24
  "D" => 4
  "G" => 7
  "E" => 5
  "Y" => 25
  "I" => 9
  "J" => 10
  "S" => 19
  "U" => 21
  "K" => 11
  "R" => 18
  "F" => 6

Next, here are two iterators in a comprehension, separated with a comma, which makes generating tables very easy. Here we're making a tuple-table:

julia> [(r,c) for r in 1:5, c in 1:2]
5×2 Array{Tuple{Int64,Int64},2}:
 (1,1)  (1,2)
 (2,1)  (2,2)
 (3,1)  (3,2)
 (4,1)  (4,2)
 (5,1)  (5,2)

r goes through five cycles, once for every value of c. Nested loops work in the opposite manner. Here the column-major order is respected, as shown when the array is filled with nanosecond time values:

julia> [Int(time_ns()) for r in 1:5, c in 1:2]
5×2 Array{Int64,2}:
 1223184391741562  1223184391742642
 1223184391741885  1223184391742817
 1223184391742067  1223184391743009
 1223184391742256  1223184391743184
 1223184391742443  1223184391743372

You can supply a test expression as well to filter the production. For example, produce all the integers between 1 and 100 that are exactly divisible by 7:

julia> [x for x in 1:100 if x % 7 == 0]
14-element Array{Int64,1}:
  7
 14
 21
 28
 35
 42
 49
 56
 63
 70
 77
 84
 91
 98
Generator expressions[edit]

Like comprehensions, generator expressions can be used to produce values from iterating a variable, but, unlike comprehensions, the values are produced on demand.

julia> sum(x^2 for x in 1:10)
385
julia> collect(x for x in 1:100 if x % 7 == 0)
14-element Array{Int64,1}:
  7
 14
 21
 28
 35
 42
 49
 56
 63
 70
 77
 84
 91
 98

Enumerating arrays[edit]

Often you want to go through an array element by element while also keeping track of the index number of each element. The enumerate() function gives you an iterable version of something, producing both an index number and the value at each index number:

julia> m = rand(0:9, 3, 3)
3x3 Array{Int64,2}:
 9  2  9
 3  4  3
 6  1  1

julia> [i for i in enumerate(m)]
9-element Array{(Int64,Any),1}:
 (1,9)
 (2,3)
 (3,6)
 (4,2)
 (5,4)
 (6,1)
 (7,9)
 (8,3)
 (9,1)

The array is checked for possible changes at each iteration of the loop.

Zipping arrays[edit]

Sometimes you want to work through two or more arrays at the same time, taking the first element of each array first, then the second, and so on. This is possible using the well-named zip() function:

julia>for i in zip(0:10, 100:110, 200:210)
     println(i)
end
(0,100,200)
(1,101,201)
(2,102,202)
(3,103,203)
(4,104,204)
(5,105,205)
(6,106,206)
(7,107,207)
(8,108,208)
(9,109,209)
(10,110,210)

You'd think it would all go wrong if the arrays were different sizes. What if the third array is too big?

julia> for i in zip(0:10, 100:110, 200:215)
    println(i)
end
(0,100,200)
(1,101,201)
(2,102,202)
(3,103,203)
(4,104,204)
(5,105,205)
(6,106,206)
(7,107,207)
(8,108,208)
(9,109,209)
(10,110,210)

but Julia isn't fooled — any oversupply in any one of the arrays is handled gracefully.

julia> for i in zip(0:15, 100:110, 200:210)
    println(i)
end
(0,100,200)
(1,101,201)
(2,102,202)
(3,103,203)
(4,104,204)
(5,105,205)
(6,106,206)
(7,107,207)
(8,108,208)
(9,109,209)
(10,110,210)

Iterable objects[edit]

The "for something in something" construction is the same for everything that you can iterate through: arrays, dictionaries, strings, sets, ranges, and so on. In Julia this is a general principle: there are a number of ways in which you can create an "iterable object", an object that is designed to be used as part of the iteration process that provides the elements one at a time.

The most obvious example we've already met is the range object. It doesn't look much when you type it into the REPL:

julia> 0:2:100
0:2:100

But it yields the numbers on demand, when you start iterating:

julia> [i for i in 0:2:100]
51-element Array{Int64,1}:
   0
   2
   4
   6
   8
  10
  12
  14
  16
  18
  20
  22
  24
  26
  28
   
  74
  76
  78
  80
  82
  84
  86
  88
  90
  92
  94
  96
  98
 100

Should you want the actual numbers from a range (or other iterable object), you can use collect():

julia> collect(0:25:100)
5-element Array{Int64,1}:
   0
  25
  50
  75
 100

This might be helpful when you have iterable objects created by other Julia functions. For example, permutations() creates an iterable object containing all the permutations of an array. You could use collect() to grab them and make a new array.

julia> collect(permutations(1:4))
24-element Array{Array{Int64,1},1}:
 [1,2,3,4]
 [1,2,4,3]
 
 [4,3,2,1]

Of course, there's a good reason why iterator objects don't produce all the values from the iteration at the same time: memory and performance. A range object doesn't take up much room, even if iterating over it might take ages (depending on how big the range is). If you generate all the numbers at once, rather than only producing them when they're needed, they all have to be stored somewhere…

Julia provides iterable objects for working with other types of data. For example, when you're working with files, you can treat an open file as an iterable object:

      for line in eachline(filehandle)
         println(length(line), line)
      end
Use eachindex()[edit]

A common pattern when iterating through arrays is to perform some task for each value of i, where i is the index number of each element, not the element:

 for i = 1:length(A) 
   # do something with i or A[i]
 end

There is a more efficient (i.e. faster in some situations) way to do this:

 for i in eachindex(A) 
   # do something with i or A[i]
 end

Even more iterators[edit]

There's a Julia package called Iterators.jl that provides some advanced iterator functions.

 julia> using Iterators

For example, partition() groups the objects in the iterator into easily-handled chunks:

 julia> for i in partition(1:10, 3, 1)
           @show i
    end
 i = (1,2,3)
 i = (2,3,4)
 i = (3,4,5)
 i = (4,5,6)
 i = (5,6,7)
 i = (6,7,8)
 i = (7,8,9)
 i = (8,9,10)

chain() works through all the iterators one after the other:

 julia> for i in chain(1:3, ['a', 'b', 'c'])
           @show i
       end
 i = 1
 i = 2
 i = 3
 i = 'a'
 i = 'b'
 i = 'c'

subsets() works through all subsets of an object. You can specify a size:

 julia> for i in subsets(collect(1:6), 3)
            @show i
        end
 i = [1,2,3]
 i = [1,2,4]
 i = [1,2,5]
 i = [1,2,6]
 i = [1,3,4]
 i = [1,3,5]
 i = [1,3,6]
 i = [1,4,5]
 i = [1,4,6]
 i = [1,5,6]
 i = [2,3,4]
 i = [2,3,5]
 i = [2,3,6]
 i = [2,4,5]
 i = [2,4,6]
 i = [2,5,6]
 i = [3,4,5]
 i = [3,4,6]
 i = [3,5,6]
 i = [4,5,6]

Making your own iterable objects[edit]

It's possible to design your own iterable objects. What you do is create a new type that has start(), next(), and done() methods. Then, a for .. end loop can work through the components of your object by calling the methods automatically as necessary.

This example shows how you can create an iterable object that iterates through prime numbers, starting at 1.

(The isprime() function lives in an external package called Primes, so before starting this section, you must add it and import it.)

 julia> Pkg.add("Primes")
 julia> using Primes

First, you define a new type that will be a prime-iterator:

struct PrimeIterator
	n::Integer
end

You will later create an iterable object of this type using the code:

first20primes = PrimeIterator(20)

The number you supply is passed to the type constructor, and stored in n. The iterator will be expected to generate every prime up to this number.

Next, you must define three methods that the iteration process will call. First, you want to add a start() method for starting the iteration process off. This function already exists in Julia (that's why you can iterate over all the basic data objects), so the Base prefix is required: you're adding a new method to existing start() function, one which is designed to handle these special objects.

function Base.start(::PrimeIterator)
  1
end

Next, add a next() method to Julia's next function which takes an iterable object and returns a tuple of two values. The first item in the tuple returned, whether returned by the first part of the if clause or the else clause, is the next value of the iterator. The last item is the state that will be preserved and passed on to the next invocation of next(). In this particular case, the code just runs up to find the next prime number above state:

function Base.next(::PrimeIterator,state)
	if state == 1
		return (1, 2)           # first return possibility
	else
		while ! isprime(state)
			state += 1
		end
		(state, state += 1)     # second return possibility
	end
end

Finally, a done() method is required so that the iteration knows when to stop. In this case this is trivial, because the construction of the PrimeIterator object expected a number, which acts as the stop point. This function is the only one which refers to the original number which was passed to the constructor. This number was called n in the type definition, so it can be accessed using the "." field accessor.

function Base.done(piter::PrimeIterator, state)
	state >= piter.n
end

With these three methods defined for the PrimeIterator type, it's now possible to iterate through primes, eg from 1 to 20:

 first20primes = PrimeIterator(20)

for i in first20primes
    println(i)
end

1
2
3
5
7
11
13
17
19

As you add more methods that handle PrimeIterator objects, you can use more and more Julia functions with it. For example, if you add a length method:

function Base.length(piter::PrimeIterator)
    piter.n
end

then Julia's length() function knows about PrimeIterators:

julia> length(first20primes)
20

For some types (but perhaps not so easy for prime numbers), you can add a getindex() method to Julia's getindex() function. This allows you to make use of the array indexing notation to find the nth prime.

We'll cheat a bit by using Primes.jl to calculate the primes.

function Base.getindex(pi::PrimeIterator, i::Int)
    1 <= i < length(pi) || throw(BoundsError(pi, i))
    i == 1 && return 1
    return primes(pi.n)[i-1]
end

This lets us do this:

julia> first20primes[1]
1

julia> first20primes[2]
2

julia> first20primes[3]
3

julia> first20primes[8]
17

julia> first20primes[9]
19

(Code inspired by Carl Vogel posts at [2].)

Nested loops[edit]

If you want to nest one loop inside another, you don't have to duplicate the for and end keywords. Just use a comma:

julia> for x in 1:10, y in 1:10
          @show (x, y)
       end
(x,y) = (1,1)
(x,y) = (1,2)
(x,y) = (1,3)
(x,y) = (1,4)
(x,y) = (1,5)
(x,y) = (1,6)
(x,y) = (1,7)
(x,y) = (1,8)
(x,y) = (1,9)
(x,y) = (1,10)
(x,y) = (2,1)
(x,y) = (2,2)
(x,y) = (2,3)
(x,y) = (2,4)
(x,y) = (2,5)
(x,y) = (2,6)
(x,y) = (2,7)
(x,y) = (2,8)
(x,y) = (2,9)
(x,y) = (2,10)
(x,y) = (3,1)
(x,y) = (3,2)
...
(x,y) = (9,9)
(x,y) = (9,10)
(x,y) = (10,1)
(x,y) = (10,2)
(x,y) = (10,3)
(x,y) = (10,4)
(x,y) = (10,5)
(x,y) = (10,6)
(x,y) = (10,7)
(x,y) = (10,8)
(x,y) = (10,9)
(x,y) = (10,10)

(The useful @show macro prints out the names of things and their values.)

One difference between the shorter and longer forms of nesting loops is the behaviour of break:

julia> for x in 1:10
          for y in 1:10
              @show (x, y)
              if y % 3 == 0
                 break
              end
          end
       end
(x,y) = (1,1)
(x,y) = (1,2)
(x,y) = (1,3)
(x,y) = (2,1)
(x,y) = (2,2)
(x,y) = (2,3)
(x,y) = (3,1)
(x,y) = (3,2)
(x,y) = (3,3)
(x,y) = (4,1)
(x,y) = (4,2)
(x,y) = (4,3)
(x,y) = (5,1)
(x,y) = (5,2)
(x,y) = (5,3)
(x,y) = (6,1)
(x,y) = (6,2)
(x,y) = (6,3)
(x,y) = (7,1)
(x,y) = (7,2)
(x,y) = (7,3)
(x,y) = (8,1)
(x,y) = (8,2)
(x,y) = (8,3)
(x,y) = (9,1)
(x,y) = (9,2)
(x,y) = (9,3)
(x,y) = (10,1)
(x,y) = (10,2)
(x,y) = (10,3)

julia> for x in 1:10, y in 1:10
          @show (x, y)
         if y % 3 == 0
           break
         end
       end
(x,y) = (1,1)
(x,y) = (1,2)
(x,y) = (1,3)

Notice that break breaks out of both inner and outer loops in the shorter form, but only out of the inner loop in the longer form.

Optimizing nested loops[edit]

With Julia, the inner loop should concern rows rather than columns. This is due to how arrays are stored in memory.

This function is badly written, because it looks at every column of a row, then at every column of the next row, and so on:

function laplacian_bad(lap_x::Array{Float64,2}, x::Array{Float64,2})
    nr,nc = size(x)
    for ir = 2:nr-1, ic = 2:nc-1 # bad loop nesting order
        lap_x[ir,ic] =
            (x[ir+1,ic] + x[ir-1,ic] +
            x[ir,ic+1] + x[ir,ic-1]) - 4*x[ir,ic]
    end
end

In this version, the two loops are nested properly:

function laplacian_good(lap_x::Array{Float64,2}, x::Array{Float64,2})
    nr,nc = size(x)
    for ic = 2:nc-1, ir = 2:nr-1 # good loop nesting order
        lap_x[ir,ic] =
            (x[ir+1,ic] + x[ir-1,ic] +
            x[ir,ic+1] + x[ir,ic-1]) - 4*x[ir,ic]
    end
end

Another way to increase the speed is to remove the array bounds checking, using the macro @inbounds:

function laplacian_good_nocheck(lap_x::Array{Float64,2}, x::Array{Float64,2})
    nr,nc = size(x)
    for ic = 2:nc-1, ir = 2:nr-1 # good loop nesting order
        @inbounds begin lap_x[ir,ic] = # no array bounds checking
            (x[ir+1,ic] +  x[ir-1,ic] +
            x[ir,ic+1] + x[ir,ic-1]) - 4*x[ir,ic]
        end
    end
end

Here's the test function:

function main_test(nr, nc)
    field = zeros(nr, nc)
    for ic = 1:nc, ir = 1:nr
        if ir == 1 || ic == 1 || ir == nr || ic == nc
            field[ir,ic] = 1.0
        end
    end
    lap_field = zeros(size(field))

    time = @elapsed laplacian_bad(lap_field, field)
    @printf "laplacian_bad:          %.3f s\n" time
    
    time = @elapsed laplacian_good(lap_field, field)
    @printf "laplacian_good:         %.3f s\n" time
    
    time = @elapsed laplacian_good_nocheck(lap_field, field)
    @printf "laplacian_good_nockeck: %.3f s\n" time
end

and the result on a 2012 core-i7 laptop:

julia> main_test(10000,10000)
laplace_bad:          3.209 s
laplace_good:         0.436 s
laplace_good_nockeck: 0.157 s

While loops[edit]

To repeat some expressions while a condition is true, use the while ... end construction.

julia> x = 0
0
julia> while x < 4
    println(x)
    x += 1
end

0
1
2
3

If you want the condition to be tested after the statements, rather than before, producing a "do .. until" form, use the following construction:

while true
   println(x)
   x += 1
   x >= 4 && break
end

0
1
2
3

Here we're using a Boolean switch rather than an if ... end statement.

Using Julia's macros, you can create your own control structures. See Metaprogramming.

Exceptions[edit]

If you want to write code that checks for errors and handles them gracefully, use the try ... catch construction.

With a catch phrase, you can handle problems that occur in your code, possibly allowing the program to continue rather than grind to a halt.

In the next example, our code attempts to change the first character of a string directly (which isn't allowed, because strings in Julia can't be modified in place):

julia> s = "string";
julia> try
          s[1] = "p"
       catch e
          println("caught an error $e")
          println("but we can continue with execution")
       end

caught an error <code>MethodError(setindex!,("string","p",1))</code> but we can continue with execution

The error() function raises an error exception with a given message.

Do block[edit]

Finally, let's look at a do block, which is another syntax form that, like the list comprehension, looks at first sight to be a bit backwards (i.e. it can perhaps be better understood by starting at the end and working towards the beginning).

Remember the find() example from earlier?

julia> smallprimes = [1,2,3,5,7,11,13,17,19,23];

julia> find(x -> x == 13, smallprimes)
1-element Array{Int64,1}:
 7

The anonymous function (x -> x == 13) is the first argument of find(), and operates on the second. But with a do block, you can lift the anonymous function out and put it in between a do ... end block construction:

julia> find(smallprimes) do x
       x == 13
       end
1-element Array{Int64,1}:
 7

You just lose the arrow and change the order, putting the find() function and its target argument first, then adding the anonymous function's arguments and body after the do.

The idea is that it's easier to write a longer anonymous function on multiple lines at the end of the form, rather than wedged in as the first argument.

Functions[edit]

« Introducing Julia
Functions
»
Controlling the flow Dictionaries and sets

Functions[edit]

Functions are the building blocks of Julia code, acting as the subroutines, procedures, blocks, and similar structural concepts found in other programming languages.

A function's job is to take a tuple of values as an argument list and return a value. If the arguments contain mutable values like arrays, the array can be modified inside the function. By convention, an exclamation mark (!) at the end of a function's name indicates that the function may modify its arguments.

There are various syntaxes for defining functions:

  • when the function contains a single expression
  • when the function contain multiple expressions
  • when the function doesn't need a name

Single expression functions[edit]

To define a simple function, all you need to do is provide the function name and argument on the left and an expression on the right of an equals sign. These are just like mathematical functions:

julia> f(x) = x * x
f (generic function with 1 method)

julia> f(2)
4
julia> g(x, y) = sqrt(x^2 + y^2)
g (generic function with 1 method)

julia> g(3,4)
5.0

Functions with multiple expressions[edit]

The syntax for defining a function with more than one expression is like this:

 function functionname(arg1, arg2)
  ''an expression''
  ''another expression''
  ''more expressions''
  ''the final expression''
 end

Here's a typical function that calls two other functions and then ends.

 function breakfast()
   maketoast()
   brewcoffee()
 end

 breakfast (generic function with 1 method)

Whatever the value returned by the final expression — here, the brewcoffee() function — that value is also returned by the breakfast() function.

You can use the return keyword to indicate a specific value to be returned:

julia> function payBills(bankBalance)
   if bankBalance < 0
      return false
   else
      return true
   end
end
payBills (generic function with 1 method)
julia> payBills(20)
true
julia> payBills(-10)
false

Some consider it good style to always use a return statement, even if it's not strictly necessary. Later we'll see how to make sure that the function doesn't go adrift if you call it with the wrong type of argument.

Returning more than one value from a function[edit]

To return more than one value from a function, use a tuple.

julia> function doublesix()
          (6,6)
       end
doublesix (generic function with 1 method)
julia> doublesix()
(6,6)

You can also write 6,6 without parentheses.

Optional arguments and variable number of arguments[edit]

You can define functions with optional arguments, so that the function can use sensible defaults if specific values aren't supplied. You provide a default symbol and value in the argument list:

julia> function xyzpos(x, y, z=0)
          println("$x, $y, $z")
       end
xyzpos (generic function with 2 methods)

And when you call this function, if you don't provide a third value, the variable z defaults to 0 and uses that value inside the function.

julia> xyzpos(1,2)
1, 2, 0
julia> xyzpos(1,2,3)
1, 2, 3

Keyword and positional arguments[edit]

When you write a function with a long list of arguments like this:

function f(p, q, r, s, t, u)
...
end

sooner or later, you will forget the order in which you have to supply the arguments. For instance, it can be:

 f("42", -2.123, atan2, "obliquity", 42, 'x')

or

 f(-2.123, 42, 'x', "42", "obliquity", atan2)

You can avoid this problem by using keywords to label arguments. Use a semicolon after the function's unlabelled arguments, and follow it with one or more keyword=value pairs:

julia> function f(p, q ; r = 4, s = "hello")
  println("p is $p")
  println("q is $q")
  return "r => $r, s => $s"
end
f (generic function with 1 method)

When called, this function expects two arguments, and also accepts a number and a string, labelled r and s. If you don't supply the keyword arguments, their default values are used:

julia> f(1,2)
p is 1
q is 2
"r => 4, s => hello"

julia> f("a", "b", r=pi, s=22//7)
p is a
q is b
"r => π = 3.1415926535897..., s => 22//7"

If you supply a keyword argument, it can be anywhere in the argument list, not just at the end or in the matching place.

julia> f(r=999, 1, 2)
p is 1
q is 2
"r => 999, s => hello"

julia> f(s="hello world", r=999, 1, 2)
p is 1
q is 2
"r => 999, s => hello world"
julia>

When defining a function with keyword arguments, remember to insert a semicolon before the keyword/value pairs.

Here's another example from the Julia manual. The rtol keyword can appear anywhere in the list of arguments or it can be omitted:

julia> isapprox(3.0, 3.01, rtol=0.1)
true
julia> isapprox(rtol=0.1, 3.0, 3.01)
true
julia> isapprox(3.0, 3.00001)
true

A function definition can combine all the different kinds of arguments. Here's one with normal, optional, and keyword arguments:

julia> function f(a1, opta2=2; key="foo")
   println("normal argument: $a1")
   println("optional argument: $opta2")
   println("keyword argument: $key")
end
f (generic function with 2 methods)

julia> f(1)
normal argument: 1
optional argument: 2
keyword argument: foo

julia> f(key=3, 1)
normal argument: 1
optional argument: 2
keyword argument: 3

julia> f(key=3, 2, 1)
normal argument: 2
optional argument: 1
keyword argument: 3

Functions with variable number of arguments[edit]

Functions can be defined so that they can accept any number of arguments:

function fvar(args...)
    println("you supplied $(length(args)) arguments")
    for arg in args
       println(" argument ", arg)
    end
end

The three dots indicate the famous splat. Here it means 'any', including 'none'. You can call this function with any number of arguments:

julia> fvar()
you supplied 0 arguments
julia> fvar(64)
you supplied 1 arguments
 argument 64
julia> fvar(64,65)
you supplied 2 arguments
 argument 64
 argument 65
julia> fvar(64,65,66)
you supplied 3 arguments
 argument 64
 argument 65
 argument 66

and so on.

Here's another example. Suppose you define a function that accepts two arguments:

julia> function test(x, y)
   println("x $x y $y")
end

You can call this in the usual way:

julia> test(12, 34)
x 12 y 34

If you have the two numbers, but in a tuple, then how can you supply a single tuple of numbers to this two argument function? Again, the answer is to use the ellipsis (splat).

julia> test((12, 34) ...)
x 12 y 34

The use of the ellipsis or 'splat' is also referred to as 'splicing' the arguments:

julia> test([3,4]...)
x 3 y 4

You can also do this:

 julia> map(test, [3, 4]...)
x 3 y 4

Local variables and changing the values of arguments[edit]

Any variable you define inside a function will be forgotten when the function finishes.

julia>function test(a,b,c)
    subtotal = a + b + c
end

test (generic function with 1 method)
julia> test(1,2,3)
6
julia> subtotal
LoadError: UndefVarError: subtotal not defined

If you want to keep values around across function calls, then you can think about using global variables.

A function can't modify an existing variable passed to it as an argument, but it can change the contents of a container passed to it.

For example, here is a function that changes its argument to 5:

julia> function set_to_5(x)
          x = 5
       end
set_to_5 (generic function with 1 method)

julia> x = 3
3

julia> set_to_5(x)
5

julia> x
3

Although the x inside the function is changed, the x outside the function isn't. Variable names in functions are local to the function.

But a function can modify the contents of a container, such as an array. This function uses the [:] syntax to access the contents of the container x, rather than change the value of the variable x:

julia> function fill_with_5(x)
          x[:] = 5
       end
fill_with_5 (generic function with 1 method)

julia> x = collect(1:10);
julia> fill_with_5(x)
5
julia> x
10-element Array{Int64,1}:
 5
 5
 5
 5
 5
 5
 5
 5
 5
 5

You can change elements of the array, but you can't change the variable so that it points to a different array. In other words, your function isn't allowed to change the binding of the argument.

Anonymous functions[edit]

Sometimes you don't want to worry about thinking up a cool name for a function. Anonymous functions — functions with no name — can be used in a number of places in Julia, such as with map(), and in list comprehensions.

The syntax uses ->, like this:

x -> x^2 + 2x - 1

which defines a nameless function that takes a argument, calls it x, and returns x^2 + 2x - 1.

For example, the first argument of the map() function is a function, and you can define an one-off function that exists just for this particular map() operation:

 julia> map(x -> x^2 + 2x - 1, [1,3,-1])
3-element Array{Int64,1}:
  2
 14
 -2

After the map() finishes, both the function and the argument x have disappeared:

julia> x
ERROR: x not defined

If you want an anonymous function that accepts more than one argument, provide the arguments as a tuple:

julia> map((x,y,z) -> x + y + z, [1,2,3], [4, 5, 6], [7, 8, 9])
3-element Array{Int64,1}:
 12
 15
 18

Notice that the results are 12, 15, 18, rather than 6, 15, and 24. The anonymous function takes the first value of each of the three arrays and adds them, followed by the second, then the third.

In addition, anonymous functions can have zero arguments, if you use an 'empty' tuple():

julia> random = () -> rand(0:10)
(anonymous function)
julia> random()
3
julia> random()
1

Map[edit]

If you already have a function and an array, you can call the function for each element of the array by using map(). This calls the function on each element in turn, collects the results, and returns them in an array. This process is called mapping:

julia> a=1:10;
julia> map(sin, a)
10-element Array{Float64,1}:
  0.841471
  0.909297
  0.14112
 -0.756802
 -0.958924
 -0.279415
  0.656987
  0.989358
  0.412118
 -0.544021

map() returns a new array but if you call map!() , you can modify the contents of the original array.

Often, you don't have to use map() to apply a function like sin() to every member of an array, because many functions automatically operate "element-wise". The timings of the two different versions are similar:

julia> @time map(sin, 1:10000);
  0.000567 seconds (11 allocations: 78.500 KB)
    
julia> @time sin.(1:10000);
  0.000451 seconds (8 allocations: 78.406 KB)

map() collects the result of each application in an array and returns the array. Sometimes you might want the 'mapping' action but you don't want the results returned as an array. For this job, use foreach():

julia> foreach(println, 1:20)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Map with multiple arrays[edit]

You can use map() with more than one array. The function is applied to the first element of each of the arrays, then to the second, and so on. The arrays must be of the same length (unlike the zip() function, which is more tolerant).

Here's an example which generates an array of imperial (non-metric) spanner/socket sizes. The second array is just a bunch of repeated 32s to match the integers from 5 to 24 in the first array. Julia simplifies the rationals for us:

julia> map(//, 5:24, fill(32,20))
20-element Array{Rational{Int64},1}:
  5//32
  3//16
  7//32
  1//4 
  9//32
  5//16
 11//32
  3//8 
 13//32
  7//16
 15//32
  1//2 
 17//32
  9//16
 19//32
  5//8 
 21//32
 11//16
 23//32
  3//4

(In reality, an imperial spanner set won't contain some of these strange sizes - I've never seen an old 17/32" spanner.)

Applying functions using the dot syntax[edit]

As well as map(), it's possible to apply functions directly to arguments that are arrays. See the section on Dot syntax for vectorizing functions.

Reduce and folding[edit]

The map() function collects the results of some function working on each and every element of an iterable object, such as an array of numbers. The reduce() function does a similar job, but after every element has been seen and processed by the function, only one is left. The function should take two arguments and return one. The array is reduced by continual application, so that just one is left.

A simple example is the use of reduce() to sum the numbers in an iterable object (which works like the built-in function sum()):

 julia> reduce(+, 1:10)
 55

Internally, this does something similar to this:

((((((((1 + 2) + 3) + 4) + 6) + 7) + 8) + 9) + 10)

After each operation adding two numbers, a single number is carried over to the next iteration. This process reduces all the numbers to a single final result.

A more useful example is when you want to apply a function to work on each consecutive pair in an iterable object. For example, here's a function that compares the length of two strings and returns the longer one:

 julia> l(a, b) = length(a) > length(b) ? a : b
 l (generic function with 1 method)

This can be used to find the longest word in a sentence by working through the string, pair by pair:

 julia> reduce(l, split("This is a sentence containing some very long strings"))
 "containing"

"This" lasts a few rounds, and is beaten by "sentence", but then "containing" takes the lead. There are no other challengers after that.

You can use an anonymous function to process an array pairwise. The trick is to make the function leave behind a value that will be used for the next iteration. This code takes an array such as [1, 2, 3, 4, 5, 6...] and returns [1 * 2, 2 * 3, 3 * 4, 4 * 5...], multiplying adjacent elements.

 julia> store = Int[];
 julia> reduce((x,y) -> (push!(store, x * y); y), 1:10)
 10
 julia> store
 9-element Array{Int64,1}:
  2
  6
 12
 20
 30
 42
 56
 72
 90

Folding[edit]

Julia also offers two related functions, foldl() and foldr(). These offer the same basic functionality as reduce(). The differences are concerned with the direction in which the traversal occurs. In the simple summation example above, our best guess at what happened inside the reduce() operation assumed that the first pair of elements were added first, followed by the second pair, and so on. However, it's also possible that reduce() started at the end and worked towards the front. If it's important, use foldl() for left to right, and foldr() for right to left. In many cases, the results are the same, but here's an example where you'll get different results depending on which version you'll use:

 julia> reduce(-, 1:10)
 -53
 
 julia> foldl(-, 1:10)
 -53
 
 julia> foldr(-, 1:10)
 -5

Julia offers other functions in this group: check out mapreduce(), mapfoldl(), and mapfoldr().

If you want to use reduce() and the fold-() functions for functions that take only one argument, use a dummy second argument:

 julia> reduce((x, y) -> sqrt(x), 256, 1:4)
 1.4142135623730951

which is equivalent to calling the sqrt() function four times:

 julia> sqrt(sqrt(sqrt(sqrt(256))))
 1.4142135623730951

Functions that return functions[edit]

You can treat Julia functions in the same way as any other Julia object, particularly when it comes to returning them as the result of other functions.

For example, let's create a function-making function. Inside this function, a function called newfunction is created, and this will raise its argument (y) to the number that was originally passed in as the argument x. This new function is returned as the value of the create_exponent_function() function.

function create_exponent_function(x)
    newfunction = function (y) return y^x end
    return newfunction
end
create_exponent_function (generic function with 1 method)

Now we can construct lots of exponent functions. First, let's build a squarer() function:

julia> squarer = create_exponent_function(2)
(anonymous function)

and a cuber() function:

julia> cuber = create_exponent_function(3)
(anonymous function)

While we're at it, let's do a "raise to the power of 4" function (called quader, although I'm starting to struggle with the Latin and Greek naming):

julia> quader = create_exponent_function(4)
(anonymous function)

These are ordinary Julia functions:

julia> squarer(4)
16
julia> cuber(5)
125
julia> quader(6)
1296

The definition of the create_exponent_function() above is perfectly valid Julia code, but it's not idiomatic. For one thing, the return value doesn't always need to be provided explicitly — the final evaluation is returned if return isn't used. Also, in this case, the full form of the function definition can be replaced with the shorter one-line version. This gives the concise version:

function create_exponent_function(x)
   y -> y^x
end
create_exponent_function (generic function with 1 method)

which acts in the same way.

make_counter = function()
     so_far = 0
     function()
       so_far += 1
     end
end

julia> a = make_counter();
julia> b = make_counter();
julia> a()
1
julia> a()
2
julia> a()
3
julia> a()
4
julia> b()
1
julia> b()
2

Here's another example of making functions:

To make it easier to see what the code is doing, here is the make_counter function written in a slightly different manner:

function make_counter()
     so_far = 0
     counter = function()
                 so_far += 1
                 return so_far
               end
     return counter
end

Methods[edit]

A function can have one or more different methods of doing a similar job. Each method usually concentrates on doing the job for a particular type.

Here is a function to check a longitude when you type in a location:

 julia> function check_longitude_1(loc)
          if -180 < loc < 180
              println("longitude $loc is a valid longitude")
          else
              println("longitude $loc should be between -180 and 180 degrees")
          end
        end
 check_longitude_1 (generic function with 1 method)

The message ("generic function with 1 method") tells you that there is currently one way you can call the check_longitude_1() function. If you call this function and supply a number, it works fine.

 julia> check_longitude_1(-182)
 longitude -182 should be between -180 and 180 degrees
 
 julia> check_longitude_1(22)
 longitude 22 is a valid longitude

But what happens when you type in a longitude in the format seen on Google Maps:

 julia> check_longitude_1("1°24'54.6\"W")
 ERROR: MethodError: `isless` has no method matching isless(::Int64, ::UTF8String)

The error tells us that the function has stopped because the concept of less than (<), which we are using inside our function, makes no sense if one argument is a string and the other a number. Strings are not less than or greater than integers because they are two different things, so the function fails at that point.

Notice that the check_longitude() function did start executing, though. The argument loc could have been anything - a string, a floating point number, an integer, a symbol, or even an array. There are many ways for this function to fail. This is not the best way to write code!

To fix this problem, we might be tempted to add code that tests the incoming value, so that strings are handled differently. But Julia proposes a better alternative: methods and multiple dispatch.

In the case where the longitude is supplied as a numeric value, the loc argument is defined as 'being of type Real'. Let's start again, define a new function, and do it properly:

 julia> function check_longitude(loc::Real)
          if -180 < loc < 180
              println("longitude $loc is a valid longitude")
          else
              println("longitude $loc should be between -180 and 180 degrees")
          end
        end

Now the check_longitude function doesn't even run if the value in loc isn't a real number. The problems of what to do when the value is a string is avoided. With a type Real, this particular method can be called with any argument provided that it is some kind of number.

We can use the applicable() function to test this. applicable() lets you know whether you can apply a function to an argument — i.e. whether there is an available method for the function for arguments with that type:

 julia> applicable(check_longitude, -30)
 true 
 
 julia> applicable(check_longitude, pi)
 true
 
 julia> applicable(check_longitude, 22/7)
 true
 
 julia> applicable(check_longitude, 22//7)
 true
 
 julia> applicable(check_longitude, "1°24'54.6\"W")
 false

The false indicates that you can't pass a string value to the check_longitude() function because there is no method for this function that accepts a string:

 julia> check_longitude("1°24'54.6\"W")
 ERROR: MethodError: `check_longitude` has no method matching check_longitude(::UTF8String)

Now the body of the function isn't even looked at — Julia doesn't know a method for calling check_longitude() function with a string argument.

The obvious next step is to define another method for the check_longitude() function, only this time one that accepts a string argument. In this way, a function can be given a number of alternative methods: one for numeric arguments, one for string arguments, and so on. Julia selects and runs one of the available methods depending on the types of arguments you provide to a function. This is the idea of multiple dispatch.

 function check_longitude(loc::String)
  # not real code, obviously!
    if endswith(loc, "W")
       println("longitude $loc is West of Greenwich")
    else
       println("longitude $loc is East of Greenwich")
    end
 end
 check_longitude (generic function with 2 methods)

Now the check_longitude() function has two methods. The code to run depends on the types of the arguments you provide to the function. And you can avoid testing the types of arguments at the start of this function, because Julia only dispatches the flow to the string-handling method if loc is a string.

You can use the built-in methods() function to find out how many methods you have defined for a particular function.

 julia> methods(check_longitude)
 # 2 methods for generic function "check_longitude":
 check_longitude(loc::Real) at none:2
 check_longitude(loc::String) at none:3

An instructive example is to see how many different methods the + function has:

 julia> methods(+)
 # 155 methods for generic function "+":
 +(x::Bool) at bool.jl:33
 +(x::Bool, y::Bool) at bool.jl:36
 +(y::AbstractFloat, x::Bool) at bool.jl:46
 +(x::Int64, y::Int64) at int.jl:8
 +(x::Int8, y::Int8) at int.jl:16
 +(x::UInt8, y::UInt8) at int.jl:16
 +(x::Int16, y::Int16) at int.jl:16
 +(x::UInt16, y::UInt16) at int.jl:16
 +(x::Int32, y::Int32) at int.jl:16
 ...
 +(x::Base.Dates.Instant) at dates/arithmetic.jl:4
 +{T<:Base.Dates.TimeType}(x::AbstractArray{T<:Base.Dates.TimeType,N}, y::Union{Base.Dates.CompoundPeriod,Base.Dates.Period}) at dates/arithmetic.jl:76
 +{T<:Base.Dates.TimeType}(y::Union{Base.Dates.CompoundPeriod,Base.Dates.Period}, x::AbstractArray{T<:Base.Dates.TimeType,N}) at dates/arithmetic.jl:77
 +{P<:Union{Base.Dates.CompoundPeriod,Base.Dates.Period}}(y::Base.Dates.TimeType, ...) at dates/arithmetic.jl:84
 +(a, b, c, xs...) at operators.jl:97

This is a long list of every method currently defined for the + function; there are many different types of thing that you can add together, including arrays, matrices, and dates. If you design your own types, you might well want to write a function that adds two of them together.

Julia chooses "the most specific method" to handle the types of arguments. In the case of check_longitude(), we have two specific methods, but we could define a more general method:

 function check_longitude(loc::Any)
       println("longitude $loc should be a string or a number")
 end
 check_longitude (generic function with 3 methods)

This method of check_longitude() is called when the loc argument is neither a Real number or a String. It is the most general method, and won't be called at all if a more specific method is available.

Type parameters in method definitions[edit]

It's possible to include type information in method definitions. You can provide one or more variables and types, in curly braces, preceding the tuple of arguments. Here's a simple example:

julia> function test{T <: Real}(a::T)
           println("$a is a $T")
       end
test (generic function with 3 methods)

julia> test(2.3)
2.3 is a Float64

julia> test(2)
2 is a Int64

julia> test(.02)
0.02 is a Float64

julia> test(pi)
π = 3.1415926535897... is a Irrational{:π}

julia> test(22//7)
22//7 is a Rational{Int64}

julia> test(0xff)
255 is a UInt8

The test() method automatically extracts the type of the single argument a passed to it and stores it in the 'variable' T. For this function, T must be a subtype of the Real type (so it can be any real number, but not a complex number). 'T' can be used like any other variable — in this method it's just printed out using string interpolation.

This mechanism is useful when you want to constrain the arguments of a particular method definition to be of a particular type. For example, the type of argument a must belong to the Real number super type, so this test() method doesn't apply when a isn't a number (subtype of Real), because then the type of the argument isn't a subtype of Real:

julia> test("str")
LoadError: MethodError: `test` has no method matching test(::ASCIIString)

julia> test(1:3)
LoadError: MethodError: `test` has no method matching test(::UnitRange{Int64})

Here's an example where you might want to write a method definition that applies to all one-dimensional integer arrays. It finds all the odd numbers in an array:

function findodds{T<:Integer}(a::Array{T,1})
   find(isodd, a)
end
findodds (generic function with 1 method)

julia> findodds(collect(1:20))
10-element Array{Int64,1}:
  1
  3
  5
  7
  9
 11
 13
 15
 17
 19

but can't be used for arrays of real numbers:

julia> findodds([1, 2, 3, 4, 5, 6, 7, 8, 9, 10.0])
ERROR: MethodError: no method matching findodds(::Array{Float64,1})

Note that, in this simple example, because you're not using the type information inside the method definition, you might be better off sticking to the simpler way of defining types, by adding qualifiers to the arguments:

function findodds(a::Array{Int64,1})
   find(isodd, a)
end

But if you wanted to do things inside the method that depended on the types of the arguments, then the type parameters approach will be useful.

Dictionaries and Sets[edit]

« Introducing Julia
Dictionaries and sets
»
Functions Strings and characters

Dictionaries[edit]

Many of the functions introduced so far have been shown working on arrays (and tuples). But arrays are just one type of collection. Julia has others.

A simple look-up table is a useful way of organizing many types of data: given a single piece of information, such as a number, string, or symbol, called the key, what is the corresponding data value? For this purpose, Julia provides the Dictionary object, called Dict for short. It's an "associative collection" because it associates keys with values.

Creating dictionaries[edit]

You can create a simple dictionary using the following syntax:

julia> dict = Dict("a" => 1, "b" => 2, "c" => 3)

Dict{String,Int64} with 3 entries:

  "c" => 3
  "b" => 2
  "a" => 1

dict is now a dictionary. The keys are "a", "b", and "c", the corresponding values are 1, 2, and 3. The => operator is called the Pair() function. In a dictionary, keys are always unique – you can't have two keys with the same name.

If you know the types of the keys and values in advance, you can (and probably should) specify them after the Dict keyword, in curly braces:

julia> dict = Dict{String,Integer}("a"=>1, "b" => 2)

Dict{String,Integer} with 2 entries:
  "b" => 2
  "a" => 1

You can also create dictionaries using the generator/comprehensions syntax:

julia> Dict(string(i) => sind(i) for i = 0:5:360)
Dict{String,Float64} with 73 entries:
  "320" => -0.642788
  "65"  => 0.906308
  "155" => 0.422618
       => 

Use the following syntax to create a typed empty dictionary:

julia> Dict{String,Int64}()
Dict{String,Int64} with 0 entries

or you can omit the types, and get an untyped dictionary:

julia> Dict()
Dict{Any,Any} with 0 entries

It's sometimes useful to create dictionary entries using a for loop:

files = ["a.txt", "b.txt", "c.txt"]
fvars = Dict()
for (n, f) in enumerate(files)
   fvars["x_$(n)"] = f
end

This is one way you could create a set of 'variables' stored in a dictionary:

julia> fvars
Dict{Any,Any} with 3 entries:
 "x_1" => "a.txt"
 "x_2" => "b.txt"
 "x_3" => "c.txt"

Looking things up[edit]

To get a value, given a key:

julia> dict = Dict("a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5)
julia> dict["a"]
1

if the keys are strings. Or, if the keys are symbols:

julia> symdict = Dict(:x => 1, :y => 3, :z => 6)
Dict{Symbol,Int64} with 3 entries:
  :z => 6
  :x => 1
  :y => 3

julia> symdict[:x]
1

Or if the keys are integers:

julia> intdict = Dict(1 => "one", 2 => "two", 3  => "three")
Dict{Int64,String} with 3 entries:
  2 => "two"
  3 => "three"
  1 => "one"

julia> intdict[2]
"two"

You can instead use the get() function, and provide a fail-safe default value if there's no value for that particular key:

julia> get(dict, "a", 0)
1
julia> get(dict, "1", 0)
0

If you don't use a default value as a safety precaution, you'll get an error if there's no key:

julia> dict = Dict("a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5)
Dict{String,Int64} with 5 entries:
  "c" => 3
  "e" => 5
  "b" => 2
  "a" => 1
  "d" => 4

julia> get(dict, "w", 0)
0

If you don't want get() to provide a default value, use a try...catch block:

try
    dict["f"]
    catch error
       if isa(error, KeyError)
           println("sorry, I couldn't find anything")
       end
end

sorry, I couldn't find anything


To change a value assigned to an existing key (or assign a value to a hitherto unseen key):

julia> dict["a"] = 10
10

(Remember that keys must be unique for a dictionary. There's always only one key called a in this dictionary, so when you assign a value to a key that already exists, you're not creating a new one, just modifying an existing one.)

To see if the dictionary contains a key, use haskey():

julia> haskey(dict, "z")
false

To check for the existence of a key/value pair:

julia> in(("b" => 2),dict)
true

To add a new key and value to a dictionary, use this:

julia> dict["d"] = 4
4

You can delete a key from the dictionary, using delete!():

julia> delete!(dict, "d")
Dict{Any,Any} with 3 entries:
  "c" => 3
  "b" => 2
  "a" => 10

You'll notice that the dictionary doesn't seem to be sorted in any way. The keys are in no particular order. This is due to the way they're stored, and you can't sort them in place. (But see Sorting, below.)

To get all keys, use the keys() function:

julia> dict = Dict("a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5);
julia> keys(dict)
Base.KeyIterator for a Dict{String,Int64} with 5 entries. Keys:
  "c"
  "e"
  "b"
  "a"
  "d"

The result is a Key iterator, that, as its name suggests, is ideally suited for iterating through a dictionary key by key:

julia> [uppercase(key) for key in keys(dict)]
5-element Array{Any,1}:
 "C"
 "E"
 "B"
 "A"
 "D"

This uses the list comprehension form ([ new-element for loop-variable in iterator ]) and each new element is collected into an array. An alternative would be:

julia> map(uppercase, collect(keys(dict)))
5-element Array{String,1}:
 "C"
 "E"
 "B"
 "A"
 "D"

To get all the values, use the values() function:

julia> values(dict)
Base.ValueIterator for a Dict{String,Int64} with 5 entries. Values:
  3
  5
  2
  1
  4

If you want to go through a dictionary and process each key/value, you can make use the fact that dictionaries themselves are iterable objects:

julia> for kv in dict
         println(kv)
       end
"c"=>3
"e"=>5
"b"=>2
"a"=>1
"d"=>4

where kv is a tuple containing each key/value pair in turn.

Or you could do:

julia> for k in keys(dict)
          println(k, " ==> ", dict[k])
       end
c ==> 3
e ==> 5
b ==> 2
a ==> 1
d ==> 4

Better, you can use a key/value tuple to simplify the iteration even more:

julia> for (key, value) in dict
         println(key, " ==> ", value)
       end
c ==> 3
e ==> 5
b ==> 2
a ==> 1
d ==> 4

Here's another example:

for tuple in Dict("1"=>"Hydrogen", "2"=>"Helium", "3"=>"Lithium")
    println("Element $(tuple[1]) is $(tuple[2])")
end

Element 1 is Hydrogen
Element 2 is Helium
Element 3 is Lithium

(Notice the string interpolation operator, $. This allows you to use a variable's name in a string and get the variable's value when the string is printed. You can include any Julia expression in a string using $().)

Sorting a dictionary[edit]

Because dictionaries don't store the keys in any particular order, you'll have to output the dictionary to a sorted array if you need to obtain the items in order:

julia> dict = Dict("a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5, "f" => 6)

Dict{String,Int64} with 6 entries:
  "f" => 6
  "c" => 3
  "e" => 5
  "b" => 2
  "a" => 1
  "d" => 4

julia> for key in sort(collect(keys(dict)))
           println("$key => $(dict[key])")
       end

a => 1
b => 2
c => 3
d => 4
e => 5
f => 6

If you really need to have a dictionary that remains sorted all the time, you can use the SortedDict data type from the DataStructures.jl package (after having installed it).

julia> Pkg.add("DataStructures")
julia> import DataStructures
julia> dict = DataStructures.SortedDict("b" => 2, "c" => 3, "d" => 4, "e" => 5, "f" => 6)
DataStructures.SortedDict{String,Int64,Base.Order.ForwardOrdering} with 5 entries:
  "b" => 2
  "c" => 3
  "d" => 4
  "e" => 5
  "f" => 6
julia> dict["a"] = 100000
100000
julia> dict
DataStructures.SortedDict{String,Int64,Base.Order.ForwardOrdering} with 6 entries:
  "a" => 100000
  "b" => 2
  "c" => 3
  "d" => 4
  "e" => 5
  "f" => 6

Simple example: counting words[edit]

A simple application of a dictionary is to count how many times each word appears in a piece of text. Each word is a key, and the value of the key is the number of times that word appears in the text.

Let's count the words in the Sherlock Holmes stories. I've downloaded the text from the excellent Project Gutenberg and stored them in a file "sherlock-holmes-canon.txt". To create a list of words from the loaded text in canon, it suffices to split the text using a regular expression, after converting the text to lower case:

julia> f = open("sherlock-holmes-canon")
julia> wordlist = split(lowercase(readstring(f)), r"\W", keep=false)

669336-element Array{SubString{String},1}:
 "the"
 "complete"
 "sherlock"
 "holmes"
 "arthur"
 "conan"
 "doyle"
 "table"
 "of"
 "contents"
 "a"
 "study"
 "in"
 "scarlet"
 "the"
 "sign"
 "of"
 "the"
 "four"
 "the"
 "adventures"
 "of"
 "sherlock"
 "holmes"
 "a"
 "scandal"
 "in"
 "bohemia"
 "the"
 
 "in"
 "our"
 "archives"
 "watson"
 "some"
 "day"
 "the"
 "true"
 "story"
 "may"
 "be"
 "told"

To store the words and the word counts, we'll create a dictionary:

julia> wordcounts = Dict{String,Int64}()
Dict{String,Int64} with 0 entries

To build the dictionary, loop through the list of words, and use get() to look up the current tally, if any. If the word has already been seen, the count can be increased. If the word hasn't been seen before, the fall-back third argument of get() ensures that the absence doesn't cause an error, and 1 is stored instead.

for word in wordlist
    wordcounts[word]=get(wordcounts, word, 0) + 1
end

Now you can look up words in the wordcounts dictionary and find out how many times they appear:

julia> wordcounts["watson"]
1040

julia> wordcounts["holmes"]
3057

julia> wordcounts["sherlock"]
415

julia> wordcounts["lestrade"]
244

Dictionaries aren't sorted, but you can use the collect() and keys() functions on the dictionary to collect the keys and then sort them. In a loop you can work through the dictionary in alphabetical order:

julia> for i in sort(collect(keys(wordcounts)))
  println("$i, $(wordcounts[i])")
end

000, 5
1, 8
10, 7
100, 4
1000, 9
104, 1
109, 1
10s, 2
10th, 1
11, 9
1100, 1
117, 2
117th, 2
11th, 1
12, 2
120, 2
126b, 3
            
zamba, 2
zeal, 5
zealand, 3
zealous, 3
zenith, 1
zeppelin, 1
zero, 2
zest, 3
zig, 1
zigzag, 3
zigzagged, 1
zinc, 3
zion, 2
zoo, 1
zoology, 2
zu, 1
zum, 2

But how do you find out the most common words? One way is to use collect() to convert the dictionary to an array of tuples, and then to sort the array by looking at the last value of each tuple:

julia> sort(collect(wordcounts), by = tuple -> last(tuple), rev=true)
19171-element Array{Pair{String,Int64},1}:
 ("the",36244)     
 ("and",17593)     
 ("i",17357)       
 ("of",16779)      
 ("to",16041)      
 ("a",15848)       
 ("that",11506)   
                  
 ("enrage",1)      
 ("smuggled",1)    
 ("lounges",1)     
 ("devotes",1)     
 ("reverberated",1)
 ("munitions",1)   
 ("graybeard",1)

To see only the top 20 words:

julia> sort(collect(wordcounts), by = tuple -> last(tuple), rev=true)[1:20]
20-element Array{Pair{String,Int64},1}:
 ("the",36244) 
 ("and",17593) 
 ("i",17357)   
 ("of",16779)  
 ("to",16041)  
 ("a",15848)   
 ("that",11506)
 ("it",11101)  
 ("in",10766)  
 ("he",10366)  
 ("was",9844)  
 ("you",9688)  
 ("his",7836)  
 ("is",6650)   
 ("had",6057)  
 ("have",5532) 
 ("my",5293)   
 ("with",5256) 
 ("as",4755)   
 ("for",4713)

In a similar way, you can use the filter() function to find, for example, all words that start with "k" and occur less than four times:

julia> filter(tuple -> startswith(first(tuple), "k") && last(tuple) < 4, collect(wordcounts))
73-element Array{Pair{String,Int64},1}:
 ("keg",1)
 ("klux",2)
 ("knifing",1)
 ("keening",1)
 ("kansas",3)
 
 ("kaiser",1)
 ("kidnap",2)
 ("keswick",1)
 ("kings",2)
 ("kratides",3)
 ("ken",2)
 ("kindliness",2)
 ("klan",2)
 ("keepsake",1)
 ("kindled",2)
 ("kit",2)
 ("kicking",1)
 ("kramm",2)
 ("knob",1)

More complex structures[edit]

A dictionary can hold many different types of values. Here for example is a dictionary where the keys are strings and the values are arrays of arrays of points. For example, this could be used to store graphical shapes describing the letters of the alphabet (some of which have two or more loops):

    julia> p = Dict{String, Array{Array}}()  <!-- p = Dict{String, Array{Array{Point}}}() -->
    Dict{String,Array{Array{T,N},N}}
    
    julia> p["a"] = Array[[Point(0,0), Point(1,1)], [Point(34, 23), Point(5,6)]]
     2-element Array{Array{T,N},1}:
     [Point(0.0,0.0), Point(1.0,1.0)]
     [Point(34.0,23.0), Point(5.0,6.0)]
    
    julia> push!(p["a"], [Point(34.0,23.0), Point(5.0,6.0)])
    3-element Array{Array{T,N},1}:
     [Point(0.0,0.0), Point(1.0,1.0)]
     [Point(34.0,23.0), Point(5.0,6.0)]
     [Point(34.0,23.0), Point(5.0,6.0)]

Or create a dictionary with some already-known values:

    julia> d = Dict("shape1" => Array [ [ Point(0,0), Point(-20,57)], [Point(34, -23), Point(-10,12) ] ])
    Dict{String,Array{Array{T,N},1}} with 1 entry:
     "shape1" => Array [ [ Point(0.0,0.0), Point(-20.0,57.0)], [Point(34.0,-23.0), Point(-10.0,12.0) ] ]

Add another array to the first one:

    julia> push!(d["shape1"], [Point(-124.0, 37.0), Point(25.0,32.0)])
    3-element Array{Array{T,N},1}:
     [Point(0.0,0.0), Point(-20.0,57.0)]
     [Point(34.0,-23.0), Point(-10.0,12.0)]
     [Point(-124.0,37.0), Point(25.0,32.0)]

Sets[edit]

A set is a collection of elements, just like an array or dictionary, with no duplicated elements.

The two important differences between a set and other types of collection is that in a set you can have only one of each element, and, in a set, the order of elements isn't important (whereas an array can have multiple copies of an element and their order is remembered).

You can create an empty set using the Set constructor function:

julia> colors = Set()
Set{Any}({})

As elsewhere in Julia, you can use curly braces to specify the type:

julia> primes = Set{Int64}()
Set{Int64}({})

You can create and fill sets in one go:

julia> colors = Set{String}(["red","green","blue","yellow"])
Set(String["yellow","blue","green","red"])

or you can get Julia to play "guess the type":

julia> colors = Set(["red","green","blue","yellow"])
Set{String}({"yellow","blue","green","red"})

Quite a few of the functions that work with arrays also work with sets. Adding elements to sets, for example, is a bit like adding elements to arrays. You can use push!():

julia> push!(colors, "black") 
Set{String}({"yellow","blue","green","black","red"})

But you can't use unshift!(), because that works only for ordered things like arrays. What happens if you try to add something to the set that's already there? Absolutely nothing. You don't get a copy added, because it's a set, not an array, and sets don't store repeated elements. To see if something is in the set, you can use in():

julia> in("green", colors)
true

There are some standard operations you can do with sets, namely find their union, intersection, and difference, with the functions, union(), intersect(), and setdiff():

julia> rainbow = Set(["red","orange","yellow","green","blue","indigo","violet"])
Set(String["indigo","yellow","orange","blue","violet","green","red"])

The union of two sets is the set of everything that is in one or the other sets. The result is another set – so you can't have two "yellow"s here, even though we've got a "yellow" in each set:

julia> union(colors, rainbow)
Set(String["indigo","yellow","orange","blue","violet","green","black","red"])

The intersection of two sets is the set that contains every element that belongs to both sets:

julia> intersect(colors, rainbow)
Set(String["yellow","blue","green","red"])

The difference between two sets is the set of elements that are in the first set, but not in the second. This time, the order in which you supply the sets matters. The setdiff() function finds the elements that are in the first set, colors, but not in the second set, rainbow:

julia> setdiff(colors, rainbow)
Set(String["black"])

Other functions[edit]

Functions that work on arrays and sets sometimes work on dictionaries too. The official documentation doesn't always spell it out, but you can quickly try things out. For example, some of the set operations can be applied to dictionaries:

 julia> d1 = Dict(1=>"a", 2 => "b")
 Dict{Int64,String} with 2 entries:
   2 => "b"
   1 => "a"
  
 julia> d2 = Dict(2 => "b", 3 =>"c", 4 => "d")
 Dict{Int64,String} with 3 entries:
   4 => "d"
   2 => "b"
   3 => "c"
 
 julia> union(d1, d2)
 4-element Array{Pair{Int64,String},1}:
  2=>"b"
  1=>"a"
  4=>"d"
  3=>"c"
 
 julia> intersect(d1, d2)
 1-element Array{Pair{Int64,String},1}:
  2=>"b"
  
 julia> setdiff(d1, d2)
  1-element Array{Pair{Int64,String},1}:
   1=>"a"

Notice that the results are returned as arrays of Pairs, rather than as Dictionaries.

Functions such as filter(), map(), and collect() which we've already seen being used with arrays also work with dictionaries:

 julia> filter((k, v) -> k == 1, d1)
 Dict{Int64,String} with 1 entry:
   1 => "a"

There's a merge() function which can merge two dictionaries:

 julia> merge(d1, d2)
 Dict{Int64,String} with 4 entries:
   4 => "d"
   2 => "b"
   3 => "c"
   1 => "a"

Strings and Characters[edit]

« Introducing Julia
Strings and characters
»
Dictionaries and sets Working with text files

Strings and characters[edit]

Strings[edit]

A string is a sequence of one or more characters, usually seen when enclosed in double quotes:

"this is a string"

There are two important things you need to know about strings.

One is, that they're immutable. You can't change them once they're created. But it's easy to make new strings from parts of existing ones.

The second is that you have to be careful when using two specific characters: double quotes ("), and dollar signs ($). If you want to include a double quote character in the string, it has to be preceded with a backslash, otherwise the rest of the string would be interpreted as Julia code, with potentially interesting results. And if you want to include a dollar sign ($) in a string, that should also be prefaced by a backslash, because it's used for string interpolation.

julia> demand = "You owe me \$50!"
"You owe me \$50!"

julia> println(demand)
You owe me $50!

julia> demandquote = "He said, \"You owe me \$50!\""
"He said, \"You owe me \$50!\""

Strings can also be enclosed in triple double quotes. This is useful because you can use ordinary double quotes inside the string without having to put backslashes before them:

julia> """this is "a" string"""
"this is \"a\" string"

You'll encounter a few specialized types of string too, which consist of one or more characters immediately followed by the opening double quote:

  • r" " indicates a regular expression
  • v" " indicates a version string
  • b" " indicates a byte literal

String interpolation[edit]

You often want to use the results of Julia expressions inside strings. For example, suppose you want to say:

"The value of x is n."

where n is the current value of x.

Any Julia expression can be inserted into a string with the $() construction:

julia> x = 42
42
julia> "The value of x is $(x)."

displays:

"The value of x is 42."

You don't have to use the parentheses if you're just using the name of a variable:

julia> "The value of x is $x."
"The value of x is 42."

To include the result of a Julia expression in a string, enclose the expression in parentheses first, then precede it with a dollar sign:

julia> "The value of 2 + 2 is $(2 + 2)."
"The value of 2 + 2 is 4."

Substrings[edit]

To extract a smaller string from a string, use getindex(s, range) or s[range] syntax. For basic ASCII strings, you can use the same techniques that you use to extract elements from arrays:

julia> s = String("a load of characters")
"a load of characters"

julia> s[1:end]
"a load of characters"

julia> s[3:6]
"load"

julia> s[3:end-6]
"load of char"

You can easily iterate through a string:

julia> for char in s
           print(char, "_")
       end
a_ _l_o_a_d_ _o_f_ _c_h_a_r_a_c_t_e_r_s_

Watch out if you take a single element from the string, rather than a string of length 1 (i.e. with the same start and end positions):

julia> s[1:1]
"a" 

julia> s[1]
'a'

The second result isn't a string, but a character (single quotes).

Unicode strings[edit]

Not all strings are ASCII. To access individual characters in Unicode strings, you can't always use simple indexing, because some characters occupy more than one index position. Don't be fooled just because some of the index numbers appear to work:

julia> su = String("AéB𐅍CD")
"AéB𐅍CD"

julia> su[1]
'A'

julia> su[2]
'é'

julia> su[3]
ERROR: UnicodeError: invalid character index
 in slow_utf8_next(::Array{UInt8,1}, ::UInt8, ::Int64) at ./strings/string.jl:67
 in next at ./strings/string.jl:92 [inlined]
 in getindex(::String, ::Int64) at ./strings/basic.jl:70

Instead of length(str) to find the length of a string, use endof(str):

julia> length(su)
6

julia> endof(su)
10

The isascii() functions tests whether a string is ASCII or contains Unicode characters:

julia> isascii(su)
false

In this string, the 'second' character, é, has 2 bytes, the 'fourth' character, 𐅍, has 4 bytes. If you can't use an iteration loop to step through the entire string, use nextind() to find the next valid element. Here's a short example of how to examine a string character by character:

julia> c = 1; 
julia> while c <= endof(su)
           println(c, " -> ", su[c])
           c = nextind(su, c)
       end
1 -> A
2 -> é
4 -> B
5 -> 𐅍
9 -> C
10 -> D

The 'third' character, B, starts with the 4th element in the string.

As an alternative, use the eachindex iterator:

julia> for charindex in eachindex(su)
           @show su[charindex]
       end
su[charindex] = 'A'
su[charindex] = 'é'
su[charindex] = 'B'
su[charindex] = '𐅍'
su[charindex] = 'C'
su[charindex] = 'D'

Splitting and joining strings[edit]

You can stick strings together (a process often called concatenation) using the multiply (*) operator:

julia> "s" * "t"
"st"

If you've used other programming languages, you might expect to use the addition (+) operator:

julia> "s" + "t"
LoadError: MethodError: `+` has no method matching +(::String, ::String)

If you can 'multiply' strings, you can also raise them to a power:

julia> "s" ^ 18
"ssssssssssssssssss"

You can also use string():

julia> string("s", "t")
"st"

but if you want to do a lot of concatenation, inside a loop, perhaps, it might be better to use the string buffer approach (see below).

To split a string, use split() function. Given this simple string:

julia> s = "You know my methods, Watson."
"You know my methods, Watson."

a simple call to the split() function divides the string at the spaces, returning a five-piece array:

julia> split(s)
5-element Array{SubString{String},1}:
 "You"
 "know"
 "my"
 "methods,"
 "Watson."

Or you can specify the string of 1 or more characters to split at:

julia> split(s, "e")
2-element Array{SubString{String},1}:
 "You know my m"
 "thods, Watson."
julia> split(s, " m")
3-element Array{SubString{String},1}:
 "You know"       
 "y"              
 "ethods, Watson."

The characters you use to do the splitting don't appear in the final result:

julia> split(s, "hod")
2-element Array{SubString{String},1}:
 "You know my met"
 "s, Watson."

If you want to split a string into separate single-character strings, use the empty string ("") which splits the string between the characters:

julia> split(s,"")
28-element Array{SubString{String},1}:
 "Y"
 "o"
 "u"
 " "
 "k"
 "n"
 "o"
 "w"
 " "
 "m"
 "y"
 " "
 "m"
 "e"
 "t"
 "h"
 "o"
 "d"
 "s"
 ","
 " "
 "W"
 "a"
 "t"
 "s"
 "o"
 "n"
 "."

You can also split strings using a regular expression to define the splitting points. Use the special regex string construction r" ":

julia> split(s, r"a|e|i|o|u")
8-element Array{SubString{String},1}:
 "Y"
 ""
 " kn"
 "w my m"
 "th"
 "ds, W"
 "ts"
 "n."

Here, the r"a|e|i|o|u" is a regular expression string, and — as you'll know if you love regular expressions — that this matches any of the vowels. So the resulting array consists of the string split at every vowel. Notice the empty strings in the results -— if you don't want those, add a false flag at the end:

julia> split(s, r"a|e|i|o|u", false)
7-element Array{SubString{String},1}:
 "Y"     
 " kn"   
 "w my m"
 "th"    
 "ds, W" 
 "ts"    
 "n."

If you wanted to keep the vowels, rather than use them for splitting work, you have to delve deeper into the world of regex literal strings. Read on.

You can join the elements of a split string in array form using join():

julia> join(split(s, r"a|e|i|o|u", false), "aiou")
"Yaiou knaiouw my maiouthaiouds, Waioutsaioun."

Character objects[edit]

Above we extracted smaller strings from larger strings:

julia> s[1:1]
"a"

But when we extracted a single element from a string:

julia> s[1]
'a'

- notice the single quotes. In Julia, these are used to mark character objects, so 'a' is a character object, but "a" is a string with length 1. These are not equivalent.

You can convert character objects to strings easily enough:

julia> string('s') * string('d')
"sd"

or

julia> string('s', 'd')
"sd"

It's easy to input 32 bits Unicode characters using \U escape sequence. The escape sequences \u and \x can be used for 16 bits and 8 bits characters:

julia> ('\U1014d','\u2640','\xa5')
('𐅍','♀','¥')

For strings, the \Uxxxxxxxx and \uxxxx syntax are more strict, and \x cannot be used for non ASCII characters.

julia> "\U0001014d2\U000026402\u26402\U000000a52\u00a52\U000000352\u00352\x352"
"𐅍2♀2♀2¥2¥2525252"

Converting to and from strings[edit]

The bin(), oct(), dec(), hex() functions turn a integer into binary, octal, decimal or hex strings.

julia> bin(11),oct(11),dec(11),hex(11)
("1011","13","11","b")
julia> a = BigInt(2)^200
1606938044258990275541962092341162602522202993782792835301376
julia> dec(a)
"1606938044258990275541962092341162602522202993782792835301376"
julia> hex(a)
"1000000000000000000000000000000000000000000000000"

The function string() can be used instead of dec()

julia> string(123)
"123"

Use parse to convert numbers in string form to actual numbers.

julia> parse(Int, "100")
100
julia> parse(Int, "100", 2)
4
julia> parse(Int, "100", 16)
256

The Int() function turns a character into an integer, and the Char() function turns an integer into a character.

julia> Char(0x203d) # the Interrobangis Unicode U+203d in hexadecimal
'‽'
julia> Int('‽')
8253
julia> hex('‽')
"203d"

To go from a single character string to the code number (such as its ASCII or UTF code number), try this:

julia> Int("S"[1])
83

If you're deeply attached to C-style printf() functionality, you'll be able to use a Julia macro (which are called by prefacing them with the @ sign):

julia> @printf("pi = %0.20f", float(pi))
pi = 3.14159265358979311600

or you can create another string using the sprintf() macro:

julia> @sprintf("pi = %0.20f", float(pi))
"pi = 3.14159265358979311600"

Convert a string to an array[edit]

To read from a string into an array, you can use the IOBuffer() function. This is available with a number of Julia functions (including printf()). Here's a string of data (it could have been read from a file):

julia> data="1 2 3 4
       5 6 7 8
       9 0 1 2"

"1 2 3 4\n5 6 7 8\n9 0 1 2"

Now you can "read" this string using functions such as readdlm(), the "read with delimiters" function:

julia> readdlm(IOBuffer(data))

3x4 Array{Float64,2}:
 1.0  2.0  3.0  4.0
 5.0  6.0  7.0  8.0
 9.0  0.0  1.0  2.0

You can add an optional type specification:

julia> readdlm(IOBuffer(data), Int)

3x4 Array{Int32,2}:
 1  2  3  4
 5  6  7  8
 9  0  1  2

Sometimes you want to do things to strings that you can do better with arrays. Here's an example.

julia> s = "/Users/me/Music/iTunes/iTunes Media/Mobile Applications";

You can explode the pathname string into an array of character objects, using collect(), which gathers the items in a collection or string into an array:

julia> collect(s)
55-element Array{Char,1}:
 '/'
 'U'
 's'
 'e'
 'r'
 's'
 '/'
 ...

Similarly, you can use split() to split the string and count the results:

julia> split(s, "")
55-element Array{Char,1}:
 '/'
 'U'
 's'
 'e'
 'r'
 's'
 '/'
 ...

To count the occurrences of a particular character object, you can use an anonymous function:

julia> count(c -> c == '/', collect(s))
6

although here converting to an array is unnecessary and inefficient. Here's a better way:

julia> count(c -> c == '/', s)
6

Finding and replacing things inside strings[edit]

If you want to know whether a string contains a specific character, use the general-purpose in() function.

julia> s = "Elementary, my dear Watson";
julia> in('m', s)
true

The contains() function, which accepts two strings, is more generally useful, because you can use substrings with one or more characters. Notice that you place the container first, then the string you're looking for:

julia> contains(s, "Wat")
true
julia> contains(s, "m")
true
julia> contains(s, "mi")
false
julia> contains(s, "me")
true

You can get the location of the first occurrence of a substring using search(). The second argument can be a single character, a vector or a set of characters, a string, or a regular expression:

julia> s ="You know my methods, Watson.";
julia> search(s, "h")
16:16

julia> search(s, ['a', 'e', 'i', 'o', 'u'])
2

This search is for the first occurrence of any of the set of characters, and 'o' was in the second position.

julia> search(s, "meth")
13:16

julia> search(s, r"m.*")
10:28

In each case, the result contains the indices of the characters, if present. If not:

julia> search(s, "mo")
0:-1

julia> s[0:-1]
""

For some tasks, you might prefer to use searchindex(), which returns either the start index or 0:

julia> searchindex(s, "m")
10

julia> searchindex(s, "mu")
0

You can also use a regular expression string (r" ") with search():

julia> search(s, r"m(y|e)")
10:11

looks for "my" or "me"

julia> s[search(s, r"m(y|e)")]
"my"

The replace() function returns a new string with a substring of characters replaced with something else:

julia> replace("Sherlock Holmes", "e", "ee")
"Sheerlock Holmees"

Usually the third argument is another string, as here. But you can also supply a function that processes the result:

julia> replace("Sherlock Holmes", "e", uppercase)
"ShErlock HolmEs"

where the function (here, the built-in uppercase() function) is applied to the matching substring.

There's no replace! function, where the "!" indicates a function that changes its argument. That's because you can't change a string — they're immutable.

Regular expressions[edit]

You can use regular expressions to find matches for substrings. Some functions that accept a regular expression are:

  • replace() changes occurrences of regular expressions
  • ismatch() returns true or false if there's a match for a regular expression
  • match() returns the first match or nothing
  • matchall() returns an array of matches
  • eachmatch() returns an iterator that lets you go through all the matches
  • search() searches a string for a match
  • split() splits a string at every match

Use replace() to replace each consonant with an underscore:

julia> replace("Elementary, my dear Watson!", r"[^aeiou]", "_")
"__e_e__a________ea___a__o__"

and the following code replaces each vowel with the results of running a function on each match:

julia> replace("Elementary, my dear Watson!", r"[aeiou]", uppercase)
"ElEmEntAry, my dEAr WAtsOn!"

With replace() you can access matches if you provide a special substitution string s"", where \1 refers to the first match, \2 to the second, and so on. A letter preceded by a space is repeated three times:

julia> replace("Elementary, my dear Watson!", r"(\s)([a-z])", s"\1\2\2\2") 
"Elementary, mmmy dddear Watson!"

For more regular expression fun, there are the -match- functions.

Here I've loaded the complete text of "The Adventures of Sherlock Holmes" from a file into the string called text:

julia> f = "/tmp/adventures-of-sherlock-holmes.txt"
julia> text = readstring(f);

To use the possibility of a match as a Boolean condition, suitable for use in an if statement for example, use ismatch().

julia> ismatch(r"Opium", text)
false

julia> ismatch(r"(?i)Opium", text)
true

The word "opium" does appear in the text, but only in lower-case, hence the first false result — regular expressions are case-sensitive. The second search, a case-insensitive search (set by the flag (?i)) for "Opium", returns true.

You could check every line for the word using ismatch() in a simple loop:

for l in split(text, "\n")
    ismatch(r"opium", l) && println(l)
end

opium. The habit grew upon him, as I understand, from some
he had, when the fit was on him, made use of an opium den in the
brown opium smoke, and terraced with wooden berths, like the
wrinkled, bent with age, an opium pipe dangling down from between
very short time a decrepit figure had emerged from the opium den,
opium-smoking to cocaine injections, and all the other little
steps - for the house was none other than the opium den in which
lives upon the second floor of the opium den, and who was
learn to have been the lodger at the opium den, and to have been
doing in the opium den, what happened to him when there, where is
"Had he ever showed any signs of having taken opium?"
room above the opium den when I looked out of my window and saw,

For more useable output (in the REPL), add enumerate() and some highlighting:

julia> bold = "\x1b[1m"; default = "\x1b[0m";
julia> for (n,l) in enumerate(split(text, "\n"))
           ismatch(r"opium", l) && println("$n $(replace(l, "opium", "$(bold)opium$(default)"))")
       end
5087 opium. The habit grew upon him, as I understand, from some
5140 he had, when the fit was on him, made use of an opium den in the
5173 brown opium smoke, and terraced with wooden berths, like the
5237 wrinkled, bent with age, an opium pipe dangling down from between
5273 very short time a decrepit figure had emerged from the opium den,
5280 opium-smoking to cocaine injections, and all the other little
5429 steps - for the house was none other than the opium den in which
5486 lives upon the second floor of the opium den, and who was
5510 learn to have been the lodger at the opium den, and to have been
5593 doing in the opium den, what happened to him when there, where is
5846 "Had he ever showed any signs of having taken opium?"
6129 room above the opium den when I looked out of my window and saw,

There's an alternative syntax for adding regex modifiers, such as case-insensitive matches. Notice the "i" following the regex string:

julia> ismatch(r"m"i, s)
true

With the eachmatch() function, you apply the regex to the string to produce an iterator. For example, to look for substrings in our text matching the letters "L", followed by some other characters, ending with "ed":

julia> lmatch = eachmatch(r"L.*?ed", text);

The result in lmatch is an iterable object containing all the matches, as RegexMatch objects. Now we can work through the iterator and look at each match in turn. You can access a number of fields of the RegexMatch, to extract information about the match. For example, the .match field contains the matched substring:

julia>for i in lmatch
   println(i.match)
end

London - quite so! Your Majesty, as I understand, became entangled
Lodge. As it pulled
Lord, Mr. Wilson, that I was a red
League of the Red
League was founded
London when he was young, and he wanted
LSON" in white letters, upon a corner house, announced
League, and the copying of the 'Encyclopaed
Leadenhall Street Post Office, to be left till called
Let the whole incident be a sealed
Lestrade, being rather puzzled
Lestrade would have noted
...
Lestrade," drawled
Lestrade looked
Lord St. Simon has not already arrived
Lord St. Simon sank into a chair and passed
Lord St. Simon had by no means relaxed
Lordship. "I may be forced
London. What could have happened
London, and I had placed

Other fields include .captures, the captured substrings as an array of strings, .offset, the offset into the string at which the whole match begins, and .offsets, the offsets of the captured substrings.

If you don't want an iterable object, use the matchall() function instead:

julia> lmatches = matchall(r"L.*?ed", text);

Now the lmatches array contains the matching substrings, which you can inspect any way you want:

julia> lmatches[4:6]
3-element Array{SubString{UTF8String},1}:
 "League of the Red"
 "League was founded"
 "London when he was young, and he wanted"

The basic match() function looks for the first match for your regex. Use the .match field to extract the information from the RegexMatch object:

julia> match(r"She.*",text).match
"Sherlock Holmes she is always THE woman. I have seldom heard\r"

It's possible to use `filter` directly on an array of strings:

filter(r"(?i)Opium", map(chomp, readlines(open(f))))
20-element Array{AbstractString,1}:
 "opium. The habit grew upon him, as I understand, from some"
 "he had, when the fit was on him, made use of an opium den in the"
 "brown opium smoke, and terraced with wooden berths, like the"
 "wrinkled, bent with age, an opium pipe dangling down from between"
 "very short time a decrepit figure had emerged from the opium den,"
 "opium-smoking to cocaine injections, and all the other little"
 "steps - for the house was none other than the opium den in which"
 "lives upon the second floor of the opium den, and who was"
 "learn to have been the lodger at the opium den, and to have been"
 "doing in the opium den, what happened to him when there, where is"
 "\"Had he ever showed any signs of having taken opium?\""
 "room above the opium den when I looked out of my window and saw,"
 "opium, while the people at the house partook of the"
 "the powdered opium?  Above all, where could he, a"
 "summer.  The opium was probably brought from London. "
 "Powdered opium is by no means tasteless.  The flavor"
 "happened to come along with powdered opium upon the"
 "mutton for supper that night.  The opium was added"
 "                            opium, and poisons generally."
 "rebels, drunk with opium and with bang, were enough to remind us"

Testing and changing strings[edit]

There are lots of functions for testing and changing strings:

  • length(str) length of string
  • sizeof(str) length/size
  • startswith(strA, strB) does strA start with strB?
  • endswith(strA, strB) does strA end with strB?
  • contains(strA, strB) does strA contain strB?
  • all(isalnum, str) is str alphanumeric?
  • all(isalpha, str) is str alphabetic?
  • isascii(str) is str ASCII?
  • all(iscntrl, str) is str control characters?
  • all(isdigit, str) is str 0-9?
  • all(islower, str) is str lowercase?
  • all(ispunct, str) does str consist of punctuation?
  • all(isspace, str) is str whitespace characters?
  • all(isupper, str) is str uppercase?
  • all(isxdigit, str) is str hexadecimal digits?
  • uppercase(str) return a copy of str converted to uppercase
  • lowercase(str) return a copy of str converted to lowercase
  • titlecase(str) return copy of str with the first character of each word converted to uppercase
  • ucfirst(str) return copy of str with first character converted to uppercase
  • lcfirst(str) return copy of str with first character converted to lowercase
  • chop(str) return a copy with the last character removed
  • chomp(str) return a copy with the last character removed only if it's a newline

Streams[edit]

To write to a string, you can use a Julia stream. The sprint() (String Print) function lets you use a function as the first argument, and uses the function and the rest of the arguments to send information to a stream.

For example, consider the following function, f. The body of the function maps an anonymous 'print' function over the arguments, enclosing them with angle brackets. When used by sprint, the function f processes the remaining arguments and sends them to the stream, which, with sprint(), is a string.

julia> function f(io::IO, args...)
    map((a) -> print(io,"<",a, ">"), args)
end

f (generic function with 1 method)
julia> sprint(f, "fred", "jim", "bill", "fred blogs")

"<fred><jim><bill><fred blogs>"

Functions like println() can take an IOBuffer or stream as their first argument. This lets you print to streams instead of printing to the standard output device:

julia> iobuffer = IOBuffer()

IOBuffer(data=Uint8[...], readable=true, writable=true, seekable=true, append=false, size=0, maxsize=Inf, ptr=1, mark=-1)

julia> for i in 1:100
           println(iobuffer, string(i))
       end

After this, the in-memory stream called iobuffer is full of numbers and newlines, even though nothing was printed on the terminal. To copy the contents of iobuffer from the stream to a string or array, you can use takebuf_string() (or takebuf_array()):

julia> takebuf_string(iobuffer)
"1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40\n41\n42\n43\n44\n45\n46\n47\n48\n49\n50\n51\n52\n53\n54\n55\n56\n57\n58\n59\n60\n61\n62\n63\n64\n65\n66\n67\n68\n69\n70\n71\n72\n73\n74\n75\n76\n77\n78\n79\n80\n81\n82\n83\n84\n85\n86\n87\n88\n89\n90\n91\n92\n93\n94\n95\n96\n97\n98\n99\n100\n"
julia>

Working with Text Files[edit]

« Introducing Julia
Working with text files
»
Strings and characters Working with dates and times

Working with text files[edit]

Reading from files[edit]

The standard approach for getting information from a text file is using the open(), read(), and close() functions.

Open[edit]

To read text from a file, first obtain a file handle:

f = open("sherlock-holmes.txt")

f is now Julia's connection to the file on disk. When you've finished with the file, you should close the connection, using:

close(f)

However, the recommended way to read a file in Julia is to wrap any file-processing functions inside a do block:

 open("sherlock-holmes") do f
    # do stuff with the open file
 end

The open file is automatically closed when this block finishes. See Controlling the flow for more about do blocks.

Slurp — reading a file all at once[edit]

You can read the entire contents of an open file at once with readstring():

  s = readstring(f)

This returns a string, complete with newlines.

Or you can use readlines() to read in the whole file as an array, with each line an element:

julia> f = open("sherlock-holmes.txt");
julia> lines = readlines(f)
76803-element Array{String,1}:
 "THE ADVENTURES OF SHERLOCK HOLMES by SIR ARTHUR CONAN DOYLE\r\n"
 "\r\n"
 "   I. A Scandal in Bohemia\r\n"
 "  II. The Red-headed League\r\n"
 ...
 "Holmes, rather to my disappointment, manifested no further\r\n"
 "interest in her when once she had ceased to be the centre of one\r\n"
 "of his problems, and she is now the head of a private school at\r\n"
 "Walsall, where I believe that she has met with considerable success.\r\n"
julia> close(f)

Now you can step through the lines:

counter = 1
for l in lines
   println("$counter $l")
   counter += 1
end
1 THE ADVENTURES OF SHERLOCK HOLMES by SIR ARTHUR CONAN DOYLE
2
3    I. A Scandal in Bohemia
4   II. The Red-headed League
5  III. A Case of Identity
6   IV. The Boscombe Valley Mystery
...
12638 interest in her when once she had ceased to be the centre of one
12639 of his problems, and she is now the head of a private school at
12640 Walsall, where I believe that she has met with considerable success.

There's a better way to do this — see enumerate(), below.

You might find the chomp() function useful — it removes the trailing newline from a string.

Line by line[edit]

The eachline() function turns a source into an iterator. This allows you to process a file a line at a time:

f = open("sherlock-holmes.txt");
for ln in eachline(f)
       print("$(length(ln)), $ln")
end
close(f)
1, THE ADVENTURES OF SHERLOCK HOLMES by SIR ARTHUR CONAN DOYLE
2,
28,    I. A Scandal in Bohemia
29,   II. The Red-headed League
26,  III. A Case of Identity
35,   IV. The Boscombe Valley Mystery

62, the island of Mauritius. As to Miss Violet Hunter, my friend
60, Holmes, rather to my disappointment, manifested no further
66, interest in her when once she had ceased to be the centre of one
65, of his problems, and she is now the head of a private school at
70, Walsall, where I believe that she has met with considerable success.

Another approach is to read until you reach the end of the file. Typically you also want to keep track of which line you're on:

 open("sherlock-holmes") do f
   line = 1
   while !eof(f)
     x = readline(f)
     println("$line $x")
     line += 1
   end
 end

An easier approach is to use enumerate() on an iterable object — you'll get the line numbering for free:

open("sherlock-holmes.txt") do f
    for i in enumerate(eachline(f))
      println(i)
    end
end

If you have a specific function that you want to call on a file, you can use this alternative syntax:

julia> function bigUpFunc(f::IOStream)
    return uppercase(readstring(f))
end
julia> upversion = bigUpFunc(open("sherlock-holmes.txt"));
julia> upversion[1:21]
"THE COMPLETE SHERLOCK"

This opens the file, runs the bigUpFunc function on it, then closes it again, assigning the processed contents to the variable.

You can use readcsv() and readdlm() functions to read lines from CSV files or files delimited with certain characters, such as data files, arrays stored as text files, and tables. And if you use the DataFrames package, there's also a readtable() specifically designed to read data into a table.

Working with paths and filenames[edit]

These functions will be useful for working with filenames:

  • cd(path) changes the current directory
  • readdir(path) returns a lists of the contents of a named directory, or the current directory,
  • abspath(path) adds the current directory's path to a filename to make an absolute pathname
  • joinpath(str, str, ...) assembles a pathname from pieces
  • isdir(path) tells you whether the path is a directory
  • splitdir(path) - split a path into a tuple of the directory name and file name.
  • splitdrive(path) - on Windows, split a path into the drive letter part and the path part. On Unix systems, the first component is always the empty string.
  • splitext(path) - if the last component of a path contains a dot, split the path into everything before the dot and everything including and after the dot. Otherwise, return a tuple of the argument unmodified and the empty string.
  • expanduser(path) - replace a tilde character at the start of a path with the current user's home directory.
  • normpath(path) - normalize a path, removing "." and ".." entries.
  • realpath(path) - canonicalize a path by expanding symbolic links and removing "." and ".." entries.
  • homedir() - current user's home directory.
  • dirname(path) - get the directory part of a path.
  • basename(path)- get the file name part of a path.

To work on a restricted selection of files in a directory, use filter() and an anonymous function to filter the file names and just keep the ones you want. (filter() is more of a fishing net or sieve, rather than a coffee filter, in that it catches what you want to keep.)

for f in filter(x -> endswith(x, "jl"), readdir())
    println(f)
end

Astro.jl
calendar.jl
constants.jl
coordinates.jl
...
pseudoscience.jl
riseset.jl
sidereal.jl
sun.jl
utils.jl
vsop87d.jl

If you want to match a group of files using a regular expression, then use ismatch(). Let's look for both JPG and PNG files (remembering to escape the "."):

for f in filter(x -> ismatch(r"\.jpg|\.png", x), readdir())
    println(f)
end

034571172750.jpg
034571172750.png
51ZN2sCNfVL._SS400_.jpg
51bU7lucOJL._SL500_AA300_.jpg
Voronoy.jpg
kblue.png
korange.png
penrose.jpg
r-home-id-r4.png
wave.jpg

To examine a file hierarchy, use walkdir(), which lets you work through a directory, and examine the files in each directory in turn.

File information[edit]

If you want information about a specific file, use stat("pathname"), and then use one of the fields to find out the information. Here's how to get all the information and the field names listed for a file "i":

julia> for n in fieldnames(stat(i))
    println(n, ": ", getfield(stat(i),n))
end

device: 16777219
inode: 2955324
mode: 16877
nlink: 943
uid: 502
gid: 20
rdev: 0
size: 32062
blksize: 4096
blocks: 0
mtime:1.409769933e9
ctime:1.409769933e9
julia>

Although you can access these fields via a 'stat' structure:

 julia> s = stat("Untitled1.ipynb")
 StatStruct(mode=100644, size=64424)

 julia> s.ctime
 1.446649269e9

you can also use some of them directly:

 julia> ctime("Untitled2.ipynb")
 1.446649269e9

although not size:

 julia> s.size
 64424

To work on specific files that meet conditions — all IPython files modified after a certain date, for example — you could use something like this:

 julia>function process_file(path)
           println(path, " ", stat(path).size)
       end 
       for afile in filter!(f ->
               endswith(f, "ipynb") &&
               (mtime(f) > Dates.datetime2unix(DateTime("2015-11-03T09:00"))),
           readdir())
           process_file(realpath(afile))
       end

Interacting with the file system[edit]

The cp(), mv(), rm(), and touch() functions have the same names and functions as their Unix shell counterparts.

To convert filenames to pathnames, use abspath(). You can map this over a list of files in a directory:

julia> map(abspath,readdir())
67-element Array{String,1}:
 "/Users/me/.CFUserTextEncoding"
 "/Users/me/.DS_Store"
 "/Users/me/.Trash"
 "/Users/me/.Xauthority"
 "/Users/me/.ahbbighrc"
 "/Users/me/.apdisk"
 "/Users/me/.atom"
...

To restrict the list to filenames that contain a particular substring, use an anonymous function inside filter() — something like this:

julia> filter(x -> contains(x, "re"),map(abspath, readdir()))
4-element Array{String,1}:
 "/Users/me/.DS_Store"
 "/Users/me/.gitignore"
 "/Users/me/.hgignore_global"
 "/Users/me/Pictures"
 ...

To restrict the list to regular expression matches, try this:

julia> filter(x -> ismatch(r"recur.*\.jl", x), map(abspath, readdir()))
2-element Array{String,1}:
 "/Users/me/julia/recursive-directory-scan.jl"
 "/Users/me/julia/recursive-text.jl"

Writing to files[edit]

To write to a text file, open it using the "w" flag and make sure that you have permission to create the file in the specified directory:

open("/tmp/t.txt", "w") do f
        write(f, "A, B, C, D\n")
     end

Here's how to write 20 lines of 4 random numbers between 1 and 10, separated by commas:

function fourrandom()
    return rand(1:10,4)
end

open("/tmp/t.txt", "w") do f
           for i in 1:20
              n1, n2, n3, n4 = fourrandom()
              write(f, "$n1, $n2, $n3, $n4 \n")
           end
       end

A quicker alternative to this is to use the writedlm() function, described next:

writedlm("/tmp/test.txt", rand(1:10, 20, 4), ", ")

Writing and reading array to and from a file[edit]

The convenient writedlm() and readdlm() functions let you write an array or collection to a file.

writedlm() writes the contents of an object to a text file, and readdlm() reads the data from a file into an array:

julia> numbers = rand(5,5)
5x5 Array{Float64,2}:
 0.913583  0.312291  0.0855798  0.0592331  0.371789
 0.13747   0.422435  0.295057   0.736044   0.763928
 0.360894  0.434373  0.870768   0.469624   0.268495
 0.620462  0.456771  0.258094   0.646355   0.275826
 0.497492  0.854383  0.171938   0.870345   0.783558

julia> writedlm("/tmp/test.txt", numbers)

You can see the file using the shell (type a semicolon ";" to switch):

<shell>  cat "/tmp/test.txt"
.9135833328830523	.3122905420350348	.08557977218948465	.0592330821115965	.3717889559226475
.13747015238054083	.42243494637594203	.29505701073304524	.7360443978397753	.7639280496847236
.36089432672073607	.43437288984307787	.870767989032692	.4696243851552686	.26849468736154325
.6204624598015906	.4567706404666232	.25809436255988105	.6463554854347682	.27582613759302377
.4974916625466639	.8543829989347014	.17193814498701587	.8703447748713236	.783557793485824

The elements are separated by tabs unless you specify another delimiter. Here, a colon is used to delimit the numbers:

julia> writedlm("/tmp/test.txt", rand(1:6, 10, 10), ":")
shell>  cat "/tmp/test.txt"
3:3:3:2:3:2:6:2:3:5
3:1:2:1:5:6:6:1:3:6
5:2:3:1:4:4:4:3:4:1
3:2:1:3:3:1:1:1:5:6
4:2:4:4:4:2:3:5:1:6
6:6:4:1:6:6:3:4:5:4
2:1:3:1:4:1:5:4:6:6
4:4:6:4:6:6:1:4:2:3
1:4:4:1:1:1:5:6:5:6
2:4:4:3:6:6:1:1:5:5

To read in data from a text file, you can use readdlm().

julia> numbers = rand(5,5)
5x5 Array{Float64,2}:
 0.862955  0.00827944  0.811526  0.854526  0.747977
 0.661742  0.535057    0.186404  0.592903  0.758013
 0.800939  0.949748    0.86552   0.113001  0.0849006
 0.691113  0.0184901   0.170052  0.421047  0.374274
 0.536154  0.48647     0.926233  0.683502  0.116988

julia> writedlm("/tmp/test.txt", numbers)

julia> numbers = readdlm("/tmp/test.txt")
5x5 Array{Float64,2}:
 0.862955  0.00827944  0.811526  0.854526  0.747977
 0.661742  0.535057    0.186404  0.592903  0.758013
 0.800939  0.949748    0.86552   0.113001  0.0849006
 0.691113  0.0184901   0.170052  0.421047  0.374274
 0.536154  0.48647     0.926233  0.683502  0.116988

Since it's so common to use files where the elements are separated with commas rather than tabs (CSV files), Julia provides "-csv" versions of these "-dlm" functions, writecsv() and readcsv(). As ever, refer to the official documentation for options and keywords. There are also a number of Julia packages specifically designed for reading and writing data to files, including DataFrames.jl and CSV.jl. Look through the Julia package directory for these and more.

Working with Dates and Times[edit]

« Introducing Julia
Working with dates and times
»
Working with text files Plotting

Working with dates and times[edit]

Functions for working with dates and times are provided in the package Dates. This is built-in to the base Julia system.

Prefixes[edit]

Only the Date(), DateTime(), and now() functions are exported; to use all other Dates functions, you’ll need to prefix each function call with an explicit Dates., e.g. Dates.dayofweek(dt), as shown in this chapter. However, you could add the line using Dates to your code to bring all exported functions into Main, so that they can be used without the Dates. prefix.

Types[edit]

This diagram shows the relationship between the various types used to store Times, Dates, and DateTimes.

Shows the hierarchy of date and date-time types in Julia v0.6.

Date, Time, and DateTimes[edit]

There are three main datatypes available:

  • A Time object represents a precise moment of time in a day. It doesn't say anything about the day of the week, or the year, though. It's accurate to a nanosecond.
  • A Date object represents just a date: no time zones, no daylight saving issues, etc... It's accurate to, well, a day.
  • A DateTime object is a combination of a date and a time of day, and so it specifies an exact moment in time. It's accurate to a millisecond or so.

Use one of these constructors to make the type of object you want:

julia> rightnow = Dates.Time(now())
16:51:56.374
julia> birthday = Dates.Date(1997,3,15)
1997-03-15
julia> armistice = Dates.DateTime(1918,11,11,11,11,11)
1918-11-11T11:11:11

The today() function returns a Date object for the current date:

julia> datetoday = Dates.today()
2014-09-02

The now() function returns a DateTime object for the current instant in time:

julia> datetimenow = now()
2014-09-02T08:20:07.437

(We used now() to make a Dates.DateTime, then converted it to a Dates.Time using Dates.Time().)

Sometimes you want UTC (the reference time for the world, without local adjustments for daylight savings):

julia> Dates.now(Dates.UTC)
2014-09-02T08:27:54.14

To create an object from a formatted string, use the DateTime() function in Dates, and supply a suitable format string that matches the formatting:

julia> Dates.DateTime("20140529 120000", "yyyymmdd HHMMSS")
2014-05-29T12:00:00
julia> Dates.DateTime("18/05/2009 16:12", "dd/mm/yyyy HH:MM")
2009-05-18T16:12:00
julia> theday = Dates.DateTime("2014-09-02T08:20:07") # defaults to expecting ISO8601 format
2014-09-02T08:20:07

See Date Formatting below for more examples.

Date and time queries[edit]

Once you have a date/time or date object, you can extract particular information from it with the following functions. For both date and datetime objects:

julia> Dates.year(birthday)
1997
julia> Dates.year(datetoday)
2014
julia> Dates.month(birthday)
3
julia> Dates.month(datetoday)
9
julia> Dates.day(birthday)
15
julia> Dates.day(datetoday)
2

and, for date/time objects:

julia> Dates.minute(now())
37
julia> Dates.hour(now())
16
julia> Dates.second(now())
8
julia> Dates.minute(rightnow)
37
julia> Dates.hour(rightnow)
16
julia> Dates.second(rightnow)
8

There's also a bunch of other useful ones:

julia> Dates.dayofweek(birthday)
6
julia> Dates.dayname(birthday)
"Saturday"
julia> Dates.yearmonth(now())
(2014,9)
julia> Dates.yearmonthday(birthday)
(1997,3,15)
julia> Dates.isleapyear(birthday)
false
julia> Dates.daysofweekinmonth(datetoday)
5
julia> Dates.monthname(birthday)
"March"
julia> Dates.monthday(now())
(9,2)
julia> Dates.dayofweekofmonth(birthday)
3

Two of those functions are very similarly named: the Dates.daysofweekinmonth() (daysof weekinmonth) function tells you how many days there are in the month with the same day name as the specified day — there are five Tuesdays in the current month (at the time of writing). The last function, dayofweekofmonth(birthday) (dayofweekofmonth), tells us that the 15th of March, 1997, was the third Saturday of the month.

Date arithmetic[edit]

You can do arithmetic on dates and date/time objects. Subtracting two dates or datetimes to find the difference is the most obvious one:

julia> datetoday - birthday
6380 days

julia> datetimenow - armistice
3023472252000 milliseconds

which you can convert to Dates.Days or Dates.Milliseconds or some other unit:

julia> Dates.Period(datetoday - birthday)
7357 days

julia> Dates.canonicalize(Dates.CompoundPeriod(datetimenow - armistice))
5138 weeks, 5 days, 5 hours, 46 minutes, 1 second, 541 milliseconds

julia> convert(Dates.Day, Dates.Period(Dates.today() - Dates.Date(2016, 1, 1)))
491 days

julia> convert(Dates.Millisecond, Dates.Period(Dates.today() - Dates.Date(2016, 1, 1)))
42422400000 milliseconds

To add and subtract periods of time to date and date/time objects, use the Dates. constructor functions to specify the period. For example, Dates.Year(20) defines a period of 20 years, and Dates.Month(6) defines a period of 6 months. So, to add 20 years and 6 months to the birthday date:

julia> birthday + Dates.Year(20) + Dates.Month(6)
2017-09-15

Here's 6 months ago from now:

julia> now() - Dates.Month(6)
2014-03-02T16:43:08

and similarly for months, weeks:

julia> now() - Dates.Year(2) - Dates.Month(6)
2012-03-02T16:44:03

and similarly for weeks and hours. Here's the date and time for two weeks and 12 hours from now:

julia> now() + Dates.Week(2) + Dates.Hour(12)
2015-09-18T20:49:16

and there are

julia> Date(Dates.year(now()), 12, 25) - Dates.today()
148 days

148 (shopping) days till Christmas (at the time this was written).

Range of dates[edit]

You can make iterable range objects that define a range of dates:

julia> d = Date(1980,1,1):Dates.Month(3):Date(2015,1,1)
1980-01-01:3 months:2015-01-01

This iterator yields the first day of every third month. To find out which of these fall on weekdays, you can provide an anonymous function to filter() that tests the day name against the given day names:

julia> weekdays = filter(dy -> Dates.dayname(dy) != "Saturday" && Dates.dayname(dy) != "Sunday" , d)
104-element Array{Date,1}:
 1980-01-01
 1980-04-01
 1980-07-01
          
 2014-07-01
 2014-10-01
 2015-01-01

julia> length(ans) / length(d)
0.7375886524822695

or about 74%.

Similarly, here's a range of times 3 hours apart from now, for a year hence:

julia> d = collect(DateTime(now()):Dates.Hour(3):DateTime(now() + Dates.Year(1)))
2929-element Array{DateTime,1}:
 2015-09-04T08:30:59
 2015-09-04T11:30:59
 2015-09-04T14:30:59
                    
 2016-09-03T20:30:59
 2016-09-03T23:30:59
 2016-09-04T02:30:59
 2016-09-04T05:30:59
 2016-09-04T08:30:59

If you have to pay a bill every 30 days, starting on the 1st of January 2017, the following code shows how the due date creeps forward every month:

julia> foreach(d -> println(Dates.format(d, "d u yyyy")), Date("2017-01-01"):Dates.Day(30):Date("2018-01-01"))
1 Jan 2017
31 Jan 2017
2 Mar 2017
1 Apr 2017
1 May 2017
31 May 2017
30 Jun 2017
30 Jul 2017
29 Aug 2017
28 Sep 2017
28 Oct 2017
27 Nov 2017
27 Dec 2017

Date formatting[edit]

To specify date formats, you use date formatting codes in a formatting string. Each character refers to a date/time element:

y  Year digit eg yyyy => 2015, yy => 15
m  Month digit eg m => 3 or 03
u  Month name eg Jan
U  Month name eg January
e  Day of week eg Tue
E  Day of week eg Tuesday
d  Day eg 3 or 03
H  Hour digit eg HH => 00
M  Minute digit eg MM => 00
S  Second digit eg S => 00
s  Millisecond digit eg .000

You can use these formatting strings with functions such as DateTime() and Dates.format(). For example, you create a DateTime object from a string by identifying the different elements in the incoming string:

 julia> Date("Tue, 28 Jul 2015", "e, d u y")
 2015-07-28
 julia> DateTime("Tue, 28 Jul 2015 11:43:14", "e, d u y H:M:S")
 2015-07-28T11:43:14

Other characters are used literally. In the second example, the formatting characters matched up as follows:

Tue, 28 Jul 2015 11:43:14
e  ,  d   u    y  H: M: S

You can supply a format string to Dates.format to format a date object. In the formatting string, you repeat the characters to control how years and days, for example, are output:

julia> timenow = now()
2015-07-28T11:43:14

julia> Dates.format(timenow, "e, dd u yyyy HH:MM:SS")
"Tue, 28 Jul 2015 11:43:14"

When you're creating a formatted date, you can double some of the components of the format string to produce a leading zero for single digit date elements:

julia> anothertime = DateTime("Tue, 8 Jul 2015 2:3:7", "e, d u y H:M:S")
2015-07-08T02:03:07
julia> Dates.format(anothertime, "e: dd u yy, HH.MM.SS") # with leading zeros
"Wed: 08 Jul 15, 02.03.07"
julia> Dates.format(anothertime, "e: d u yy, H.M.S")
"Wed: 8 Jul 15, 2.3.7"

To convert a date string from one format to another, you can use DateTime() and a format string to convert the string to a DateTime object, then DateFormat() to output the object in a different format:

julia> formatted_date = "Tue, 28 Jul 2015 11:43:14"
"Tue, 28 Jul 2015 11:43:14"
julia> temp = Dates.DateTime(formatted_date, "e, dd u yyyy HH:MM:SS")
2015-07-28T11:43:14
julia> Dates.format(temp, "dd, U, yyyy HH:MM, e")
"28, July, 2015 11:43, Tue"

If you're doing a lot of date formatting (you can apply date functions to an array of strings), it's a good idea to pre-define a DateFormat object and then use that for bulk conversions (this is quicker):

julia> dformat = Dates.DateFormat("y-m-d");
julia> Date([
       "2010-01-01", 
       "2011-03-23", 
       "2012-11-3", 
       "2013-4-13", 
       "2014-9-20", 
       "2015-3-1"
       ], dformat)

6-element Array{Date,1}:
 2010-01-01
 2011-03-23
 2012-11-03
 2013-04-13
 2014-09-20
 2015-03-01

There are some built-in formats that you can use. For example, there's Dates.ISODateTimeFormat to give you the ISO8601 format:

julia> DateTime([
              "2010-01-01",
              "2011-03-23",
              "2012-11-3",
              "2013-4-13",
              "2014-9-20",
              "2015-3-1"
              ], Dates.ISODateTimeFormat)
6-element Array{DateTime,1}:
 2010-01-01T00:00:00
 2011-03-23T00:00:00
 2012-11-03T00:00:00
 2013-04-13T00:00:00
 2014-09-20T00:00:00
 2015-03-01T00:00:00

and here's good old RFC1123:

julia> Dates.format(now(), Dates.RFC1123Format)
"Sat, 30 Jul 2016 16:36:09"

Date adjustments[edit]

Sometimes you want to find a date nearest to another - for example, the first day of that week, or the last day of the month that contains that date. You can do this with the functions like Dates.firstdayofweek() and Dates.lastdayofmonth(). So, if we're currently in the middle of the week:

julia> Dates.dayname(now())
"Wednesday"

the first day of the week is returned by this:

julia> Dates.firstdayofweek(now())
2014-09-01T00:00:00

A more general solution is provided by the tofirst(), tolast(), tonext(), and toprev() methods.

With tonext() and toprev(), you can provide a (possibly anonymous) function that returns true when a date has been correctly adjusted. For example, the function:

d->Dates.dayofweek(d) == Dates.Tuesday

returns true if the day d is a Tuesday. Use this with the tonext() method:

julia> Dates.tonext(d->Dates.dayofweek(d) == Dates.Tuesday, birthday)
1997-03-18 # the first Tuesday after the birthday

Or you can find the next Sunday following the birthday date:

julia> Dates.tonext(d->Dates.dayname(d) == "Sunday", birthday)
1997-03-16 # the first Sunday after the birthday

With tofirst() and tolast(), you supply the date, and the first day of that month is returned. Supply the keyword argument of=Year to get the first day of that year.

julia> Dates.tofirst(birthday, 1)
1997-03-03
julia> Dates.tofirst(birthday, 1, of=Year)
1997-01-06

Rounding dates and times[edit]

You can use round(), floor(), and ceil(), usually used to round numbers up or down to the nearest preferred values, to adjust dates forward or backwards in time so that they have 'rounder' values.

julia> now()
2016-09-12T17:55:11.378

julia> Dates.format(round(DateTime(now()), Dates.Minute(15)), Dates.RFC1123Format)
"Mon, 12 Sep 2016 18:00:00"

The ceil() adjusts dates or times forward in time:

julia> ceil(birthday, Dates.Month)
1997-04-01

julia> ceil(birthday, Dates.Year)
1998-01-01

julia> ceil(birthday, Dates.Week)
1997-03-17

Recurring dates[edit]

It's useful to be able to find all dates in a range of dates that satisfy some particular criteria. For example, you can work out the second Sunday in a month by using the Dates.dayofweekofmonth() and Dates.dayname() functions.

For example, let's create a range of dates from the first of September 2014 until Christmas Day, 2014:

julia> dr = Dates.Date(2014,9,1):Dates.Date(2014,12,25)
2014-09-01:1 day:2014-12-25

Now an anonymous function similar to the ones we used in tonext() earlier finds a selection of those dates in that range that satisfy that function:

julia> filter(d -> Dates.dayname(d) == "Sunday", dr)
16-element Array{Date,1}:
 2014-09-07
 2014-09-14
 2014-09-21
 2014-09-28
 2014-10-05
 2014-10-12
 2014-10-19
 2014-10-26
 2014-11-02
 2014-11-09
 2014-11-16
 2014-11-23
 2014-11-30
 2014-12-07
 2014-12-14
 2014-12-21

These are the dates of every Sunday between September 1st 2014 until Christmas Day, 2014.

By combining criteria in the anonymous function, you can build up more complicated recurring events. Here's a list of all the Tuesdays in that period which are on days that are odd numbered and greater than 20:

julia> filter(d->Dates.dayname(d) == "Tuesday" && isodd(Dates.day(d)) && Dates.day(d) > 20, dr)
4-element Array{Date,1}:
 2014-09-23
 2014-10-21
 2014-11-25
 2014-12-23

and here's every second Tuesday in 2016 between April and November:

dr = Dates.Date(2015):Dates.Date(2016);
filter(dr) do x
                  Dates.dayofweek(x) == Dates.Tue &&
                  Dates.April <= Dates.month(x) <= Dates.Nov &&
                  Dates.dayofweekofmonth(x) == 2
           end
8-element Array{Base.Dates.Date,1}:
 2015-04-14
 2015-05-12
 2015-06-09
 2015-07-14
 2015-08-11
 2015-09-08
 2015-10-13
 2015-11-10

Unix time[edit]

You sometimes have to deal with another type of timekeeping: Unix time. Unix time is a count of the number of seconds that have elapsed since the beginning of the year 1970 (the birth of Unix). In Julia the count is stored in a 64 bit integer, and we'll never see the end of Unix time. (The universe will have ended long before 64 bit Unix time reaches the maximum possible value, which will be in approximately 292 billion years from now, at 15:30:08 on Sunday, 4 December 292,277,026,596.)

In Julia, the time() function, used without arguments, returns the Unix time value of the current second:

julia> time()
1.414141581230945e9


The strftime() ("string format time") function, which lives in the Libc module, converts a number of seconds in Unix time to a more readable form:

julia> Libc.strftime(86400 * 365.25 * 4) # 4 years worth of Unix seconds
"Tue  1 Jan 00:00:00 1974"

You can choose a different format by supplying a format string, with the different components of the date and time defined by '%' letter codes:

julia> Libc.strftime("%A, %B %e at %T, %Y", 86400 * 365.25 * 4)
"Tuesday, January  1 at 00:00:00, 1974"

The strptime() function takes a format string and a date string, and returns a TmStruct expression. This can then be converted to a Unix time value by passing it to time():

julia> Libc.strptime("%A, %B %e at %T, %Y", "Tuesday, January  1 at 00:00:00, 1974")
Base.Libc.TmStruct(0,0,0,1,0,74,2,0,0,0,0,0,0,0)

julia> time(ans)
1.262304e8

julia> time(Libc.strptime("%Y-%m-%d","2014-10-1"))
1.4121216e9

Julia also offers a unix2datetime() function, which converts a Unix time value to a date/time object:

julia> Dates.unix2datetime(time())
2014-10-24T09:26:29.305

Moments in time[edit]

DateTimes are stored as milliseconds. You can access the field instant to see the actual value.

 julia> moment=now()
 2017-02-01T12:45:46.326
 
 julia> moment.instant
 Base.Dates.UTInstant{Base.Dates.Millisecond}(63621636346326 milliseconds)

 julia> Dates.value(moment)
 63621636346326

If you use the more precise Dates.Time type, you can access nanoseconds.

 julia> moment = Dates.Time(now())
 17:38:44.33
 
 julia> moment.instant
 63524330000000 nanoseconds
 
 julia> Dates.value(moment)
 63524330000000

Timing and monitoring[edit]

If you want to tell how long functions take to run, you can use tic() and toc(), which provide the start and finish buttons of a virtual stopwatch:

function test(n)
    tic()
    for i in 1:n
       x = sin(rand())
    end
    toc()
end

julia> test(100000000)
elapsed time: 1.313614113 seconds
1.313614113

toc() prints the time since the most recent tic() and returns the value as well.

The @elapsed macro returns the number of seconds an expression took to evaluate:

function test(n)
     for i in 1:n
        x = sin(rand())
     end
end

julia> @elapsed test(100000000)
1.309819509

The @time macro tells you how long an expression took to evaluate, and how memory was allocated.

julia> @time test(100000000)
  2.532941 seconds (4 allocations: 160 bytes)

Plotting[edit]

« Introducing Julia
Plotting
»
Working with dates and times Metaprogramming

Plotting[edit]

There are a number of different packages for plotting in Julia, and there's probably one to suit your needs and tastes. This section is a quick introduction to one of them, Plots.jl, which is interesting because it talks to many of the other plotting packages. Before making plots with Julia, download and install the following packages:

julia> Pkg.add("Plots")
julia> Pkg.add("PyPlot")
julia> Pkg.add("GR")
julia> Pkg.add("UnicodePlots")
julia> Pkg.add("PlotlyJS")

The first package, Plots, is a high-level plotting package that interfaces with other plotting packages, which here are referred to as 'back-ends'. They act as the graphics "engines" that produce the graphics. Each of these is also a stand-alone plotting package, and can be used separately, but the advantage of using Plots as the interface is, as you'll see, a simpler and consistent interface.

You can start using the Plots.jl package in a Julia session in the usual way:

using Plots

You usually want to plot one or more series, arrays of numerical values. Alternatively, you can provide one or more functions to generate numerical values. The sample data used here is a simple array of numerical values representing the value of the Equation of Time for every day in the current year. (These values were once used to adjust mechanical clocks to account for the erratic orbit of the earth as it wobbles its way around its elliptical orbit.)

using Astro # obtain using Pkg.clone("http://github.com/cormullion/Astro.jl")
days = DateTime(2016,1,1, 0,0,0):DateTime(2016,12,31,0,0,0); # an array of datetimes
eq_values = Float64[equation_time(Dates.datetime2julian(day)) for day in DateTime(2016,1,1, 0,0,0):DateTime(2016,12,31,0,0,0)]

We now have an array of 366 Float64 values:

366-element Array{Float64,1}:
 -3.12598
 -3.59633
 -4.97289
 -5.41857
 -5.85688
  ⋮
 -1.08709
 -1.57435
 -2.05845
 -2.53887
 -3.01508

To plot this series, just pass it to Plots' plot() function.

plot(eq_values)

examples of plotting in Julia using Plots.jl

This has used the first available plotting engine, defaulting to PyPlot. Plots has treated the series as y-values, added other plotting "furniture", automatically provided the x-values, and then plotted everything for you.

If you want to switch to a different engine, use one of the provided functions: gr(), unicodeplots(), plotly(), and so on. For example, to switch to using the Unicodeplots plotting package (which uses Unicode characters to make plots, and is ideal for use in the REPL/terminal), do this:

unicodeplots();
plot(eq_values)


       +------------------------------------------------------------+   
    17 |                                                ,--u        | y1
       |                                              ./   "\       |   
       |                                             ./      \      |   
       |                                            ./        .     |   
       |                                            /         \.    |   
       |                                           /           \    |   
       |                                          .`           ",   |   
       |                                         ,`             \   |   
       |                   .r--\.                /               .  |   
       |                  /`    \.              /                \  |   
       |----------------nr-------fhr-----------v------------------v*|   
       |               .F          \.         ,`                  |.|   
       |,              /            \,       ,/                    `|   
       |l             /              "\.    ,`                      |   
       |".           /                 \-ur/`                       |   
       | \.         /`                                              |   
       |  l        ,`                                               |   
       |   \      ./                                                |   
       |   "\.  ./`                                                 |   
   -15 |     '--"                                                   |   
       +------------------------------------------------------------+   
       0                                                          370 

The GR engine/back-end is also a good general purpose plotting package:

gr();
plot(eq_values)

examples of plotting in Julia using Plots.jl

Plotting a function[edit]

Switch back to using PyPlot back-end:

pyplot()

The Equation of Time graph can be approximately modeled by a function combining a couple of sine functions:

equation(d) = -7.65 * sind(d) + 9.87 * sind(2d + 206);

It's easy to plot this function for every day of a year. Pass the function to plot(), and use a range to specify the start and end values:

plot(equation, 1:366)

examples of plotting in Julia using Plots.jl

To combine the two plots, so as to compare the equation and the calculated series versions, Plots lets you add another plot to an existing one. The usual Julia convention of using "!" to modify the argument is available here (in an implicit way — you don't actually have to provide the current plot as an argument): the second plot function, plot!() modifies the previous plot:

plot(eq_values);
plot!(equation, 1:366)

examples of plotting in Julia using Plots.jl

Customizing the plots[edit]

There is copious documentation for the Plots.jl package, and after studying it you'll be able to spend hours tweaking and customizing your plots to your heart's content. Here are a few examples.

The ticks along the x-axis show the numbers from 1:366, derived automatically from the single series provided. It would be better to see the dates themselves. First, create the strings:

datestrings = Dates.format(days, "u dd")

The supplied value for the xticks option is a tuple consisting of two arrays/ranges:

(xticks = (1:14:366, datestrings[1:14:366]))

the first provides the numerical values, the second provides matching text labels for the ticks.

Extra labels and legends are easily added, and you can access colors from the Colors.jl package. Here's a prettier version of the basic plot:

plot(equation, 1:7:366,
    label  = "equation of time (function)",
    line=(:purple, 0.85, 3, :dot));
   
plot!(
    eq_values,

    label  = "equation of time (calculated)",
    line=(:black, 0.5, 6, :solid),

    size=(800, 600),

    xticks = (1:14:366, datestrings[1:14:366]),
    yticks = -20:2.5:20, 

    ylabel = "Minutes faster or slower than GMT", 
    xlabel = "day in year", 
    
    title  = "The Equation of Time",
    xrotation = rad2deg(pi/3),
   
    fillrange = 0,
    fillalpha = 0.25,
    fillcolor = :lightgoldenrod,
    
    background_color = :ivory       
    )

examples of plotting in Julia using Plots.jl

Other packages[edit]

UnicodePlots[edit]

If you work in the REPL a lot, perhaps you want a quick and easy way to draw plots that use text rather than graphics for output? The UnicodePlots.jl package uses Unicode characters to draw various plots, avoiding the need to load various graphic libraries. It can produce:

  • scatter plots
  • line plots
  • bar plots (horizontal)
  • staircase plots
  • histograms (horizontal)
  • sparsity patterns
  • density plots

Download and add it to your Julia installation:

Pkg.add("UnicodePlots")

You have to do this just once. Now you load the module and import the functions:

using UnicodePlots

Here is a quick example of a line plot:

myPlot = lineplot([1, 2, 3, 7], [1, 2, -5, 7], title="My Plot", border=:dotted)
                       My Plot
      ⡤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢤
   10 ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
      ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
      ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
      ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
      ⡇⠀⠀⠀⠀⠔⠒⠊⠉⢣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
      ⡇⠉⠉⠉⠉⠉⠉⠉⠉⠉⠫⡉⠉⠉⠉⠉⠉⢉⠝⠋⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠁⢸
      ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠱⡀⠀⢀⡠⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
      ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⠔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
      ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
  -10 ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
      ⠓⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠚
      0                                       10

And here's a density plot:

myPlot = densityplot(collect(1:100), randn(100), border=:dotted)
      ⡤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢤
   10 ⡇                                        ⢸
      ⡇                                        ⢸
      ⡇                                        ⢸
      ⡇                                        ⢸
      ⡇                                        ⢸
      ⡇                                        ⢸
      ⡇                                        ⢸
      ⡇                            ░           ⢸
      ⡇ ░░░        ░ ▒░  ▒░     ░  ░ ░ ░ ░   ░ ⢸
      ⡇░░  ░▒░░▓▒▒ ▒░░ ▓░░ ░░░▒░ ░ ░   ▒ ░ ░▒░░⢸
      ⡇▓▒█▓▓▒█▓▒▒▒█▒▓▒▓▒▓▒▓▓▒▓▒▓▓▓█▒▒█▓▒▓▓▓▓▒▒▒⢸
      ⡇    ░     ░         ░░░ ░    ▒ ░ ░ ░░ ░ ⢸
      ⡇                          ░             ⢸
      ⡇                                        ⢸
      ⡇                                        ⢸
      ⡇                                        ⢸
      ⡇                                        ⢸
      ⡇                                        ⢸
      ⡇                                        ⢸
  -10 ⡇                                        ⢸
      ⠓⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠚
      0                                      100

(Note that it needs the terminal environment for the displayed graphs to be 100% successful - when you copy and paste, some of the magic is lost.)

Vega[edit]

Vega allows you to create visualizations in a web browser window. Vega is a visualization grammar, a declarative format for creating and saving visualization designs. With Vega you can describe data visualizations in a JSON format, and generate interactive views using either HTML5 Canvas or SVG. You can produce:

  • Area plots
  • Bar plots/Histograms
  • Line plots
  • Scatter plots
  • Pie/Donut charts
  • Waterfall charts
  • Wordclouds

The vega.js and d3.js libraries needed to render graphics are provided as part of the package. Vega.jl works with both IPython/Jupyter notebooks and the Julia REPL. When using IPython/Jupyter notebooks, the graphics will automatically be printed in-line. Submitting plots via the REPL will either open a new tab in the currently open (default) browser, or trigger the default browser to open.

To use Vega, first add the package to your Julia installation. You have to do this just once:

Pkg.add("Vega")

Here's how to create a stacked area plot.

using Vega
x = [0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9]
y = [28, 43, 81, 19, 52, 24, 87, 17, 68, 49, 55, 91, 53, 87, 48, 49, 66, 27, 16, 15]
g = [0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1]
a = areaplot(x = x, y = y, group = g, stacked = true)

graphic created with Julia and Vega.jl

A general feature of Vega is that you can modify a visualization after you've created it. So, let's change the color scheme using a function (notice the "!" to indicate that the arguments are modified):

colorscheme!(a, ("Reds", 3))

graphic created with Julia and Vega.jl

You can create pie (and donut) charts easily by supplying two arrays. The x array provides the labels, the y array provides the quantities:

 fruit = ["peaches", "plums", "blueberries", "strawberries", "bananas"];
 bushels = [100, 32, 180, 46, 21];
 piechart(x = fruit, y = bushels, holesize = 125)

a pie/donut chart created in Julia/Vega.jl

Bokeh[edit]

Bokeh.jl provides an interface to Bokeh, the Python interactive visualization library that targets modern web browsers for presentation. Its goal is to provide elegant, concise construction of novel graphics in the style of D3.js, but also deliver this capability with high-performance interactivity over very large or streaming datasets.

First, add the package to your Julia installation. You have to do this just once:

Pkg.add("Bokeh")

Here's an example of a date plot. Bokeh can render the X or Y axis as dates.

using Bokeh
using Dates # for version 0.3
start = Date(2015, 6, 21)
days = 120
x = map(d -> start + Dates.Day(d), 1:days)
y = 15 + randn(days) * 4
plot(x, y, title="A typical British Summer", legends=["Temperature"])

a plot produced by Julia and Bokeh.jl

Metaprogramming[edit]

« Introducing Julia
Metaprogramming
»
Plotting Modules and packages

What is metaprogramming?[edit]

Meta-programming is when you write Julia code to process and modify Julia code. With the meta-programming tools, you can write Julia code that modifies other parts of your source files, and even control if and when the modified code runs.

In Julia, the execution of raw source code takes place in two stages. (In reality there are more stages than this, but at this point we'll focus on just these two.)

Stage 1 is when your raw Julia code is parsed — converted into a form that is suitable for evaluation. You'll be familiar with this phase, because this is when all your syntax mistakes are noticed... The result of this is an abstract syntax tree or AST (Abstract Syntax Tree), a structure that contains all your code, but in a format that is easier to manipulate than the human-friendly syntax normally used.

Stage 2 is when that parsed code is executed. Usually, when you type code into the REPL and press Return, or when you run a Julia file from the command line, you don't notice the two stages, because they happen so quickly. However, with Julia's metaprogramming facilities, you can access the code after it's been parsed but before it's evaluated.

This lets you do things that you can't normally do. For example, you can convert simple expressions to more complicated expressions, or examine code before it runs and change it so that it runs faster. Any code that you intercept and modify using these meta-programming tools will eventually be evaluated in the usual way, running as fast as ordinary Julia code.

You may have already used two existing examples of meta-programming in Julia:

- the @time macro:

julia> @time [sin(cos(i)) for i in 1:100000];
elapsed time: 0.00721026 seconds (800048 bytes allocated)

The @time macro inserts a "start the stopwatch" command at the beginning of the code, before passing the expression on to be evaluated. When the code has finished running, a "finish the stopwatch" command is added, followed by the calculations to report the elapsed time and memory usage.

- the @which macro

julia> @which 2 + 2
+(x::Int64,y::Int64) at int.jl:33

This macro doesn't allow the expression 2 + 2 to be evaluated at all. Instead, it reports which method would be used for these particular arguments. And it also tells you the source file that contains the method's definition, and the line number.

Other uses for meta-programming include the automation of tedious coding jobs by writing short pieces of code that produce larger chunks of code, and the ability to improve the performance of 'standard' code by producing the sort of faster code that perhaps you wouldn't want to write by hand.

Quoted expressions[edit]

For meta-programming to work, there has to be a way to stop Julia evaluating expressions as soon as the parsing phase has finished. This is the ':' (colon) prefix operator:

julia> x = 3
3
julia> :x
:x

To Julia, the :x is an unevaluated or quoted symbol.

(If you're unfamiliar with the use of quoted symbols in computer programming, think of how quotes are sometimes used in writing to distinguish between ordinary use and special use. For example, in the sentence:

'Copper' contains six letters.

the quotes indicate that the word 'Copper' is not a reference to the metal, but to the word itself. In the same way, in :x, the colon before the symbol is to make you and Julia think of 'x' as an unevaluated symbol rather than as the value 3.)

To quote whole expressions rather than individual symbols, start with a colon and then enclose the Julia expression in parentheses:

julia> :(2 + 2)

:(2 + 2)

There's an alternative form of the :( ) construction that uses the quote ... end keywords to enclose and quote an expression:

julia> quote
           2 + 2
       end

quote  # none, line 2:
    2 + 2
end

julia> expression = quote
          for i = 1:10
              println(i)
          end
       end

quote  # none, line 3:
    for i = 1:10 # none, line 4:
        println(i)
    end
end

This object expression is of type Expr:

julia> typeof(expression)
Expr

It's parsed, primed, and ready to go.

Evaluating expressions[edit]

There's also a function for evaluating an unevaluated expression. It's called eval():

julia> eval(:x)
3
julia> eval(:(2 + 2))
4
julia> eval(expression)
1
2
3
4
5
6
7
8
9
10

With these tools, it's possible to create any expression and store it without having it evaluate:

julia> e = :(
           for i in 1:10
               println(i)
           end
       )

:(for i = 1:10 # line 2:
    println(i)
end)

and then to recall and evaluate it later:

julia> eval(e)
1
2
3
4
5
6
7
8
9
10

It's also possible to modify the contents of the expression before it's evaluated.

Inside Expressions[edit]

Once you have Julia code in an unevaluated expression, rather than as a piece of text in a string, you can do things with it.

Here's another expression:

julia> P = quote
           a = 2
           b = 3
           c = 4
           d = 5
           e = sum([a,b,c,d])
       end

quote  # none, line 2:
    a = 2 # line 3:
    b = 3 # line 4:
    c = 4 # line 5:
    d = 5 # line 6:
    e = sum([a,b,c,d])
end

Notice the helpful line numbers that have been added to each line of the quoted expression. (Don't be confused by the fact that the labels for each line are added on the end of the previous line.)

We can use the fieldnames() function to see what's inside this expression:

julia> fieldnames(P)
3-element Array{Symbol,1}:
 :head
 :args
 :typ

The head field is Block. The args field is another array, containing expressions (including comments). We can examine these with the usual Julia techniques. For example, what's the second subexpression:

julia> P.args[2]
:(a = 2)

Print them out:

julia> for (n, expr) in enumerate(P.args)
    println(n, ": ", expr)
end

1:  # none, line 2:
2: a = 2
3:  # none, line 3:
4: b = 3
5:  # none, line 4:
6: c = 4
7:  # none, line 5:
8: d = 5
9:  # none, line 6:
10: e = sum([a,b,c,d])

As you can see, the expression P contains a number of sub-expressions. We can modify this expression quite easily; for example, we can change the last line of the expression to use prod() rather than sum(), so that, when P is evaluated, it will return the product rather than the sum of the variables.

julia> eval(P)
14

julia> P.args[end] = quote prod([a,b,c,d]) end
quote  # none, line 1:
    prod([a,b,c,d])
end

julia> eval(P)
120

Alternatively, you can target the sum() symbol directly by burrowing into the expression:

julia> P.args[end].args[end].args[1]
:sum

julia> P.args[end].args[end].args[1] = :prod
:prod

julia> eval(P)
120

The Abstract Syntax Tree[edit]

This way of representing your code once it's been parsed is referred to as the AST (Abstract Syntax Tree). It's a nested hierarchical structure that's designed to allow both you and Julia to easily process and modify the code.

The very useful dump function lets you easily visualise the hierarchical nature of an expression. For example, the expression :(1 * sin(pi/2)) is represented like this:

julia> dump(:(1 * sin(pi/2)))
Expr
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol *
    2: Int64 1
    3: Expr
      head: Symbol call
      args: Array{Any}((2,))
        1: Symbol sin
        2: Expr
          head: Symbol call
          args: Array{Any}((3,))
            1: Symbol /
            2: Symbol pi
            3: Int64 2
          typ: Any
      typ: Any
  typ: Any

You can see that the AST consists entirely of Exprs and atoms (e.g. symbols, numbers).

Expression interpolation[edit]

In a way, strings and expressions are similar — any Julia code they happen to contain is usually unevaluated, but you can have some of the code evaluated using interpolation. We've met the string interpolation operator, the dollar sign ($). When used inside a string, and possibly with parentheses to enclose the expression, this evaluates the Julia code and inserts the resulting value into the string at that point:

julia> "the sine of 1 is $(sin(1))"
"the sine of 1 is 0.8414709848078965"

In just the same way, you can use the dollar sign to include the results of executing Julia code interpolated into an expression (which is otherwise unevaluated):

julia> quote s = $(sin(1) + cos(1)); end
quote  # none, line 1:
    s = 1.3817732906760363
end

Even though this is a quoted expression and hence unevaluated, the value of sin(1) + cos(1) was calculated and inserted into the expression, replacing the original code. This operation is called "splicing".

As with string interpolation, the parentheses are needed only if you want to include the value of an expression — a single symbol can be interpolated using just a single dollar sign.

Macros[edit]

Once you know how to create and handle unevaluated Julia expressions, you'll want to know how you can modify them. A macro is a way of generating a new output expression, given an unevaluated input expression. When your Julia program runs, it first parses and evaluates the macro, and the processed code produced by the macro is eventually evaluated like an ordinary expression.

Here's the definition of a simple macro that prints out the contents of the thing you pass to it, and then returns the expression to the calling environment (here, the REPL). The syntax is very similar to the way you define functions:

macro p(n)
    if typeof(n) == Expr 
       println(n.args)
    end
    return n
end

You run macros by preceding the name with the @ prefix. This macro is expecting a single argument. You're providing unevaluated Julia code, you don't have to enclose it with parentheses, like you do for function arguments.

First, let's call this with a single numeric argument:

julia> @p 3
3

Numbers aren't expressions, so the if condition inside the macro didn't apply. All the macro did was return n. But if you pass an expression, the code in the macro has the opportunity to inspect and/or process the expression's content before it is evaluated, using the .args field:

julia> @p 3 + 4 - 5 * 6 / 7 % 8
Any[:-,:(3 + 4),:(((5 * 6) / 7) % 8)]
2.7142857142857144

In this case, the if condition was triggered, and the arguments of the incoming expression were printed in unevaluated form. So you can see the arguments as an array of expressions after being parsed by Julia but before being evaluated. You can also see how the different precedence of arithmetic operators has been taken into account in the parsing operation. Notice how the top-level operators and subexpressions are quoted with a colon (:).

In this simple example, the macro p returned the argument, which was then evaluated. But it doesn't have to — it could return a quoted expression instead.

As an example, the built-in @time macro returns a quoted expression rather than using eval() to evaluate the expression inside the macro. The quoted expression returned by @time is evaluated in the calling context when the macro has done its work. Here's the definition:

macro time(ex)
    quote
        local t0 = time()
        local val = $(esc(ex))
        local t1 = time()
        println("elapsed time: ", t1-t0, " seconds")
        val
    end
end

Notice the $(esc(ex)) expression. This is the way that you 'escape' the code you want to time, which is in ex, so that it isn't evaluated in the macro, but left intact until the entire quoted expression is returned to the calling context and executed there. If this just said $ex, then the expression would be interpolated and evaluated immediately.

If you want to pass a multi-line expression to a macro, use the begin ... end form:

julia> @p begin
    2 + 2 - 3
end

Any[:( # none, line 2:),:((2 + 2) - 3)]
1

(You can also call macros with parentheses similar to the way you do when calling functions, using the parentheses to enclose the arguments:

julia> @p(2 + 3 + 4 - 5)
Any[:-,:(2 + 3 + 4),5]
4

This would allow you to define macros that accepted more than one expression as arguments.)

eval() and @eval[edit]

There's an eval() function, and an @eval macro. You might be wondering what's the difference between the two?

julia> ex = :(2 + 2)
:(2 + 2) 

julia> eval(ex)
4

julia> @eval ex
:(2 + 2)

The function version expands the expression and evaluates it. The macro version doesn't expand the expression you supply to it automatically, but you can use the interpolation syntax to evaluate the expression and pass it to the macro.

julia> @eval $(ex)
4

In other words:

julia> @eval $(ex) == eval(ex)
true

Scope and context[edit]

When you use macros, you have to keep an eye out for scoping issues. In the previous example, the $(esc(ex)) syntax was used to prevent the expression from being evaluated in the wrong context. Here's another contrived example to illustrate this point.

macro f(x)
    quote
        s = 4
        (s, $(esc(s)))
    end
end

This macro declares a variable s, and returns a quoted expression containing s and an escaped version of s.

Now, outside the macro, declare a symbol s:

julia> s = 0

Run the macro:

julia> @f 2
(4,0)

You can see that the macro returned different values for the symbol s: the first was the value inside the macro's context, 4, the second was an escaped version of s, that was evaluated in the calling context, where s has the value 0. In a sense, esc() has protected the value of s as it passes unharmed through the macro. For the more realistic @time example, it's important that the expression you want to time isn't modified in any way by the macro.

Expanding macros[edit]

To see what the macro expands to just before it's finally executed, use the macroexpand() function. It expects a quoted expression containing one or more macro calls, which are then expanded into proper Julia code for you so that you can see what the macro would do when called.

julia> macroexpand(quote @p 3 + 4 - 5 * 6 / 7 % 8 end)
Any[:-,:(3 + 4),:(((5 * 6) / 7) % 8)]
quote  # none, line 1:
    (3 + 4) - ((5 * 6) / 7) % 8
end

(The #none, line 1: is a filename and line number reference that's more useful when used inside a source file than when you're using the REPL.)

Here's another example. This macro adds a dotimes construction to the language.

macro dotimes(n, body)
    quote
        for i = 1:$(esc(n))
            $(esc(body))
        end
    end
end

This is used as follows:

@dotimes 3 println("hi there")
hi there
hi there
hi there

Or, less likely, like this:

@dotimes 3 begin
    for i in 4:6
        println("i is $i")
    end
end

i is 4
i is 5
i is 6
i is 4
i is 5
i is 6
i is 4
i is 5
i is 6

If you use macroexpand() on this, you can see what happens to the symbol names:

macroexpand( 
    quote  
        @dotimes 3 begin
            for i in 4:6
                println("i is $i")
            end
        end
    end 
)

with the following output:

quote  # none, line 1:
    begin  # none, line 3:
        for #378#i = 1:3 # line 4:
            begin  # none, line 2:
                for i = 4:6 # line 3:
                    println("i is $i")
                end
            end
        end
    end
end

The i local to the macro itself has been renamed to #378#i, so as not to clash with the original i in the code I've passed to it.

A more useful example: @until[edit]

Here's how to define a macro that is more likely to be useful in your code.

Julia doesn't have an until condition ... do some stuff ... end statement. Perhaps you'd like to type something like this:

until x > 100
    println(x)
end

You'll be able to write your code using the new until macro like this:

until ''condition''
    ''block_of_stuff''
end

but, behind the scenes, the work will be done by actual code with the following structure:

while true
    ''block_of_stuff''
    if ''condition''
        break
    end
end

This forms the body of the new macro, and it will be enclosed in a quote ... end block, like this, so that it executes when evaluated , but not before:

quote
    while true
        ''block_of_stuff''
        if ''condition''
            break
        end
    end
end

So the nearly-finished macro code is like this:

macro until(''condition'', ''block_of_stuff'')
    quote
        while true
            ''block_of_stuff''
            if ''condition''
                break
            end
        end
    end
end

All that remains to be done is to work out how to pass in our code for the block_of_stuff and the condition parts of the macro. Recall that $(esc(...)) allows code to pass through 'escaped' (i.e. unevaluated). We'll protect the condition and block code from being evaluated before the macro code runs.

The final macro definition is therefore:

macro until(condition, block)
    quote
        while true
            $(esc(block))
            if $(esc(condition))
                break
            end
        end
    end
end

The new macro is used like this:

julia> i = 0 
julia> @until i == 10 begin
           i += 1
           println(i)
       end

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10

or

julia> x = 5
 5
 
 julia> @until x < 1 (println(x); x -= 1)
 5
 4
 3
 2
 1

Under the hood[edit]

If you want a more complete explanation of the compilation process than that provided here, visit the links shown in Further Reading, below.

Julia performs multiple 'passes' to transform your code to native assembly code. As described above, the first pass parses the Julia code and builds the 'surface-syntax' AST, suitable for manipulation by macros. A second pass lowers this high-level AST into an intermediate representation, which is used by type inference and code generation. In this intermediate AST format all macros have been expanded and all control flow has been converted to explicit branches and sequences of statements. At this stage the Julia compiler attempts to determine the types of all variables so that the most suitable method of a generic function (which can have many methods) is selected.

Further reading[edit]