Understanding few syntax of Julia code

I am very new to Julia language, so I started to read the documentation and all builtin functions. Now, I am learning one github project for my work. Since I am more comfortable with Python, I tried to translate Julia’s code to python by my understanding, but I got a few weird syntaxes that I didn’t understand and I got stuck with them. Can anyone point me out the meaning of those syntaxes? Thanks in advance!

syntax that I don’t understand

those julia code line that I didn’t understand because I didn’t find them either in documentations.

var1 = Tuple{Integer, Vector}[]

here we declare object var 1, what’s real example for that? what’s python version?

also if X::Matrix, n::Int, then what’s the meaning of ? in the below? How should I code this in python?

K = [( i >= j ? dot(view(X,:,i), view(X,:,j)) : 0.0 )::Float64 for i=1:n, j=1:n]

I am not sure meaning of -> in below:

for i=1:n 
      id_i = find(x -> x[1] == i, var1)
      xi_i_list =  map(x -> x[2], var1[id_i])

lastly, I just don’t understand the meaning of .> in below:

act= zeros(100)
alpha = zeros(10)

  for i=1:100
    idx = find(x::Tuple{Integer, Vector} -> x[1] == i, var1)
    act[i] = sum(alpha[idx] .> 1e-3)

As a newbie, I am trying to understand the role of find(), map(). To the best, I wish I could write the above Julia code with Python. But I have a hard time understanding the code. Can anyone give possible interpretations and corresponding python codes for learning purposes? Thanks in advance!

Many of these things can’t be written in Python because Python fundamentally doesn’t have the same concepts in it.

var1 = Tuple{Integer, Vector}[]

This creates an empty array whose element type is Tuple{Integer, Vector} and binds that array to var1.

The meaning of ? in your example is that it’s part of the ternary operator. Essentially x ? y : z is equivalent to the following Julia code:

if x
    y
else
    z
end

x -> x[1] == 5 creates an anonymous function. In Python these are lambdas.

alpha[idx] .> 1e-3

is an instance of broadcasting. It’s equivalent to something like:

map(x -> x[idx] > 1e-3, alpha)
14 Likes

Thank you! I am wondering how should we simplify this K = [( i >= j ? dot(view(X,:,i), view(X,:,j)) : 0.0 )::Float64 for i=1:n, j=1:n]? Is there any simple Julia code for this?

if var1 = Tuple{Integer, Vector}[] creates empty array, how this lines

act= zeros(100)
alpha = zeros(10)

  for i=1:100
    idx = find(x::Tuple{Integer, Vector} -> x[1] == i, var1)
    act[i] = sum(alpha[idx] .> 1e-3)

can be written in simpler way?

I sitll don’t understand find(), map() function. for instance, if idi as:

function idi(m::Integer, i::Integer)
  return ((i-1)*m+1 : i*m)::UnitRange{Int64}
end

, then what’s meaning of this: map(i -> idi(20, i), collect(1:5))? I should avoid asking more than one question, but it is all about syntax of Julia, and I might need Julia community support.

1 Like

