8 ms·
Negative array subscripts. So a[-1] means the last element of an array, a[-2] means the second last, and so on.
by Doctor_Fegg 4y ago
Negative array subscripts. So a[-1] means the last element of an array, a[-2] means the second last, and so on.
- scubbo 4y agoI was really surprised when I learned my second language (after Python) to realize that this wasn't standard!
- unnouinceput 4y agoThat's for languages that can't define arrays with custom start/stop indexes. But those that have custom indexes they can very easily expand/implement as helper class (for example array.indexFromLast(1) which means array[Length(array)]. This way you can have best of both worlds.
- masklinn 4y agoSurely if your language has custom indexes / ranges `Length(array)` is completely broken and the language provides something like "Index`Last" you can hook on? Because an array with indexes [3, 7) has length 4, but 4 is not the index of the last element.
- kqr 4y agoYes, then I would expect 'First and 'Last with the obvious meaning, and something like 'Range which returns an iterator of all indices.
- unnouinceput 4y agoYup, correct. What I meant above with array[Length(array)] is for the languages that don't have it. Let me be more clear. C/C++ doesn't have custom array indexes and as such <array[std::size(array) - 1]> is returning the last element of said array. Delphi has custom array indexes and as such, taking your example with defining an array in the form <example_array : array[3..7] of integer>, I would not get the last element in case of <example_array[Length(example_array) - 1]. In this case I would have 2 options. Option 1 would be to use <High> function as in <example_array[High(example_array)]> to access example_array[7] element. Delphi also has <Low> function so you can iterate through a custom defined array by using <for> keyword with the help of them. Option 2 would be to actually build my own helper (this is the most wanted case when you're dealing with multi-dimensional arrays that also have custom indexes) and I would have something like <example_array.FromLastIndex(0)> to access example_array[7] element. Hope this cleared the confusion.
- Jtsummers 4y agoThat's what Ada does, yes. You'd let the array (or whatever collection) do the work for you: for I in A'Range loop A(I) = A(I) + A(I); end loop; Whatever the range is, this will work. If you really need the first and last elements or want to be explicit: Start := A'First; End := A'Last; And if the type of the range (since any discrete type can be used) doesn't support simple incrementing with +1 or similar, you can use 'Succ to step through: Index := A'First; Index := Whatever_Type'Succ(Index); Also 'Pred to work backwards. Those can be wrapped up in a simpler function if desired.
- touisteur 4y agoAnd with its array slice mechanisms, Ada is one of the most easy/productive language to handle arrays. Being able to give subarrays to a procedure and preventing buffer overruns everywhere, reducing screw-up scope everywhere is a superpower I didn't know I needed before starting writing proved parsers.
- ablob 4y agoAs long as there exists a bijection between whatever you choose as an index and the natural numbers starting from 0 it is fine. (I.e. the range of valid indices must be a countable set) In your example that bijection could be: 3 -> 0 4 -> 1 5 -> 2 6 -> 3 This works for vectors as well, so why not have a range from (0,0) to (5,5) to index into an array arr? You could write the function that does the mapping manually: arr[(x,y)] = backing_array[x / 5 + y] //bounds checks omitted But here it can be automated quite simply to allow for vectors of even 3 or 4 dimensions. Just know that custom indexes / ranges are not automagically broken. Personally, I like how much easier it is to read the intent with custom indices.
- feoren 4y agoAny language that supports overriding the index operation should support this. You should be able to do this in C# with a struct with a backing array, for instance. If you're going to do this, use the word "Circular" in it, and I would also insist that if a has 4 elements, then a[0] == a[4] == a[8]. In other words, you always just take the (positive) index modulo the size of the area. Then a[-1] is the same as a[N-1] for an array of size N. This could be useful in a lot of contexts, but should be made explicit.
- anamexis 4y agoWhere would the circular indexing like that be useful?
- AlotOfReading 4y agoIt's frequently used in signal processing to the point where it's considered one of the defining features of DSPs. One common case is filtering over a fixed size buffer of samples. If you have circular indexing, you can simply overwrite the earliest sample and increment the base reference to the next element. I'm not sure I'd want it for every list, but there are certain places it's nice. [1] https://www.allaboutcircuits.com/technical-articles/circular-buffer-a-critical-element-of-digital-signal-processors/ https://www.allaboutcircuits.com/technical-articles/circular...
- feoren 4y agoFirst of all, it handles the "get me the 2nd to last element" case automatically, but in a way that doesn't feel like a weird edge case: it's more "mathematically sound", basically. I always want mathematical soundness if possible because it leads to serendipity, the opposite of technical debt. Where technical debt is "dammit, this is going to take so much longer than it should!"; serendipity is "oh wow I can implement this cool new feature just by combining these other two things in a new way, in like 2 lines. This is going to be way faster than I thought." Mathematical soundness / purity leads to serendipity. Directly, it supports caches very well. You just increment the number of things you've ever cached and that's where your next cached value goes; you don't care when it overwrites an old value. There are other cases where you just need some variant of a thing, but you don't actually care that much about which variant you get. You might want to vary your wording in auto-generated text, for instance, by rotating synonyms. Or rotating the tiles you use in a 2D game. In this case I'd define an interface where you pass in a "seed" integer and it gives you back some deterministic example; a circular array is the simplest implementation of this interface (but there are others). You could also do simple load balancing by sending work to Worker[workCount++]. While usually you want to track each workers' existing workload (because the work takes unpredictable time), this simple approach could be sufficient if all your work completes in about the same time. If you're doing fancy math or science computing, you may be working with finite groups or fields, whose elements you could stick in an N-dimensional circular array (based on the characteristics of the field).
- tester756 4y agoif it works on literals only, so a[x] doesnt work if x is negative, then ok. otherwise seems like an errors that are hard to spot.
- caseyohara 4y agoRuby happily allows this and I can't recall it ever being an issue. It's no more prone to errors than `x` being greater than the number of elements. > arr = ["a", "b", "c", "d", "e"] > x = -2 > arr[x] => "d"
- edflsafoiewq 4y agoIf you intended to calculate an index and you accidentally get len(arr), you get a runtime error. But if you accidentally get -1 you silently get the last element instead. Similar to the argument about signed/unsigned indices in low level languages.
- ilyt 4y agoIf you calculated wrong you can land on positive-but-existing value just fine... > Similar to the argument about signed/unsigned indices in low level languages. Think that one has to do more with convenience where most of stuff uses int by default
- feoren 4y agoThis is such a weird take to me. You're saying: I want to add a rule, where this structure responds to a request in a certain way, based on how the programmer wrote the request in the calling code. Layer upon layer upon layer of weird, janky, edge-case, pseudo-rules, with no consistency, no clear mental model; an absolute nightmare of a programming language. No longer can you possibly intuit what a[-1] really means, nor can you intuit the rules of indexing. You've broken TWO mental models in one fell swoop. No longer can I look at your programming language and assume that anywhere I see a 7, I can replace it with a variable whose value is 7. That is no longer true in your language! Variables no longer work intuitively in your language. Think about that! What an absolute nightmare! This is exactly the difference between a language like PHP and a pure functional language. PHP says: usually we want to do X, but sometimes Y, so we'll make Z which does X unless Q is true in which case T1 will be set and Y will happen most of the time when you want it assuming you called it the write way and put an @ in the right spot otherwise P will happen because I hadn't had lunch when I wrote that and it seemed like P was pretty likely to be the case when T1 was set but an @ was not written but lately I've been feeling like maybe T2 should also be set sometimes so if you call Z and you want X but T1 is written and you don't want to write an @ then you can just set CONSTANT_FOO_BAR_WITHOUT_X_SET_AT to 17 because the other 16 codes are already used for other things. Functional languages say: what if everything was just math?
- _a_a_a_ 4y agoAs a choice then perhaps but as a default and unalterable behaviour it can be a bloody timewaster when negative subscripts are a runtime error in your work. I've hit that in python and didn't enjoy it.
- pavon 4y agoA nice alternative I've seen is that negative index is an error, but there is special syntax for indexing from the back like array[end], array[end-1], array[end-n], where n is a (positive) variable. Likewise, end can be used in range definitions like array[5:end]. Julia and Matlab both have this.
- int_19h 4y agoC# has a very nice approach to this: indices aren't simple numbers, but values of type Index [1], which store both the offset and the direction, and can be implicitly created for plain ints. When you do want to index from the end, you use the unary ^ operator to create a reverse index. Thus, you can write things like a[^1] or a[0..^1]. But, more importantly, it means that any custom collection type can define an indexer that can handle reverse indices in the manner that is appropriate for that particular collection; it's not just for arrays. [1] https://learn.microsoft.com/en-us/dotnet/api/system.index https://learn.microsoft.com/en-us/dotnet/api/system.index
- _a_a_a_ 4y agoThat's a lot of machinery which I feel is going to benefit relatively few people and cases. I suppose I should learn it just in case but my suspicion is that MS is adding extra stuff which they hope people will use which will act as a lock-in to C#. Ergo the benefit of this is to MS not to the end user ISTM.
- int_19h 4y agoAll the indexed sequential collections in the standard library use it, for starters, and those collections are in turn used by the majority of users. OTOH a convenience feature as a lock-in is hard to believe.
- c3534l 4y agoWhile I agree, I've met people who think the idea of a negative index is completely absurd. Their brains seem to immediately reject the concept.
- BurningFrog 4y agoThere are always some of those, for every novelty. Sometimes it's me.
- luisrudge 4y agojavascript has something akin to that: [1,2].at(-2) returns 1
- CSMastermind 4y agoYes please, there was nothing on the article's list that particularly resonated with me but I really wish this was standard in every language.
- xigoi 4y agoThe problem is that it adds runtime overhead and can cause silent bugs.
- dataflow 4y agoI'm fine with an array type that supports this, but not as the default. I've been bitten in the past by code that ran error-free while giving incorrect output due to this feature suddenly making the indexing valid. I'd prefer it to be opt-in somehow so that the default behavior for negatives is invalid, not silently wrong yet valid behavior.
- signaru 4y agoMatlab has the opposite where your syntactically valid but mysteriously non-working code suddenly has a large array. EDIT: On the other hand, I think Matlab's array(end - number) indexing syntax is a good compromise of convenience and less error prone explicitness.
- qsi 4y agoYou can also overload end in Matlab for your custom classes as it's actually a method.
- gnuvince 4y agoI like D's approach of using `d[$]` to get the last element, `d[$-1]` to get the element before last, etc.
- david2ndaccount 4y ago`$` refers to the length of the array, so `d[$]` is an array-bounds error. `d[$-1]` is needed for the last element, but you can’t do that blindly, you have to check that the length is nonzero or you’ll get unsigned underflow to ulong.max
- WalterBright 4y agoWhich will also cause an array-bounds error. So you're covered!
- melagonster 4y agoperl have $d[$#d] represent the index of last element.
- bvrmn 4y ago$my_properly_named_array[$#my_properly_named_array] doesn't look like QoL.
- tmtvl 4y agoYeah, but $#list is better used for writing loops: foreach my $i (0..$#list) { say "$i: $list[$i]"; } For getting the last element from a list you can just use -1 (and of course further negative numbers work like you would expect, -2 is second to last and so on): my @last_three = @items[-1, -2, -3];
- melagonster 4y agoyes, I forgot perl have negative index!
- touisteur 4y agoAda has 'Last, 'First and 'Length attributes to similar effect.
- Pet_Ant 4y agoOnly downside I can see is performance hit. You need to check if it’s negative then you need now calculate the length first… which is a bit of overhead but considering how often you use arrays in a tight-loop… I mean if you can optimize out the check because you can detect that the index is always positive…
- hajile 4y agoYou can have an optional length prepended to arrays that use that feature.