0

I am trying to use an ArrayList to store and retrieve items by an index value. My code is similar to this:

ArrayList<Object> items = new ArrayList<>();

public void store (int index, Object item)
{
    while(items.size() < index) items.add(null);
    items.set(index, item);
}

The loop seems ugly and I would like to use items.setMinimumSize(index + 1) but it does not exist. I could use items.addAll(Arrays.asList(new Object[index - items.size()])) but that allocates memory and seems overly complex as a way to just make an array bigger. Most of the time the loop will iterate zero times, so I prefer simplicity over speed.

The index values probably won't exceed 200, so I could use:

Object[] items = new Object[200];

public void store (int index, Object item)
{
     items[index] = item;
}

but this would break if it ever needs over 200 values.

Are these really my only options? It seems that I am forced into something more complex than it should be.

  • 2
    I think you just call `setCapacity()`. Edit: nope, `ensureCapacity()`. Array lists increase in size automatically so normally you wouldn't call this, just add elements and let its size grow on its own. – markspace Sep 23 '20 at 14:19
  • You can create a list of N size like this : `new ArrayList<>(n)`. Where N is should be < Integer.MAX – NewUser Sep 23 '20 at 14:22
  • Why are you adding null items? An ArrayList will automatically increase its backing store as necessary. You could also use the Constructor that sets the intialCapacity. – NomadMaker Sep 23 '20 at 14:22
  • Your code makes it seem as though you need a sparse indexable list. [This](https://stackoverflow.com/q/32150495/1707353) might be useful. – Jeff Holt Sep 23 '20 at 14:25
  • @NewUser no. That creates an empty arraylist. try it: new ArrayList<>(100).isEmpty() returns true. That creates an arraylist that has _a capacity_ for 100 items initially. It doesn't actually fill these slots. – rzwitserloot Sep 23 '20 at 14:29

3 Answers3

3

I would consider using a Map instead of a List construct. Why not this :

//depending on your requirements, you might want to use another Map
//implementation, just read through the docs...
Map<Integer, Object> items = new HashMap<>();

public void store (int index, Object item)
{
    items.put(index, item);
}

This way you can avoid that senseless for loop.

Amir Afghani
  • 35,568
  • 16
  • 81
  • 120
0

The problem is, what you want isn't really an arraylist. It feels like what you want is a Map<Integer, T> instead of an ArrayList<T>, as you clearly want to map an index to a value; you want this operation:

Add a value to this list such that list.get(250) returns it.

And that is possible with arraylist, but not really what it is for, and when you use things in a way that wasn't intended, what usually ends up happening is that you write a bunch of weird code and go: "really? Is this right?" - and so it is here.

There's nothing particularly wrong with doing what you're doing, given that you said the indices aren't going to go much beyond 200, but, generally, I advise not creating semantic noise by using things for what they aren't. If you must, create an entirely new type that encapsulates exactly this notion of a sparse list. If you must, implement it by using an arraylist under the hood, that'd be fine.

Alternatively, use something that already exists.

a map

From the core library, why not.. new TreeMap<Integer, T>()? treemaps keep themselves sorted (so if you loop through its keys, you get them in order; if you add 5, then 200, then 20, you get the keys in order '5, 20, 200' as you'd expect. The performance is O(1) or O(log n), but with a higher runup (this is extremely unlikely to matter one iota if you have a collection of ~200 items in it. Wake me up when you add a million, then performance might be even measurable, let alone noticable) - and you can toss a 'key' of a few million at it if you want, no problem. The 'worst case scenario' is far better here, you basically cannot cause this to be a problem, whereas with a sparse, array-backed list, if I tossed an index of 3 billion at it, you would then have a few GB worth of blank memory allocated; you'd definitely notice that!

A sparse list

java itself doesn't have sparse lists, but other libraries do. Search the web for something you like, add it as a dependency, and keep on going.

rzwitserloot
  • 44,252
  • 4
  • 27
  • 37
0

The loop seems ugly and I would like to use items.setMinimumSize(index + 1) but it does not exist.

A List contains a sequence of references, without gaps (but possibly with null elements). Indexes into a list correlate with that sequence, and the list size is defined as the number of elements it contains. You cannot manipulate elements with indexes greater than or equal to the list's current size.

The size is not to be confused with the capacity of an ArrayList, which is the number of elements it presently is able to accommodate without acquiring additional storage. To a first approximation, you can and should ignore ArrayList capacity. Working with that makes your code specific to ArrayList, rather than general to Lists, and it's mostly an issue of optimization.

Thus, if you want to increase the size of an ArrayList (or most other kinds of List) so as to ensure that a certain index is valid for it, then the only alternative is to add elements to make it at least that large. But you don't need to add them one at a time in a loop. I actually like your

items.addAll(Arrays.asList(new Object[index - items.size()])) 

, but you need one more element. The size needs to be at least index + 1 in order to set() the element at index index.

Alternatively, you could use

items.addAll(Collections.nCopies(1 + index - items.size(), null));

That might even be cheaper, too.

John Bollinger
  • 121,924
  • 8
  • 64
  • 118