1 based (array) indexing

The advise given is this.

Don’t use:

for i in 1:length(A)

instead use:

for i ∈ axes(A,1), j ∈ axes(A,2)
A[i,j] = sin(x[i]) * cos(y[j])
end

because A could be an array that’s been defined under an indexing regime that starts say from -2 and not 1.

But I often in Python have situations where I submit certain indices to a function and fetch only those columns or rows.

But how is this sustainable in Julia? Every time you say want to slice the rows [3,7,11,12] from an (imported alien module) Array you first have to transform those 1-based indices to the indices of that Array (I don’t know maybe [3,7,11,12] - firstindex(A)). I find it error prone to keep the books over those indices.

My question: what is good coding practise and should I really worry. Does it really mean to be agnostic about the imported arrays? I cannot imagine that many people would be using 0-based indexing.

And can I use

for i in 1:length(A)

in my own code that will never be imported or seen by another person?

(By the way: no I am joining the pathetic mob that has nothing better to do than critise Julia. For some it’s like sports to blog about Julia’s problems. The most hilarious post I have ever seen someone criticising Julia was not so long ago the German Master Science student who used Julia in a Micky Mouse dissertation project.)

3 Likes

It’s fine to use 1:length(A) if you know the type of A. The problem comes only if you are using highly generic code that may be extended and/or used by other people with different array types in the future.

12 Likes

Can you give an example in code? Often it’s possible to pass a slice or a view instead of an index.

1 Like

No concrete code but I remember I sometimes pass a list of indices as a slice argument to the first array index of a numpy array.

It made me just think that the often given ‘eachindex’ solution doesn’t take into account that people often wouldn’t stride through an array index for index in successive order.

1-based indexing is just easier to use in arrays and more natural than 0 based (although to be honest I never liked the column major order in Fortran and always preferred rows first but it is good that Julia adopted the Fortran 1-based column major scheme consistently for pragmatic reasons.)

1 Like

You can do whatever you want. You can ignore any hints and tips and advice as you wish to, it’s totally up to you. No one will ever judge you because it’s unlikely that anyone sees your code.

If you want to share your code, if you want to provide some packages for the community, you are still completely free to do it in your own way and you can still ignore any help or good styles.

BUT !! , if you do this, you may face more criticism as needed. No problem so far because you can handle it.

BUT !!, because you wanted your shared code to be used by someone (definition of share), you may face problems, like very high load of issues and problems in your code you need to take care of. Your own way brought you more work as it needs to be. Thinking of better ways and when and how to apply them beforehand helps to reduce workload later.
This is what it is about when people propose using e.g. eachindex instead if 1:length(A).

Still, it needs some understanding and forethinking to apply all the tips and tricks at the right place and the right time, right circumstances. Sometimes it’s not needed at all, sometimes it’s highly important. It all depends. That’s where you come into the scene to decide for the right code. And it’s you who will suffer from wrong decisions. This is the day of a software developer. If this does not apply to you, you are again free to ignore everything regarding writing code.

Here my thoughts are no, because row 3 is something different as the third row. Row 3 is the row with index 3, whatever the starting index is. Well, there is no as short syntax to say give me the third row, as to say give me row with index 3. Actually row [3] can mean both things, it’s the code which decides what it is. And it’s the documentation of the interface which decides what it should be. In real life (mine) of a software developer, problems like these are rare. Clearly, as we all know, they (the problems) can be constructed in a plausible way and it’s fine not to use Julia at all as a conclusion. If I would react like that I would loose much more than I would gain so I still use Julia where I think that it makes my life as a software developer easier. And I use it frequently.

Actually you lost me here, but I decided to answer anyways. I think sarcasm is not a good way to start a serious discussion. But I also believe that you already received something unpleasant from us (this community) so you thought let them know in an ironic/sarcastic way. What do I want say? I don’t know, perhaps just: Live is difficult. People are good but it’s hard to see it.

4 Likes

I usually circumvent this by using the begin or end keywords in array indexing if I’m not sure if the array is 1-based. E.g. to get all but first and last, you could do:

a=rand(100)
b=a[begin+1:end-1]

But only in more complicated cases. I try to stick to broadcasting as much as possible and then using eachindex or axes if need. But if you need the middle of an array, or a section, these keywords can be useful as it avoids having to deal with 1 or 0 based indexing in most cases.

1 Like

I wouldn’t worry much. In a problem where you have to access positions of an object indicated by an irregular, fixed set of indices like [3,7,11,12], etc., that object will usually be one of a known type, possibly one defined by yourself. In that case, as @Oscar_Smith indicated, there is no need to deal with generic indexing.

