append 2d array

edited October 2015 in How To...
int [][] GRE = new int[GREsize][GREsize]; // GREsize = 5

at a certain moment in the program GREsize becomes 6 so the array shroud expand to accommodate the new values.

GRE = append(GRE,6,6); // does not work.

Will the suggested code also works with expand() and shorten() ?

Thanks

Answers

  • edited October 2015 Answer ✓

    Append, shorten and expand only work with a 1D array. In your example you have 5 1D arrays, in an array. So you gotta get all of those arrays separately and call those functions.

    for(int[] i : GRE)
    {
       append(i, 6);
    }
    
  • edited October 2015 Answer ✓

    http://forum.Processing.org/two/discussion/8045/how-to-format-code-and-text

    For most cases, when some array's dimension isn't of a fixed length, we're better off replacing it w/ a dynamically-sized List instead: https://Processing.org/reference/ArrayList.html

    For your particular case, an IntList is the most fit: https://Processing.org/reference/IntList.html

    However, when that re-dimension just happens sporadically, we can still decide to keep on using a regular array and rely on append(), expand() and shorten() in order to achieve dynamic length too.

    I've come up w/ expandGrid() as an enhanced expand()'s replacement for 2D arrays: :ar!

    /**
     * Expand2DArray (v3.41)
     * GoToLoop (2015-Oct-17)
     * forum.Processing.org/two/discussion/13048/append-2d-array
     */
    
    int[][] grid = new int[5][5];
    
    void setup() {
      displayGrid(randomFillGrid(grid, 10));
      println();
    
      grid = expand2D(grid, grid.length - 2, grid.length + 3);
      displayGrid(grid);
      exit();
    }
    
    int[][] randomFillGrid(int[][] grid, int maxVal) {
      for (int[] row : grid)  for (int col = row.length; col-- != 0
        ; row[col] = (int) random(maxVal));
      return grid;
    }
    
    static final void displayGrid(int[][] grid) {
      for (int row = 0; row != grid.length; ++row) {
        print("\nRow: #" + row, TAB);
        for (int col : grid[row])  print(col, ' ');
      }
    }
    
    static final int[][] expand2D(int[][] grid, int dim) {
      return expand2D(grid, dim, dim);
    }
    
    static final int[][] expand2D(int[][] grid, int rows, int cols) {
      rows = abs(rows);
      cols = abs(cols);
    
      grid = grid != null? (int[][]) expand(grid, rows) : new int[rows][];
    
      for (int row = 0; row != rows; ++row)
        grid[row] = grid[row] != null? expand(grid[row], cols) : new int[cols];
    
      return grid;
    }
    
  • The 'colouredmirrorball' is definitely an elegant solution but I have no idea how to separate the int [][] array in int [] row's and int [] col's to call the function and then join them again to an int [][] array . Anyway thanks for your help.

  • Answer ✓

    ... but I have no idea how to separate the int[][] array in int[] row's and int[] col's to call the function...

    I dunno why you'd think so since in expandGrid(int[][] grid, int dim) we gotta pass the whole 2D array:
    grid = expandGrid(grid, grid.length + 1);

  • edited October 2015 Answer ✓

    The @colouredmirrorball is definitely an elegant solution...

    • I'm afraid to say it doesn't work! :-SS
    • Function expand() returns a new array w/ the same content but diff. length.
    • So we still need to reassign it back to the variable holding the original array if we don't want it to be a mere clone.
    • That is, append() doesn't change the original passed array.
    • So something like append(i, 6); doesn't have any effect at all w/o assigning it to some variable!
    • After that replacement, the original array is eligible to be garbage-collected.
    • However, in order to reassign, we gotta use the [] operator.
    • Therefore an enhanced for ( : ) loop like for(int[] i : GRE) doesn't work for it either.
    • Also as the last step, we still need to use expand() over the array's outer dimension.
    • Then instantiate a new "row" of "cols" and assign it back to the newly expanded outer index.
  • Thanks for your comment. does grid = expandGrid(grid, grid.length - 1); also shorten the array?

  • Indeed it does. sorry for my hasty reaction. I will implement your code tomorrow. it's midnight now.

  • edited October 2015 Answer ✓

    Does grid = expandGrid(grid, grid.length - 1); also shorten the array?

    Indeed it does! But it's buggy the way it was implemented; since I wasn't thinking about that option! 8-|
    The last outer row index is always replaced by a new int[dim] array! :-&
    In order to fix that, we need an if () to check whether the desired dim is indeed > current length before proceeding w/ the grid[dim - 1] = new int[dim];:

    static final int[][] expandGrid(int[][] grid, int dim) {
      if (grid == null)  return null;
    
      final int len = grid.length;
      if (len == (dim = abs(dim)))  return grid;
    
      for (int row = 0; row != len; grid[row] = expand(grid[row++], dim));
    
      grid = (int[][]) expand(grid, dim);
      if (len < dim)  grid[dim - 1] = new int[dim];
    
      return grid;
    }
    

    Leaving the buggy original implementation here for "historical" reasons: :-j

    static final int[][] expandGrid(int[][] grid, int dim) {
      for (int row = 0; row != grid.length; grid[row] = expand(grid[row++], dim));
      (grid = (int[][]) expand(grid, dim))[dim - 1] = new int[dim];
      return grid;
    }
    
  • edited October 2015

    I'm afraid to say it doesn't work!

    Yup, sorry, typed it on my phone during a boring lecture, without a chance to test it. Though, maybe, this works?

    for(int[] i : GRE)
    {
       i = append(i, 6);
    }
    
  • edited October 2015 Answer ✓

    Unfortunately not! It "violates" these 2 issues I've mentioned before: :-<

    1. However, in order to reassign, we gotta use the [] operator.
    2. Therefore an enhanced for ( : ) loop like for(int[] i : GRE) doesn't work for it either.

    @ i = append(i, 6); you're merely reassigning another array w/ new element 6 to the iterator i.
    In short, just making iterator i point to another array rather than the actual 1 it got from GRE.
    You're not putting the append()'s new array back to the original array GRE's outer dimension at all!

    Actually, we shouldn't reassign iterators ever! Placing a final in it would safe-guard us against it:
    for (final int[] i : GRE) i = append(i, 6); // it won't compile due to iterator's reassignment!

    Try out this mini sample below and see that println(grid[0].length); finally displays 4 only after the regular for ( ; ; ) loop: :>

    int[][] grid = new int[3][3];
    println(grid[0].length); // 3
    
    for (int[] i : grid)  i = append(i, 6);
    println(grid[0].length); // 3
    
    for (int i = 0; i != grid.length; grid[i] = append(grid[i++], 6));
    println(grid[0].length); // 4
    
    exit();
    

    P.S.: Be aware that append() merely adds 1 new element to an array's dimension:
    https://Processing.org/reference/append_.html
    So it can only increment its length 1 by 1.

    We can only specify the exact length we want w/ expand() though: L-)
    https://Processing.org/reference/expand_.html

  • Answer ✓

    I must admit this topic interested me and in particular creating a single function that can resize a 2D array (expand or contract or both) and for both square and oblong arrays. So I came up with this example

    int[][] gre = new int[5][5];
    
    void setup() {
      populateArray(gre);
      printArray(gre);
      gre = resizeArray(gre, 6, 6);
      printArray(gre);
      gre = resizeArray(gre, 3, 5);
      printArray(gre);
    }
    
    void populateArray(int[][] a) {
      for (int i = 0; i < a.length; i++)
        for (int j = 0; j < a[i].length; j++)
          a[i][j] = i*100 + j;
    }
    
    void printArray(int[][] a) {
      println("---------------------------------------------");
      for (int i = 0; i < a.length; i++) {
        for (int j = 0; j < a[i].length; j++)
          print(a[i][j] + "\t");
        println();
      }
      println("---------------------------------------------");
    }
    
    int[][] resizeArray(int[][] a, int size0, int size1) {
      int asize0 = a.length;
      int asize1 = a[0].length;
      // Return original array in new array is illegal size
      // or the same size as the old array
      if (size0 <= 0 || size1 <=0 || (asize0 == size0 && asize1 == size1))
        return a;
      // create new array of requested size
      int [][] b = new int [size0][size1];
      // Need to find the maximum number of elements that can be copied
      int len0 = min(asize0, size0);
      int len1 = min(asize1, size1);
      for (int i = 0; i < len0; i++)
        arrayCopy(a[i], 0, b[i], 0, len1);
      return b;
    }
    

    If you run it you get the following output

    ---------------------------------------------
    0   1   2   3   4   
    100 101 102 103 104 
    200 201 202 203 204 
    300 301 302 303 304 
    400 401 402 403 404 
    ---------------------------------------------
    ---------------------------------------------
    0   1   2   3   4   0   
    100 101 102 103 104 0   
    200 201 202 203 204 0   
    300 301 302 303 304 0   
    400 401 402 403 404 0   
    0   0   0   0   0   0   
    ---------------------------------------------
    ---------------------------------------------
    0   1   2   3   4   
    100 101 102 103 104 
    200 201 202 203 204 
    ---------------------------------------------
    

    It is also possible to expand one dimension and contract the other dimension at the same time.

  • edited October 2015 Answer ✓

    ... and for both square and oblong arrays.

    • My expandGrid() implementation picks up where Processing's expand() had stopped.
    • So I've decided not to start over from zero. Thus no need for arrayCopy(). :P
    • Since the OP only had a square grid 2D array in mind, I didn't bother w/ oblong 1s.
    • However, it's pretty easy to add those by passing both rows (outer) & cols (inner) dimensions to expandGrid() rather than just dim.
    • But to my surprise, I've found another bug: @-)
    • Somehow I was always assuming that the expansion wouldn't be more than 1! b-(
    • So I had to account for that when expanding the rows:
    • Lo & behold the newest & most powerful & flexible expand2D() now: \m/

    static final int[][] expand2D(int[][] grid, int dim) {
      return expand2D(grid, dim, dim);
    }
    
    static final int[][] expand2D(int[][] grid, int rows, int cols) {
      rows = abs(rows);
      cols = abs(cols);
    
      grid = grid != null? (int[][]) expand(grid, rows) : new int[rows][];
    
      for (int row = 0; row != rows; ++row)
        grid[row] = grid[row] != null? expand(grid[row], cols) : new int[cols];
    
      return grid;
    }
    

    P.S.: Another issue I've realized just now:

    • expand2D() should never return the original passed int[][] 2D array.
    • And none of its inner dimensions should be kept even if no resize are needed on any of them.
    • B/c it is assumed expand2D() will always return a new clone() of the passed array.
    • For the cases the caller decides not to assign it back to the original variable but to another 1.
    • Both variables should always point to distinct arrays! L-)
  • Answer ✓

    The OP asks

    Will the suggested code also works with expand() and shorten() ?

    so we need a solution that does both.

  • Thanks a lot! I have now two perfectly working solutions. Unfortunately I am not in a position to make an assessment of the quality of the code. It works and I realize that writing code is not an exact science.

  • edited October 2015

    Unfortunately I am not in a position to make an assessment of the quality of the code.

    • My implementation relies on Processing's expand() function. And furthers it to accept 2D arrays.
    • While @quark's implementation is made from scratch! And directly calls arrayCopy().
    • Which btW is exactly what expand(), shorten() & append() do internally after all.
    • Although I find it shouldn't return the passed array even when the other parameters are wrong.
    • B/c it's assumed to always return a new array for all cases.

    P.S.: Version 3.3 available now: Got rid of final int len = min(grid.length, rows); :>

    P.S.: Another v3.4: Got rid of if (grid == null) return null;.

    Now in case passed parameter grid is null, instead it returns a new int[rows][cols] 2D array w/ specified rows x cols dimensions! \m/

  • Unfortunately I am not in a position to make an assessment of the quality of the code.

    I disagree, although software quality is a huge topic in itself even novices can have an opinion.

    Look at the solutions on offer, which one makes the most 'sense' to you, which one looks 'cleaner', which one does everything you might want, either now or in the future e.g. contract as well as expand, cope with 'oblong' arrays.

    @GoToLoop

    While quark's implementation is made from scratch! And directly calls arrayCopy().

    What is the problem with that? Don't forget arrayCopy is a Processing function the same way expand is so calling either is just as valid.

    Although I find it shouldn't return the passed array even when the other parameters are wrong. B/c it's assumed to always return a new array for all cases.

    It is your assumption that the function should always return a new array and is no more valid than any other assumption. There is no software engineering reason why the the function shouldn't return the unchanged array.

    rows = abs(rows);
    cols = abs(cols);
    I don't agree with this code because it will end up changing the array even when illegal negative values are entered. Invalid data should be reported or ignored it should not be used to modify valid data.

  • edited October 2015

    GoToLoop: While quark's implementation is made from scratch! And directly calls arrayCopy().

    quark: What is the problem with that?

    I guess only you can reply that! B/c I utterly fail to see where have you found any "negativity" about listing characteristics of your implementation! @-)

    Since it's made from scratch, obviously it is faster than mine which relies on Processing's API! ~:>

    Processing's arrayCopy() is merely a wrapper for Java's System.arraycopy() after all:
    http://docs.Oracle.com/javase/8/docs/api/java/lang/System.html#arraycopy-java.lang.Object-int-java.lang.Object-int-int-

    Although Processing's expand() calls that as well. But only after instantiating a new 1D array. >-)

  • I guess only you can reply that! B/c I utterly fail to see where have you found any "negativity" about listing characteristics of your implementation!

    Now I have looked again neither can I oops! :\">

  • edited October 2015

    It is your assumption that the function should always return a new array and is no more valid than any other assumption.

    Now that's where my critique had started, not before! L-)
    And seems like you refuse to accept that behavior is very bug prone! [-(

    I believe you agree that your resizeArray() returns a distinct int[][] array w/ the contents of the passed int[][] array, right?

    However, if we blindly "trust" your implementation, we may end up w/ a nasty, hard-to-find bug: ~X(

    int[][] gre = new int[2][1];
    
    void setup() {
      gre[0][0] = 10;
      gre[1][0] = 20;
      printArray(gre); // 10, 20
    
      int[][] supposedClone = resizeArray(gre, 2, 1); // Bug is right here!
      printArray(supposedClone); // 10, 20
    
      supposedClone[0][0] = -50;
      printArray(supposedClone); // -50, 20
      printArray(gre); // -50, 20  WtF?! Shoulda still been 10, 20!!!
    
      // Indeed both gre & supposedClone points to the same int[][]:
      assert supposedClone != gre; // They're not distinct at all!
      exit();
    }
    
    void printArray(int[][] a) {
      println("---------------------------------------------");
      for (int i = 0; i < a.length; i++) {
        for (int j = 0; j < a[i].length; j++)  print(a[i][j] + "\t");
        println();
      }
      println("---------------------------------------------");
    }
    
    int[][] resizeArray(int[][] a, int size0, int size1) {
      int asize0 = a.length;
      int asize1 = a[0].length;
    
      // Undesirable bug-prone behavior:
      if (size0 <= 0 || size1 <= 0 || asize0 == size0 && asize1 == size1)  return a;
    
      int[][] b = new int[size0][size1];
    
      int len0 = min(asize0, size0);
      int len1 = min(asize1, size1);
    
      for (int i = 0; i < len0; i++)  arrayCopy(a[i], 0, b[i], 0, len1);
    
      return b;
    }
    
  • edited October 2015

    Looks like we both made some assumptions based on our own personal interpretation of the OPs requirements.

    My premises were
    1) A copy of the original array was not required because the resized array would be used in instead.
    2) A new array would only be needed if the array size actually changed.
    3) Requests to resize the array using dimension sizes <=0 would be ignored and the original array would continue to be used.
    4) The resize operation could be used to enlarge or reduce the array size
    5) As much or the original data should be retained.

    The 'bug' you reported in the last post was in fact a logical error because your premises were not the same as mine. My fault really I should have documented the resize function to say what it did.

    So for the sake of completeness and to clarify my code I have documented the sketch and the function clearly stating the programs functionality. Note that I have NOT changed the resize function code rather I have documented it.

    /**
    Program to demonstrate the resizing of an array. It 
    is based on the following premises
    1) A copy of the original array was not required 
    because the resized array would be used in instead.
    2) A new array would only be needed if the array 
    size actually changed.
    3) Requests to resize the array using dimension 
    sizes <=0 would be ignored and the original array 
    would continue to be used.
    4) The resize operation could be used to enlarge 
    or reduce the array size    
    5) As much or the original data should be retained.
    */
    int[][] gre = new int[5][5], gre2;
    boolean resized;
    
    void setup() {
      populateArray(gre);
      println("Original array");
      printArray(gre);
    
      println("Expand array");
      gre2 = resizeArray(gre, 6, 6);
      resized = (gre != gre2);
      gre = gre2;
      if (resized)
        printArray(gre2);
      else
        println("Original array returned");
    
      println("New array size = old array size");
      gre2 = resizeArray(gre, 6, 6);
      gre = gre2;
      resized = (gre != gre2);
      if (resized)
        printArray(gre2);
      else
        println("Original array returned");
    
      println("New array size with invalid sizes");
      gre2 = resizeArray(gre, 6, 6);
      gre = gre2;
      resized = (gre != gre2);
      if (resized)
        printArray(gre2);
      else
        println("Original array returned");
    }
    
    void populateArray(int[][] a) {
      for (int i = 0; i < a.length; i++)
        for (int j = 0; j < a[i].length; j++)
          a[i][j] = i*100 + j;
    }
    
    void printArray(int[][] a) {
      println("---------------------------------------------");
      for (int i = 0; i < a.length; i++) {
        for (int j = 0; j < a[i].length; j++)
          print(a[i][j] + "\t");
        println();
      }
      println("---------------------------------------------");
    }
    
    /**
     This method will return an array[size0][size1] provided that
     1) size0 and size1 are both greater than 0, and
     2) the new array size is different from the size of the initial array
     If these conditions are not met then the original array is returned, 
     otherwise a new 2D array of the requested size is created and as many 
     elements as possible are copied from the initial array. Any new elements
     will be 0 (zero).
     @ parameter a 2D array where both dimensions are >=1
     @ parameter size0 first dimension must be >=1
     @ parameter size1 second dimension must be >=1
     */
    
    int[][] resizeArray(int[][] a, int size0, int size1) {
      int asize0 = a.length;
      int asize1 = a[0].length;
      // Return original array in new array is illegal size
      // or the same size as the old array
      if (size0 <= 0 || size1 <=0 || (asize0 == size0 && asize1 == size1))
        return a;
      // create new array of requested size
      int [][] b = new int [size0][size1];
      // Need to find the maximum number of elements that can be copied
      int len0 = min(asize0, size0);
      int len1 = min(asize1, size1);
      for (int i = 0; i < len0; i++)
        arrayCopy(a[i], 0, b[i], 0, len1);
      return b;
    }
    

    The output of this sketch is

    Original array
    ---------------------------------------------
    0   1   2   3   4   
    100 101 102 103 104 
    200 201 202 203 204 
    300 301 302 303 304 
    400 401 402 403 404 
    ---------------------------------------------
    Expand array
    ---------------------------------------------
    0   1   2   3   4   0   
    100 101 102 103 104 0   
    200 201 202 203 204 0   
    300 301 302 303 304 0   
    400 401 402 403 404 0   
    0   0   0   0   0   0   
    ---------------------------------------------
    New array size = old array size
    Original array returned
    New array size with invalid sizes
    Original array returned
    
  • edited October 2015
    • Indeed, my 1st implementations only did the minimum requested.
    • But as I started to refine it time and time again, I tried to make it "distribution" ready. :P
    • Since your implementation is well documented now, at least some1 trying to debug a code relying on it has a better chance to found out and fix it.
    • Although as I said, variables supposed to point to distinct objects ending up pointing to same objects is a very nasty & difficult to find type of bug when dealing w/ big code base! :-&
    • Thus it's rather much better for an API to be consistent in what it does and returns. :-B
  • Thus it's rather much better for an API to be consistent in what it does and returns.

    true - that's why we should document code - to explain the API :)

  • edited October 2015

    Take notice that by merely deleting:
    if (size0 <= 0 || size1 <= 0 || asize0 == size0 && asize1 == size1) return a;

    resizeArray() would pass my edgy case test above just like expand2D()! *-:)

    Besides simplifying a lot its API documentation. :-bd

  • if (size0 <= 0 || size1 <= 0) return a;

    This would then pass your test case because It would mean that it would duplicate the array even if it is the same size. I could live with that. :)

    It is still necessary to handle requests for invalid dimension sizes i.e. <= 0

    Your solution of taking the absolute values of the sizes is not something I would do for the reasons I gave earlier.

    The only acceptable options would be to have my function return either null or the original array. The disadvantage of null is that the user would have to test the returned value every time this method is called. Returning the original array still allows the user to test if the array has changed but it is now optional.

  • edited October 2015

    It is still necessary to handle requests for invalid dimension sizes i.e. <= 0

    Java arrays already check for that. Following statement would fail outright: :>
    int [][] b = new int [size0][size1];

    Your solution of taking the absolute values of the sizes is not something I would do for the reasons I gave earlier.

    Examples where programmers take a more strict approach or tries to reasonably guess/assume what the passed arguments meant are plenty to find.

    Even in Processing's API we can find the usage of abs() to deal w/ nonsensical negative values:
    ellipse(width>>1, height>>1, -width*.75, -height*.75);

    Every1 knows dimensions can't be negative the same way that array indices can't either! >-)

    Also, pixels can't have fractional values. But round() is silently used on "drawing" APIs. :(|)

    Even in our own browsers, we don't type in http:// or even www. anymore.
    But they're silently & internally "corrected" nonetheless. :P

    The disadvantage of null is that the user would have to test the returned value every time this method is called.

    My own expand2D() can't return a null value in any circumstance.
    In summary, it's effectively a @NotNull return method. \m/

    However, your resizeArray() has a case where it indeed returns null:
    It happens when we pass a null int[][] argument! 3:-O

    In summary resizeArray() has 3 types of return:

    1. Expected distinct new int[][] array.
    2. Non-expected same int[][] passed array.
    3. And non-expected null.

    Among those 3 above, #2 is the nastier to hunt down when debugging.
    That's what I've been trying to warn since the beginning!

    The 3rd null case is the most common in Java and many other languages.
    Any @Nullable return method (absolute majority btW) demands a null check in most cases anyways.

    Therefore I don't see why checking for null would be problematic since it's expected in many places after all.

    Returning the original array still allows the user to test if the array has changed but it is now optional.

    I disagree! Majority of folks wouldn't know and/or remember that a method which is supposed to return a distinct object can in some "rare" (invalid arguments) cases return the same object!

    It's not in the same league as testing for null. Which is a very common case in programming! ~O)

    Just take a look at my test case. If I didn't comment "bug is here", that statement would look completely "innocent" for any1 debugging the code: >:)

    int[][] supposedClone = resizeArray(gre, 2, 1); // I'm innocent! Bug isn't here!!!

  • Every1 knows dimensions can't be negative the same way that array indices can't either!

    It is likely that the new array sizes are calculated at runtime rather than hardcoded in the source code. If the sizes are <=0 then there is a problem. We could handle that problem ourselves so the program continues rather than allow the JRE to bring our program to a halt, both are valid choices provided we make the user aware of the one we chose.

    If the calculated size is [2][-3] then to assume that the user is happy, and that the program will function as expected with an array [2][3] is presumptuous.

    Examples where programmers take a more strict approach or tries to reasonably guess/assume what the passed arguments meant are plenty to find.

    The key word there is reasonably I don't think converting [2][-3] to [2][3] is reasonable because there is nothing to support that decision, there is no logic to it. In fact it is purely an arbitrary decision on your part. If programmers do apply constraints to the method parameters then they should document it.

    However, your resizeArray() has a case where it indeed returns null: It happens when we pass a null int[][] argument!

    Not true the function would fail at the first line because it doesn't test for null parameters. But if the user did pass null and the program crashed it is NOT a software bug it is because the user did not honour the contract specified in the function documentation. It quite clearly states that the method expects an array with minimum size [1][1]

    I disagree! Majority of folks wouldn't know and/or remember that a method which is supposed to return a distinct object can in some "rare" (invalid arguments) cases return the same object!

    I wouldn't expect them to remember any more than I remember all the Java or Processing methods I have ever used that's why we have Javadocs and references. I would look at the API documents to find out what parameters and limits apply. Again wrong it quite clearly states that the method expects an array with minimum size.

    Two more points on this bit
    1) It is quite clear in the function documents that the method does not always return a new array.
    2) If the dimensions are calculated then invalid sizes might not be as rare as you expect. It depends on the code that calls this method.

    I think we will have to agree to disagree on this. :)

Sign In or Register to comment.