Julia Interview Questions with Answers
Most Asked Julia Interview Questions for Data Science and Engineering Roles
Introduction
Julia is a high‑performance, dynamic language for technical computing that combines the ease of Python with the speed of C. This page compiles the most frequently asked Julia interview questions – from basic syntax and multiple dispatch to advanced metaprogramming, parallel computing, and interfacing with C/Python – essential for any data scientist, researcher, or software engineer.
Why Julia?
- High performance – JIT compiled to native code
- Multiple dispatch – generic programming at its best
- Built‑for‑science – linear algebra, machine learning, plotting
- Seamless interoperability with C, Python, and R
- Dynamic and interactive – REPL and Jupyter friendly
- Rapidly growing ecosystem and community
Most Asked Julia Interview Questions
Julia is a high-level, high-performance dynamic programming language designed for technical computing. It combines the ease of use of Python with the speed of C.
- High-performance: Compiled to native code via JIT
- Dynamic: Interactive and easy to use
- Multiple dispatch: Functions can have multiple definitions
- Designed for science: Linear algebra, machine learning, plotting
- Interoperability: Call C, Python, R, and other languages
# Hello World in Julia
println("Hello, World!")Variables in Julia are declared using the = operator. Julia is dynamically typed, so you don't need to specify the type explicitly.
- Assignment:
x = 10 - Dynamic typing: Types are inferred at runtime
- Constants:
const PI = 3.14159 - Global scope: Variables defined at top level
- Local scope: Variables defined inside functions
# Variables in Julia
x = 10 # Integer
y = 3.14 # Float
name = "Julia" # String
is_active = true # Boolean
println(x)
println(y)
println(name)
println(is_active)Julia has a rich set of data types organized in a hierarchy. The most common types include integers, floating-point numbers, strings, booleans, and composite types.
- Integer:
Int,Int8,UInt16 - Floating-point:
Float64,Float32 - String:
String - Boolean:
Bool(true/false) - Symbol:
:symbol_name - Tuple: Immutable ordered collection
- Array: Mutable collection of elements
- Dict: Key-value pairs
# Data Types in Julia
# Integer types
a = 10 # Int (defaults to Int64)
b = Int8(127) # Int8
c = UInt16(255) # Unsigned Int
# Floating point
d = 3.14 # Float64
e = Float32(2.5) # Float32
# String
f = "Hello Julia"
# Boolean
g = true
h = false
# Symbol
i = :symbol_name
# Tuple
j = (1, "hello", 3.14)
# Array
k = [1, 2, 3, 4, 5]
# Dictionary
l = Dict("name" => "Julia", "version" => 1.9)
println(typeof(a)) # Int64
println(typeof(b)) # Int8Functions in Julia can be defined using the function keyword or using assignment syntax. Julia supports multiple dispatch, meaning functions can be defined for different argument types.
- Function declaration:
function name(args) ... end - One-liner:
name(args) = expression - Anonymous functions:
x -> x^2 - Keyword arguments:
function f(; kw="default") - Multiple dispatch: Define same function for different types
# Functions in Julia
# Function declaration
function add(a, b)
return a + b
end
# One-line function
subtract(a, b) = a - b
# Function with default parameters
function greet(name="Guest")
return "Hello, $name!"
end
# Multiple dispatch
function area(shape::String, dimensions...)
if shape == "circle"
r = dimensions[1]
return π * r^2
elseif shape == "rectangle"
w, h = dimensions
return w * h
end
end
# Anonymous function
square = x -> x^2
double = x -> 2x
# Function with keyword arguments
function create_person(name; age=0, city="Unknown")
return (name=name, age=age, city=city)
end
println(add(5, 3))
println(subtract(10, 4))
println(greet("Alice"))
println(area("circle", 5))
println(square(4))
println(create_person("Alice", age=25, city="NYC"))Arrays are mutable collections of elements in Julia. They can be 1D (vectors), 2D (matrices), or multi-dimensional. Arrays are 1-indexed and support element-wise operations.
- Creation:
[1, 2, 3]orzeros(3,3) - 1-indexed: First element is at index 1
- Element-wise: Use dot operator (
.) - Methods:
push!,pop!,map,filter,reduce - Comprehensions:
[x^2 for x in 1:10]
# Arrays in Julia
arr = [1, 2, 3, 4, 5]
# Map - transform each element
doubled = map(x -> x * 2, arr)
println(doubled) # [2, 4, 6, 8, 10]
# Filter - select elements
evens = filter(x -> x % 2 == 0, arr)
println(evens) # [2, 4]
# Reduce - aggregate
sum = reduce(+, arr)
println(sum) # 15
# Comprehension
squares = [x^2 for x in 1:10]
println(squares)
# Push and pop
push!(arr, 6)
println(arr)
pop!(arr)
println(arr)
# Array operations
a = [1, 2, 3]
b = [4, 5, 6]
c = a .+ b # Element-wise addition
println(c)Dictionaries are key‑value pairs in Julia, similar to maps in other languages. They provide efficient lookup by key.
- Creation:
Dict("key" => "value") - Access:
dict["key"] - Add/Update:
dict["new_key"] = value - Keys and values:
keys(dict),values(dict) - Comprehensions:
Dict(i => i^2 for i in 1:5)
# Dictionaries in Julia
# Create dictionary
person = Dict("name" => "Alice", "age" => 25, "city" => "NYC")
# Access values
println(person["name"])
println(person["age"])
# Add/update values
person["country"] = "USA"
person["age"] = 26
# Get with default
city = get(person, "city", "Unknown")
# Keys and values
println(keys(person))
println(values(person))
# Iterate over dictionary
for (key, value) in person
println("$key: $value")
end
# Delete key
delete!(person, "country")
# Check if key exists
println(haskey(person, "name"))
# Dict comprehension
squares = Dict(i => i^2 for i in 1:5)
println(squares)Tuples are immutable ordered collections of values in Julia. They are useful for returning multiple values from functions.
- Creation:
(1, "hello", 3.14) - Access:
tuple[1] - Named tuples:
(name="Alice", age=25) - Unpacking:
a, b, c = tuple - Concatenation:
(t1..., t2...)
# Tuples in Julia
# Create tuple
t = (1, "hello", 3.14, true)
# Access elements
println(t[1])
println(t[2])
# Named tuples
person = (name="Alice", age=25, city="NYC")
println(person.name)
println(person.age)
# Tuple unpacking
a, b, c = (10, 20, 30)
println(a, b, c)
# Function returning multiple values
function divide(a, b)
return div(a, b), a % b
end
quotient, remainder = divide(10, 3)
println("Quotient: $quotient, Remainder: $remainder")
# Tuple concatenation
t1 = (1, 2, 3)
t2 = (4, 5, 6)
t3 = (t1..., t2...)
println(t3)Julia provides standard control flow statements including conditionals, loops, and exception handling.
- If-else:
if condition ... end - Ternary:
condition ? a : b - For loops:
for i in 1:10 ... end - While loops:
while condition ... end - Break/Continue:
break,continue
# Control Flow in Julia
# If-else statement
age = 25
if age < 18
println("Minor")
elseif age < 65
println("Adult")
else
println("Senior")
end
# Ternary operator
status = age >= 18 ? "Adult" : "Minor"
println(status)
# For loop
for i in 1:5
println(i)
end
# For loop with array
fruits = ["apple", "banana", "orange"]
for fruit in fruits
println(fruit)
end
# While loop
i = 1
while i <= 5
println(i)
i += 1
end
# Break and continue
for i in 1:10
if i == 6
break
end
if i % 2 == 0
continue
end
println(i)
endComprehensions are a concise way to create arrays from other arrays using a generator expression. They are similar to list comprehensions in Python.
- Array comprehension:
[x^2 for x in 1:10] - Filtering:
[x for x in 1:20 if x % 2 == 0] - Nested comprehension:
[(i, j) for i in 1:3, j in 1:3] - Dict comprehension:
Dict(i => i^2 for i in 1:5) - Generator expression: Lazy evaluation with
sum(x^2 for x in 1:100)
# Comprehensions in Julia
# Array comprehension
squares = [x^2 for x in 1:10]
println(squares)
# Filter with comprehension
evens = [x for x in 1:20 if x % 2 == 0]
println(evens)
# Nested comprehension
matrix = [(i, j) for i in 1:3, j in 1:3]
println(matrix)
# Dict comprehension
square_dict = Dict(i => i^2 for i in 1:5)
println(square_dict)
# Generator expression (lazy)
sum_squares = sum(x^2 for x in 1:100)
println(sum_squares)
# Conditional comprehension
results = [if x % 2 == 0 "even" else "odd" end for x in 1:10]
println(results)Julia provides powerful string manipulation capabilities including interpolation, concatenation, and various utility functions.
- Creation:
"Hello" - Concatenation:
"Hello" * " " * "World" - Interpolation:
"Hello, $name" - Functions:
length,uppercase,lowercase,replace - Split/Join:
split,join
# Strings in Julia
# String creation
str1 = "Hello"
str2 = "World"
str3 = """Multi-line
string"""
# String concatenation
greeting = str1 * " " * str2
println(greeting)
# String interpolation
name = "Julia"
version = 1.9
println("Welcome to $name version $version")
# String functions
text = "Hello, World!"
println(length(text))
println(uppercase(text))
println(lowercase(text))
println(replace(text, "World" => "Julia"))
# Substring
println(text[1:5])
# Split and join
words = split("Hello World Julia")
println(words)
joined = join(words, "-")
println(joined)
# String comparison
println("hello" == "hello")
println("hello" < "world")
# String formatting
println(Printf.@sprintf("Value: %.2f", 3.14159))Modules are used to organize and encapsulate code, preventing namespace pollution. They allow you to export specific functions and variables.
- Definition:
module MyModule ... end - Export:
export function_name - Import:
using .MyModule - Private: Functions not exported are private
- Inclusion:
include("file.jl")
# Modules in Julia
# Defining a module
module MyMath
export add, subtract, PI
const PI = 3.14159
function add(a, b)
return a + b
end
function subtract(a, b)
return a - b
end
# Private function (not exported)
function multiply(a, b)
return a * b
end
end
# Using a module
using .MyMath
println(add(5, 3))
println(subtract(10, 4))
println(MyMath.PI)
# println(MyMath.multiply(2, 3)) # Error: not exported
# Import specific functions
import .MyMath: PI
println(PI)
# Including external files
# include("math_functions.jl")Julia's type system is robust and supports abstract types, concrete types, and parametric types. Types enable multiple dispatch and performance optimization.
- Abstract types:
abstract type Animal end - Concrete types:
struct Dog <: Animal ... end - Mutable types:
mutable struct Person ... end - Parametric types:
struct Point{T} ... end - Type hierarchy: Types can inherit from abstract types
# Types in Julia
# Abstract type
abstract type Animal end
# Concrete type
struct Dog <: Animal
name::String
age::Int
end
# Mutable struct
mutable struct Person
name::String
age::Int
city::String
end
# Constructor
function Person(name::String, age::Int)
return Person(name, age, "Unknown")
end
# Type parameters
struct Point{T}
x::T
y::T
end
# Usage
dog = Dog("Rex", 3)
person = Person("Alice", 25)
p1 = Point(1.0, 2.0)
p2 = Point(1, 2)
# Field access
println(dog.name)
println(person.age)
# Type inheritance
abstract type Vehicle end
struct Car <: Vehicle
make::String
model::String
end
struct Bike <: Vehicle
brand::String
endMultiple dispatch is a core feature of Julia where the function to call is determined by the types of all arguments, not just the first one.
- Definition: Define same function for different types
- Resolution: Most specific method is called
- Performance: Enables type-specific optimizations
- Example:
function area(shape::Circle) - Benefits: Code organization, reusability, and performance
# Multiple Dispatch in Julia
# Define functions with different signatures
function describe(x::Int)
return "Integer: $x"
end
function describe(x::Float64)
return "Float: $x"
end
function describe(x::String)
return "String: $x"
end
# More specific types
function describe(x::Array{Int64,1})
return "Array of Ints: $x"
end
# Abstract type dispatch
abstract type Shape end
struct Circle <: Shape
radius::Float64
end
struct Rectangle <: Shape
width::Float64
height::Float64
end
function area(shape::Circle)
return π * shape.radius^2
end
function area(shape::Rectangle)
return shape.width * shape.height
end
# Usage
println(describe(42))
println(describe(3.14))
println(describe("Hello"))
println(describe([1, 2, 3]))
circle = Circle(5.0)
rectangle = Rectangle(4.0, 6.0)
println(area(circle))
println(area(rectangle))Julia provides try-catch-finally blocks for error handling, similar to other languages. You can also throw custom errors.
- Try-catch:
try ... catch e ... end - Finally:
try ... finally ... end - Throw:
throw(DomainError("message")) - Error types:
BoundsError,DomainError,MethodError - Check type:
isa(e, BoundsError)
# Exceptions and Errors in Julia
# Try-catch block
try
# Code that might error
result = 10 / 0
println(result)
catch e
println("Error caught: $e")
end
# Specific error handling
try
arr = [1, 2, 3]
println(arr[10])
catch e
if isa(e, BoundsError)
println("Index out of bounds!")
else
println("Other error: $e")
end
end
# Finally block
try
file = open("data.txt", "r")
# Process file
println("File opened successfully")
catch
println("Error opening file")
finally
println("Cleanup performed")
end
# Throwing errors
function divide(a, b)
if b == 0
throw(DomainError("Cannot divide by zero"))
end
return a / b
end
# Using error
try
println(divide(10, 0))
catch e
println("Error: $e")
endJulia provides functions for reading and writing files, including line-by-line reading and CSV handling.
- Read file:
open("file.txt", "r") do file ... end - Write file:
open("file.txt", "w") do file ... end - Line by line:
eachline(file) - CSV: Use
CSV.readandCSV.write - File operations:
readdir,isfile,isdir
# File I/O in Julia
# Reading files
try
open("example.txt", "r") do file
content = read(file, String)
println(content)
end
catch
println("File not found")
end
# Reading line by line
try
open("data.txt", "r") do file
for line in eachline(file)
println(line)
end
end
catch
println("Error reading file")
end
# Writing files
open("output.txt", "w") do file
write(file, "Hello, World!
")
write(file, "This is line 2
")
end
# Appending to files
open("output.txt", "a") do file
write(file, "Appended line
")
end
# Reading CSV
using CSV
# data = CSV.read("data.csv", DataFrame)
# Writing CSV
# CSV.write("output.csv", data)Julia uses the built-in package manager Pkg for managing packages. You can add, update, and remove packages using Pkg commands.
- Add:
Pkg.add("PackageName") - Using:
using PackageName - Status:
Pkg.status() - Update:
Pkg.update() - Remove:
Pkg.rm("PackageName")
# Packages in Julia
# Using Pkg
using Pkg
# Add package
# Pkg.add("Plots")
# Pkg.add("DataFrames")
# Pkg.add("CSV")
# Using packages
using Plots
using DataFrames
using CSV
# Check installed packages
# Pkg.status()
# Update packages
# Pkg.update()
# Remove package
# Pkg.rm("SomePackage")
# Environment management
# Pkg.activate("myenv")
# Pkg.add("HTTP")
# Using package in code
# using HTTP
# response = HTTP.get("https://example.com")
# println(response.body)Julia's Plots.jl package provides a unified interface for plotting. You can create various types of plots including line plots, scatter plots, and histograms.
- Load:
using Plots - Line plot:
plot(x, y) - Scatter:
scatter(x, y) - Histogram:
histogram(data) - 3D plot:
surface(x, y, z)
# Plotting in Julia
using Plots
# Simple plot
x = 1:10
y = x.^2
plot(x, y, title="Square Function", label="x²")
# savefig("plot.png")
# Multiple series
y2 = 2x .+ 1
plot(x, y, label="x²")
plot!(x, y2, label="2x+1")
# Scatter plot
scatter(x, y, title="Scatter Plot")
# Histogram
data = randn(1000)
histogram(data, bins=30, title="Histogram")
# 3D plot
x = 1:10
y = 1:10
z = [i^2 + j^2 for i in x, j in y]
surface(x, y, z, title="3D Surface")
# Subplots
plot(x, y, label="Line")
scatter!(x, y2, label="Scatter")
# plotly() # Switch to interactive backendDataFrames.jl provides tabular data structures similar to pandas in Python. It offers various operations for data manipulation and analysis.
- Create:
DataFrame(Name=["Alice"], Age=[25]) - Access columns:
df.Nameordf[:, "Name"] - Add column:
df.NewCol = values - Filter:
filter(row -> row.Age > 30, df) - Group by:
groupby(df, :City)
# DataFrames in Julia
using DataFrames
# Create DataFrame
df = DataFrame(
Name=["Alice", "Bob", "Charlie", "David"],
Age=[25, 30, 35, 40],
City=["NYC", "LA", "Chicago", "Boston"]
)
println(df)
# Access columns
println(df.Name)
println(df[:, "Age"])
println(df[!, :City])
# Select rows
println(df[1:2, :])
println(df[df.Age .> 30, :])
# Add column
df.Salary = [50000, 60000, 70000, 80000]
println(df)
# Modify column
df.Age = df.Age .+ 1
# Sort DataFrame
sorted_df = sort(df, :Age)
println(sorted_df)
# Group and aggregate
using Statistics
grouped = groupby(df, :City)
mean_ages = combine(grouped, :Age => mean => :MeanAge)
println(mean_ages)
# Filter
filtered = filter(row -> row.Age > 30, df)
println(filtered)Julia's Statistics module provides functions for statistical analysis including mean, median, standard deviation, and correlation.
- Mean:
mean(data) - Median:
median(data) - Std:
std(data) - Correlation:
cor(x, y) - Quantiles:
quantile(data, [0.25, 0.5, 0.75])
# Statistics in Julia
using Statistics
# Basic statistics
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
println(mean(data))
println(median(data))
println(std(data))
println(var(data))
# Random data
using Random
random_data = randn(1000)
println(mean(random_data))
println(std(random_data))
# Correlation
x = 1:100
y = 2x .+ randn(100)
println(cor(x, y))
# Quantiles
println(quantile(data, [0.25, 0.5, 0.75]))
# Summary statistics
summary(data)
# Statistical tests
# using HypothesisTests
# ttest(data, 5.5)Julia's LinearAlgebra module provides comprehensive linear algebra operations including matrix multiplication, decomposition, and eigenvalue computation.
- Matrix multiplication:
A * b - Solve system:
A \ b - Eigenvalues:
eigvals(A) - Determinant:
det(A) - Inverse:
inv(A)
# Linear Algebra in Julia
using LinearAlgebra
# Vectors and matrices
A = [1 2 3; 4 5 6; 7 8 10]
b = [1, 2, 3]
# Matrix operations
println(A * b)
println(A' * A) # Transpose
# Solving linear systems
x = A b
println(x)
# Matrix decomposition
LU = lu(A)
println(LU)
# Eigenvalues
eigenvalues = eigvals(A)
println(eigenvalues)
# Determinant and inverse
println(det(A))
println(inv(A))
# Identity matrix
I = Matrix{Float64}(I, 3, 3)
println(I)
# Special matrices
zeros_matrix = zeros(3, 3)
ones_matrix = ones(3, 3)
println(zeros_matrix)
println(ones_matrix)Julia's Dates module provides comprehensive date and time handling functionality.
- Current date:
now() - Create date:
Date(2024, 1, 1) - Date arithmetic:
date + Day(10) - Date difference:
now() - date - Formatting:
DateTime("2024-01-01", dateformat"yyyy-mm-dd")
# Dates and Time in Julia
using Dates
# Current date and time
now = now()
println(now)
# Date creation
date1 = Date(2024, 1, 1)
date2 = DateTime(2024, 1, 1, 12, 0, 0)
println(date1)
println(date2)
# Date arithmetic
println(date1 + Day(10))
println(date1 + Month(2))
println(date2 + Hour(3))
# Date difference
diff = now() - date2
println(diff)
# Formatting dates
println(DateTime("2024-01-01", dateformat"yyyy-mm-dd"))
println(DateTime("2024-01-01T12:00:00"))
# Date functions
println(year(now()))
println(month(now()))
println(day(now()))
println(dayofweek(now()))
println(dayname(now()))
# Date range
dates = Date(2024, 1, 1):Day(1):Date(2024, 1, 10)
for d in dates
println(d)
endJulia supports regular expressions through the Regex module. You can match, replace, and search using regex patterns.
- Create regex:
r"hello" - Match:
match(r"hello", text) - Find all:
collect(eachmatch(r"hello", text)) - Capture groups:
r"(\d{4})-(\d{2})-(\d{2})" - Replace:
replace(text, r"\d+" => "NUM")
# Regular Expressions in Julia
using Regex
# Create regex
re = r"hello"
text = "hello world"
# Match
match_result = match(re, text)
println(match_result)
# Find all
text2 = "hello world hello again"
matches = collect(eachmatch(r"hello", text2))
println(length(matches))
# Regex with capture groups
re2 = r"(d{4})-(d{2})-(d{2})"
text3 = "Date: 2024-01-01"
match2 = match(re2, text3)
if match2 !== nothing
println(match2[1]) # year
println(match2[2]) # month
println(match2[3]) # day
end
# Replace with regex
replaced = replace("Hello 123 World", r"d+" => "NUM")
println(replaced)
# Case insensitive
re3 = r"hello"i
println(match(re3, "HELLO world"))
# Regex compilation
re4 = Regex("^\d{3}-\d{4}$")
println(match(re4, "123-4567") !== nothing)Julia provides built-in support for parallel computing through multiple paradigms including distributed computing, threading, and GPU computing.
- Add workers:
addprocs(4) - Parallel map:
@distributed for i in 1:100 - Asynchronous:
@spawn - Shared arrays:
SharedArray{Int}(100) - Threading:
@threads for i in 1:100
# Parallel Computing in Julia
using Distributed
# Add workers
# addprocs(4)
# Parallel map
@everywhere function f(x)
return x^2
end
# Parallel for loop
# @sync @distributed for i in 1:100
# println("Processing: $i")
# end
# @spawn for asynchronous tasks
task = @spawn begin
sleep(2)
return "Task completed"
end
# result = fetch(task)
# println(result)
# Shared arrays
using SharedArrays
shared_arr = SharedArray{Int}(100)
# Parallel reduction
# using Distributed
# sum = @sync @distributed (+) for i in 1:1000
# i^2
# end
# Threading
using Base.Threads
@threads for i in 1:100
println("Thread $tid: $i")
end
# Atomic operations
using Base.Atomics
counter = Atomic{Int}(0)
for i in 1:100
@spawn atomic_add!(counter, 1)
endMetaprogramming in Julia allows you to write code that generates other code. This includes macros, expressions, and code generation techniques.
- Expressions:
ex = :(2 + 3) - Macros:
macro name(expr) ... end - Quote:
quote ... end - Interpolation:
:\$x + 3 - Generate functions:
@generated function
# Metaprogramming in Julia
# Expressions
ex = :(2 + 3)
println(eval(ex))
# Quote
ex2 = quote
x = 10
y = 20
x + y
end
println(eval(ex2))
# Macro definition
macro sayhello()
return :(println("Hello, World!"))
end
# Macro with arguments
macro greet(name)
return :(println("Hello, $name!"))
end
# Using macros
@sayhello()
@greet("Julia")
# Interpolate in expressions
x = 5
ex3 = :($x + 3)
println(eval(ex3))
# Generate functions
function generate_expr(n)
return :(println("Number: $n"))
end
# String macro
x = 42
println(meta_parse("x + 1"))Julia can call C functions directly using the ccall function. This provides high performance and access to existing C libraries.
- ccall:
ccall((:function_name, lib), return_type, (arg_types,), args) - Load library:
Libdl.dlopen("lib.so") - C structs:
mutable struct Point ... end - Memory management:
Libc.mallocandLibc.free - C strings:
unsafe_convert(Ptr{UInt8}, str)
# Interoperability with C in Julia
# Calling C functions
# using Libdl
# # Load C library
# lib = dlopen("libm.so.6")
# # Define function
# function c_sin(x::Float64)
# return ccall(
# (:sin, lib),
# Float64,
# (Float64,),
# x
# )
# end
# # Call C function
# println(c_sin(0.5))
# # C structs
# mutable struct Point
# x::Cfloat
# y::Cfloat
# end
# # C pointers
# function allocate_buffer(n::Int)
# return Libc.malloc(n * sizeof(Int))
# end
# function free_buffer(ptr)
# Libc.free(ptr)
# end
# # C string handling
# c_string = Base.unsafe_convert(Ptr{UInt8}, "Hello")
# println(c_string)Julia provides several techniques for performance optimization including type stability, preallocation, and compiler annotations.
- Type annotations:
function f(x::Float64) - Constants:
const GLOBAL = 10.0 - @code_warntype: Check type stability
- Preallocate:
Vector{Float64}(undef, n) - @inbounds: Disable bounds checking
# Performance Optimization in Julia
# Performance tips
# 1. Use type annotations
function sum_array(A::Vector{Float64})
s = 0.0
for x in A
s += x
end
return s
end
# 2. Avoid global variables
const GLOBAL_CONST = 10.0
function use_global()
return GLOBAL_CONST * 2
end
# 3. Use @code_warntype to check type stability
# @code_warntype sum_array([1.0, 2.0, 3.0])
# 4. Preallocate arrays
function preallocate()
arr = Vector{Float64}(undef, 1000)
for i in 1:1000
arr[i] = i^2
end
return arr
end
# 5. Use @inbounds for bounds checking
function sum_inbounds(A)
s = 0.0
@inbounds for i in eachindex(A)
s += A[i]
end
return s
end
# 6. Use @fastmath for aggressive optimizations
function fast_sum(A)
s = 0.0
@fastmath for x in A
s += x
end
return s
end
# 7. Avoid dynamic dispatch
function process(x::Float64)
return x * 2
end
# 8. Use views for slicing
view(arr, 1:10)Julia provides networking capabilities through HTTP clients, servers, and WebSockets. The HTTP.jl package is commonly used for web communication.
- HTTP client:
HTTP.get("https://api.github.com") - HTTP server:
HTTP.serve(request_handler, "127.0.0.1", 8080) - WebSockets:
WebSockets.serve("127.0.0.1", 8080) do ws - TCP client:
connect("example.com", 80) - TCP server:
listen(8080)
# Networking in Julia
using Sockets
# HTTP client
using HTTP
# response = HTTP.get("https://api.github.com")
# println(String(response.body))
# HTTP server
# using HTTP
# function request_handler(req::HTTP.Request)
# return HTTP.Response(200, "Hello, World!")
# end
# HTTP.serve(request_handler, "127.0.0.1", 8080)
# WebSockets
# using WebSockets
# WebSockets.serve("127.0.0.1", 8080) do ws
# while true
# msg = read(ws, String)
# write(ws, "Echo: $msg")
# end
# end
# TCP client
# sock = connect("example.com", 80)
# write(sock, "GET / HTTP/1.1
Host: example.com
")
# response = read(sock, String)
# println(response)
# close(sock)
# TCP server
# server = listen(8080)
# while true
# sock = accept(server)
# @async begin
# write(sock, "Hello from server!
")
# close(sock)
# end
# endThe JSON.jl package provides functions for encoding and decoding JSON data, which is useful for API communication and data exchange.
- Encode:
JSON.json(data) - Pretty print:
JSON.json(data, 2) - Decode:
JSON.parse(json_string) - Read file:
JSON.parsefile("data.json") - Nested structures: Handles complex nested data
# Working with JSON in Julia
using JSON
# Encode to JSON
data = Dict(
"name" => "Alice",
"age" => 25,
"city" => "NYC",
"hobbies" => ["reading", "coding"]
)
json_string = JSON.json(data)
println(json_string)
# Pretty print
pretty_json = JSON.json(data, 2)
println(pretty_json)
# Decode from JSON
json_str = "{"name":"Bob","age":30,"city":"LA"}"
parsed = JSON.parse(json_str)
println(parsed["name"])
println(parsed["age"])
# Working with arrays
json_array = JSON.json([1, 2, 3, 4, 5])
println(json_array)
parsed_array = JSON.parse(json_array)
println(parsed_array)
# Nested structures
nested = Dict(
"user" => Dict(
"id" => 1,
"profile" => Dict(
"name" => "Alice",
"email" => "alice@example.com"
)
)
)
println(JSON.json(nested, 2))
# Read JSON from file
# data = JSON.parsefile("data.json")Julia's Test module provides testing capabilities including assertions, test sets, and benchmarking tools.
- Basic test:
@test 1 + 1 == 2 - Test sets:
@testset "Description" begin ... end - Floating point:
@test 0.1 + 0.2 ≈ 0.3 - Throws:
@test_throws DomainError sqrt(-1) - Benchmarking:
@benchmark sum(1:1000)
# Testing in Julia
using Test
# Basic tests
@test 1 + 1 == 2
@test 2 * 3 == 6
# Test with floating point
@test 0.1 + 0.2 ≈ 0.3
# Test macros
@testset "Math operations" begin
@test 2 + 2 == 4
@test 3 * 3 == 9
@test 10 / 2 == 5
end
# Nested test sets
@testset "Advanced tests" begin
@testset "Trigonometry" begin
@test sin(0) == 0
@test cos(0) == 1
end
@testset "Logarithms" begin
@test log(1) == 0
@test exp(1) == ℯ
end
end
# Test with throws
@test_throws DomainError sqrt(-1)
# Test failure
# @test 1 == 2
# Benchmarking
using BenchmarkTools
# @benchmark sum(1:1000)
# Property-based testing
# using PropCheck
# @check for x in integers(1:10)
# x + 1 > x
# endJulia provides various debugging tools including logging, @show, interactive debuggers, and stack traces.
- @show:
@show x + y - Logging:
@info,@warn,@error - Debugger:
@enter function(args) - Stack trace:
showerror(stdout, e, catch_backtrace()) - Breakpoints:
breakpoint function_name
# Debugging in Julia
# Using @show macro
x = 10
y = 20
@show x + y
# Using @debug for debugging messages
@debug "Debug message" x y
# Using @warn for warnings
@warn "This is a warning" x y
# Using @info for informational messages
@info "Processing data" size=100
# Using @error for error messages
@error "Something went wrong" error="Division by zero"
# Interactive debugging
# using Debugger
# @enter function_to_debug(args)
# Stack trace
try
error("Something went wrong")
catch e
println("Stack trace:")
showerror(stdout, e, catch_backtrace())
end
# Logging
using Logging
with_logger(ConsoleLogger()) do
@info "This is an info message"
end
# Breakpoint debugging
# using Debugger
# breakpoint function_to_debugAbstract types define type hierarchies and interfaces. They provide a way to organize types and define common behavior through multiple dispatch.
- Abstract types:
abstract type Animal end - Inheritance:
struct Dog <: Animal - Interfaces: Define functions that work on abstract types
- Type hierarchy:
Mammal <: Animal - Interface functions:
function make_sound(animal::Animal)
# Abstract Types and Interfaces
# Abstract type hierarchy
abstract type Animal end
abstract type Mammal <: Animal end
abstract type Bird <: Animal end
# Concrete types
struct Dog <: Mammal
name::String
age::Int
end
struct Cat <: Mammal
name::String
age::Int
end
struct Sparrow <: Bird
name::String
wingspan::Float64
end
# Interface functions
function make_sound(animal::Animal)
return "Some sound"
end
function make_sound(animal::Dog)
return "Woof!"
end
function make_sound(animal::Cat)
return "Meow!"
end
function make_sound(animal::Sparrow)
return "Chirp!"
end
# Type hierarchy checking
dog = Dog("Rex", 3)
cat = Cat("Whiskers", 2)
sparrow = Sparrow("Tweet", 15.0)
println(make_sound(dog))
println(make_sound(cat))
println(make_sound(sparrow))
# Type checking
println(dog isa Animal)
println(dog isa Mammal)
println(dog isa Dog)
println(dog isa Cat)Parameterized types allow you to define generic types that work with multiple type parameters. This enables type-safe generic programming.
- Definition:
struct Point{T} ... end - Usage:
Point(1.0, 2.0) - Multiple parameters:
struct Pair{A,B} - Type constraints:
where {T<:Number} - Covariance: Supports type relationships
# Parameterized Types
# Basic parameterized type
struct Point{T}
x::T
y::T
end
# Creating points
p1 = Point(1.0, 2.0)
p2 = Point(1, 2)
p3 = Point{Float64}(3.0, 4.0)
# Function with parameterized types
function distance(p1::Point{T}, p2::Point{T}) where {T<:Number}
return sqrt((p1.x - p2.x)^2 + (p1.y - p2.y)^2)
end
# Multiple parameters
struct Pair{A,B}
first::A
second::B
end
# Covariance and contravariance
struct Container{T}
value::T
end
# Type constraints
function process_value(x::T) where {T<:Number}
return x * 2
end
# Generic functions
function get_first(x::Tuple)
return x[1]
end
# Usage
println(distance(Point(0.0, 0.0), Point(3.0, 4.0)))
println(process_value(5))
println(process_value(3.14))Macros in Julia are powerful tools for metaprogramming. They manipulate expressions before compilation, enabling domain-specific language creation.
- Definition:
macro name(expr) ... end - Usage:
@name - String macros:
macro uppercase_str(s) - Debug macros:
@dbg expr - Code generation: Generate functions or expressions
# Macros and Metaprogramming
# Basic macro
macro times_two(expr)
return :(2 * $expr)
end
# Usage
@times_two 5
# Macro with arguments
macro pow(base, exp)
return :($base ^ $exp)
end
@pow 2 3
# String macros
macro uppercase_str(s)
return :(uppercase($s))
end
# Custom macro for debugging
macro dbg(expr)
return quote
println("Expression: $(string($expr))")
println("Value: ", $expr)
end
end
# Using debug macro
x = 10
@dbg x + 5
# Macro for creating functions
macro define_square(name)
return quote
function $(esc(name))(x)
return x^2
end
end
end
@define_square square_func
println(square_func(5))
# Macro for custom syntax
macro repeat(n, expr)
return quote
for i in 1:$n
$expr
end
end
end
@repeat 3 println("Hello")Generators and coroutines in Julia provide ways to work with lazy sequences and cooperative multitasking. Tasks and Channels are key components.
- Generators:
Task(fibonacci_generator) - Channels:
Channel() do ch ... end - Tasks:
@asyncand@sync - Produce/Consume:
produce,consume - Stateful functions:
function counter() ... end
# Generators and Coroutines
# Generator function
function fibonacci_generator()
a, b = 0, 1
while true
produce(a)
a, b = b, a + b
end
end
# Using generator with Task
task = Task(fibonacci_generator)
for i in 1:10
println(consume(task))
end
# Custom generator using Channel
function fibonacci_channel()
Channel() do ch
a, b = 0, 1
while true
put!(ch, a)
a, b = b, a + b
end
end
end
# Using Channel
for n in take(fibonacci_channel(), 10)
println(n)
end
# Coroutine with state
function counter(start=0)
state = start
return function()
state += 1
return state
end
end
counter = counter()
println(counter())
println(counter())
println(counter())
# Async/await with tasks
function async_example()
task = @async begin
sleep(1)
return "Done"
end
return fetch(task)
end
println(async_example())Julia provides advanced array operations including broadcasting, reshaping, and various linear algebra operations for scientific computing.
- Broadcasting:
A .+ 1 - Reshaping:
reshape(arr, 3, 3) - Matrix operations:
A * B - Element-wise:
A .* B - Linear algebra:
norm,trace,diag
# Advanced Array Operations
# Array initialization
A = zeros(3, 3)
B = ones(3, 3)
C = fill(5.0, 3, 3)
I = Matrix{Float64}(I, 3, 3)
# Reshaping
arr = 1:9
matrix = reshape(arr, 3, 3)
println(matrix)
# Transpose
println(matrix')
# Broadcasting
A = [1 2 3; 4 5 6; 7 8 9]
B = A .+ 1
C = A .* 2
D = A .^ 2
# Element-wise operations
println(B)
println(C)
println(D)
# Matrix multiplication
X = rand(3, 3)
Y = rand(3, 3)
Z = X * Y
println(Z)
# Element-wise multiplication
W = X .* Y
println(W)
# Linear algebra functions
using LinearAlgebra
norm_X = norm(X)
trace_X = trace(X)
diag_X = diag(X)Julia's Missings.jl provides tools for working with missing values, including arrays with missing data and functions for handling them.
- Missing values:
[1, 2, missing, 4] - Check missing:
ismissing.(data) - Remove missing:
collect(skipmissing(data)) - Replace missing:
coalesce.(data, 0) - Skip missing:
sum(skipmissing(data))
# Working with Missing Data
using Missings
# Creating arrays with missing values
data = [1, 2, missing, 4, 5, missing, 7]
println(data)
# Check for missing values
println(ismissing.(data))
println(any(ismissing.(data)))
# Remove missing values
clean_data = collect(skipmissing(data))
println(clean_data)
# Replace missing values
replaced = coalesce.(data, 0)
println(replaced)
# Operations with missing values
x = [1, 2, missing, 4]
y = [5, 6, missing, 8]
z = x .+ y # Results in [6, 8, missing, 12]
println(z)
# Ignoring missing values
sum_complete = sum(skipmissing(x))
println(sum_complete)
# Working with DataFrames
using DataFrames
df = DataFrame(
A=[1, 2, 3, 4],
B=[missing, 5, 6, missing],
C=["x", missing, "z", "w"]
)
println(df)
println(describe(df))
# Drop missing rows
df_clean = dropmissing(df)
println(df_clean)Julia provides built-in functions for sorting arrays and searching for elements. These operations are optimized for performance.
- Sort:
sort(arr) - In-place sort:
sort!(arr) - Custom sort:
sort(arr, by = x -> x[1]) - Search:
findall(x -> x > 5, arr) - Binary search:
searchsorted(arr, 7)
# Sorting and Searching
# Basic sorting
arr = [5, 2, 8, 1, 9, 3]
sort!(arr)
println(arr)
# Sorting without mutation
arr2 = [5, 2, 8, 1, 9, 3]
sorted = sort(arr2)
println(arr2)
println(sorted)
# Sorting with custom comparator
arr3 = [(5, "apple"), (3, "banana"), (8, "cherry")]
sort!(arr3, by = x -> x[1])
println(arr3)
# Sorting descending
arr4 = [5, 2, 8, 1, 9, 3]
sort!(arr4, rev=true)
println(arr4)
# Search functions
arr5 = [1, 3, 5, 7, 9, 11]
println(findall(x -> x > 5, arr5))
println(findfirst(x -> x > 5, arr5))
println(findlast(x -> x > 5, arr5))
# Binary search (requires sorted array)
idx = searchsorted(arr5, 7)
println(idx)
# Contains
println(7 in arr5)
println(4 in arr5)Julia supports a wide range of mathematical operations including basic arithmetic, special functions, and linear algebra. Many of these are built-in.
- Basic arithmetic:
+,-,*,/,^ - Trigonometric:
sin,cos,tan - Special functions:
gamma,beta,erf - Random numbers:
rand,randn - Statistics:
mean,std,cor
# Mathematical Operations
# Basic arithmetic
x = 10
y = 3
println(x + y)
println(x - y)
println(x * y)
println(x / y)
println(x % y)
println(x ^ y)
# Mathematical functions
println(sin(π/4))
println(cos(π/4))
println(tan(π/4))
println(exp(1))
println(log(ℯ))
println(log10(100))
println(sqrt(9))
# Special functions
using SpecialFunctions
println(gamma(5))
println(beta(2, 3))
println(erf(1.0))
# Random numbers
using Random
Random.seed!(123)
println(rand())
println(randn())
println(rand(1:10))
println(rand(3, 3))
# Statistics
using Statistics
data = randn(1000)
println(mean(data))
println(std(data))
println(var(data))
# Linear algebra
using LinearAlgebra
A = rand(3, 3)
println(eigvals(A))
println(det(A))Julia supports various data serialization formats including HDF5, JLD2, and BSON. These formats are useful for saving and loading data.
- HDF5:
h5open("data.h5", "w") do file - JLD2:
@save "data.jld2" arr metadata - BSON:
BSON.@save "data.bson" arr metadata - Read:
read(file, "dataset") - Write:
write(file, "dataset", data)
# Data Serialization
# Using HDF5
using HDF5
# Write to HDF5
# h5open("data.h5", "w") do file
# write(file, "dataset", rand(10, 10))
# write(file, "metadata", Dict("name" => "experiment1"))
# end
# # Read from HDF5
# h5open("data.h5", "r") do file
# data = read(file, "dataset")
# metadata = read(file, "metadata")
# println(data)
# println(metadata)
# end
# Using JLD2
using JLD2
# Write to JLD2
# @save "data.jld2" arr metadata
# arr = 1:10
# metadata = Dict("version" => "1.0")
# @save "data.jld2" arr metadata
# # Read from JLD2
# @load "data.jld2" arr metadata
# println(arr)
# println(metadata)
# Using BSON
using BSON
# # Write to BSON
# BSON.@save "data.bson" arr metadata
#
# # Read from BSON
# BSON.@load "data.bson" arr metadata
# println(arr)
# println(metadata)PyCall.jl allows Julia to call Python libraries directly. This provides access to the extensive Python ecosystem from Julia.
- Import:
pyimport("numpy") - Use Python functions:
np.sum(arr) - Create Python objects:
np.array([1, 2, 3]) - Plotting:
pyimport("matplotlib.pyplot") - Conversion:
pyconvert(Array, py_arr)
# Interfacing with Python
using PyCall
# Import Python modules
np = pyimport("numpy")
plt = pyimport("matplotlib.pyplot")
# Using numpy arrays
arr = np.array([1, 2, 3, 4, 5])
println(arr)
println(np.sum(arr))
println(np.mean(arr))
# Creating arrays from Python
py_arr = np.random.randn(10, 10)
println(size(py_arr))
# Using matplotlib
x = np.linspace(0, 2π, 100)
y = np.sin(x)
# plt.plot(x, y)
# plt.show()
# Calling Python functions
math = pyimport("math")
println(math.sqrt(16))
println(math.factorial(5))
# Converting between Julia and Python
julia_arr = [1, 2, 3, 4]
py_arr2 = pyconvert(PyObject, julia_arr)
println(py_arr2)
# Working with pandas
pd = pyimport("pandas")
df = pd.DataFrame(Dict("col1" => [1, 2, 3], "col2" => ["a", "b", "c"]))
println(df)Reverse a string by converting it to a character array, reversing it, and joining it back together.
- Method:
join(reverse(collect(s))) - Alternative: Iterate in reverse
- Performance: O(n) time complexity
- Unicode: Works with Unicode strings
# Reverse a string
function reverse_string(s::String)
return join(reverse(collect(s)))
end
println(reverse_string("hello")) # "olleh"
# Alternative using iteration
function reverse_string_iter(s::String)
chars = collect(s)
reversed = [chars[i] for i in length(chars):-1:1]
return join(reversed)
endCheck if a string is a palindrome by comparing it to its reverse. The function should handle case sensitivity and whitespace.
- Method:
s == join(reverse(collect(s))) - Case insensitive:
lowercase - In-place: Two-pointer comparison
- Complexity: O(n) time, O(n) space
# Check palindrome
function is_palindrome(s::String)
cleaned = lowercase(strip(s))
return cleaned == join(reverse(collect(cleaned)))
end
println(is_palindrome("racecar")) # true
println(is_palindrome("hello")) # false
# Without extra allocation
function is_palindrome_inplace(s::String)
chars = collect(lowercase(s))
i, j = 1, length(chars)
while i < j
if chars[i] != chars[j]
return false
end
i += 1
j -= 1
end
return true
endFind the maximum value in an array using the built-in maximum function or by manual iteration.
- Built-in:
maximum(arr) - Manual: Iterate and track max
- Empty array: Handle with
isempty - Complexity: O(n) time
# Find max in array
function find_max(arr)
return maximum(arr)
end
println(find_max([1, 5, 3, 9, 2])) # 9
# Manual implementation
function find_max_manual(arr)
max_val = arr[1]
for x in arr
if x > max_val
max_val = x
end
end
return max_val
endRemove duplicate elements from an array using unique or Set. This preserves order in the result.
- Built-in:
unique(arr) - Set:
collect(Set(arr)) - Order:
uniquepreserves order - Complexity: O(n) time
# Remove duplicates
function remove_duplicates(arr)
return unique(arr)
end
println(remove_duplicates([1, 2, 2, 3, 3, 4])) # [1, 2, 3, 4]
# Using Set
function remove_duplicates_set(arr)
return collect(Set(arr))
endMerge two arrays using vcat or the spread operator. This creates a new array without modifying the originals.
- vcat:
vcat(arr1, arr2) - Spread:
[arr1..., arr2...] - In-place:
append!(arr1, arr2) - Unique merge:
unique(vcat(arr1, arr2))
# Merge arrays
function merge_arrays(arr1, arr2)
return vcat(arr1, arr2)
end
println(merge_arrays([1, 2], [3, 4])) # [1, 2, 3, 4]
# Alternative with concatenation
function merge_concat(arr1, arr2)
return [arr1..., arr2...]
endConvert a string to a number using parse or tryparse for safe conversion.
- parse:
parse(Float64, str) - Integer:
parse(Int, str) - Tryparse:
tryparse(Float64, str) - Error handling: Check for valid input
# Convert string to number
function string_to_number(str::String)
return parse(Float64, str)
end
println(string_to_number("42")) # 42.0
# Convert to integer
function string_to_int(str::String)
return parse(Int, str)
end
println(string_to_int("42")) # 42Iterate through a dictionary's key-value pairs using a for loop. Access keys and values using destructuring.
- For loop:
for (key, value) in dict - Keys:
keys(dict) - Values:
values(dict) - Pair iteration:
for pair in dict
# Loop through dictionary
function loop_dict(dict)
for (key, value) in dict
println("$key => $value")
end
end
data = Dict("name" => "Alice", "age" => 25, "city" => "NYC")
loop_dict(data)Delay function execution using sleep for blocking delays or @async for non-blocking delays.
- Blocking:
sleep(seconds) - Async:
@async begin sleep(2); fn() end - Task: Create and fetch a task
- Timer: Use
Timerfor periodic execution
# Delay function execution
using Dates
function delayed_execution(delay_seconds, fn)
sleep(delay_seconds)
return fn()
end
# Example usage
result = delayed_execution(2, () -> println("After 2 seconds"))
println(result)
# Async version
function async_delay(delay_seconds, fn)
@async begin
sleep(delay_seconds)
fn()
end
endMake an HTTP GET request using HTTP.jl. Handle errors and parse the response.
- GET:
HTTP.get(url) - Response:
String(response.body) - POST:
HTTP.post(url, headers, body) - Error handling: Try-catch for network errors
# HTTP GET request
using HTTP
function fetch_data(url::String)
try
response = HTTP.get(url)
return String(response.body)
catch e
println("Error: $e")
return nothing
end
end
# Example
# data = fetch_data("https://api.github.com")
# println(data)
# POST request
function post_data(url::String, data::Dict)
try
json_data = JSON.json(data)
response = HTTP.post(url,
["Content-Type" => "application/json"],
json_data)
return String(response.body)
catch e
println("Error: $e")
return nothing
end
endCreate a task that simulates a promise with asynchronous execution and result handling.
- Task:
@async - Fetch:
fetch(task) - Error handling: Try-catch for task errors
- Chaining: Combine multiple tasks
# Create a promise-like task
function create_promise(should_resolve::Bool)
return @async begin
sleep(1)
if should_resolve
return "Success!"
else
error("Failed!")
end
end
end
# Using the promise
task = create_promise(true)
result = fetch(task)
println(result)
# With error handling
task2 = create_promise(false)
try
result2 = fetch(task2)
println(result2)
catch e
println("Caught error: $e")
endCalculate factorial using recursion or iteration. Handle edge cases like 0 and negative numbers.
- Recursive:
n <= 1 ? 1 : n * factorial(n-1) - Iterative: Loop from 2 to n
- Edge cases: 0! = 1, handle negatives
- Performance: Iterative is faster
# Factorial
function factorial(n::Int)
if n <= 1
return 1
end
return n * factorial(n-1)
end
println(factorial(5)) # 120
# Iterative version
function factorial_iterative(n::Int)
result = 1
for i in 2:n
result *= i
end
return result
endCalculate Fibonacci numbers using recursion, iteration, or memoization. Handle base cases.
- Recursive:
n <= 1 ? n : fib(n-1) + fib(n-2) - Iterative: Loop with variables
- Memoization: Cache results for performance
- Complexity: O(2^n) recursive, O(n) iterative
# Fibonacci
function fibonacci(n::Int)
if n <= 1
return n
end
return fibonacci(n-1) + fibonacci(n-2)
end
println(fibonacci(8)) # 21
# Iterative version
function fibonacci_iterative(n::Int)
a, b = 0, 1
for i in 2:n
a, b = b, a + b
end
return n > 0 ? b : a
endPrint numbers from 1 to n, replacing multiples of 3 with "Fizz", multiples of 5 with "Buzz", and multiples of both with "FizzBuzz".
- Logic: Check divisibility by 3 and 5
- Order: Check 15 first, then 3, then 5
- Output: Print each result
- Use case: Common interview question
# FizzBuzz
function fizzbuzz(n::Int)
for i in 1:n
if i % 15 == 0
println("FizzBuzz")
elseif i % 3 == 0
println("Fizz")
elseif i % 5 == 0
println("Buzz")
else
println(i)
end
end
end
fizzbuzz(15)Find the missing number in a consecutive sequence using the formula n*(n+1)/2 - sum.
- Formula:
n * (n + 1) ÷ 2 - sum(arr) - XOR: XOR all numbers and indices
- Edge cases: Empty array, missing first or last
- Complexity: O(n) time, O(1) space
# Find missing number
function find_missing(arr)
n = length(arr) + 1
total = n * (n + 1) ÷ 2
sum_arr = sum(arr)
return total - sum_arr
end
println(find_missing([1, 2, 4, 5, 6])) # 3Find duplicate elements in an array using a Set to track seen elements. Collect duplicates into a set or array.
- Set: Track seen elements
- Filter:
filter(x -> x in seen, arr) - Complexity: O(n) time
- Returns: Set or array of duplicates
# Find duplicates
function find_duplicates(arr)
seen = Set()
duplicates = Set()
for x in arr
if x in seen
push!(duplicates, x)
else
push!(seen, x)
end
end
return collect(duplicates)
end
println(find_duplicates([1, 2, 3, 2, 4, 3])) # [2, 3]Calculate the sum of all elements in an array using sum or manual iteration.
- Built-in:
sum(arr) - Manual:
reduce(+, arr) - For loop: Iterate and accumulate
- Empty array: Returns 0
# Sum of array
function sum_array(arr)
return sum(arr)
end
println(sum_array([1, 2, 3, 4, 5])) # 15
# Manual implementation
function sum_array_manual(arr)
s = 0
for x in arr
s += x
end
return s
endCalculate the average by dividing the sum by the length. Handle empty arrays.
- Method:
sum(arr) / length(arr) - Integer division:
div(sum(arr), length(arr)) - Empty array: Return 0 or handle separately
- Precision: Returns Float64
# Average of array
function average_array(arr)
return sum(arr) / length(arr)
end
println(average_array([1, 2, 3, 4, 5])) # 3.0
# With integer division
function average_integer(arr)
return div(sum(arr), length(arr))
endSort an array in ascending order using sort. Use sort! for in-place sorting.
- Non-mutating:
sort(arr) - Mutating:
sort!(arr) - Custom comparator:
sort(arr, by = x -> x) - Strings: Sort lexicographically
# Sort array ascending
function sort_ascending(arr)
return sort(arr)
end
println(sort_ascending([5, 2, 8, 1, 9])) # [1, 2, 5, 8, 9]
# In-place sorting
function sort_ascending!(arr)
sort!(arr)
return arr
endSort an array in descending order using sort with rev=true.
- Non-mutating:
sort(arr, rev=true) - Mutating:
sort!(arr, rev=true) - Alternative:
sort(arr) |> reverse - Custom comparator:
sort(arr, by = x -> -x)
# Sort array descending
function sort_descending(arr)
return sort(arr, rev=true)
end
println(sort_descending([5, 2, 8, 1, 9])) # [9, 8, 5, 2, 1]
# In-place sorting
function sort_descending!(arr)
sort!(arr, rev=true)
return arr
endFlatten a nested array using recursion or vec for simple arrays. Handle multiple levels of nesting.
- Recursive: Iterate and flatten sub-arrays
- vec:
vec(arr)for simple cases - Deep flatten: Custom recursive function
- Complexity: O(n) time
# Flatten nested array
function flatten_array(arr)
result = []
for x in arr
if isa(x, Vector)
append!(result, flatten_array(x))
else
push!(result, x)
end
end
return result
end
println(flatten_array([1, [2, [3, 4], 5], 6])) # [1, 2, 3, 4, 5, 6]
# Using Base function
function flatten_using_base(arr)
return vec(arr)
endSplit an array into chunks of a specified size using slicing in a loop.
- Method:
arr[i:min(i+size-1, end)] - Step:
i:size:length(arr) - Incomplete chunk: Handle remaining elements
- Use case: Batch processing
# Chunk array
function chunk_array(arr, size::Int)
chunks = []
for i in 1:size:length(arr)
push!(chunks, arr[i:min(i+size-1, end)])
end
return chunks
end
println(chunk_array([1, 2, 3, 4, 5, 6], 2)) # [[1, 2], [3, 4], [5, 6]]Implement binary search on a sorted array. Return the index of the target or -1 if not found.
- Algorithm: Divide and conquer
- Time: O(log n)
- Recursive: Recursively search left or right half
- Built-in:
searchsorted
# Binary search
function binary_search(arr, target)
left, right = 1, length(arr)
while left <= right
mid = (left + right) ÷ 2
if arr[mid] == target
return mid
elseif arr[mid] < target
left = mid + 1
else
right = mid - 1
end
end
return -1
end
println(binary_search([1, 2, 3, 4, 5, 6, 7], 5)) # 5 (1-indexed)
# Using Base function
searchsorted([1, 2, 3, 4, 5, 6, 7], 5)Implement quick sort using a pivot and recursion. Partition the array around the pivot.
- Algorithm: Choose pivot, partition, recurse
- Time: O(n log n) average, O(n²) worst
- In-place: Implement in-place for performance
- Pivot choice: First, last, or random
# Quick sort
function quick_sort(arr)
if length(arr) <= 1
return arr
end
pivot = arr[1]
left = [x for x in arr[2:end] if x < pivot]
right = [x for x in arr[2:end] if x >= pivot]
return [quick_sort(left)..., pivot, quick_sort(right)...]
end
println(quick_sort([5, 3, 8, 4, 2, 7, 1, 6]))
# In-place quick sort
function quick_sort!(arr, first, last)
if first < last
splitpoint = partition!(arr, first, last)
quick_sort!(arr, first, splitpoint-1)
quick_sort!(arr, splitpoint+1, last)
end
return arr
endImplement merge sort using divide-and-conquer. Merge two sorted sub-arrays.
- Algorithm: Divide, sort, merge
- Time: O(n log n)
- Stable: Maintains relative order
- Space: O(n) auxiliary space
# Merge sort
function merge_sort(arr)
if length(arr) <= 1
return arr
end
mid = div(length(arr), 2)
left = merge_sort(arr[1:mid])
right = merge_sort(arr[mid+1:end])
return merge(left, right)
end
function merge(left, right)
result = []
i, j = 1, 1
while i <= length(left) && j <= length(right)
if left[i] <= right[j]
push!(result, left[i])
i += 1
else
push!(result, right[j])
j += 1
end
end
append!(result, left[i:end])
append!(result, right[j:end])
return result
end
println(merge_sort([5, 3, 8, 4, 2, 7, 1, 6]))Implement bubble sort with optimization to stop early if no swaps occur.
- Algorithm: Compare adjacent, swap if needed
- Time: O(n²) worst case
- Optimization: Early termination
- Use case: Educational, small datasets
# Bubble sort
function bubble_sort(arr)
sorted = copy(arr)
n = length(sorted)
for i in 1:n-1
for j in 1:n-i
if sorted[j] > sorted[j+1]
sorted[j], sorted[j+1] = sorted[j+1], sorted[j]
end
end
end
return sorted
end
println(bubble_sort([5, 3, 8, 4, 2, 7, 1, 6]))
# Optimized bubble sort
function bubble_sort_optimized(arr)
sorted = copy(arr)
n = length(sorted)
for i in 1:n-1
swapped = false
for j in 1:n-i
if sorted[j] > sorted[j+1]
sorted[j], sorted[j+1] = sorted[j+1], sorted[j]
swapped = true
end
end
if !swapped
break
end
end
return sorted
endFind common elements between two arrays using list comprehension or Set operations.
- Comprehension:
[x for x in arr1 if x in arr2] - Set:
intersect(Set(arr1), Set(arr2)) - Efficiency: Use Set for O(n) time
- Duplicates: Set removes duplicates
# Intersection of arrays
function intersection(arr1, arr2)
return [x for x in arr1 if x in arr2]
end
println(intersection([1, 2, 3, 4], [3, 4, 5, 6])) # [3, 4]
# Using Set for efficiency
function intersection_set(arr1, arr2)
set2 = Set(arr2)
return [x for x in arr1 if x in set2]
endCombine two arrays with unique elements using Set operations.
- Set:
collect(Set([arr1..., arr2...])) - Unique:
unique(vcat(arr1, arr2)) - Order: Set doesn't preserve order
- Efficiency: O(n) time
# Union of arrays
function union(arr1, arr2)
return collect(Set([arr1..., arr2...]))
end
println(union([1, 2, 3], [3, 4, 5])) # [1, 2, 3, 4, 5]
# Alternative using vcat and unique
function union_alt(arr1, arr2)
return unique(vcat(arr1, arr2))
endFind elements in the first array that are not in the second array.
- Comprehension:
[x for x in arr1 if x not in arr2] - Symmetric:
[difference(arr1, arr2)..., difference(arr2, arr1)...] - Efficiency: Use Set for O(n) time
- Use case: Set operations
# Difference of arrays
function difference(arr1, arr2)
return [x for x in arr1 if x not in arr2]
end
println(difference([1, 2, 3, 4], [3, 4, 5, 6])) # [1, 2]
# Symmetric difference
function symmetric_difference(arr1, arr2)
return [difference(arr1, arr2)..., difference(arr2, arr1)...]
endGroup an array of objects by a property using a dictionary.
- Method: Iterate and group into dict
- Key: Use property as key
- Value: Array of items with that key
- Use case: Data aggregation
# Group by property
function group_by(arr, key)
groups = Dict()
for item in arr
group_key = getproperty(item, key)
if haskey(groups, group_key)
push!(groups[group_key], item)
else
groups[group_key] = [item]
end
end
return groups
end
data = [
(type="fruit", name="apple"),
(type="fruit", name="banana"),
(type="veg", name="carrot")
]
println(group_by(data, :type))Create a deep copy of an object by recursively copying nested structures.
- Method: Recursive copying
- Dicts: Copy keys and values recursively
- Arrays: Copy elements recursively
- Performance: O(n) where n is object size
# Deep clone object
function deep_clone(obj)
if isa(obj, Dict)
return Dict(key => deep_clone(value) for (key, value) in obj)
elseif isa(obj, Vector)
return [deep_clone(x) for x in obj]
elseif isa(obj, Tuple)
return Tuple(deep_clone(x) for x in obj)
else
return obj
end
end
original = Dict("a" => 1, "b" => Dict("c" => 2))
cloned = deep_clone(original)
cloned["b"]["c"] = 3
println(original["b"]["c"]) # 2
println(cloned["b"]["c"]) # 3Perform immutable updates on nested data structures by copying at each level.
- Method: Copy object and update path
- Path: Use dot notation for nested access
- Libraries: Use Setfield.jl for convenience
- Use case: State management
# Immutable update
function update_immutable(obj, path, value)
parts = split(path, ".")
if length(parts) == 1
new_obj = copy(obj)
new_obj[parts[1]] = value
return new_obj
else
first_part = parts[1]
rest_path = join(parts[2:end], ".")
new_obj = copy(obj)
if haskey(new_obj, first_part)
new_obj[first_part] = update_immutable(new_obj[first_part], rest_path, value)
else
new_obj[first_part] = update_immutable(Dict(), rest_path, value)
end
return new_obj
end
end
state = Dict("user" => Dict("name" => "Alice", "age" => 25))
new_state = update_immutable(state, "user.age", 26)
println(state["user"]["age"]) # 25
println(new_state["user"]["age"]) # 26Implement a pipe function that composes functions from left to right.
- Method:
pipe(fns...)(value) - Implementation: Reduce with function application
- Use case: Function composition
- Direction: Left to right
# Pipe function
function pipe(fns...)
return function(value)
result = value
for fn in fns
result = fn(result)
end
return result
end
end
double(x) = x * 2
add_ten(x) = x + 10
square(x) = x^2
process = pipe(double, add_ten, square)
println(process(5)) # (5*2+10)^2 = 400Implement a compose function that composes functions from right to left.
- Method:
compose(fns...)(value) - Implementation: ReduceRight with function application
- Use case: Function composition
- Direction: Right to left
# Compose function
function compose(fns...)
return function(value)
result = value
for fn in reverse(fns)
result = fn(result)
end
return result
end
end
process2 = compose(square, add_ten, double)
println(process2(5)) # (5*2+10)^2 = 400Implement memoization to cache function results based on arguments.
- Method: Cache in dictionary
- Key: Serialize arguments
- Use case: Expensive function calls
- Trade-off: Memory for speed
# Memoization
function memoize(fn)
cache = Dict()
return function(args...)
key = tuple(args...)
if haskey(cache, key)
return cache[key]
end
result = fn(args...)
cache[key] = result
return result
end
end
fibonacci_memo = memoize(function(n)
if n <= 1
return n
end
return fibonacci_memo(n-1) + fibonacci_memo(n-2)
end)
println(fibonacci_memo(10))Implement a function that ensures a function is called only once.
- Method: Use a flag and closure
- Implementation: Track if called
- Use case: Initialization, setup
- Thread safety: Not needed in single-threaded
# Once function
function once(fn)
called = false
result = nothing
return function(args...)
if !called
called = true
result = fn(args...)
end
return result
end
end
initialize = once(() -> begin
println("Initialized")
return Dict("id" => 1, "name" => "App")
end)
initialize()
initialize()Implement debounce with leading edge execution, which runs immediately on first call then waits.
- Method: Track last call time
- Implementation: Immediate execution, then cooldown
- Use case: Save actions, API calls
- Difference: Leading vs trailing edge
# Debounce with leading edge
function debounce_leading(fn, delay::Int)
last_call = 0
timeout_id = nothing
return function(args...)
now_time = time()
if now_time - last_call < delay
if timeout_id !== nothing
cancel(timeout_id)
end
timeout_id = @async begin
sleep(delay)
last_call = time()
fn(args...)
end
else
last_call = now_time
fn(args...)
end
end
endImplement throttle with leading edge execution, which runs at most once per time period.
- Method: Track last call time
- Implementation: Execute if enough time has passed
- Use case: Scroll events, resize events
- Difference: Leading vs trailing edge
# Throttle with leading edge
function throttle_leading(fn, delay::Int)
last_call = 0
return function(args...)
now_time = time()
if now_time - last_call >= delay
last_call = now_time
fn(args...)
end
end
endImplement deep equality comparison for nested structures.
- Method: Recursive comparison
- Base cases: Primitive values
- Objects: Compare keys and values recursively
- Arrays: Compare elements recursively
# Deep equal
function deep_equal(obj1, obj2)
if obj1 === obj2
return true
end
if typeof(obj1) != typeof(obj2)
return false
end
if isa(obj1, Dict)
if length(obj1) != length(obj2)
return false
end
for (key, value) in obj1
if !haskey(obj2, key)
return false
end
if !deep_equal(value, obj2[key])
return false
end
end
return true
elseif isa(obj1, Vector)
if length(obj1) != length(obj2)
return false
end
for (i, value) in enumerate(obj1)
if !deep_equal(value, obj2[i])
return false
end
end
return true
else
return obj1 == obj2
end
endImplement the Observable pattern for event notification and subscription.
- Observable: Maintains subscribers
- Subscribe: Add callback to subscribers
- Notify: Call all subscribers
- Unsubscribe: Remove callback
# Observable pattern
mutable struct Observable
subscribers::Vector{Function}
end
function Observable()
return Observable(Function[])
end
function subscribe(obs::Observable, callback::Function)
push!(obs.subscribers, callback)
return function()
filter!(x -> x !== callback, obs.subscribers)
end
end
function notify(obs::Observable, data)
for callback in obs.subscribers
callback(data)
end
end
# Usage
obs = Observable()
unsubscribe = subscribe(obs, data -> println("Received: $data"))
notify(obs, "Hello") # Received: Hello
unsubscribe()
notify(obs, "World") # Nothing happensImplement the Singleton pattern to ensure only one instance of a class exists.
- Method: Store instance in a constant
- Lazy: Create instance only when needed
- Global: Access from anywhere
- Use case: Configuration, logging
# Singleton pattern
mutable struct Singleton
data::Dict
end
function Singleton()
if !isdefined(@__MODULE__, :_singleton_instance)
@eval const _singleton_instance = Singleton(Dict())
end
return _singleton_instance
end
function set_value(s::Singleton, key, value)
s.data[key] = value
end
function get_value(s::Singleton, key)
return get(s.data, key, nothing)
end
# Usage
s1 = Singleton()
s2 = Singleton()
set_value(s1, "name", "Alice")
println(get_value(s2, "name")) # Alice
println(s1 === s2) # trueImplement the Factory pattern for creating objects without specifying the exact class.
- Method: Factory function or class
- Benefits: Decouples creation from usage
- Parameterized: Pass parameters for customization
- Use case: Creating different types
# Factory pattern
abstract type User end
struct Admin <: User
name::String
end
struct Guest <: User
name::String
end
struct RegularUser <: User
name::String
end
function create_user(type::String, name::String)
if type == "admin"
return Admin(name)
elseif type == "guest"
return Guest(name)
else
return RegularUser(name)
end
end
# Usage
admin = create_user("admin", "Alice")
println(typeof(admin)) # AdminImplement the Strategy pattern for interchangeable algorithms.
- Context: Uses a strategy
- Strategy: Interface for algorithms
- Benefits: Runtime switching
- Use case: Payment methods, sorting
# Strategy pattern
abstract type PaymentStrategy end
struct CreditCardStrategy <: PaymentStrategy end
struct PayPalStrategy <: PaymentStrategy end
struct CryptoStrategy <: PaymentStrategy end
function pay(strategy::CreditCardStrategy, amount::Float64)
println("Paid $amount with Credit Card")
end
function pay(strategy::PayPalStrategy, amount::Float64)
println("Paid $amount with PayPal")
end
function pay(strategy::CryptoStrategy, amount::Float64)
println("Paid $amount with Crypto")
end
mutable struct PaymentContext
strategy::PaymentStrategy
end
function execute_payment(context::PaymentContext, amount::Float64)
pay(context.strategy, amount)
end
# Usage
context = PaymentContext(CreditCardStrategy())
execute_payment(context, 100.0)
context.strategy = PayPalStrategy()
execute_payment(context, 50.0)Implement the Observer pattern for one-to-many dependency notification.
- Subject: Maintains observers
- Observer: Receives updates
- Benefits: Loose coupling
- Use case: Event handling, pub/sub
# Observer pattern
mutable struct Subject
observers::Vector{Function}
state::String
end
function Subject()
return Subject([], "")
end
function attach(subject::Subject, observer::Function)
push!(subject.observers, observer)
end
function detach(subject::Subject, observer::Function)
filter!(x -> x !== observer, subject.observers)
end
function notify(subject::Subject)
for observer in subject.observers
observer(subject.state)
end
end
function set_state(subject::Subject, state::String)
subject.state = state
notify(subject)
end
# Usage
subject = Subject()
observer1(data) = println("Observer1 received: $data")
observer2(data) = println("Observer2 received: $data")
attach(subject, observer1)
attach(subject, observer2)
set_state(subject, "Hello World")Implement the Decorator pattern for adding behavior dynamically.
- Component: Base interface
- Decorator: Wraps component
- Benefits: Flexible extension
- Use case: Logging, authentication
# Decorator pattern
struct Coffee
cost::Float64
description::String
end
function milk_decorator(coffee::Coffee)
return Coffee(coffee.cost + 2.0, coffee.description * ", Milk")
end
function sugar_decorator(coffee::Coffee)
return Coffee(coffee.cost + 1.0, coffee.description * ", Sugar")
end
# Usage
coffee = Coffee(5.0, "Coffee")
coffee = milk_decorator(coffee)
coffee = sugar_decorator(coffee)
println(coffee.description) # Coffee, Milk, Sugar
println(coffee.cost) # 8.0Implement the Command pattern for encapsulating requests.
- Command: Encapsulates request
- Invoker: Executes commands
- Receiver: Performs work
- Benefits: Undo/redo, queuing
# Command pattern
abstract type Command end
mutable struct AddCommand <: Command
receiver::Vector{Int}
value::Int
end
function execute(cmd::AddCommand)
push!(cmd.receiver, cmd.value)
end
function undo(cmd::AddCommand)
pop!(cmd.receiver)
end
# Usage
receiver = [1, 2, 3]
cmd = AddCommand(receiver, 4)
execute(cmd)
println(receiver) # [1, 2, 3, 4]
undo(cmd)
println(receiver) # [1, 2, 3]Implement the Memento pattern for state capture and restoration.
- Originator: Creates and restores mementos
- Memento: Stores internal state
- Caretaker: Manages mementos
- Benefits: Undo/redo
# Memento pattern
mutable struct Memento
state::Dict
end
mutable struct Originator
state::Dict
end
function save_state(originator::Originator)
return Memento(copy(originator.state))
end
function restore_state(originator::Originator, memento::Memento)
originator.state = memento.state
end
mutable struct Caretaker
mementos::Vector{Memento}
end
function Caretaker()
return Caretaker([])
end
# Usage
originator = Originator(Dict("value" => 1))
caretaker = Caretaker()
push!(caretaker.mementos, save_state(originator))
originator.state["value"] = 2
push!(caretaker.mementos, save_state(originator))
originator.state["value"] = 3
restore_state(originator, caretaker.mementos[1])
println(originator.state["value"]) # 1Implement the Mediator pattern for centralized communication.
- Mediator: Encapsulates communication
- Colleague: Communicates through mediator
- Benefits: Loose coupling
- Use case: Chat systems, UI components
# Mediator pattern
mutable struct Colleague
name::String
mediator::Any
end
mutable struct Mediator
colleagues::Vector{Colleague}
end
function Mediator()
return Mediator([])
end
function register(mediator::Mediator, colleague::Colleague)
colleague.mediator = mediator
push!(mediator.colleagues, colleague)
end
function send(mediator::Mediator, message::String, sender::Colleague)
for colleague in mediator.colleagues
if colleague !== sender
receive(colleague, message)
end
end
end
function receive(colleague::Colleague, message::String)
println("$(colleague.name) received: $message")
end
# Usage
mediator = Mediator()
alice = Colleague("Alice", mediator)
bob = Colleague("Bob", mediator)
register(mediator, alice)
register(mediator, bob)
send(mediator, "Hello Bob!", alice)Implement the Chain of Responsibility pattern for processing requests.
- Handler: Processes or forwards request
- Chain: Linked list of handlers
- Benefits: Decoupling
- Use case: Logging, authentication
# Chain of Responsibility
abstract type Handler end
mutable struct AuthHandler <: Handler
next_handler::Union{Handler, Nothing}
end
function AuthHandler()
return AuthHandler(nothing)
end
mutable struct LoggerHandler <: Handler
next_handler::Union{Handler, Nothing}
end
function LoggerHandler()
return LoggerHandler(nothing)
end
function set_next(handler::Handler, next_handler::Handler)
handler.next_handler = next_handler
return next_handler
end
function handle(handler::AuthHandler, request::Dict)
if haskey(request, "token")
println("Authentication passed")
if handler.next_handler !== nothing
handle(handler.next_handler, request)
end
else
println("Authentication failed")
end
end
function handle(handler::LoggerHandler, request::Dict)
println("Logging request: $(get(request, "url", "unknown"))")
if handler.next_handler !== nothing
handle(handler.next_handler, request)
end
end
# Usage
auth = AuthHandler()
logger = LoggerHandler()
set_next(auth, logger)
handle(auth, Dict("token" => "valid", "url" => "/api"))Implement the State pattern for changing behavior with state.
- Context: Maintains state
- State: Defines behavior
- Benefits: Clean state management
- Use case: State machines
# State pattern
abstract type State end
struct ReadyState <: State end
struct ProcessingState <: State end
struct CompletedState <: State end
mutable struct Context
state::State
end
function Context()
return Context(ReadyState())
end
function set_state(context::Context, state::State)
context.state = state
end
function handle(context::Context, state::ReadyState)
println("Ready: Waiting for input")
end
function handle(context::Context, state::ProcessingState)
println("Processing: Working on task")
end
function handle(context::Context, state::CompletedState)
println("Completed: Task finished")
end
function request(context::Context)
handle(context, context.state)
end
# Usage
context = Context()
request(context) # Ready: Waiting for input
set_state(context, ProcessingState())
request(context) # Processing: Working on task
set_state(context, CompletedState())
request(context) # Completed: Task finishedImplement the Proxy pattern for controlling access to objects.
- Subject: Real object
- Proxy: Controls access
- Benefits: Access control, lazy loading
- Use case: Virtual proxies, protection
# Proxy pattern
struct RealSubject end
function request(::RealSubject)
println("RealSubject: Handling request")
end
mutable struct Proxy
real_subject::Union{RealSubject, Nothing}
end
function Proxy()
return Proxy(nothing)
end
function request(proxy::Proxy)
if proxy.real_subject === nothing
proxy.real_subject = RealSubject()
end
if check_access()
request(proxy.real_subject)
log_access()
end
end
function check_access()
println("Proxy: Checking access")
return true
end
function log_access()
println("Proxy: Logging access")
end
# Usage
proxy = Proxy()
request(proxy)Implement the Flyweight pattern for sharing objects to save memory.
- Flyweight: Shared object
- Factory: Manages flyweights
- Benefits: Memory optimization
- Use case: Text rendering, caching
# Flyweight pattern
mutable struct Flyweight
shared_state::String
end
function operation(flyweight::Flyweight, unique_state::String)
println("Shared: $(flyweight.shared_state), Unique: $unique_state")
end
mutable struct FlyweightFactory
flyweights::Dict{String, Flyweight}
end
function FlyweightFactory()
return FlyweightFactory(Dict{String, Flyweight}())
end
function get_flyweight(factory::FlyweightFactory, shared_state::String)
if !haskey(factory.flyweights, shared_state)
factory.flyweights[shared_state] = Flyweight(shared_state)
end
return factory.flyweights[shared_state]
end
# Usage
factory = FlyweightFactory()
fw1 = get_flyweight(factory, "state1")
fw2 = get_flyweight(factory, "state1")
fw3 = get_flyweight(factory, "state2")
operation(fw1, "unique1")
operation(fw2, "unique2")
operation(fw3, "unique3")Implement the Bridge pattern for separating abstraction from implementation.
- Abstraction: High-level interface
- Implementation: Low-level operations
- Benefits: Separation of concerns
- Use case: Cross-platform
# Bridge pattern
abstract type Implementation end
struct ConcreteImplementationA <: Implementation end
struct ConcreteImplementationB <: Implementation end
function operation(impl::Implementation)
if isa(impl, ConcreteImplementationA)
println("ConcreteImplementationA: Operation")
elseif isa(impl, ConcreteImplementationB)
println("ConcreteImplementationB: Operation")
end
end
mutable struct Abstraction
impl::Implementation
end
function operation(abstraction::Abstraction)
println("Abstraction: Additional logic")
operation(abstraction.impl)
end
# Usage
impl_a = ConcreteImplementationA()
impl_b = ConcreteImplementationB()
abstraction1 = Abstraction(impl_a)
abstraction2 = Abstraction(impl_b)
operation(abstraction1)
operation(abstraction2)Implement the Adapter pattern for converting interfaces.
- Target: Expected interface
- Adaptee: Existing interface
- Adapter: Bridges target and adaptee
- Benefits: Reusability, legacy integration
# Adapter pattern
struct Target end
function request(::Target)
println("Target: Request")
end
struct Adaptee end
function specific_request(::Adaptee)
println("Adaptee: Specific Request")
end
mutable struct Adapter
adaptee::Adaptee
end
function request(adapter::Adapter)
specific_request(adapter.adaptee)
end
# Usage
adaptee = Adaptee()
adapter = Adapter(adaptee)
request(adapter)Implement the Facade pattern for simplifying complex subsystems.
- Facade: Simplified interface
- Subsystem: Complex components
- Benefits: Simplified interface
- Use case: Library APIs
# Facade pattern
struct SubsystemA end
struct SubsystemB end
function operation_a(::SubsystemA)
println("SubsystemA: Operation")
end
function operation_b(::SubsystemB)
println("SubsystemB: Operation")
end
mutable struct Facade
subsystem_a::SubsystemA
subsystem_b::SubsystemB
end
function Facade()
return Facade(SubsystemA(), SubsystemB())
end
function operation(facade::Facade)
operation_a(facade.subsystem_a)
operation_b(facade.subsystem_b)
println("Facade: Complex operation")
end
# Usage
facade = Facade()
operation(facade)Implement the Composite pattern for tree structures.
- Component: Interface for all objects
- Leaf: Individual object
- Composite: Container of components
- Benefits: Uniform interface
# Composite pattern
abstract type Component end
struct Leaf <: Component
name::String
end
function operation(leaf::Leaf)
println("Leaf $(leaf.name): Operation")
end
mutable struct Composite <: Component
name::String
children::Vector{Component}
end
function Composite(name::String)
return Composite(name, [])
end
function add(composite::Composite, component::Component)
push!(composite.children, component)
end
function remove(composite::Composite, component::Component)
filter!(x -> x !== component, composite.children)
end
function operation(composite::Composite)
println("Composite $(composite.name): Operation")
for child in composite.children
operation(child)
end
end
# Usage
leaf1 = Leaf("A")
leaf2 = Leaf("B")
composite = Composite("Root")
add(composite, leaf1)
add(composite, leaf2)
operation(composite)Implement the Visitor pattern for adding operations to objects.
- Visitor: Defines operations
- Element: Accepts visitors
- Benefits: Adding operations without modifying
- Use case: Compilers, AST
# Visitor pattern
abstract type Element end
struct ElementA <: Element end
struct ElementB <: Element end
abstract type Visitor end
struct ConcreteVisitor <: Visitor end
function visit(visitor::ConcreteVisitor, element::ElementA)
println("Visiting ElementA")
end
function visit(visitor::ConcreteVisitor, element::ElementB)
println("Visiting ElementB")
end
function accept(element::ElementA, visitor::ConcreteVisitor)
visit(visitor, element)
end
function accept(element::ElementB, visitor::ConcreteVisitor)
visit(visitor, element)
end
# Usage
visitor = ConcreteVisitor()
element_a = ElementA()
element_b = ElementB()
accept(element_a, visitor)
accept(element_b, visitor)Implement the Iterator pattern for sequential access.
- Iterator: Traverses collection
- Aggregate: Creates iterator
- Benefits: Uniform traversal
- Use case: Collection traversal
# Iterator pattern
mutable struct Iterator
collection::Vector
index::Int
end
function Iterator(collection::Vector)
return Iterator(collection, 1)
end
function next(iterator::Iterator)
item = iterator.collection[iterator.index]
iterator.index += 1
return item
end
function has_next(iterator::Iterator)
return iterator.index <= length(iterator.collection)
end
mutable struct CustomCollection
items::Vector
end
function CustomCollection()
return CustomCollection([])
end
function add(collection::CustomCollection, item)
push!(collection.items, item)
end
function get_iterator(collection::CustomCollection)
return Iterator(collection.items)
end
# Usage
collection = CustomCollection()
add(collection, "A")
add(collection, "B")
add(collection, "C")
iterator = get_iterator(collection)
while has_next(iterator)
println(next(iterator))
endImplement the Template Method pattern for algorithm skeletons.
- AbstractClass: Defines template method
- ConcreteClass: Implements steps
- Benefits: Code reuse
- Use case: Frameworks, algorithms
# Template Method pattern
abstract type AbstractClass end
function template_method(::AbstractClass)
println("Step 1")
println("Step 2")
println("Step 3")
end
mutable struct ConcreteClass <: AbstractClass end
function template_method(concrete::ConcreteClass)
println("Step 1")
concrete_step2()
println("Step 3")
end
function concrete_step2()
println("Concrete Step 2")
end
# Usage
concrete = ConcreteClass()
template_method(concrete)Implement the Builder pattern for constructing complex objects.
- Builder: Constructs parts
- Director: Orchestrates construction
- Product: Constructed object
- Benefits: Step-by-step construction
# Builder pattern
mutable struct Product
parts::Vector{String}
end
function Product()
return Product([])
end
function add(product::Product, part::String)
push!(product.parts, part)
end
function list_parts(product::Product)
println(join(product.parts, ", "))
end
mutable struct Builder
product::Product
end
function Builder()
return Builder(Product())
end
function reset(builder::Builder)
builder.product = Product()
end
function build_step_a(builder::Builder)
add(builder.product, "Part A")
end
function build_step_b(builder::Builder)
add(builder.product, "Part B")
end
function get_result(builder::Builder)
return builder.product
end
mutable struct Director
builder::Builder
end
function build_minimal(director::Director)
build_step_a(director.builder)
end
function build_full(director::Director)
build_step_a(director.builder)
build_step_b(director.builder)
end
# Usage
builder = Builder()
director = Director(builder)
build_minimal(director)
product = get_result(builder)
list_parts(product)Implement the Prototype pattern for cloning objects.
- Prototype: Cloneable object
- Clone: Creates a copy
- Benefits: Performance, avoids constructors
- Use case: Complex objects
# Prototype pattern
mutable struct Prototype
name::String
nested::Dict
end
function clone(prototype::Prototype)
return Prototype(prototype.name, copy(prototype.nested))
end
function deep_clone(prototype::Prototype)
return Prototype(prototype.name, deepcopy(prototype.nested))
end
# Usage
original = Prototype("Original", Dict("value" => 42))
copy = clone(original)
copy.name = "Copy"
copy.nested["value"] = 99
println(original.name) # Original
println(original.nested["value"]) # 42 (shallow copy)
deep_copy = deep_clone(original)
deep_copy.nested["value"] = 100
println(original.nested["value"]) # 42 (deep copy)