First of all, this topic will certainly relate to safety and performance.
Benefits:
It only iterates over the list once
No extra objects created, or other unneeded complexity
No problems with trying to use the index of a removed item, because... well, think about it!
There are three kinds of actuaries: those who can count, and those who can't.
There are three kinds of actuaries: those who can count, and those who can't.
There are three kinds of actuaries: those who can count, and those who can't.
Carey Brown wrote:Is it supposed to remove multiple elems during one complete run of the loop, or only remove one element?
Piet Souris wrote:Question 1: both
For instance, if you remove the element at index 0, then the next element to inspect is also at index 0, not index1.
I would simply use a Stream and a filter, much easier (has an implicit loop)
And nothing wrong with going backwards, easier than going forward.
Piet Souris wrote:And the get-method is very inefficient for linked lists
Carey Brown wrote:
Carey Brown wrote:Practicing...
Tan Quang wrote:
Carey Brown wrote:
A long time ago, I've read an article, they have said that is: With ArrayList, you should browse by index Ascending (from 0 to size), and the List is reverse (from size to 0).
I wonder why there is this distinction? Why ArrayList shouldn't browse index in the direction of backwards (from size to 0)?
Mike Simmons wrote:The quoted Stack Overflow answer seems very poor. Yes, looping in reverse is a little better than a naive forward loop here. And it's a little easier to understand - see how the "forward indexed loop" requires an extra i-- to make it work. But it's still a pretty horrible option, from a performance perspective. Here's a comparison of several options I can think of here. All of these start with a list of values from 1 to SIZE, and then remove all odd values. Try changing the value of SIZE to see how much it can affect things. Note that the differences between several options are too small to measure accurately, unless you increase SIZE quite a bit... but the crappy answers like the one given on Stack Overflow are way too slow to handle much larger lists...
List 1 initial size ===> 1000000
forward indexed loop, new size ==> 500000
cost time: 41.232
List 2 initial size ===> 1000000
reverse indexed loop, new size ==> 500000
cost time: 17.133
Carey Brown wrote:Going backwards in an ArrayList while removing is more efficient. See Mike's timings.
Tan Quang wrote:
And the first question is: If I have to choose 1 in 2, I should choose to reduce size or reduce index?
Piet Souris wrote:Question 1: both
Tan Quang wrote:Why is both? Just 1 in 2, if you choose both will give incorrect results.
Tan Quang wrote:Therefore, with the forwards loop, we need to reduce both i and size.
There are three kinds of actuaries: those who can count, and those who can't.
Don't know what you mean by "the result will be reversed". No it won't. The ArrayList is still in the same order, minus the removed entries.Tan Quang wrote:
Carey Brown wrote:Going backwards in an ArrayList while removing is more efficient. See Mike's timings.
If ignoring the problem is that the result will be reversed, so why shouldn't we use the backward loop? Especially with ArrayList as in some documents?
Tan Quang wrote:Using get(index) with List is a bad thing, but it brings better performance with ArrayList.
Piet Souris wrote:
But perhaps I misunderstood your question.
Carey Brown wrote:Don't know what you mean by "the result will be reversed". No it won't. The ArrayList is still in the same order, minus the removed entries.
Mike Simmons wrote:Using get(index) with an ArrayList isn't bad at all.
Mike Simmons wrote:The problem is, you're also using remove(index) - that has poor performance with both ArrayList and LinkedList. Every time you remove one element, you are also forcing it to move every subsequent entry in the ArrayList by one. So if you have 1000 entries, and delete entry #2, entries 3-1000 will all shift left by one, and become the new entries 2-999. And you do that sort of thing every time you call remove(). That is why the performance is so bad for this method.
The reason why it's faster to do this in reverse is, it's faster to remove things the closer they are to the end. If you start with the end, that's easy, and by the time you get to the beginning, you have already removed half the entries afterwards. So you still have to move a bunch of entries, but you 're moving less than you would if you went from front to back.
Interestingly, if you were to use add(index, element) to add a new entry, rather than remove(index), you would find it's faster to go forward, rather than in reverse. That's because if you add lines, you're making the list bigger as you go, so if do the beginning first, you're adding to the beginning when it's as short as it's ever going to be.
Anyway, if for some reason you really really need to do this only using get(index) and remove(index), I guess going in reverse is the best way. But, as shown, if you're allowed to use any other techniques, there are numerous much better ways to do this.
That is because you wrote both loops incorrectly. Don't try using variables in the loop header when you can use a feature of the list:-In the following instance the loop corrects itself every time anything is added to or removed from the list; there is no need for size++; or size--;:-If you iterate the list backwards, any changes to is size will be “behind” the point of iteration and will not cause any problems. That is why iterating backwards is preferred; it doesn't need i++; nor i--;nor size++; nor size--;Tan Quang wrote:. . . the forwards loop will throw IndexOutOfBoundsException if only reduces i without reducing size, and will give incorrect results if only reduces size without reducing i. Therefore, with the forwards loop, we need to reduce both i and size. . . .
I think you have misunderstood the difference between declared tyes and runtime types. If you declare a variable as type List<XYZ>, which is correct, that permits the runtime type of the list to be a LinkedList<XYZ> or an ArrayList<XYZ>. It is therefore best to write your loop to accommodate both kinds of List. I tried MS' code on JShell and got the following results:-Oops ... I just said that it was BAD with the List, NOT BAD with ArrayList.
Using get() on a LinkedList seems even slower, but that might be because remove(i) causes boxing conversion to an Integer.RemovingRows.main(null);
forward indexed loop
time: 138.106
reverse indexed loop
time: 45.547
iterator on ArrayList
time: 139.262
iterator on LinkedList
time: 0.064
make new list
time: 0.058
removeIf
time: 0.076
removeIf on LinkedList
time: 0.069
stream new list
time: 0.057
jshell> run()
| Exception java.lang.RuntimeException: Over 1000s at i = 234000
| at run (#19:12)
| at (#20:1)
That includes remove(). So repeated removals of odd numbers will run in quadratic time. If you iterate backwards in MS' array list, after the first removal, nothing needs to be moved, after the second removal one item, after the third two items, etc. This doesn't change if you use an Iterator. If you iterate forwards, after the first removal you move 599,998 items, after the second 599,997 items, after the third 599,996 items. This actually shows the problem of removing elements from the beginning and middle of an array list; it runs in linear time O(n) where n is the number of elements to the “right” of the removal point (well, I think it does!). Removal of elements from the middle of a linked list is much faster (constant time), but only if you have a handle to th element to remove already. That is why the removal technique with an Iterator runs so much faster on a linked list; the Iterator already has a reference to the element to remove. Creating a new List, either in a loop or with a Stream, is just as fast. I don't know how removeIf() is implemented; that is fast, but slightly slower on a linked list.All of the other operations run in linear time (roughly speaking).
I shall have to remember always to use removeIf() for removing elements from a list.removeIf on linked list: 0.258s
Campbell Ritchie wrote:Using get() on a LinkedList seems even slower...
I think you misunderstood what I said. I said that the removal app runs even slower with a linked list and get(). I wasn't saying that get() is slower on a linked list, even though we all know get() does run slowly on a linked list. Remember that you may not be removing the element at index i, but it might be boxed to the Integer, in which case it would be using equals() as well. That might be the explanation for its unusually slow execution.Tan Quang wrote:
Campbell Ritchie wrote:Using get() on a LinkedList seems even slower...
Yes, that's why I usually only use get(index) with ArrayList . . .
Tan Quang wrote:So, the code that converts to Iterator as I understand and explain exactly not (especially continue keyword)? If it's not correct, please explain the problem and suggest me how to fix it.
P/s: Yes, new methods all bring better performance get / remove(index) but perhaps but the new methods should be discussed in another topic. Please take a look at this as the topic on a Java version that doesn't support those improvement methods.
Campbell Ritchie wrote:I suggest you don't use continue; in that context because you can readily replace it with else...
Campbell Ritchie wrote:Since you should usually declare a List as type List<XYZ>, it makes it awkward to use the get(), remove(), etc. methods at all.
Mike Simmons wrote:Other than the unnecessary continue, there's nothing really wrong with your implementation using Iterator.remove(). I already showed code to do that, and the timing is just as bad as the forward loop, for the same fundamental reason. Each remove() is moving a bunch of elements by one index position. Lots of unnecessary work.
For whatever reason you need to use Java 7... you can still use the method I labeled as Iterator on LinkedList, or make new list. Those still work in JDK 7. I'm sorry if you really really want to use the same ArrayList you started with, but it's just a bad idea under Java 7. There's only so much we can do if you are unwilling or unable to use better techniques.
Continue is very efficient but it "smells" like a GOTO and GOTOs are highly discouraged in high level languages as a potential source of bugs and reduced readability. There are cases where a carefully placed continue will improve the code but even then you're asking the seasoned reader to stop and understand why the continue was chosen at all. So, all things being equal, you should choose 'else' over 'continue' in your case.Tan Quang wrote:Why I shouldn't use continue keyword? Is it slow? Or does it cost a lot of resources? Or both?
First of all, your new list does not contain copies of all of the objects in the original list, it only contains copies of some of the references, and references are quite small. Secondly, more often than not the new list is then assigned to the old list variable thus freeing up the old list for garbage collection.Tan Quang wrote:But how does their memory cost? It seems that it is not low (especially the continuous creation of new lists)...
Carey Brown wrote:First of all, your new list does not contain copies of all of the objects in the original list, it only contains copies of some of the references, and references are quite small. Secondly, more often than not the new list is then assigned to the old list variable thus freeing up the old list for garbage collection.
Don't worry about either. You might be able to work out the CPU load and memory load if you use a profiling tool or jconsole or similar. The memory load for removeIf() is probably slight and that for creating a new List is probably the same as the memory footprint of the new List. 500,000 references on a 64‑bit machine might occupy 8 bytes each, so that makes slightly over 4MB. Think back to 1962 when 4MB was an astronomical amount of memory and would have cost millions. Now I can buy 1000× that much memory for about the same price as a pizza. The CPU load per second is probably the same for all techniques, so the faster algorithms will use less power.Tan Quang wrote:. . . I'm curious about the cost (memory, CPU) that it uses? . . .
You can make your conditions as simple or as complicated as you like. You can split conditions and call filter() repeatedly, on each part. Remember, the more experienced you are, the simpler you will make your conditions.. . . with complex conditions, the use of it doesn't seem feasible.
Campbell Ritchie wrote:You can make your conditions as simple or as complicated as you like. You can split conditions and call filter() repeatedly, on each part. Remember, the more experienced you are, the simpler you will make your conditions.
Mike Simmons wrote:The removeIf() implementation is very fast, and requires little extra memory. And there is absolutely no problem at all with putting complex conditions in it. Why would there be a problem?
Nobody has said that. We said that adding or removing at the beginning or middle of an array list runs in linear time.Tan Quang wrote:. . . I know that with ArrayList, add / remove elements is not a good idea.
Wrong question. The correct question is, “Which technique provides the better scope for stupid errors on the programmer's part?” Or, maybe, “Which technique provides the better scope for confusing anybody reading your code?”. . . Which method will bring better performance for add / remove elements?
Campbell Ritchie wrote:Nobody has said that. We said that adding or removing at the beginning or middle of an array list runs in linear time.
....Wrong question. The correct question is, “Which technique provides the better scope for stupid errors on the programmer's part?” Or, maybe, “Which technique provides the better scope for confusing anybody reading your code?”
Tan Quang wrote:Well, it seems I have found the answer (combined with ArrayList's source code) for this question.
With ArrayList.removeRange (0, ArrayList.size() will be similar to ArrayList.removeAll(ArrayList).
But they are slower than ArrayList.clear().
Mike Simmons wrote:Um, no. Those methods don't even do the same thing. Well, clear() and removeRange(0, list.size()) do exactly the same thing, with nearly the same speed. But removeAll() takes an argument, and has to check every element in its own list to see if it's contained in the argument list, and only delete those elements that are in the argument list. That takes a lot more time - but more importantly, it's a different operation, so why even compare the time they take? Pick the operation that matches what you need to do, not the one that's fastest.
ArrayList.clear() is O(n) and of removeAll is O(n^2)
Can you smell this for me? I think this tiny ad smells like blueberry pie!
We need your help - Coderanch server fundraiser
https://coderanch.com/wiki/782867/Coderanch-server-fundraiser
|