-3

Background

ArrayList is known to have bad performance in case you want to perform a lot of modifications on it (insert/remove), as it uses an array behind the scenes.

The reason is that for each insert/remove, cells are shifted aside, as ArrayList is using a simple array behind the scenes.

This means, for example, that if you have N items in the ArrayList, and you add M items into it (say, in the beginning), it might take at least O(N*M) to add them.

This is also written in the docs. About addition:

Inserts the specified element at the specified position in this list. Shifts the element currently at that position (if any) and any subsequent elements to the right (adds one to their indices).

and about removal:

Removes the element at the specified position in this list. Shifts any subsequent elements to the left (subtracts one from their indices).

This is why a simple loop of add operation is out of the question, as I might need to add a lot of items to an already large list of items.

The problem

I wish to be able to remove and optionally replace items of an ArrayList, even with items count that's different than the original one.

For example:

if the input is an array of {0,1,2,3,4,5}, I could replace the "1" item with 2 items "99","100" so that the output would be {0,99,100,2,3,4,5}

What I've tried

It is possible to remove multiple items as such:

    final ArrayList<Integer> list = new ArrayList<>();
    for (int i = 0; i <= 5; ++i)
        list.add(i);
    // list is {0,1,2,3,4,5}
    list.subList(1, 2).clear();
    // list is now {0,2,3,4,5}

This is better than removing items one by one, as I think this won't shift the cells of the array within the ArrayList object.

What I really want, is to replace items, so this is what I've made :

public static void replaceItems(ArrayList<Integer> list, int fromIndex, int toIndexExcluding, ArrayList<Integer> deltaItems) {
    int indexToTakeItemFrom = 0;
    Log.d("AppLog", "sizeBefore:" + list.size());
    int minEnsuredCapacity = list.size() - (toIndexExcluding - fromIndex) + deltaItems.size();
    Log.d("AppLog", "minEnsuredCapacity:" + minEnsuredCapacity);
    list.ensureCapacity(minEnsuredCapacity);
    //replacing items:
    for (int i = fromIndex; i < toIndexExcluding && i < list.size(); ++i) {
        if (indexToTakeItemFrom >= deltaItems.size()) {
            list.subList(i, toIndexExcluding).clear();
            Log.d("AppLog", "size after:" + list.size());
            return;
        }
        list.set(i, deltaItems.get(indexToTakeItemFrom++));
    }
    //add remaining items
    list.addAll(fromIndex + indexToTakeItemFrom, deltaItems.subList(indexToTakeItemFrom, deltaItems.size()));
    Log.d("AppLog", "size after:" + list.size());
}

This seems to work well. Examples:

// list is {0,1,2,3,4,5}
replaceItems(list, 1, 1, deltaList); //nothing to replace, so just add {99,100} to index 1
//list is now {0,99,100,1,2,3,4,5}

// list is {0,1,2,3,4,5}
replaceItems(list, 1, 2, deltaList); //replace {1} with {99,100}
//list is now {0,99,100,2,3,4,5}

// list is {0,1,2,3,4,5}
replaceItems(list, 1, 3, deltaList); //replace {1,2} with {99,100}
//list is now {0,99,100,3,4,5}

// list is {0,1,2,3,4,5}
replaceItems(list, 1, 4, deltaList); //replace {1,2,3} with {99,100}
//list is now {0,99,100,4,5}

In terms of efficiency, since the shifting is done 1-2 times at most, it should be O(M+N), and not O(M*N), where M is the number of items to replace, and N is the number of current items in the list.

The questions

  1. Is my code correct? Have I forgotten of some end cases? Maybe capacity calculation is wrong in some cases?

  2. Does subList even create a new List object, copying items from the original? Or does it just have some basic pointers behind the scenes, which are constant in size, so I shouldn't worry about using it?

  3. Is it the most efficient one I could make, given that I use ArrayList? Is there maybe a better API to use for this? Is there a way to simplify how this works? Does my code shift cells only when it really needs, and only max of a constant times (instead of based on input) ?

android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • 1
    https://stackoverflow.com/a/27252759/6722100 may be of use. – achAmháin Nov 21 '17 at 15:43
  • First rule of optimizing code is MEASURE. If you are really want performance use int[] instead. – leonardkraemer Nov 21 '17 at 15:47
  • @notyou This is incorrect, because adding multiple items can be inefficient, as I wrote. Behind the scenes, ArrayList is using an array, so if you add items in the middle, it will shift all of the cells after this position forward. If you add N items, and the array is of size M, it's about O(N*M). – android developer Nov 21 '17 at 15:47
  • @leoderprofi I actually thought about this, but I use ArrayList nevertheless, for the ease of use in other cases in the code. – android developer Nov 21 '17 at 15:49
  • Why the downvote though? – android developer Nov 21 '17 at 15:49
  • Fair enough. I just came across it and thought it may assist you. Downvote wasn't me, if you care. – achAmháin Nov 21 '17 at 15:49
  • Still this is not a matter of opinion, you should measure the performance in different scenarios and select what works best for you. – leonardkraemer Nov 21 '17 at 15:52
  • @notyou I've added explanation about this in the question, to make it clear what I'm talking about. – android developer Nov 21 '17 at 15:52
  • @leoderprofi BTW, if the docs themselves say it's inefficient, there is no need to measure. You should do what you can, always, to make an efficient code, based on your needs. Since in my case it's an Android app, performance is very important. The list is used in the UI, so it's always good to minimize the time to handle it before showing it. – android developer Nov 22 '17 at 08:26
  • If this is a very frequent operation (and the only frequent bulk operation) then modelling the list as a "list of lists" might be more efficient, especially if the sub-ranges to be replaced over multiple calls don't overlap. – Joachim Sauer Nov 22 '17 at 14:47
  • @JoachimSauer I don't understand how a list of lists can help here (you still will have to perform the same operations on them, instead of on a single list), but this would also mean it would be extra hard to manage it, and get the X's item. – android developer Nov 22 '17 at 17:31
  • @androiddeveloper: the idea being that the range you want to replace should always be a whole list, so the whole replace operation becomes a single reference assignment. You can wrap that in its own List interface and hide the complexity. The usefulness depends on the structure of the data and how why exactly you want to replace sublists like this. Can't say more without knowing the specifics of the use case. – Joachim Sauer Nov 22 '17 at 17:33
  • @JoachimSauer The list is used to show items in RecyclerView, so quick access like an array is quite important. The replacement isn't for the entire list (though it is possible, it's rare). – android developer Nov 22 '17 at 23:27

