Help fo working with Iters

I’ve the below Python code:

def initial_trend(series, slen):
    sum = 0.0
    for i in range(slen):
        sum += float(series[i+slen] - series[i]) / slen
    return sum / slen

That is converted to the below Rust code:

fn initial_trend(series: &[i32], slen: usize) -> f32 {
    series[..slen].iter().zip(&series[slen..])
        .map(|(&a, &b)| (b as f32 - a as f32) / slen as f32).sum::<f32>() / slen as f32
}

Or can be re-written as:

fn initial_trend(series: &[i32], slen: usize) -> f32 {
    let x = series[..slen].iter().zip(&series[slen..]);
    let y = x.map(|(&a, &b)| (b as f32 - a as f32) / slen as f32);
    let z = y.sum::<f32>();
    z / slen as f32
}

for the input, with slen = 12:

series = [30,21,29,31,40,48,53,47,37,39,31,29,17,9,20,24,27,35,41,38,
          27,31,27,26,21,13,21,18,33,35,40,36,22,24,21,20,17,14,17,19,
          26,29,40,31,20,24,18,26,17,9,17,21,28,32,46,33,23,28,22,27,
          18,8,17,21,31,34,44,38,31,30,26,32];

I’ve the below output in Rust code above

x = Zip { a: Iter([30, 21, 29, 31, 40, 48, 53, 47, 37, 39, 31, 29]), b: Iter([17, 9, 20, 24, 27, 35, 41, 38, 27, 31, 27, 26, 21, 13, 21, 18, 33, 35, 40, 36, 22, 24, 21, 20, 17, 14, 17, 19, 26, 29, 40, 31, 20, 24, 18, 26, 17, 9, 17, 21, 28, 32, 46, 33, 23, 28, 22, 27, 18, 8, 17, 21, 31, 34, 44, 38, 31, 30, 26, 32]), index: 0, len: 12 }

y = Map { iter: Zip { a: Iter([30, 21, 29, 31, 40, 48, 53, 47, 37, 39, 31, 29]), b: Iter([17, 9, 20, 24, 27, 35, 41, 38, 27, 31, 27, 26, 21, 13, 21, 18, 33, 35, 40, 36, 22, 24, 21, 20, 17, 14, 17, 19, 26, 29, 40, 31, 20, 24, 18, 26, 17, 9, 17, 21, 28, 32, 46, 33, 23, 28, 22, 27, 18, 8, 17, 21, 31, 34, 44, 38, 31, 30, 26, 32]), index: 0, len: 12 } }

z = -9.416667

w = -0.78472227

I want to write the equivalent Julia code, so I wrote the below:

function initial_trend(series, slen)
    x = zip(series[1:slen], series[slen+1:end])
    y = map((a, b) -> (b - a) / 2, x)
    z = sum(y) / slen
end

With this x looks to be correct as it generates:

Base.Iterators.Zip{Tuple{Array{Int64,1},Array{Int64,1}}}(([30, 21, 29, 31, 40, 48, 53, 47, 37, 39, 31, 29], [29, 17, 9, 20, 24, 27, 35, 41, 38, 27, 31, 27, 26, 21, 13, 21, 18, 33, 35, 40, 36, 22, 24, 21, 20, 17, 14, 17, 19, 26, 29, 40, 31, 20, 24, 18, 26, 17, 9, 17, 21, 28, 32, 46, 33, 23, 28, 22, 27, 18, 8, 17, 21, 31, 34, 44, 38, 31, 30, 26, 32]))

But y is wrong, not working, it gave me:

MethodError: no method matching (::getfield(Main, Symbol("##17#18")))(::Tuple{Int64,Int64})
Closest candidates are:
  #17(::Any, !Matched::Any) at In[38]:1

it looks I do not understand working with tuples in arrays with map!
And not sure if I’ve to d something before x, and if there is a way to make all of them in single line like the one in Rust

You are probably looking for

map(((a, b),) -> ...)

(note extra ()s), but I don’t see the point of ziping and then splatting. Can’t you just do

y = map((a, b) -> b - a / 2, series[1:slen], series[slen:end])

? Or even

(series[slen:end] .- series[1:slen]) ./ 2

Incidentally, it may be better to just ask for what you want to achieve in Julia, instead of pasting code from other languages. Julia has different idioms, and not everyone is fluent in other computer languages.

