Why does Julia not print `false` inside a conditional? - julia

Julia 1.4.2
The behaviour that stumps me occurs in the first and fourth cases:
julia> if (val = (1 == 2))
println(val)
end
julia> if (val = (1 == 1))
println(val)
end
true
julia> if (val = (1 == 1))
println(val + 5)
end
6
julia> if (val = (1 == 2))
println(val + 5)
end
julia> 1 == 2
false
julia> 1 == 1
true
julia> println(1==2)
false
julia> println(1==1)
true
I observe this in the REPL and in Jupyter.
My questions are:
Why does this happen?
How can I retrieve the value of val, preferably the "truthy" value, in both cases?

Let's look at the first example in more details
# initially, `val` does not exist
julia> val
ERROR: UndefVarError: val not defined
# 1==2 is false
# the assignment returns the value of the right-hand side -> false
julia> if (val = (1 == 2))
# therefore, this branch is not taken
println(val)
else
# but this one is
println("in the else clause")
end
in the else clause
# `if` returns whatever the branch taken evaluates to.
# In this case, `println` returns nothing, so the `if` block does not
# return anything either
julia> #show ans
ans = nothing
# `if` doesn't introduce a new scope, so the newly created `val` variable
# can still be inspected
julia> val
false

Related

sum function in Julia is giving error if the array is empty

I am trying to create a code which identifies if the elements in an array are monotonic or not.
I wrote the below code and got the error -
function isMonotonic(array)
if length(array) <= 2
return true
end
check_up = []
check_down = []
for i in range(2, length(array))
if array[i] <= array[i-1]
append!(check_up, 1)
end
if array[i] >= array[i - 1]
append!(check_down, 1)
end
end
if sum(check_up) == length(array) - 1 || sum(check_down) == length(array) - 1
return true
else
return false
end
end
isMonotonic([1, 2, 3, 4, 5, 6 , 7])
I am getting the below error
Error: Methoderror: no method matching zero(::Type{Any})
I think it is because I am trying to sum up the empth array, I want to understand how to overcome this problem in general, I have a solution for the above code, but in genral I want to know the reason and how to use it. I do not want to first check if the array is empty or not and then do the sum.
If you wanted to save yourself lots of effort, the simplest solution would just be:
my_ismonotonic(x) = issorted(x) || issorted(x ; rev=true)
This will return true if x is sorted either forwards, or in reverse, and false otherwise.
We could maybe make it a little more efficient using a check so we only need a single call to issorted.
function my_ismonotonic(x)
length(x) <= 2 && return true
for n = 2:length(x)
if x[n] > x[1]
return issorted(x)
elseif x[n] < x[1]
return issorted(x ; rev=true)
end
end
return true
end
# Alternatively, a neater version using findfirst
function my_ismonotonic(x)
length(x) <= 2 && return true
ii = findfirst(a -> a != x[1], x)
isnothing(ii) && return true # All elements in x are equal
if x[ii] > x[1]
return issorted(x)
else
return issorted(x ; rev=true)
end
end
The loop detects the first occurrence of an element greater than or less than the first element and then calls the appropriate issorted as soon as this occurs. If all elements in the array are equal then the loop runs over the whole array and returns true.
There are a few problems of efficiency in your approach, but the reason you are getting an actual error message is because given the input, either this expression sum(check_up) or this expression sum(check_down) will effectively result in the following call:
sum(Any[])
There is no obvious return value for this since the array could have any type, so instead you get an error. If you had used the following earlier in your function:
check_up = Int[]
check_down = Int[]
then you shouldn't have the same problem, because:
julia> sum(Int[])
0
Note also that append! is usually for appending a vector to a vector. If you just want to add a single element to a vector use push!.

Creating subset using or statement