3 Answers3

1

Since you already understand how you can do bulk removal and insertion efficiently using subList(), why don't you combine the two by first bulk removing the desired range by clear()ing the sublist, and then bulk adding the desired elements into the same subList?

I'm not sure what exactly you're trying to achieve, but if I understand it right then it should be possible efficiently via:

public static void replaceItems(ArrayList<Integer> list, int fromIndex, int toIndexExcluding, ArrayList<Integer> deltaItems) {
    List<Integer> subList = list.subList(fromIndex, toIndexExcluding);
    subList.clear();
    subList.addAll(deltaItems);
}

But maybe I misunderstand because I have no idea why your code contains a for loop; that doesn't look correct?

Tobias
  • 1,107
  • 7
  • 8
  • This would cause 2 shifting of cells, in the case of replacing items (which is the most common thing the algorithm does). I wanted most efficient way. However, this is a very nice and short solution. Does it work ? Maybe you could improve it, by firstly setting the values that are shared between the "sublist" and the "deltaItems" , and only then add items. But then it becomes as complex as my code, I guess... – android developer Nov 22 '17 at 17:28
  • Yeah, what's the problem with 2x moving values? You can't do better than 1x moving values, and I strongly suspect that doing this in bulk (as per my code) is actually faster than trying to replace the items one-by-one if the sublist and the replacement have similar size. In your attempt to micro-optimize, you have made your own code both buggy and slow (because you incorrectly call subList().clear() multiple times, and because you don't account for the case where deltaList is larger than the cleared sublist). You have also written indexToTakeItemFrom where I'm guessing you mean i. – Tobias Nov 22 '17 at 17:56
  • The book "Effective Java" (written by Joshua Bloch, the author of the Collections API) specifically has sections on the power of the sublist API ("The resulting API has a very high power-to-weight ratio"). I highly recommend this book. There is a reason why the List interface does not have specialized methods for use cases such as yours: Such added API would not "pull its own weight" because it would add little power but have high conceptual weight. – Tobias Nov 22 '17 at 18:01
  • Where do you see in my code multiple calls to "sublist().clear()" ? It's called once at most, while in your case, it's always called once. What you do is shifting items because of removal, and then shifting items back after adding. In my case, it's only shifting once: either for removal or for adding, or not at all in case of just setting items. About the book, are you talking about this: http://www.informit.com/articles/article.aspx?p=31551&seqNum=3 ? If so, I don't see what's wrong with subList here. He actually praises the way it's implemented. Which case do you see here as buggy? – android developer Nov 23 '17 at 00:21
  • Also, it's ok if "deltaList " is larger. The idea is to replace and possible make it larger. It's the indices that you have to be careful of . Not the "deltaList". – android developer Nov 23 '17 at 00:23
0

Do you have a particular reason to be using an ArrayList in this case?

If you're going to be manipulating the List a lot in this fashion, a LinkedList might be more appropriate as it will perform better on insertions and removals elements.

If that's not the case, ArrayList manipulation is certainly an option, however like mentioned by others, arrays (int[]) would be better suited.

Gorazd Rebolj
  • 803
  • 6
  • 10
  • ArrayList is based on an array, so it's used only for being more comfortable (plus it has its own optimizations). LinkedList has its own disadvantages, which includes something we use a lot: access the items in various positions. Sadly I can't use LinkedList because of that. It's an int array only for demonstration. In the real app, it's something else. – android developer Nov 21 '17 at 17:01
  • The fact that ArrayList is backed by an array is precisely the reason I'd be careful about using it. If you're sensitive about the memmory usage, you need to be very much aware of how resizing works when doing something like this. – Gorazd Rebolj Nov 21 '17 at 17:13
  • Just like any other data structure, you have to be careful to add, remove and replace the items in the correct places. – android developer Nov 21 '17 at 17:14
-1

I dont know why you need to do all this in this way ...

ArrayList<Integer> list
list.remove(indexNumber); //remove the index and shift all element
list.add(indexNumber, value); // add a value in indexNumber index and shift all element

Now if you want to remove multiple item, use a loop. If you want multiple items, then also use a loop.

achAmháin
  • 4,176
  • 4
  • 17
  • 40
  • As I wrote to you, this is highly inefficient in the very possible case of adding items not to the end of the list. This will result in at least O(N*M), because each item you add to the list will shift all of the items that are after it. – android developer Nov 21 '17 at 16:55