Also, you may just want to read through the Julia manual first to learn about these things, otherwise your experience with Julia might just end up being quite frustrating.

for this I got:

DimensionMismatch("arrays could not be broadcast to a common size")

Just think about it for a sec. Eg imagine that series has 10 elements and slen is 2.

for this also,I got the same:

y = map((a, b) -> (b - a) / 2, series[1:12], series[12+1:end])
DimensionMismatch("dimensions must match")

Of course, it’s the same problem.

Maybe:

function initial_trend(series, slen)
   sum = 0.0
   for i in 1:slen
       sum += series[i+slen]-series[i]
   end
   return sum/slen
end

I worked from the python and removed what I suspect is a redundant /slen (it is being done both in the loop and outside the loop).

P.S. I’m assuming that series is a vector of floats.

Thanks, actually the other slen is required, not extra

     sum += (series[i+slen]-series[i]) / slen

Your code with returning the slen gave me the correct output, so now how can I covert this Julia code to functional format using Iter

So, how can we enforce it to work based on the shortest array, so for 10 and 2, it will take only the first 2 elements from the array of 10

zip does that. did you read my very first reply? nope it does not work that way. I would just calculate indexes to match in length, like @dmolina suggests, and maybe use @view.

In that case, I would suggest:

y = map((a, b) -> (b - a) / 2, series[1:slen-1], series[slen+1:2*slen])

Be careful with indexes, julia series[1:n] includes n index, while Python does not.

Edit: Sorry, it was missing a parenthesis.

3 Likes

I got:

DimensionMismatch("dimensions must match")

Iter b is bigger than iter a so it fails, how can I ask it to take the first x elements in iter b where x is the length(a)

My code was working inspired in Python, which does not explore to the end, so until 2*slen. Please, do not submit versions if other language if it is not right.

If you want to avoid problem you should use zip as @Tamas_Papp suggested:

function initial_trend(series, slen)
   sum = 0.0

   for (a, b) in zip(series[1:slen], series[slen+1:end])
        sum += (b-a)/slen
   end
   return sum/slen
end

This code should give the same than Rust version.

The zip(series[1:slen], series[slen+1:end]) is fine, and your code gave correct result,
Can I replace:

for (a,b) in .. 
     sum+=(b-a)/slen
end

with map() function?

I do not think so, zips stop when one of them finish before the other one, but for some reason this check is only done in a for loop, not inside a map or the collect function.

1 Like

Two comments then:

  1. If you need to divide by slen twice, shouldn’t that still happen outside the loop? (i.e. return sum/slen^2 gives me the correct answer).
  2. I don’t understand what you are trying to do or need. I frankly get lost with all the zips and maps when the simple for loop is perfectly readable and produces the correct answer.

Thanks to all the inputs and feedbacks given, I solved it as:

initial_trend(series, slen) = sum(
    map(i -> (last(i) - first(i)) / slen, 
        zip(series[1:slen], series[slen+1:2*slen])
        )
    ) / slen

I was having 2 issues:

  1. As @dmolina mentioed zipping is not stopping in mapping when one of the iters finish before the other one

  2. Tuple in mapping is not handled as map((a,b) -> ... , x), it is handled as map(i -> ... , x), then can be handled as first(i) and so on.

You are correct, but I’m trying to keep the coding formula matching with the mathematical formulas

I would like to do it the functional programming way. thanks a lot for your feedbacks

I used @btime from BenchmarkTools to compare the loop version to the sum/map/zip version. I’m not sure if you care about speed or if this is an academic exercise but the simple loop runs 5x faster with less memory usage.

julia> @btime initial_trend(series, 12)
  131.302 ns (7 allocations: 624 bytes)
-0.7847222222222222

julia> @btime initial_trend_for_loop(series, 12)
  24.727 ns (1 allocation: 16 bytes)
-0.7847222222222222
2 Likes

If you want a “functional” approach, you can also do

initial_trend(series, slen) =
    mapreduce(+, zip(series[1:slen], series[slen+1:2*slen])) do (a, b)
        (b - a) / slen
    end / slen

If you care performance a bit then,

function initial_trend(series, slen)
    x = @view series[1:slen]
    y = @view series[slen+1:2*slen]
    n = min(length(x), length(y))
    mapreduce(+, (@view x[1:n]), (@view y[1:n])) do a, b
        (b - a) / slen
    end / slen
end

The latter in principle should be almost as fast as the raw loop and has better accuracy.

1 Like