Best practices

Or
for (i, item) in pairs(vec)
if you want the indice in the vec with the item

7 Likes

I agree, but, more emphatically: enumerate is the wrong function, pairs is the right one, but people always forget it.

Edit: Ah, well, @gustaphe did say ‘counter’, which is alright. I just have a knee-jerk reaction to all the uses of enumerate.

3 Likes

eachindex usually improves performance anyways because it uses cartesian directly.

3 Likes

Yeah, I see it the other way, I normally either need the indices or just the items, if I need something together with the items it’s probably a counter.

(Plus, pairs is so obscure a name I didn’t dare recommending it without double checking)

Oh, that is very different from my experience. I think it’s probably 80-20 that I need pairs.

Yeah, that’s what I think is a bit sad. Particularly because I very often see enumerate being used to get indices in the wild.

I would say (as a mnemotechnic) that pairs is for pairs of (key, value) , here (index,value), while, clearly, enumerate goes from one to the end, so is for a counter.

By the way, for enumerate it is easy to test for the first iteration ( i == 1), but how to test with last iteration ? ( does ( i == end) works? I do not see how it could). Possibly it is no a real worry, because less frequently interesting that test for first iter, but it arises sometime nevertheless.

On the other side with pairs you can do ( i == firstindex(v) ) and ( i == lastindex(v) ), even if it is somewhat verbose?

Comment or suggestions welcome.

2 Likes

If vec is meant to a traditional (1-indexed) vector, then the “generality” argument should be weighed against the simplicity/portability of length().

I believe this trade-off is a bit different for package developers and end-users (especially newcomers to Julia).

Not sure I understand. Is 1:length() more portable?

As for simplicity, eachindex() is simpler than 1:length(), but perhaps less familiar to newcomers. But, once you’ve heard of it, you know it.

1 Like

Is 1:length() more portable?

Well, in the sense that it looks pretty similar to what you would do in some of Julia’s competitors.

1 Like

Absolutely, but there are plenty of reasonable things pairs(vec) could have meant. I don’t know what I would have wanted it to be called, I just know that I never feel entire certain what pairs does (or more likely what it’s called).

i == length(vec) && return
1 Like

That’s a feature. Less risk of off-by-one if you’re translating from eachindex than from 1:length.

eachindex is fine and even for (i, item) in enumerate(vec) is something that most of us can handle (although it isn’t pretty).
The real problem starts when dealing with matrices (and more). Things like for t = 4:T;i =2:N;x[t,i] = x[t-3,i-1];... are very easy in traditional notation, but become rather involved when you want to handle OffsetArrays (and what not). I believe this might become a serious hurdle in trying to attract new users.

1 Like

Thanks all for the discussion!

1 Like
for c in CartesianIndices(M)

?

btw, enumerate doesn’t work with OffsetArray because i starts at 1

in this case, perhaps this:

pairs doesn’t seem to be compatible with map the way that enumerate is… is there an OffsetArrays-compatible alternative to enumerate that is map compatible?

Can you show an example of the incompatibility?

1 Like

enumerate can be mapped over:

julia> map(enumerate(rand(10))) do (i,x)
               (i,x)
       end
10-element Vector{Tuple{Int64, Float64}}:
 (1, 0.3408824363518682)
 (2, 0.052613595813357006)

pairs can not:

julia> map(pairs(rand(10))) do (i,x)
               (i,x)
       end
ERROR: map is not defined on dictionaries

Perhaps not as straightforward, but one may define

julia> _pairs(v) = zip(eachindex(v), v)
_pairs (generic function with 1 method)

which can be mapped over:

julia> v = OffsetArray(1:4, 2);

julia> map(_pairs(v)) do x
       x
       end
4-element OffsetArray(::Vector{Tuple{Int64, Int64}}, 3:6) with eltype Tuple{Int64, Int64} with indices 3:6:
 (3, 1)
 (4, 2)
 (5, 3)
 (6, 4)

maybe this helps

 function mapover(ints)
      isodd(length(ints)) && error("input must be of even length")
      xys = Iterators.flatmap(pairs(ints)) do x
          x
      end
      ixs = collect(xys)
      twotuples = ((ixs[i], ixs[i+1]) for i=1:2:length(ixs))
      collect(twotuples)
  end

 ints = rand(1:999, 6);
 result = mapover(ints);

 ints'
 # 1×6 adjoint(::Vector{Int64}) with eltype Int64:
 #    157  11  305  977  626  989

 result
 #= 6-element Vector{Tuple{Int64, Int64}}:
 (1, 157)
 (2, 11)
 (3, 305)
 (4, 977)
 (5, 626)
 (6, 989)
=#