How can i make Julia array index to start from zero 0 and not from one 1?

My proposal to set up the debate and use 0.5 as starting index didn’t get much traction… :roll_eyes:

14 Likes

Perhaps not completely relevant in the context of Julia, but here’s what Dijkstra had to say about this topic:

To be clear, I’m not trying to participate in a holy war here, I just appreciate this topic as a part of history of CS. Like going through a museum for fun.

2 Likes

Indeed. One of the examples I like to use comes from something we can all relate to. A few years back, the CDC’s guidance for when you could return to work after getting COVID was (at one point) “five days after symptoms appear.” Of course, they meant five day-length intervals (distances), leading to conversations like this:

Me: you’re back sooner than I thought?
Acquaintance: it’s day five
Me: but it hasn’t been five days, only four.
Acquaintance: so you’re telling me the fifth day is day six?
Me: basically, yes
Acquaintance: <brain segfaults>

I recall interviews on TV about this, as well as explainers about how to count from day 0. I’m guessing the 0-based crowd had no issues with this, though :wink:.

In the category of close but egregiously bad here I have to nominate Go. I think it is much more likely that both non-programmers and programmers (who are aware that Julia is 1-based) will understand Julia’s s[2:5] to mean four elements of s starting at 2, than being able to tell you what the same thing in Go means:

this gets a slice of the elements s[2] , s[3] , and s[4] .

Half-open slices/ranges are par for the course in zero-based languages. That way, x[i:i + n] gives you n elements starting at x[i]. Also, m:k and k:n are adjacent but non-overlapping, such that concatenating them gives m:n. You can split a Python list into the first k elements and the rest as simply as x[:k], x[k:]. All of these small wins, where you avoid writing n - 1 and k + 1, are the reason zero-based indexing appeals to so many, and it wouldn’t work if the ranges were inclusive at both ends.

2 Likes

Note that, for modular index arithmetic, Julia provides the function mod1(i, N) and the even more general mod(i, n:m), so you can express most things just as concisely as in zero-based languages.

The two things I haven’t found an elegant solution for are

  • Things like x[i:(i + n - 1)] to get n elements starting at i
  • Calculations involving strides, where you always have to subtract one. For example, the linear index corresponding to A[i, j] is (j - 1) * size(A, 1) + i. You don’t often have to write these calculations explicitly, however, the one important exception is when computing block offsets in CUDA kernels, and there you even have to remember to use Int32(1) rather than 1 for performance.

I also felt a slight ick about 1-based indexing and inclusive ranges when I first came to Julia from Python, but I’ve since realized that both conventions have their strengths, and in practice the difference is inconsequential. You get used to it in no time. It has no bearing on whether I can get my stuff done.

I do however sympathize with people who go back and forth a lot between languages with different conventions.

8 Likes

That half-open argument was popularized by Dijkstra (though it’s worth noting he wasn’t strictly talking about indexing) but I never really bought it despite starting with 0-based indexing. It’s just as easy to be in a situation where m:k and k:n intuitively start at m, end at n, and overlap at k (ironically one of the times this came up was to plot half-open intervals in step functions) to avoid having to write m:k+1 and k:n+1. Inclusive ranges are also easier to reverse, reverse(m:n) is n:-1:m instead of n-1:-1:m-1, which looks weirder with a negative index when m == 0. Slicing syntax may avoid that negative index by omitting it entirely e.g. Python, which can’t be computed at runtime and confines it to indexing brackets. That’s what I meant by arguments being mirrored across the sides, makes it too easy to highlight the contexts where one indexing scheme looks neater.

For iteration bounds in while loops, it’s trivial to use <, >, <=, >= to make whatever closed or open intervals we want, but not so in for-loops and array indices. The nice thing about Julia’s indexing is that the indexing object can be any array, so we can just define a half-open interval type. It would be more transparent (just store the inputs as bounds instead of 1:3:5 and 1:3:6 converging on 1:3:4), but lose egality (===) of equivalent ranges and possibly easy hashing.

1 Like

Yup, generally; nice list of comparative syntax here: Comparison of programming languages (array) - Wikipedia

I like what Ruby does with offering options with syntactically meaningful differences for readability.

Variety is the spice of life! Can’t deny it sounds to me like a great way to facilitate mixups and a never-ending trickle of fencepost errors, but if I’m past judging languages for their choice of convention, I guess I also shouldn’t judge them for refusing to choose.

1 Like

The whole 1-versus-0 indexing argument perplexes me. I would like to say it amuses me but it does not. I have used Pascal and Ada for many years. These languages allow any indexing base whatsoever. This is useful in many ways and if you like 0 or 1 then go crazy with your choice. If you have an image with the origin in the center then feel free to index from say -128 to 127. No problem. Ada even allows indexing an array by any enumerated type. Ada also allows indexing an array by more than one method in the same program or changing the base when sending to a subprogram, convenient, for example, if performing FFTs on the array. The key driver should be abstraction and this is a simple example of that. Apparently compiler writers do not find this to be a particularly difficult problem to solve.

3 Likes

It doesn’t naturally have pointers. Pointers are kind of existing as an interface with C.

Probably not the most elegant solution, but clear nonetheless:

x[range(i, length=n)]
LinearIndices(A)[i,j]
5 Likes

You could also define your own infix operator if you want to make this more convenient

julia> a .. b = a:b-1
.. (generic function with 1 method)

julia> 1..3
1:2

julia> [1,2,3][1..3]
2-element Vector{Int64}:
 1
 2

9 Likes