Only when dealing with arrays of unpredictable type (hence with unknown indexing maybe), you should take care of generic indexing. In such case some good practices are:

  • Use eachindex, enumerate or axes to obtain correct index values.
  • Use begin / end keywords as aliases for the first or last correct indices (See @jmair’s answer).
  • For more complicated cases, some functions in the Iterators module may be useful. I particularly like:
    • Iterators.take and Iterators.drop instead of indexing with 1:n or n+1:length(A), respectively.
    • Iterators.partition instead of for i in 0:n:length(A)-n and then indexing with i.+(1:n) inside the loop.
    • Iterators.peel when I have to do an initial operation with the first item of a collection, and then iterate in a loop from the second to the last.
8 Likes

There are steps you can take to check your assumptions. For example, you could specialize for methods for array types that you are expecting.

There is also Base.require_one_based_indexing that can be used to check that thr array works as you would expect.

Another thing would be to make your indexing relative to begin or end.

3 Likes

I had written a simple package to do exactly this:

pkg> add https://github.com/jishnub/OrdinalIndexing.jl

julia> using OrdinalIndexing, OffsetArrays

julia> A = reshape(1:36, 2:7, 2:7)
6×6 OffsetArray(reshape(::UnitRange{Int64}, 6, 6), 2:7, 2:7) with eltype Int64 with indices 2:7×2:7:
 1   7  13  19  25  31
 2   8  14  20  26  32
 3   9  15  21  27  33
 4  10  16  22  28  34
 5  11  17  23  29  35
 6  12  18  24  30  36

julia> A[3rd, :]
6-element OffsetArray(::Vector{Int64}, 2:7) with eltype Int64 with indices 2:7:
  3
  9
 15
 21
 27
 33

julia> A[[3]rd, :]
1×6 OffsetArray(::Matrix{Int64}, 1:1, 2:7) with eltype Int64 with indices 1:1×2:7:
 3  9  15  21  27  33
7 Likes

This is great (I don’t need it). The extendability of Julia is always astounding… Awesome!

1 Like

Also note that if the numbers [3,7,11,12] are passed by the user together with the array, they likely correspond to actual array indices. That’s the whole point of OffsetArrays: A[3] is not the third element from the beginning.
If so, then no further processing is required, and you should just use the indices as-is.

1 Like

Even someone using offset arrays could use your function if they used OffsetArrays.no_offset_view(A), which converts offset array A back to 1-based indexing.

The best course of action would be that people should just stop using offset arrays. Or no package should ever be accepted that uses offset arrays which are later being exposed to users.

One can even argue if you use offset arrays you are not a good developer that follows best practise.

The offset arrays become the new Fortran GOTO’s.

Accepted by whom? On who’s authority? Do you have any other rules you’d like to share?

What if someone has legitimate use cases?

Anyone can create containers with whatever indexing the want at any time, good luck trying to outlaw them.

7 Likes

Any examples of those legitimate reasons?

I have had to deal with GOTOs in Fortran 90 (no typo) code. The authors allegedly also hat legitimate reasons.

It’s none of my business what other people’s legitimate reasons are.

Nevertheless, there are some examples on the OffsetArrays.jl repo, and here’s a blog post with examples from when it was launched:

If you don’t like them, just don’t use them or accept them in your code. Don’t get into other people’s business, and decide for them if their reasons are ‘acceptable’.

3 Likes
  • Moving C/python code to Julia without worrying about shifting indices
  • In many spectral bases (such as real Fourier/Chebyshev), the frequency labels start at zero, so zero-based indexing offers a natural mapping
  • The azimuthal order of spherical harmonics varies from -m to m, so an array with these indices makes life much easier. Similarly, the frequency labels for FFTs vary between -n/2 and n/2-1.
13 Likes

I would hope that no one is actually using OffsetArrays to directly translate array indexing from C/Python to Julia. If someone wrote Julia code that I had to read and use and did this type of translation, it would make the code harder to understand, and I’d have to make sure the packages being used are compatible with OffsetArrays.

If I’m translating Python code to Julia, the cognitive load I’m expecting is:

  • 0-based to 1-based
  • Exclusive to inclusive ranges
  • Row-major to column-major

At some point you’re going to have to deal with all of these details. Anything else would make things even more confusing.

2 Likes

I once had to translate large IDL code to Python.

IDL data language like Fortran uses 1 based indexing and column major arrays.

The 0 vs 1 based array index was not the problem.

The IDL code made use of a lot of matrix multiplications and transposes of multi dimensional arrays etc.

I thought I understand what the IDL code does but often I was simply not able to reproduce the code in Python (taking care of column vs row major arrays) without using Python’s fall back mechanism where one can enforce numpy arrays in column major mode.

This saved me, e.g:
a = np.ndarray(shape=(200,3000), order=‘F’)

There is a reason natural numbers are called natural.

Unnatural counting schemes are domain specific (e.g. FFT frequency indexing) and perhaps too specialised to be accommodated in generic array routines.