I have a data frame with 6 columns and thousands of rows containing share transactions. I want to identify rows with bad price data. The following function gives me a subset with the rows with good price data:
function in_price_range(df)
price_good = subset(df, :UnitPrice => X-> (trough_share_price .<= X .<= peak_share_price), skipmissing=true)
return price_good
end
For a subset for bad data I tried:
function out_price_range(df)
price_discrepancy = subset(df, :UnitPrice => X-> (X .< trough_share_price || X .> peak_share_price), skipmissing=true)
return price_discrepancy
end
However, that givers error TypeError: non-boolean (BitVector) used in boolean context
I tried .|| rather than || but that then gives error: syntax: "|" is not a unary operator
How do I fix the code?
In Julia, || is
help?> ||
search: ||
x || y
Short-circuiting boolean OR.
The short-circuiting part meaning, that if x is true, || will not even bother to evaluate y. In other words, this will make a branch in the code. For example:
julia> 5 < 7 || print("This is unreachable")
true
This is great if you want to write code that is efficient for a case like
if something_easy_to_evaluate || something_costly_to_evaluate
# Do something
end
In other words, this is control flow! Obviously, this cannot be broadcasted. For that, what you want is the regular or operator |, which you can broadcast with .|. So for example:
julia> a = rand(3) .< 0.5
3-element BitVector:
1
0
0
julia> b = rand(3) .< 0.5
3-element BitVector:
0
1
0
julia> a .|| b
ERROR: syntax: "|" is not a unary operator
Stacktrace:
[1] top-level scope
# none:1
julia> a .| b
3-element BitVector:
1
1
0
The same applies to && vs &; the former is only used for control-flow, the latter is normal bitwise and.

Is there a Kronecker delta in Julia?

