8 ms·
0-based indexing with closed intervals is better for slicing. This shouldn't be controversial. It's because you can represent a zero interval cleanly: [3,3) is
by samatman 6y ago
0-based indexing with closed intervals is better for slicing. This shouldn't be controversial. It's because you can represent a zero interval cleanly: [3,3) is an empty interval after slot 2, representing a single cell is [3,4).
This has two nice properties. One is that two slices are adjacent if the beginning and ends match, and the other, far more important, is that the length of the slice is end - start.
That's the one that really gets us something. It means you can do relatively complex offset math, without having to think about when you need to add or subtract an additional 1 to get your result.
I use Lua every day, and work with abstract syntax trees. I mess this up all. the. time.
Of course you can use closed intervals and stick with 1-based indexing. But for why you shouldn't, I'm going to Appeal To Authority: read Djikstra, and follow up with these.
https://wiki.c2.com/?WhyNumberingShouldStartAtZero https://wiki.c2.com/?WhyNumberingShouldStartAtZero
https://wiki.c2.com/?WhyNumberingShouldStartAtOne https://wiki.c2.com/?WhyNumberingShouldStartAtOne
https://wiki.c2.com/?ZeroAndOneBasedIndexes https://wiki.c2.com/?ZeroAndOneBasedIndexes
- WA 6y agoTrue, but there are examples where 1-based indexing is easier, like returning the last element based on length. I think array[array.length] is easier to understand than array[array.length - 1]. Or the predecessor of the last element: array[array.length - 2] makes you think, whereas array[array.length - 1] is more obvious.
- robertlagrant 6y agoThis is why Python has negative indices :) Then it's just my_list[-1] or my_list[-2].
- KarlKode 6y agoBut shouldn't it be [-0] to get the last element if [0] denotes the first element?
- codeflo 6y agoI think the most mathematically natural way to interpret negative indices is to threat them as numbers modulo list length. Then 0 = length, and -1 = length - 1.
- bvrmn 6y agoC programmers can give you a couple of advises about "correct" modulo on negative numbers.
- nemetroid 6y agoNegative indices have an implied ”len(list)” in front of them. They can be seen as an application of the idea of half-open intervals (0 <= i < len(list)), so it makes sense that they’re not symmetrical.
- naniwaduni 6y agoIs this sensible, or is it an unprincipled ad-hoc justification for a feature that happens to be useful? Or another way: Is this more reasonable than, say, implicitly taking indices mod len(list)? How about implicitly flooring indices? If so, why?
- nemetroid 6y agoI'm not sure on what principles you'd make a principled justification. My most common use of negative indices is for slicing the end of the list, in which context the interpretation similar to len(list) makes sense. E.g., list[:-2] does what you'd expect (the same as list[2:], except from the other end). > implicitly taking indices mod len(list)? Isn't this doing that (plus some bounds checking)? -1 mod 4 evaluates to 3. > implicitly flooring indices? Not sure I understand what this means.
- bvrmn 6y ago[0] denotes an element with offset 0 from the start of the array. [-1] denotes an element with offset -1 from the start of the array.
- adrian_b 6y agoIcon (from the SNOBOL author) had the negative indices many years before Python and it is likely that this Python feature was inspired by it.
- tester34 6y agosounds like a great way to introduce bugs
- robertlagrant 6y agoHow so?
- tester34 6y agobecause when you misscalculate the index, then it will not crash, but use the wrong one I'm getting this right?
- robertlagrant 6y agoI don't understand why that would be more error prone than using list[list.length-1]. It's just list[-1].
- tester34 6y agoimagine if you used list[index] where index would be calculated if you misscalculated something and went on negative, then you'd have big exception/error, but when negative indices are viable, then the code will work fine
- Mikhail_K 6y agoNegative indices are bug-prone. If you mess up iteration bounds, you should get an error, not silent incorrect indexing.
- robertlagrant 6y agoCan you give an example?
- Mikhail_K 6y agoSure. Let's take array elements in reverse, from m to m-n. Suppose n erroneously becomes larger than m. That should produce an error, not silently take several tailing elements.
- samatman 6y agoWhich Lua has also, for strings, and it's quite convenient. Doesn't work for tables, though, because you can actually put something at foo[-1], which can be useful. I find that foo[#foo+1] is more common than foo[#foo] in my code, that is to say I add to an array more often than I access the last slot. Although I typically use the idiomatic table.insert(foo, bar) instead— but that's mostly because it's slightly awkward to add the +1 all the time.
- dragonwriter 6y ago> 0-based indexing with closed intervals is better for slicing. This shouldn't be controversial. base is irrelevant to this, and you want (and show!) half-open, not closed, intervals. > This has two nice properties. One is that two slices are adjacent if the beginning and ends match, and the other, far more important, is that the length of the slice is end - start. Yeah, those are all properties of half-open intervals, irrespective of indexing base. It would be as true of π-based indexing as it is of 0-based.
- bvrmn 6y agoFor </<= intervals and 1-based indexing you have to write `for(i=1; i++; i <= N)`. So you lost nice property of having `upper - lower` number of iterations.
- dragonwriter 6y agoFor half-open intervals, you have upper-lower iterations regardless of base. In C for loop terms, it's: for (i=lower;i<upper;i++) {} If you are using 0-based indexing, lower is zero and upper is the number of elements for a full iteration over all indexes; with 1-based indexing lower is 1 and upper is one greater than the number of elements. Now, despite the utility of half-open intervals, most people’s intuition is around ordinal index ranges, so 0-based indexing is counterintuitive with half-open intervals because slices start at 0, and 1-based indexing is counterintuitive with them because they end at N+1. This is because, useful or not, half-open intervals are counterintuitive, but they are worth developing the intuition for because they combine nicely.
- bvrmn 6y ago> with 1-based indexing lower is 1 and upper is one greater than the number of elements. Do you see +1 tweak? Also consider that "for loop" can't be expressed as </* interval and always expressed as <=/* interval. So 1-based indexing have to be either 1 <= i <= N (closed interval, bad, N - 1 != number of iterations) or 1 <= i < N + 1 (half open interval, good, but +1 tweak).
- deleted 6y ago
- quietbritishjim 6y ago> I use Lua every day, and work with abstract syntax trees. I mess this up all. the. time. I think this is the clincher in your argument (rather than Djikstra). I only use languages with 0-based indexing, and it seems natural to me, but I could almost be convinced that if only I used 1-based indexing regularly then I'd be fine with it too. But here you are with actual experience of using 1-based indexing regularly and you don't feel that way, which blows that idea out of the water.
- Lio 6y agoI've messed up array indexing under both systems. I prefer 0-based but only due to familiarity if I'm being honest. Leads me to the old joke: There are only 2 difficult things in computing. Naming things, cache invalidation and off by one errors.
- yodelshady 6y agoWhy on Earth would I want a "zero interval" slice? It has none of the properties I want in a slice, and that a slice of literally any other length will have. In fact, it means every slice I use needs error-handling, because I might get... something that's functionally not a slice, but is still called one. If that doesn't scream "type error" at you, I don't know what would. In fact, that's precisely why 0 is a comparatively recent invention. Most list-based problems were solved just fine before then. All these arguments are poor rationalisation for the field's inability to move on from the 8-bit era, where the loss of 1/256 of your address space mattered and compilers had bigger problems to solve than translating indices to offsets.
- codeflo 6y agoYou want a zero-slice because it’s a simpler base case in almost any even mildly complex algorithm. Without empty lists, you need many additional branches throughout a code base to handle the “no element” case, causing more potential bugs. There’s nothing “8-bit” about cleaner code, quite the opposite.
- yodelshady 6y agoNo, you have no branches, because the "no element" case is *not a slice! It is a type error at generation! Instead, I'm dealing with that case at literally every one of thousands of places where I might receive it*! That's not clean.
- alentist 6y agoIt is. Just like 0 is a natural number, the empty string is a string, the empty set is a set, etc. If you've worked with mathematical proofs before, you'll have experience with how they make proofs shorter and more elegant. That's why they're natural base cases.
- jstimpfle 6y agoWikipedia says that Egyptians had a 0 as early as 1770 BC - almost 4000 years ago. If that's a "relatively recent" invention, what about computers? 0 comes up if you're doing any arithmetic at all. It's a natural and useful concept for almost anything. As long as you always can take something aways where there is something, you'll need 0. It wouldn't be very useful to make something such as a non-empty list type, to then be able to take away all elements except the last one, which must remain. In more mathematical terms, 0 is called a neutral element (relative to the addition operation), and almost anything you might want to do (for example subtraction) requires as a consequence a neutral element.
- wruza 6y agoA programmer “strengthens their ankle and leans forward by holding their arm out, preparing to relax the shoulder” into a bar. The bartender bursts out laughing for few sprints. I mess it up as well, both in Lua and C. The key to success is to not calculate values by adding or subtracting them, but to use meaningful names instead. E.g. dist(a, b), holen(a, b), endidx(start, len) and so on. Forget about ever writing “1” in your code, point your finger and call operations out loud. It doesn’t eradicate errors, but at least elevates them to the problem domain level. Off by one is not exclusive to 1-based or 0-based, it is exclusive to thinking you’re smart enough to correctly shorten math expressions in your head every damn time. (But sometimes I also feel too clever and after some thinking I’m just writing (i+l2+b+2) because it’s obvious if b already comes negative and means “from behind” in “0-based -1 is last” terms.)
- stabbles 6y agoNow do a closed range in type uint8_t representing [0x00, 0xff] Or how about a closed range in uint64_t that ends at 0xffffffffffffffff Or a range from roughly a = -9223372036854775808 to b = 9223372036854775807 in int64_t. b - a will overflow.
- user-the-name 6y agoHow about an empty range in type uint8_t? The fact of the matter is, for a type that can hold N distinct values, there are N + 1 different possible interval lengths. You can not represent intervals over a fixed-width type in that same type.
- stabbles 6y agoExactly, there's no silver bullet for ranges
- samatman 6y agoDid you mean open range? I've never encountered a circumstance where closed ranges are useful, though I presume they exist. And yes, I think any typed language with a less expressive range syntax than Ada has some work to do. That still leaves open the question of the default, and I maintain that 0 <= .. < n is the correct one.
- Mikhail_K 6y ago> 0-based indexing with closed intervals is better for slicing. Given how confused and inconsistent Python slicing syntax is, it obviously isn't.
- ModernMech 6y agoAlso given how Matlab is essentially slicing-oriented programming and is 1-based, I've always found it perfectly intuitive.
- contravariant 6y agoIf we're being pedantic then [3,3) = [x,x) = {}
- TheRealPomax 6y agoThat... makes no sense? Whether you start counting a list at 0 or 1, if your language of choice supports [x,x) notation, that syntax will effect an empty interval, and [x,x+1) will effect a single cell, no matter whether your list indexes the first element as 0, or 1. The only difference is which cell [x,x+1) refers to, which is the part that keeps sparking the controversy.