Julia was built for scientific computing, machine learning, data mining, large-scale linear algebra, distributed and parallel computing. It is a flexible dynamic language with performance comparable to traditional statically-typed languages. Julia is also faster than Python by default as it comes with JIT compilation and type declarations. Follow along and check the 27 most common Julia programming interview questions and answers for your next machine learning or data science interview.
struct vs mutable struct's composite objects?Composite objects declared with struct are immutable; they cannot be modified after construction. This may seem odd at first, but it has several advantages:
structs can be packed efficiently into arrays, and in some cases the compiler is able to avoid allocating immutable objects entirely.However, an immutable object might contain mutable objects, such as arrays, as fields. Those contained objects will remain mutable; only the fields of the immutable object itself cannot be changed to point to different objects.
Let's assume I have two vectors x = [1, 2] and y = [3, 4]. How to best combine them to get a matrix m = [1 2; 3 4]?
There different ways to combine the vectors, two approaches are:
reshape each vector:
julia> m = [reshape(x, 1, :); reshape(y, 1, :)]
2×2 Matrix{Int64}:
1 2
3 4Using the transpose of each vector.
julia> m = [transpose(x); transpose(y)]
2×2 Matrix{Int64}:
1 2
3 4 Given an array a how to select elements that are greater than 4?
julia> a = 2:7
2:7There are two solutions:
Using Higher-order filtering:
julia>filter(x -> x > 4, a)
3-element Vector{Int64}:
5
6
7This calls the filter function with the predicate x -> x > 4 (an anonymous function).
Broadcasting and indexing: This performs a broadcasting comparision between the elements of a and 4:
julia>a[a .> 4]
3-element Vector{Int64}:
5
6
7Zero dimensional arrays are arrays of the form Array{T,0}. For instance, A = zeros() is a 0-dimensional array and a mutable container that contains one element, which can be set by A[] = 1.0 and retrieved with A[]. All zero-dimensional arrays have the same size (size(A) == ()), and length (length(A) == 1). But importantly, zero-dimensional arrays are not empty.
Scalars are not mutable containers: In particular, if x = 0.0 is defined as a scalar, it is an error to attempt to change its value via x[] = 1.0.. Another difference is that a scalar can participate in linear algebra operations such as 2 * rand(2,2), but the analogous operation with a zero-dimensional array fill(2) * rand(2,2) is an error.
Also a scalar x can be converted into a zero-dimensional array containing it via fill(x), and conversely, a zero-dimensional array a can be converted to the contained scalar via a[].
!) mean after the name of a function in Julia?It is a Julia convention to append a bang ! to names of functions that modify one or more of their arguments. This convention warns the user that the function is not pure, i.e., that it has side effects. A function with side effects is useful when you want to update a large data structure or variable container without having all the overhead from creating a new instance.
For example, we can create a function that adds 1 to each element in a vector V, so instead of:
function add_one(V)
for i in 1:length(V)
V[i] += 1
end
return nothing
endwe use:
function add_one!(V)
for i in 1:length(V)
V[i] += 1
end
return nothing
end...) do?Depending on the context, the slat operator (...) means two different things:
In the context of function definitions, ... indicate that the function accepts an arbitrary number of arguments. This can be useful for things where you need to pass an unknown number of arguments and don't want to have to bother constructing an array first before passing it in when working with the function interactively. For example:
func geturls(urls::Vector)
# some code to retrieve URL's from the network
end
geturls(urls...) = geturls([urls...])
# slightly nicer to type than building up an array first then passing it in.
geturls("http://google.com", "http://facebook.com")
# when we already have a vector we can pass that in as well since julia has method dispatch
geturls(urlvector)In the context of working with functions ... can also be used to apply a function to a sequence of arguments. The ... operator causes a single function argument to be split apart into many different arguments when used in the context of a function call. For example, the add_elements function below takes three arguments to be added together, so we use the splat operator ... which takes a collection (often an array, vector, tuple, or range) and converts it into a sequence of arguments:
add_elements(a, b, c) = a + b + c
# Output: add_elements (generic function with 1 method)
my_collection = [1, 2, 3]
add_elements(my_collection...)
# Output: 6== and === comparison operators in Julia?=== implements Henry Baker's EGAL predicate: x === y is true when two objects are programmatically indistinguishable – i.e. you cannot write code that demonstrates any difference between x and y. This boils down to the following rules:
===checks object identity: x === y is true if x and y are the same object, stored at the same location in memory.x === y is true if x and y have the same type –and thus the same structure–, and their corresponding components are all recursively ===.Int or Float64), x === y is true if x and y contain exactly the same bits.
These rules, applied recursively, define the behavior of ===.=== operator is not overloadable: it is a builtin function with fixed, pre-defined behavior. You cannot extend or change its behavior.== on the other hand, is user-definable or overloaded: it has fallback definitions that give it useful default behavior on user-defined types, but you can change that as you see fit by adding new, more specific methods to == for your types. Also, this operator and implements "abstract value equality", you can think of this as intuitive equality: If two numbers are numerically equal, they are ==. For example:
julia> 0.5 == 1/2 == 1//2
true
julia> 2/3 == 2//3
false=> and -> operators in Julia?-> is used to define anonymous functions, For example: x -> x^2 + 2x - 1.=> is used to define a pair (e.g., in a dictionary) For example: Dict("A"=>1, "B"=>2).Array and Vector in Julia?The difference is that Vector is a 1-dimensional Array, so when you write e.g. Vector{Int} it is a shorthand to Array{Int, 1}:
julia> Vector{Int64}
Vector{Int64} (alias for Array{Int64, 1})So when you call constructors Array([1,2,3]) and Vector([1,2,3]) they internally get translated to the same call Array{Int,1}([1,2,3]) as you passed a vector to them.
Another difference is that Array is a parametric type that has two parameters given in curly braces:
eltype).ndims).In Vector there is only one parameter that represent the element type.
:: vs <: operators in types?Those operators are particularly important when working with types:
The :: operator is used to constrain an object of being of a given type. For example, a::B means “a must be of type B”.
The <: operator has a similar meaning, but it’s a bit more relaxed in the sense that the object can be of any subtypes of the given type. For example, A<:B means “A must be a subtype of B”, that is, B is the “parent” type and A is its “child” type.
The primary reason to use these operators is to confirm that the program works the way it is expected. In some cases, providing this extra information can also improve performance.
Given some string, (hello, for example). Create a function to count all unique caracters. Expected output:
julia> s = "hello"
julia> counts(s)
'h' => 1
'l' => 2
'e' => 1
'o' => 1One approach is to create an empty dictionary to gradually store each character as a key and its frequency (counter) as the value, then with the haskey function, we evaluate if some character is repeated and if it is, we increase the counter by 1.
julia> s = "hello"
julia> count_char(s) = begin
res = Dict{Char, Int}()
for c in s
if haskey(res, c)
res[c] += 1
else
res[c] = 1
end
end
res
end
julia> count_char(s)
Dict{Char, Int64} with 4 entries:
'h' => 1
'l' => 2
'e' => 1
'o' => 1A positional argument holds the place for specified value by its ordinal position in the function's argument list. They are typical mandatory, but can be made optional when default values are provided. For example, x and real, imag are positional arguments in these function definitions:
sqrt(x) = x^0.5complex(real, imag) = real + imag*imA keyword argument holds the place of a specified value by its name, i.e. can be passed in any order that they are written. They become optional arguments when the default value is not provided. For example, digitcount and digits are keyword arguments in this function definition:
roundedstring(x; digitcount) = string(round(x, digits=digitcount))The last (rightmost) positional argument comes before the first keyword argument. A semicolon (;) demarks the start of keyword arguments, even when there are no positional arguments. When both kinds of arguments are used, the semicolon separates the positional from the keyword arguments.
In Julia, structures roughly corresponds to what is known as classes in other languages. In this aspect, there are two approaches: inheritance and composition:
The preference in the Julia community is to use composition over inheritance. Consider the following example, where you first define a generic Person structure and then two more specific Student and Employee structures:
struct Shoes
shoesType::String
colour::String
end
struct Person
myname::String
age::Int64
end
struct Student
p::Person
school::String
shoes::Shoes
end
struct Employee
p::Person
monthlyIncomes::Float64
company::String
shoes::Shoes
endInstead of using inheritance declaring Student and Employee as subtypes of Person, you use composition to assign a field p of type Person to both of them. It is thanks to this field that you do not need to repeat the fields that are common to both.
Finally, you can then create instances of the specialized type, either by creating the referenced object first or doing that inline in the constructor of the specialized type:
gymShoes = Shoes("gym","white")
proShoes = Shoes("classical","brown")
Marc = Student(Person("Marc",15),"Divine School",gymShoes)
MrBrown = Employee(Person("Brown",45),3200,"ABC Inc.", proShoes)I would like to create a function with an internal state, but I do not want to have to initialize this internal state outside of the function. I want to rely only on calls to this function and I do not want the function to return anything, the following code illustrate the problem:
function foo()
if @isdefined state
state += 1
else
state = 1 # 1st call: initialize internal state
end
endwhich gives the following expected output:
julia> foo()
1
julia> foo()
2
julia> foo()
3You can use a let block to create a new local state created in the scope of the assignment, in this way we hide the state.
julia> let state = Ref{Union{Int, Nothing}}(nothing)
global f
function f()
if state[] !== nothing
state[] += 1
else
state[] = 1
end
state[]
end
end
f (generic function with 2 methods)which gives the following output:
julia> f()
1
julia> f()
2
julia> f()
3Note that we need global f just to ensure that the function f is defined in global scope.
I have a dataframe with a columns containing a list of strings (e.g. ["Type A", "Type B", "Type D"]). How does one then performs a one-hot encoding?
Original Dataframe
col1 | col2 |
102 |[a] |
103 |[a,b] |
102 |[c,b] |After One-hot encoding
col1 | col2_a | col2_b | col2_c |
102 | 1 | 0 | 0 |
103 | 1 | 1 | 0 |
102 | 0 | 1 | 1 |Consider:
using DataFrames
df = DataFrame(col1=102:104, col2=[["a"], ["a","b"], ["c","b"]])
# obtain unique elements of the dataframe
ux = unique(reduce(vcat, df.col2))
# perform one hot encoding
transform(df, :col2 .=> [ByRow(v -> x in v) for x in ux] .=> Symbol.(:col2_, ux))Output:
3 rows × 5 columns
col1 col2 col2_a col2_b col2_c
Int64 Array… Bool Bool Bool
1 102 ["a"] 1 0 0
2 103 ["a", "b"] 1 1 0
3 104 ["c", "b"] 0 1 1One simple approach only uses Julia's base library and creates one sample_function:
sample_function(items, weights) = items[findfirst(cumsum(weights) .> rand())]
sample_function(["a", 2, 5, "h", "hello", 3], [0.1, 0.1, 0.2, 0.2, 0.1, 0.3])Output:
"h"Another approach is using the StatsBase.jl package, i.e.
using Pkg
Pkg.add("StatsBase") # Only do this once, obviously
using StatsBase
items = ["a", 2, 5, "h", "hello", 3]
weights = [0.1, 0.1, 0.2, 0.2, 0.1, 0.3]
sample(items, Weights(weights))Output:
"h"It means that the type of the output is predictable from the types of the inputs. In particular, it means that the type of the output cannot vary depending on the values of the inputs. The following code is not type-stable:
julia> function unstable(flag::Bool)
if flag
return 1
else
return 1.0
end
end
unstable (generic function with 1 method)It returns either an Int or a Float64 depending on the value of its argument. Since Julia can't predict the return type of this function at compile-time, any computation that uses it must be able to cope with values of both types, which makes it hard to produce fast machine code.
:: operator do?When appended to an expression computing a value, the :: operator is read as "is an instance of". It can be used anywhere to assert that the value of the expression on the left is an instance of the type on the right:
When the type is abstract, it suffices for the value to be implemented by a concrete type that is a subtype of the abstract type. If the type assertion is not true, an exception is thrown, otherwise, the left-hand value is returned. For example:
julia> (1+2)::AbstractFloat
ERROR: TypeError: in typeassert, expected AbstractFloat, got a value of type Int64
julia> (1+2)::Int
3When appended to a variable on the left-hand side of an assignment, or as part of a local declaration, :: declares the variable to always have the specified type, like a type declaration in a statically-typed language such as C. For example:
julia> function foo()
x::Int8 = 100
x
end
foo (generic function with 1 method)
julia> x = foo()
100
julia> typeof(x)
Int8.==" in Julia? What will be the output of the code snippet?Consider:
y = [1,5,5,7,9,8,1,1,2,3,1]
x = sum(y .== 1)
# Output?.) is for vectorized operations: It basically applies your selected operation to each element of your vector.y .== 1 will check equality to 0 for each element of your vector y, meaning x will be the number of values from y being equal to 1, which is 4.The scope of a variable is the region of code within which a variable is visible. Certain constructs in the language introduce scope blocks, which are regions of code that are eligible to be the scope of some set of variables.
There are two main types of scopes in Julia: global scope and local scope, the latter can be nested in soft local scope and hard local scope.
Soft local scopes:
The soft local scope inherits both read and write variables. For example:
x,y = 0, 1
for i = 1:10
x = i + y + 1
end
julia> x
12Within soft scopes, the global keyword is never necessary, although allowed.
Hard local scopes:
global is needed to assign to a global variable.Example:
x,y = 1,2
function foo()
x = 2 # assignment introduces a new local
return x + y # y refers to the global
end
julia> foo()
4
julia> x
1A type of an object, in plain English, refers to the set of characteristics that describe the object. For example, an object of type sheet can be described with its dimensions, its weight, its size, or its color. All values in Julia are true objects belonging to a given type (they are individual “instances” of the given type). Julia's types include:
A primitive type is made of a fixed amount of bits (like all numerical types such as Int64, Float64, and Char).
A composite type or structure, is an object where its characteristics are described using multiple fields and a variable number of bits. They can be seen as a collection of named fields, an instance of which can be treated as a single value.
Both structures and primitive types can be user-defined and are hierarchically organized. Structures correspond to what is known as classes in other languages.
missing, Nothing and NaN in Julia?nothing (type Nothing): This is the value returned by code blocks and functions that do not return anything. It is a single instance of the singleton type Nothing, and is closer to C-style NULL (sometimes it is referred to as the “software engineer’s null”). Most operations with nothing values will result in a run-type error. In a certain context, it is printed as #NULL.
missing (type Missing): Represents a missing value in a statistical sense. There should be a value but you don’t know what it is (so it is sometimes referred to as the “data scientist’s null”). Most operations with missing values will propagate (silently).
NaN (type Float64): Represents when an operation results in a Not-a-Number value (e.g., 0/0). It is similar to missing in that it propagates silently rather than resulting in a run-type error.
Ref() function?Amazone runs the internet as we know it. Amazon Web Services (AWS) offers a comprehensive suite of machine learning (ML) services that cater to various needs and expertise levels. Follow along and learn the 23 most common AWS machine-learning intervi...
Azure Machine Learning (Azure ML) is a cloud-based service for creating and managing machine learning solutions. It’s designed to scale, distribute, and deploy machine learning models to the cloud. Follow along and learn the 23 most common Azure Mach...
Hadoop is an open-source big data processing framework. It leverages distributed computing to store and process large datasets in a fault-tolerant manner. According to recent reports, Apache Hadoop is one of the most sought-after big data skills with...