If I have some declared some Points in Julia (p_1,...,p_n). Is there some function or algorithm for kronecker delta (f_i(p_j)=1 if i=j and f_i(p_j)=0 if i != j)
It would be very helpful.
Thank you so much.
If you want a kronecker delta function you can use the ==(x,y) function (as indicated by #mbauman in the comments).
julia> δ(x,y) = ==(x,y)
δ (generic function with 1 method)
julia> δ(1,1)
true
julia> δ(1,2)
false
Note that this returns true or false instead of 1 and 0 but the former are essentially equal to the latter and will behave in the same way, for example ==(1,1) * 2 will give 2. In fact, true isa Integer in Julia.
Another option might be to use I the (lazy) identity matrix built into Julia (LinearAlgebra that is):
julia> using LinearAlgebra
julia> I[1,1]
true
julia> I[1,2]
false
For an arbitrary number of arguments, you can do:
δ(x, y) = ==(x, y)
function δ(x, y, z...)
!δ(x, y) && return false
for i in z
!δ(x, i) && return false
end
return true
end
or if you make the convention that δ(x) := true:
δ(x, y) = ==(x, y)
function δ(z...)
for i in z
!δ(z[1], i) && return false
end
return true
end

generating expressions and then checking them in Julia

My goal is to be able to generate a list of expressions, p.g., check that a number is in some interval, and then evaluate it.
I was able to do it in the following way.
First, a function genExpr that creates such an Expr:
function genExpr(a::Real, b::Real)::Expr
quote
x < $(a + b) && x > $(a - b)
end
end
Create two expressions:
e1 = genExpr(0,3)
e2 = genExpr(8,2)
Now, my problem is how to pass these expressions to a function along with a number x. Then, this function, checks if such a number satisfies both conditions. I was able to achieve it with the following function:
function applyTest(y::Real, vars::Expr...)::Bool
global x = y
for var in vars
if eval(var)
return true
end
end
return false
end
This works, but the appearance of global suggests the existence of a better way of obtaining the same goal. And that's my question: create a function with arguments a number and a list of Expr's. Such function returns true if any condition is satisfied and false otherwise.
This looks like a you are probably looking into using a macro:
macro genExpr(a::Real, b::Real)
quote
x-> x < $(a + b) && x > $(a - b)
end
end
function applyTest(y::Real, vars::Function...)::Bool
any(var(y) for var in vars)
end
Testing:
julia> e1 = #genExpr(0,3)
#15 (generic function with 1 method)
julia> e2 = #genExpr(8,2)
#17 (generic function with 1 method)
julia> applyTest(0,e1,e2)
true
However, with this simple code a function just generating a lambda would be as good:
function genExpr2(a::Real, b::Real)
return x-> x < (a + b) && x > (a - b)
end

Julia splat operator unpacking

In Python, one can use the * operator in the unpacking of an iterable.
In [1]: head, *tail = [1, 2, 3, 4, 5]
In [2]: head
Out[2]: 1
In [3]: tail
Out[3]: [2, 3, 4, 5]
I would like to produce the same behavior in Julia. I figured that the equivalent ... operator would work, but it seems to just produce an error in this context.
julia> head, tail... = [1, 2, 3, 4, 5]
ERROR: syntax: invalid assignment location "tail..."
I was able to produce the results I want using the following, but this is an ugly solution.
julia> head, tail = A[1], A[2:end]
(1,[2,3,4,5])
Can I unpack the array such that tail would contain the rest of the items after head using the splat (...) operator? If not, what is the cleanest alternative?
Edit: This feature has been proposed in #2626. It looks like it will be part of the 1.0 release.
As of Julia 1.6
It is now possible to use ... on the left-hand side of destructured assignments for taking any number of items from the front of an iterable collection, while also collecting the rest.
Example of assigning the first two items while slurping the rest:
julia> a, b, c... = [4, 8, 15, 16, 23, 42]
# 6-element Vector{Int64}:
# 4
# 8
# 15
# 16
# 23
# 42
julia> a
# 4
julia> b
# 8
julia> c
# 4-element Vector{Int64}:
# 15
# 16
# 23
# 42
This syntax is implemented using Base.rest, which can be overloaded to customize its behavior.
Example of overloading Base.rest(s::Union{String, SubString{String}}, i::Int) to slurp a Vector{Char} instead of the default SubString:
julia> a, b... = "hello"
julia> b
# "ello"
julia> Base.rest(s::Union{String, SubString{String}}, i=1) = collect(SubString(s, i))
julia> a, b... = "hello"
julia> b
# 4-element Vector{Char}:
# 'e': ASCII/Unicode U+0065 (category Ll: Letter, lowercase)
# 'l': ASCII/Unicode U+006C (category Ll: Letter, lowercase)
# 'l': ASCII/Unicode U+006C (category Ll: Letter, lowercase)
# 'o': ASCII/Unicode U+006F (category Ll: Letter, lowercase)
That does indeed sound like a job for a macro:
function unpack(lhs, rhs)
len = length(lhs.args)
if len == 1
# just remove the splatting
l, is_splat = remove_splat(lhs.args[1])
return :($l = $(esc(rhs)))
else
new_lhs = :()
new_rhs = quote
tmp = $(esc(rhs))
$(Expr(:tuple))
end
splatted = false
for (i, e) in enumerate(lhs.args)
l, is_splat = remove_splat(e)
if is_splat
splatted && error("Only one splatting operation allowed on lhs")
splatted = true
r = :(tmp[$i:end-$(len-i)])
elseif splatted
r = :(tmp[end-$(len-i)])
else
r = :(tmp[$i])
end
push!(new_lhs.args, l)
push!(new_rhs.args[4].args, r)
end
return :($new_lhs = $new_rhs)
end
end
remove_splat(e::Symbol) = esc(e), false
function remove_splat(e::Expr)
if e.head == :(...)
return esc(e.args[1]), true
else
return esc(e), false
end
end
macro unpack(expr)
if Meta.isexpr(expr, :(=))
if Meta.isexpr(expr.args[1], :tuple)
return unpack(expr.args[1], expr.args[2])
else
return unpack(:(($(expr.args[1]),)), expr.args[2])
end
else
error("Cannot parse expression")
end
end
It is not very well tested, but basic things work:
julia> #unpack head, tail... = [1,2,3,4]
(1,[2,3,4])
julia> #unpack head, middle..., tail = [1,2,3,4,5]
(1,[2,3,4],5)
A few Julia gotchas:
x,y = [1,2,3] #=> x = 1, y = 2
a = rand(3)
a[1:3], y = [1,2,3] #=> a = [1.0,1.0,1.0], y = 2
The macro follows this behavior
#unpack a[1:3], y... = [1,2,3]
#=> a=[1.0,1.0,1.0], y=[2,3]

Resources