0

I have a 2D Array and I want to insert a number of rows in the middle of the array at some row number.

The Array already has existing information, so ReDim Preserve doesn't quite work if i wanted to simply place the new information in the middle.

Any ideas?

M B
  • 67
  • 9

3 Answers3

1

Assuming two 1-based 2-dim datafield inputs, I demonstrate an alternative to @FaneDuru's valid approach using a solitary combo list creation as data container and allowing a simple item insertion via the now disponible .AddItem method.

Note that of course this approach can be refined in many directions, e.g. by allowing different index bases for input arrays. - Methodical hint: Assigning the zero-based combo's .Column property (i.e. the inverted .List prop) and transposing it back allows here to regain the original one-based indexing of the input array as a ReDim Preserve would fail (it can only redim the last dimension).

Sub AddElement(datafield, ByVal rowNum As Long, newData)
'Note: assumes 1-based 2dim input arrays with same column counts
'create zero-based combo container on the fly
With CreateObject("Forms.ComboBox.1")
    .list = datafield     ' assign datafield to zero-based list elems
    .AddItem , rowNum - 1 ' add Null values to zero-based list index
    'assign new data to each zero-based list column
        Dim col As Long
        For col = LBound(newData, 2) To UBound(newData, 2)
            .list(rowNum - 1, col - 1) = newData(1, col)
        Next col
    'overwrite original datafield with transposed list values to regain 1-based elems
    datafield = Application.Transpose(.Column)    ' overwriting data (with zerobased dimensions)
End With

End Sub

As the datafield argument is passed implicitly by reference (ByRef), the original data input will be overwritten.

Example Call with insertion of a new 2nd row

Dim data:    data = Sheet1.Range("A1:C4").Value
Dim newData: newData = Sheet2.Range("A1:C1").Value
AddElement data, 2, newData
T.M.
  • 9,436
  • 3
  • 33
  • 57
  • Feel free to study as well Fane Duru's tricky answer upvoting it if instructive or helpful @B-M – T.M. Aug 15 '21 at 08:03
  • 1
    Very interesting! Voted it up. I do not know if this can be used without array size limitations, but nice idea. I think it can be improved to not need any `Application.Transpose`, which it is known that it has size limitations. I have an idea and I (think) know what `list` property could not be used instead of transposed `.column`. No time now to deal with it, but if what I have in mind will be covered by reality and I can do it in 5 - 10 minutes, I will post a new answer showing it. Anyhow, nice approach... – FaneDuru Aug 15 '21 at 12:19
1

In replay to @T.M. nice approach:

Try using the next function, which does not need transposing of column array:

Function AddElement(datafield, ByVal rowNum As Long, newData) As Variant
    'Note: assumes 1-based 2dim input arrays with same column counts
    'create zero-based combo container on the fly
    With CreateObject("Forms.ComboBox.1")
        .list = datafield     ' assign datafield to zero-based list elems
        .AddItem , rowNum - 1 ' add Null values to zero-based list index
        'assign new data to each zero-based list column
            Dim col As Long
            For col = LBound(newData, 2) To UBound(newData, 2)
                .list(rowNum - 1, col - 1) = newData(1, col)
            Next col
        AddElement = .list 'it returns a zero based 2D array...
    End With
End Function

It can be tested in this way:

Sub testAddArrayrow()
 Dim sh As Worksheet, newArr: sh = ActiveSheet  'was easier for me to check...
 Dim data:    data = sh.Range("A1:C4").Value
 Dim newData: newData = sh.Range("A6:C6").Value
    newArr = AddElement(data, 2, newData)
    sh.Range("P1").Resize(UBound(newArr) + 1, UBound(newArr, 2) + 1).Value = newArr '+ 1 because of zero bazed 2D returned array
End Sub

We must make some researches in order to check the array size limitations, if any. It will be very interesting to not have such limitations, but I simply cannot hope too much from this direction... :)

Anyhow, the brilliant idea remains...

