Why isn't initializing ArrayList size working?

edited October 2017 in Questions about Code

Hi there.

According to the java docs I can declare an ArrayList with an initial size: public ArrayList(int initialCapacity)

Yet when I execute this:

ArrayList<PVector> tpv = new ArrayList<PVector>(2);
println("1: init tpv size: ",tpv.size());
tpv.add(new PVector());
tpv.add(new PVector());
println("2: init tpv size: ",tpv.size());

I get the following output:

1: init tpv size:  0
2: init tpv size:  2

This is a simplified case of my ultimate goal which is trying to initialize tpv with the size of another PVector array: ArrayList<PVector> tpv = new ArrayList<PVector>(pva.size());

Am I doing something wrong? Am I misunderstanding the javadoc? Can I declare tpv with an initial size?

Answers

  • edited October 2017
    • Capacity isn't the same as the actual size() of a container.
    • Java's ArrayList container uses an array internally.
    • Its capacity refers to the current length of that internal array.
    • Not the number of elements currently stored in it, which we can grab via its size() method.
    • Current size() is always less or equal to the current capacity.
    • Once size() exceeds the capacity, the container automatically creates another internal array w/ a bigger capacity length.
  • Ok. Wrapping my head around that. (It's been a long time since I've dealt with a strictly typed language.)

    So I declared a capacity and nothing more. It's just an empty container of a given size with nothing in it. Which is why I get an IndexOutOfBoundsException when I attempt to put an element in the array at index 0, even though I've initialized it with a size of 2: the slot at zero itself hasn't been initialized to contain an element yet. Is that it?

    Ok, so to extend the question: how do I initialize tpv with PVector elements? In a loop of .add() methods? Is there not a way java can automatically assume that I want my array initialized with PVector elements, instead of just creating blank slots that I need to fill by hand?

  • edited October 2017 Answer ✓

    @Aleksi -- re:

    According to the java docs I can declare an ArrayList with an initial size:

    I could be wrong, but I suspect you are not approaching this problem in the right way.

    An array ([]) is given a fixed size on declaration, and you manage that. However, part of the entire point of an ArrayList is that its capacity is almost always an internal implementation detail -- an ArrayList automatically makes itself bigger as you add things, so you usually don't and shouldn't care what it is doing internally.

    Just add stuff. It is whatever length it is based on stuff you add. While it is technically possible to reference the capacity, if you are doing so then you are (usually) using it wrong.

    I want my array initialized with PVector elements

    Don't bother with capacity. Just add as many things as you want. If you want brevity of initialization, you can do it in a one-line for loop:

    int pvCount = 100;
    ArrayList<PVector> tpv = new ArrayList<PVector>();
    for(int i=0; i<pvCount; i++){ tpv.add(new PVector()); }
    println("tpv size: ", tpv.size()); // "100"
    
    // wait, I forgot one!
    tpv.add(new PVector());
    println("tpv size: ", tpv.size()); // "101"
    

    ...of course, if you don't yet know what goes in your PVectors then you might be adding them at the wrong time. It is kind of like counting out exactly as many empty boxes as you need to move, loading the empty boxes into your moving van, and then putting your stuff into each box that is already in the van. Why would you do that? Just put your stuff in boxes. Every time you fill a box, put the full box in the van.

    On the other hand, if you want a collection of n points that each have a default initialization (0,0) -- like you plan to random-walk a cloud of dots from the center of the screen -- then adding a lot of new PVectors with no arguments makes sense.

  • edited October 2017

    The point of setting the capacity is to avoid unnecessary resizing of the underlying array. It starts with a capacity of 10 and when you add the 8th element to the list (75% capacity exceeded) it doubles the capacity. It does this by

    1) Creating a new array (double the existing size)
    2) Copies the elements from the existing array to the new array
    3) Sets the reference of the underlying array to the new array
    4) Dispose of the old array (garbage collected)

    So it you want to initialise the list to hold 100 elements (and you add them 1 by 1) it will be resized 4 times
    10 > 20
    20 > 40
    40 > 80
    80 > 160

    To avoid this simply set the capacity to 200 when creating the list.

  • edited October 2017

    Thank you both for your answers.

    My purpose in initializing the capacity of the array was to repeatedly loop through the known number of elements of other arrays to store and process ever-changing results. I know, going into the loop, that the size of the source arrays will never change.

    Coming from untyped languages that don't need to first "add" elements that are then emended is just one more "gotcha" that I'm hitting as I get my legs working in java.

  • Hi quark, thank you for the (cross-posted) explanation.

  • If you have a collection (List, Set, Map) of PVector objects called c then you can create a new ArrayList with these PVectors then use

    ArrayList<PVector> tpv = new ArrayList<PVector>(c);

    If you have an array of PVector objects called a then you can create a new ArrayList with these PVectors then use

    ArrayList<PVector> tpv = new ArrayList<PVector>(Arrays.asList(a));

  • edited October 2017

    It starts with a capacity of 10 and when you add the 8th element to the list (75% capacity exceeded)...

    @quark, that 75% threshold is the default value for the HashMap, not the ArrayList! :-\"

    AFAIK, even though Java doesn't specify the growth value for it, the underlying array is doubly-resized exactly when we attempt to add() an element when the size() is already equal to the internal capacity. Not completely sure though. It's Java flavor dependent. :P

  • edited October 2017

    @quark

    Wouldn't tpv just contain pointers to the same memory locations of each element in c? Is there a way to copy each source PVector element to tpv in a declaration? The values in tpv will change; the source ones cannot.

  • ArrayList<PVector> tpv = new ArrayList<PVector>(Arrays.asList(a));

    @quark, unless we import the Arrays utility class 1st, your code line's gonna fail! #-o

    Alternatively, use its whole package name: *-:)
    ArrayList<PVector> vecs = new ArrayList<PVector>(java.util.Arrays.asList(a));

  • edited October 2017

    Coming from untyped languages that don't need to first "add" elements...

    @Aleksi, I don't think the add() got anything to do w/ typed or untyped languages.

    If by untyped you've meant JS, it's just its array implementation is much more flexible (and slower) than Java's.

    The only Java container which comes a little closer to JS' array is the LinkedList:
    http://Docs.Oracle.com/javase/8/docs/api/java/util/LinkedList.html

  • Wouldn't tpv just contain pointers to the same memory locations of each element in c?

    What do you mean by c. I don't see anything called c in your posted example. :-/

  • edited October 2017

    Is there a way to copy each source PVector element to tpv in a declaration?

    • Generally in Java we don't add anything to a non-array container at the moment of its instantiation.
    • Only after its creation we can add() elements to it via its reference variable(s).
    • It seems like you don't wanna add the same object to the container.
    • In this case, you have to clone it before adding it.
    • For PVector objects, we can use its method get() to get a clone of it. :-bd
  • edited October 2017

    @GoToLoop

    What I meant was that in java I can't simply say something like

    v = [PVector([1,2,3])]

    like I've grown accustomed to. No, in java I need to jump through some ugly hoops first:

    ArrayList<PVector>v = new ArrayList<PVector>(); v.add(new PVector()); // initialize first v.set(0, new PVector(1,2,3)); // reset later

    No matter how I look at that, it seems kind of crazy to me. 8-}

    But every language has its own quirks and nomenclature, and as I learn java I come to understand how and why it does what it does.

    (And if by JS you meant JavaScript, other than a drive-by familiarity, I've never had the (dis)pleasure of using it.)

  • edited October 2017

    @GoToLoop: > What do you mean by c. I don't see anything called c in your posted example.

    I was referring to @quarks's example; ArrayList<PVector> tpv = new ArrayList<PVector>(c);

    Thank you again for your replies and advice.

  • edited October 2017

    And if by JS you meant JavaScript, I've never had the (dis)pleasure of using it.

    So my other guess is Python. Guess what, Processing's IDE (PDE) got Python Mode too! :-$

    What I meant was that in Java I can't simply say something like: vecs = [ PVector([1, 2, 3]) ]

    If you just need a regular Java array, you can do the following: ~O)

    PVector[] vecs = { new PVector(1, 2, 3) };
    println(vecs);
    exit();
    

    BtW, no need to pass a List to PVector like you did in Python. :-\"
    The following is pretty enough: vecs = [ PVector(1, 2, 3) ]

  • edited October 2017

    Yes, python indeed. ;) Except if I'm going to learn processing, I'd rather learn it in it's native environment, rather than work with a bastardization of python like jython. (Plus I get to learn another language!)

    As for the regular java array, thanks for the info. However, in the method I'm developing I don't know the size of the PVector arrays coming in that tpv needs to copy, hence my initial question.

  • edited October 2017

    I don't know the size of the PVector arrays coming in that tpv needs to copy,

    Are you sure of it? Where those PVector objects come from?

    Indeed, if you're not sure of the size, or the size keeps changing later, vanilla arrays are very awkward for such, and you're gonna need another container type, like the aforementioned ArrayList. 8-|

  • edited October 2017

    Keep in mind that in Java only actual regular vanilla arrays are allowed to use the brackets [] syntax.

    Any other containers have to rely on methods for the same purpose. ~:>

  • edited October 2017

    Yes, I'm sure of it. That's why I'm using the ArrayList. ;)

    Oh, the array of PVectors are coming from a csv that I parse. Some fields in a row can have an arbitrary number of vectors. The method I'm developing processes the arrays of PVectors using tpv as a mutable copy of the original arrays. That's why it would be convenient to instantiate tpv as a copy of a source array.

  • ArrayList<PVector>v = new ArrayList<PVector>();
    v.add(new PVector()); // initialize first
    v.set(0, new PVector(1,2,3)); // reset later
    

    why not

    // define the list (at top of code)
    ArrayList<PVector>v = new ArrayList<PVector>();
    // when reading the data
    v.add(new PVector(1, 2, 3));
    

    or is there a reason you're adding specifically at 0? java dequeues let you push() and pop() if that's what you want:

    https://docs.oracle.com/javase/7/docs/api/java/util/ArrayDeque.html

  • edited October 2017

    Hi koogs.

    I have a nested loop further in the method. The inner loop does a counter iteration over the source arrays, using tpv to process the source array values without changing them.

    Essentially it looks like this:

    ArrayList<PVector>tpv = new ArrayList<PVector>();    
    for (int cntOuter=0; cntOuter<whatever; cntOuter++){
        for (int cntInner=0; cntInner<sourceArray.size(); cntInner++){
          // stuff
          tpv.set(cntInner, someMethod(sourceArray.get(cntInner), ...parms...));
          // stuff
        }
      }
    

    I don't know what the sourceArray PVector values are until I enter the loop.

    The example you've quoted was a short-hand illustration of what I need to do: create a placeholder element before the loop; set the element inside the loop.

  • edited October 2017

    I appreciate everyone's insights. Thank you.

    I ended up taking the advice of @jeremydouglass with the following code:

    ArrayList<PVector> tpv = new ArrayList<PVector>();
    for(int i=0; i<endpts0.size(); i++){ tpv.add(new PVector()); }
    

    It's not what I would call elegant; but, with my currently limited knowledge of java, this seems like a perfectly adequate solution for now -- because things are working.

  • edited October 2017

    Oh, the array of PVectors are coming from a csv that I parse. Some fields in a row can have an arbitrary number of vectors.

    Can you post a sample of that ".csv" file?
    If more folks posted their external files at the beginning, solutions would arrive faster. I-)

  • edited October 2017

    I fail to see how posting the .csv file is germaine to the question I posed ;;), but here's the relevant excerpt:

    endptsFrom  endptsTo
    tCenter;tCenter pW;pE
    pW;pE   tCenter;tCenter
    tCenter;tCenter pN;pS
    (pS.x, pW.y, -5)  (tWidth.x, -tHalf.y)
    pN;pS   tCenter;tCenter
    

    Entries are tab separated (rather than comma). There are over a score of quasi-enums that serve as common vectors. Vectors can be entered as normal vectors (ie: (x,y,z)), as quasi-enums (ie: pW;vNW), or any combination thereof (ie: (pS.x, tHalf.y, -5)). Multiple vectors in a field are delimited by semi-colons.

    I have a method that parses the entries, converts them to their proper values, and stores them in the relevant ArrayList.

  • I fail to see how posting the .csv file is germaine to the question I posed

    code is easier to fix if we have working examples, rather than just descriptions of fragments of the problem.

  • edited October 2017

    Understood, and I agree. But the contents and parsing of the csv don't have anything to do with the question about initializing a new ArrayList with a copy of another ArrayList's values... does it? I thought rather than muddy the waters with irrelevant information I'd just focus on the question at hand.

  • @Aleksi -- I believe koogs is recommending, in general, to start with an MCVE on the forums to get the best help.

  • Thank you, @jeremydouglass. I will remember that in the future.

  • edited October 2017

    I fail to see how posting the .csv file is germaine...
    Entries are tab separated (rather than comma).

    Then your file is ".tsv" and not ".csv", right? ;;)

    But the contents and parsing of the csv don't have anything to do with the question about initializing a new ArrayList with a copy of another ArrayList's values... does it?

    You're right on that! But posting enough info early on helps us see what your actual end goal is.
    Before posting your file, your questions weren't making much sense. At least for moi. ~:>

    I don't know the size of the PVector array coming in that tpv needs to copy, ...

    That's the main reason I've asked you to post your file. L-)
    You kept stating that you couldn't use a vanilla array b/c you apparently was unable to determine the size for the array.

    That didn't make sense to me, b/c every time I've dealt w/ ".csv" & ".tsv" files, I was always able to determine the size before creating the array for storing those data!
    And even your quasi-enum ".tsv" file hasn't changed that in the least! $-)
    In short, you don't need a List, just a vanilla Java array! \m/

    In order to demo that, I've coded a sketch which tries to somehow emulate yours.
    At least what I could guess. What I didn't, I've just made it up! :ar!

    For example, I dunno what PVector a "tCenter", "tHalf" or "tWidth" would represent. :-/
    So I've just arbitrarily created a Map container, which maps those quasi-enum strings from your ".tsv" to a specific PVector. :-j

    Apart from that Map, notice that everything else is just regular Java arrays. ~O)

  • edited October 2017 Answer ✓

    Anyways, here's my made-up sketch, attempting to guess how yours works: 3:-O

    vectors.tsv:

    endptsFrom  endptsTo
    tCenter;tCenter pW;pE
    pW;pE   tCenter;tCenter
    tCenter;tCenter pN;pS
    (pS.x, pW.y, -5)    (tWidth.x, -tHalf.y)
    pN;pS   tCenter;tCenter
    

    QuasiPVecParser.pde:

    /**
     * QuasiPVecParser (v1.0.1)
     * GoToLoop (2017-Oct-17)
     *
     * Forum.Processing.org/two/discussion/24562/
     * why-isn-t-initializing-arraylist-size-working#Item_31
     */
    
    import java.util.Arrays;
    import java.util.Map;
    
    static final String FILENAME = "vectors", EXT = ".tsv";
    
    PVecEndPointsPair[] vecPairs;
    Table t;
    
    void setup() {
      size(400, 300);
      smooth(3);
      noLoop();
      frameRate(60.0);
      strokeWeight(2.5);
    
      getSurface().setResizable(true);
    
      t = loadTable(FILENAME + EXT, "header");
      keyPressed();
      printArray(vecPairs);
    }
    
    void draw() {
      clear();
    
      for (final PVecEndPointsPair pair : vecPairs) {
        final PVector[] begins = pair.from, ends = pair.to;
        final int len = min(begins.length, ends.length);
    
        for (int i = 0; i < len; ++i) {
          final PVector from = begins[i], to = ends[i];
          stroke((color) random(#000000));
          line(from.x, from.y, to.x, to.y);
        }
      }
    }
    
    void keyPressed() {
      vecPairs = createPVecCoordPairsFromTable(t);
      redraw = true;
    }
    
    void mousePressed() {
      keyPressed();
    }
    
    PVecEndPointsPair[] createPVecCoordPairsFromTable(final Table t) {
      final int rows = t.getRowCount();
      final PVecEndPointsPair[] coords = new PVecEndPointsPair[rows];
      final PVecParser parser = new PVecParser();
    
      for (int i = 0; i < rows; ++i) {
        final TableRow tr = t.getRow(i);
        final PVector[] from = parser.parseVecs(tr.getString(0));
        final PVector[] to = parser.parseVecs(tr.getString(1));
    
        coords[i] = new PVecEndPointsPair(from, to);
      }
    
      return coords;
    }
    
    class PVecEndPointsPair {
      final PVector[] from, to;
    
      PVecEndPointsPair(final PVector[] begins, final PVector[] ends) {
        from = begins;
        to = ends;
      }
    
      @ Override String toString() {
        return "\nFrom: " + Arrays.toString(from)
          + "\nTo:   " + Arrays.toString(to);
      }
    }
    
    class PVecParser {
      static final String TOKENS = ",()" + WHITESPACE;
    
      final PVector VEC_NULL = new PVector(Float.NaN, Float.NaN, Float.NaN);
    
      final PVector[] coords = {
        new PVector(width>>1, height>>1), // tCenter
        new PVector(width>>2, height>>2), // tHalf
        new PVector(width, height>>2), // tWidth
        new PVector(width>>2, height), // tHeight
        new PVector(width>>1, 0), // pN
        new PVector(width>>1, height), // pS
        new PVector(0, height>>1), // pW
        new PVector(width, height>>1), // pE
        new PVector(0, 0), // vNW
        new PVector(width, 0), // vNE
        new PVector(0, height), // vSW
        new PVector(width, height) // vSE
      };
    
      final String[] QUASI_ENUMS = {
        "tCenter", "tHalf", "tWidth", "tHeight", 
        "pN", "pS", "pW", "pE", 
        "vNW", "vNE", "vSW", "vSE"
      };
    
      final Map<String, PVector> vecEnums =
        new HashMap<String, PVector>(1 + QUASI_ENUMS.length, 1.0);
    
      PVecParser() {
        int idx = 0;
        for (final String q : QUASI_ENUMS)  vecEnums.put(q, coords[idx++]);
      }
    
      PVector[] parseVecs(final String s) {
        final String[] splits = split(s, ';');
        final int len = splits.length;
        final PVector[] vecs = new PVector[len];
    
        for (int i = 0; i < len; ++i) {
          final String[] tokens = splitTokens(splits[i], TOKENS);
          final int tokLen = tokens.length;
    
          if (tokLen == 1) {
            vecs[i] = vecEnums.getOrDefault(tokens[0], VEC_NULL).get();
            continue;
          }
    
          final float[] xyz = new float[tokLen];
          for (int j = 0; j < tokLen; xyz[j] = parseCoord(tokens[j++]));
    
          vecs[i] = new PVector().set(xyz);
        }
    
        return vecs;
      }
    
      float parseCoord(String s) {
        float f = float(s);
        if (f == f)  return f;
    
        final float neg = s.charAt(0) == '-'? -1.0 : 1.0;
        if (neg < 0.0)  s = s.substring(1);
    
        final String[] tokens = split(s, '.');
        final PVector vec = vecEnums.getOrDefault(tokens[0], VEC_NULL);
    
        switch (tokens[1].charAt(0)) {
        case 'x':
          return vec.x * neg;
        case 'y':
          return vec.y * neg;
        case 'z':
          return vec.z * neg;
        }
    
        return f * neg;
      }
    }
    
  • edited October 2017

    @GoToLoop... Either you have a great deal of free time, a zealous commitment to helping us out, a delight in solving problems, or some other urge driving you... but whatever it is I thank you for going that extra mile. =D>

    Even though my code works, still being new at java it's not what I would call elegant, or even java-esque, so much as clumsy brute force. But I'm just starting to get off my training wheels with java, so I'm ready to move to the next level. Your example is timely and I will study it.

    While I followed the same general approach (eg: using a HashMap for my quasi-enums; switch statements for parsing the endpts fields; etc.), your code, working with Java, is far more elegant than my lumbering and bloated code. I also see some things I haven't come across yet, like @ Override String toString....

    I was going to ask what the advantage is of a using a vanilla array over an ArrayList. The link you provided suggests a performance difference, but are there other reasons for using a vanilla array over an ArrayList?

    Thank you again for your example.

  • Answer ✓

    The reason I've posted the link above was more about the capacity subject you were asking about 1st. :\">

    About which 1 is faster, a vanilla array or an ArrayList, for access time, raw arrays beat down any List.

    An ArrayList is used in place of vanilla arrays when we need to keep changing its length.

    B/c I was able to pre-determine the length of all of my arrays, and they didn't change later on, I didn't pick ArrayList containers, and simply used 100% vanilla arrays in my code above. \m/

  • edited October 2017

    @Override String toString() {}

    When we println() an object in Java, by default it displays garbage. :-&

    Unfortunately we need to @Override Object::toString() in an object's class in order to have any meaningful info spit out from our objects: :-<

    1. http://Docs.Oracle.com/javase/8/docs/api/java/lang/Object.html#toString--
    2. http://Docs.Oracle.com/javase/8/docs/api/java/lang/Override.html
Sign In or Register to comment.