For the first, @views K = [( i >= j ? dot((X[:,i], X[:,j]) : 0.0 ) for i=1:n, j=1:n]
For the second, I would replace the inside of the for loop with

    idx = find(x -> x[1] == i, var1)
    act[i] = sum(x -> x > 1e-3, alpha[idx])

Thanks a lot! how should we understand find() and map() function in julia? for instance if idi=collect(1:10), then map(10 -> idi(20, 10), collect(1:5)) ? how do write this in python? any idea? Thanks!

‘map(f, c)’ takes an anonymus function ‘f’(‘x->x^2’ is an example. x is the input, and x^2 is the output) and maps it to an iterable/collection ‘c’. Mapping means applyng the function elementwise.

But the easiest thing is to read the docs, and play with a couple of examples until you het a feel for it: Collections and Data Structures · The Julia Language

2 Likes

Nitpick: not just anonymous functions, it can also take named functions, like sin or sort.

(I mention this, because many people tend to create anonymous functions for map, even though it’s unnecessary, such as map(x->sin(x), X), when you only really need map(sin, X).)

4 Likes

Good nitpick. As I formulated myself incorrectly, I was clearly not sharp on this detail. Thanks

This provides very good examples to be addressed in Julia doc for the convenience of beginners. When I started to learn Julia, I feel really frustrated encoutering these syntax sugars and strange symbols. I think it may be a good idea to list all these things (links are OK) in a single page of the doc and make it available in the tutorial section.

3 Likes

x-ref: how to understand few symbols and syntaxs in Julia? - Stack Overflow

4 Likes

Just to nitpick, Python has a ternary operator, but it is:

expression_if_true if condition else expression_if_false

instead of

condition ? expression_if_true : expression_if_false

So ternaries should be perfectly translatable to Python.

While this is correct, I am not sure if this helps that much someone coming from Python, because I am not sure if in Python the classic if ... else blocks can return values (i.e., are expressions).

1 Like

As far as I remember, the ternary operator in python has a slightly unusual syntax.

expr1 ? expr2 : expr3

becomes

expr2 if expr1 else expr3

which indeed is an expression returning a value. For example

>>> [x if x % 2 == 0 else 0 for x in range(10)] 
[0, 0, 2, 0, 4, 0, 6, 0, 8, 0]
1 Like

I was wrong about the what expression goes in which clause in Python (have corrected it now, what a bizarre choice, to not have the condition first). However, I was saying that, different from Julia, the traditional if ... else .. blocks does not seem to be expressions but only statements. In other words, you can do this in Julia:

x = if some_condition
    ...
else
    ...
end

but I am not sure you can do something like this in Python

x = if(condition):
    ...
else:
    ...
1 Like

You’re right that Python has its main control flow structure as statements only and not expressions; this is done to mostly prevent using blocks with line breaks inside of other blocks.

3 Likes

This code is sub-optimal. var1 is defined as Tuple{Integer, Vector}[] which is rather bad, since Integer and Vector are abstract types and this affect performance negatively.

It seems that the intention was to use Tuple{Int, Vector{Float64}}[], i.e. var1 is a Vector (list in python terminology) of tuples, which looks like [(1, [1.0, 2.0]), (2, [3.0, 4.0, 5.0])]. You can see the result of this choice in the fact, that K has ::Float64 annotation. Normally, compiler infer types like these automatically.

Secondly, it looks like an improper structure for the task itself, because

for i=1:n 
      id_i = find(x -> x[1] == i, var1)
      xi_i_list =  map(x -> x[2], var1[id_i])

just extract all values by the i key. For example, if var1 = [(1, [1., 2.]), (2, [3., 4.]), (1, [5., 6.])] then xi_i_list is just

# i = 1
[[1., 2.], [5., 6.]] 

# i = 2
[[3., 4.]]

or in more details, if i == 1, then find (actually it is findall in modern Julia, looks like this code is outdated) find all indices, where first value of the tuple is 1, so id_i = [1, 3]. Following map extracts second element of the tuple and put them all together in a list, producing necessary result.

This looks overly complicated and not very efficient. Depending on a task, one can either use dict from the start, so python version would looks like

var1 = {1: [[1., 2.], [5., 6.]], 2: [[3., 4.]]}

or (even better), make Vector{Vector{Vector{Float64}}}

var1 = [[[1., 2.], [5., 6.]], [[3., 4.]]]

and xi_i_list is just var1[i]. Of course it requires accurate manipulation with var1, but I guess it is manageable.

If structure can’t be changed for some reasons, then you can do the following in python

for i in range(n):
    xi_i_list = [z[1] for z in var1 if z[0] == i + 1]
3 Likes

Thanks a lot for your support in the Julia community.

How could we simplify this in Julia: K = [( i >= j ? dot(view(X,:,i), view(X,:,j)) : 0.0 )::Float64 for i=1:n, j=1:n] ? I understand how view() function works but still some glitch that writing this in pythonic way. Any idea? what’s possible alternative of collect() in python? Thanks!

Not sure if that is simplify. But that line does the following.

With:

julia> X = Float64[ 1 2 ; 3 4 ]
2×2 Array{Float64,2}:
 1.0  2.0
 3.0  4.0

julia> using LinearAlgebra

julia> n = 2
2

Your line does:

julia> K = [( i >= j ? dot(view(X,:,i), view(X,:,j)) : 0.0 )::Float64 for i=1:n, j=1:n]
2×2 Array{Float64,2}:
 10.0   0.0
 14.0  20.0

and that can be written more explicitly as:

julia> K = Matrix{Float64}(undef,2,2) # Create empty 2x2 matrix
2×2 Array{Float64,2}:
 2.5e-323  6.92624e-310
 5.0e-324  6.92624e-310

julia> for i in 1:2
         for j in 1:2
           if i >= j
             K[i,j] = dot(view(X,:,i),view(X,:,j))
           else
             K[i,j] = 0.
           end
         end
       end

julia> K
2×2 Array{Float64,2}:
 10.0   0.0
 14.0  20.0

If you wrap that into a function, for instance:

julia> function computeK(X,n)
         K = Matrix{Float64}(undef,n,n)
         for i in 1:n
           for j in 1:n
             if i>=j
               K[i,j] = dot(view(X,:,i),view(X,:,j))
             else
               K[i,j] = 0.
             end
           end
         end
         return K
       end
computeK (generic function with 1 method)

julia> computeK(X,n)
2×2 Array{Float64,2}:
 10.0   0.0
 14.0  20.0


That is not worst than that “one-liner”:

julia> f(X,n) = [( i >= j ? dot(view(X,:,i), view(X,:,j)) : 0.0 )::Float64 for i=1:n, j=1:n]
f (generic function with 1 method)

julia> @btime f($X,$n)
  75.759 ns (1 allocation: 112 bytes)
2×2 Array{Float64,2}:
 10.0   0.0
 14.0  20.0

julia> @btime computeK($X,$n)
  58.127 ns (1 allocation: 112 bytes)
2×2 Array{Float64,2}:
 10.0   0.0
 14.0  20.0

Actually, here, it was faster. This is an important difference relative to Python. You are not constrained to writing vectorized code all the time to get good performance.

Concerning the collect() of Python, if that is for garbage collection (as I could find in google), the equivalent is probably GC.gc(), but one does not use that very often, if the code is written carefully to avid unnecessary allocations.

1 Like

From my rapid google search, you can do somethng similar with a numpy array, as explained here.

In general you cannot do that in Python, and subarrays will do copies, at least that is what is explained there.

1 Like

I am not sure if Jared is referring to the collect of Python but instead to how to do Julia collect in Python.

Hi, thanks for the follow-up thread. I am saying collect(1:10) in julia, which I understand how collect() works. I am curious how should I do the same things in numpy.

I think I found alternative of julia’ collect() in python: list(range(m, n))