FaneDuru
  • 38,298
  • 4
  • 19
  • 27
  • 1
    If the intention is to rewrite the (now *zero*-based) data to a sheet range without need to stick to the original 1-based datafield, returning the `.List` results is clearly the preferred way *(btw my original approach)*. Anyway it's interesting that writing data back doesn't care about boundaries @FaneDuru :) – T.M. Aug 15 '21 at 14:05
  • @T.M. I think I understood the reason you used `.column` property and I only tried showing how such a returned array can be dropped on a range. – FaneDuru Aug 15 '21 at 14:30
0

Please, try the next way:

Sub TestInsertRow()
    Dim sh, arr, arrInsR, arrFin
    
    Set sh = ActiveSheet
    arr = sh.[A1:C5].Value       'array to insert a row
    arrInsR = sh.[A11:C11].Value 'row to be inserted
    arrFin = InsertRowElab(arr, arrInsR, 3) 'inserted row to be the third
   sh.[J10].Resize(UBound(arrFin), UBound(arrFin, 2)).Value = arrFin
End Sub

Private Function InsertRowElab(arr As Variant, arrIns As Variant, insRowNo As Long) As Variant 'it inserts a row
   Dim i&, k&, Ar, arrR, arrRows, arrCols
   
   arrR = Application.Transpose(Application.Evaluate("row(1:" & UBound(arr) & ")")) 'build the rows array
    Debug.Print "Joined rows array: " & Join(arrR, "#"):                                        'so the joined array looks
   arrRows = Split(Replace(Join(arrR, "|"), insRowNo - 1 & "|", insRowNo - 1 & "|0|"), "|")      'insert the row, placing 0 in the array
    Debug.Print "Joined rows array containing the inserted row (0): " & Join(arrRows, "#")       'so the joined array containing the new row (0) looks
   arrCols = Application.Transpose(Application.Evaluate("row(1:" & UBound(arr, 2) & ")")) 'Array(1, 2, 3)
   Ar = Application.Index(arr, Application.Transpose(arrRows), arrCols)
   'fill the inserted row:
   For i = LBound(Ar) To UBound(Ar, 2): Ar(insRowNo, i) = arrIns(1, i): Next i
   InsertRowElab = Ar
End Function
FaneDuru
  • 38,298
  • 4
  • 19
  • 27
  • 1
    Just FYI this will not work with large arrays because of the limitations of transpose. – Scott Craner Aug 13 '21 at 21:00
  • 1
    @Scott Craner I know about its limitations. In case of large arrays, `Transpose` function limitations can be solved using a custom transpose function, being comparable fast. Easy to be written. I only tried showing a way to insert a row (it can be adapted to insert a slice of more rows) in a 2D array, as it was requested in the question. Just for the sake of showing how to be done... – FaneDuru Aug 14 '21 at 11:07
  • 1
    Nicely done and a very tricky roundabout way to get the new order of array rows (`arrRows`) by a consecutive *join*, *replace* and *split* action +:) - It might make it easier for other users to understand this approach having a look at [Some peculiarities of the the `Application.Index` function](https://stackoverflow.com/questions/51688593/excel-vba-insert-new-first-column-in-datafield-array-without-loops-or-api-call/51714153#51714153) @FaneDuru – T.M. Aug 14 '21 at 17:44
  • @T.M. I have a **friend** here with whom I debated he issue... :) – FaneDuru Aug 14 '21 at 18:03
  • Appreciate your always valuable solutions and contributions. - Fyi As you, too like *alternatives*, you might be interested in my post demonstrating a combo list vehicle. @FaneDuru – T.M. Aug 14 '21 at 19:54
  • @T.M. Where should I see the mentioned post? Did you forget to place a link? – FaneDuru Aug 14 '21 at 20:09
  • It's in the same post as a further [answer](https://stackoverflow.com/questions/68777731/excel-vba-insert-new-row-into-an-array/68786557#68786557) @FaneDuru – T.M. Aug 15 '21 at 07:48