-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Remove elements from std.ArrayList while iterating over it #3037
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
c506046
to
24aea24
Compare
Huh. That's weird. This should have been a compile error:
|
Note that |
Should't the normal iterator be like it currently is, and then add a removable iterator? Changing the current iterator breaks everywhere its used, while adding a new one doesn't. Also, perhaps one could consider taking a function in the initialization function of this removing iterator, that has a signature like |
@DutchGhost If you specifically want only the old behavior---you don't want the user to be able to modify the elements---then you'd call As to the predicate idea, I think the way it is here is clearer.
I think the way I have it right now is more flexible, and perfectly clear. I also asked in the IRC while making this PR as to whether I should make the current situation the default or not (i.e: |
I expect an Iterator function to just simply return me an iterator without fancy things. |
But decreases performance. |
That is the case with PR. It only does "fancy" things (I'd argue that being able to remove an element while iterating over it isn't very fancy) when you call the procedures. It otherwise doesn't do anything different.
What you're describing is exactly what this PR does. You literally call the "fancy" procedure alongside iterating.
I have a patch for that. 😉 |
Did some rudimentary performance test of [debug mode] using this branch and compared to master.
Of course, benchmarking is a dark art, so... pinch of salt, and all that. const std = @import("std");
const warn = std.debug.warn;
const time = std.time;
pub fn main() !void {
var a = std.heap.direct_allocator;
var l = std.ArrayList(usize).init(a);
var r = std.rand.DefaultPrng.init(32); // Xoroshiro128
var i: usize = 0;
while (i < 10000000) : (i += 1) {
var x = r.next();
try l.append(x);
}
var j: usize = 0;
var average_sum: f64 = 0;
while (j < 100) : (j += 1) {
var timer = try time.Timer.start();
var itr = l.iterator();
var sum: usize = 0;
while (itr.next()) |e| {
sum +%= e;
}
var took = timer.read();
warn("took {} ns\n", took);
average_sum += @intToFloat(f64, took);
}
var average_time = @floatToInt(u64, std.math.round(average_sum / @intToFloat(f64, j+1)));
warn("average {}\n", average_time);
} |
@Tetralux, what I ment was that if I wanted an iterator that is able to do fancy thing, I'd call a method on the ArrayList that suggests the iterator I'm getting from it can do more than just iterating. With your change, the default iterator would do more than just iterating, which you pay for, even if its just a little. You might argue that you moved the old one to a new struct and new method, but then already existing code has to opt out of your change, and call something new, while if you only added ".removingIter" (or whatever name you'd like) to the list of methods, that isn't the case. So,
|
std/array_list.zig
Outdated
// the next element's index was incremented when we | ||
// called `next` before, so we decrement it here, before we get the item. | ||
// NOTE: volatile code! don't use a ternary if, or stack variable for new index here! | ||
if (it.removed) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid this branch by putting the decrement into the remove function.
Then this can be a non-branching it.removed = false
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interestingly, in debug mode at least, what you suggest is actually slower than the way it currently is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The rudimentary test above seems to indicate that if you remove the branch, you lose around >5%.
I did move the decrement out of next
though. Good call on that.
da552ff
to
a66810f
Compare
|
(Updated comment.) |
f4d9040
to
63d3bd0
Compare
Had some trouble figuring out how to make the semantics that removal procedures always remove the last element returned from
|
Actually scratch that. |
I'm confused. The change should have just been erroring in remove if |
@daurnimator What you suggest is the first thing I tried. But as explained above, this is faster for [release-safe] and [debug mode]. |
74413e3
to
e27d1bc
Compare
Rebased to master. |
… iterating std.ArrayList.
Add tests for removing the first element
…n 1% of normal removal procedures.
This makes removal procedures alwayts remove the last element returned from next. Before, they'd remove the first element if you called for removal before any calls to next.
e27d1bc
to
ddccf82
Compare
You can now remove an element from a
std.ArrayList
, while iterating over one.By ordered-removal:
By swap-removal:
If you only have a
*const std.ArrayList
, there's.iteratorConst()
, which gives you an iterator with only the old behavior - i.e: which cannot remove elements.