1

In a recent post I demonstrated how to use array arguments in the Application.Index() function (instead of single indices) to rearrange the current columns order in any direction (switch columns, omit/delete columns).

    Application.Index(data, {vertical rows array}, {horizontal columns array})

This approach doesn't need loops and allows to get any new column order defined just by listing the new column positions, in OP e.g. via

     Array(1, 4, 2)

in other words

  • the 1st column,
  • (the 3rd one omitted=deleted),
  • the remaining columns 4 and 2 in switched order*:
Sub DeleteAndSwitch()
'[1]get data
    Dim data: data = Sheet1.Range("A1:D4")
'[2]reorder columns via Array(1, 4, 2), i.e. get 1st column, 4th and 2nd column omitting the 3rd one
'   (evaluation gets all existing rows as vertical 2-dim array)
    data = Application.Index(data, Evaluate("row(1:" & UBound(data) & ")"), Array(1, 4, 2))
'[3]write to any target
    Sheet2.Range("A1").Resize(UBound(data), UBound(data, 2)) = data
End Sub

A related comment asked:

"I can slice a 2D array, I can eliminate column, reorder columns, but I cannot ►insert a column(s) slice in such an array... In fact, I can do it using iteration, but did you find a similar way to insert such a vertical slice?"

Methodical hints

As is at least frequently known, a given column (e.g. the 4th) can be sliced from an array (here e.g. data) via

    Column4Data = Application.Index(data, 0, 4)

resulting itself in a 1-based 2-dim "vertical" array.

It's not possible, however to assign a vertical slice to another one; the following code would raise a 1004 error (Application-defined or object-defined error):

Application.Index(data, 0, 4) = Application.Index(data, 0, 1)

Question

Is there any possibility to insert a column(s) slice in an array (without iteration)?

There does exist a possibility to arrange such column data in a temporary array of arrays ("jagged array") and to build a 2-dim array from this base.

In order not to overcharge this post I'll demonstrate this rather unknown approach as ►separate answer looking forward to any other or better approach.

Related link Some pecularities of the Application.Index() function

T.M.
  • 9,436
  • 3
  • 33
  • 57
  • Why not just paste your data to a worksheet, move it around on the sheet as desired, and then copy the range back to an array? – johny why Aug 22 '21 at 21:03
  • 1
    Appreciate your hint in comment: this post intended mainly to inform about further possibilities of `Application.Index()` as addition to a prior answer. - If the target is to insert only one column without further column order re-sortings, I'd prefer your approach when using Excel VBA without any doubt (see also preface to the Addition part). @johnywhy – T.M. Aug 23 '21 at 09:45

1 Answers1

2

Jagged array approach using Application.Index()

For the sake of completeness I show this approach in order to demonstrate a further and widely unknown possibility of the Application.Index() function.

By adding (transposed) slices to a temporary "Array of Arrays" first, it's possible to create a 2-dim array in a second step via double zero arguments using the following syntax (c.f. section [2]b):

    data = Application.Transpose(Application.Index(data, 0, 0))
Sub InsertSlices()
'Auth: https://stackoverflow.com/users/6460297/t-m
'[0]define extra array (or slice AND transpose from other data source)
    Dim Extra: Extra = Array(100, 200, 300, 400)   ' example data
'[1]get data
    Dim data: data = Tabelle7.Range("A1:D4")
'[2]a) rewrite data as 1-dim array of sliced column arrays
    data = Array(Extra, Slice(data, 1), Slice(data, 4), Slice(data, 2))
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'[2]b) rebuild as 2-dim array
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    data = Application.Transpose(Application.Index(data, 0, 0))
'[3]write to any target
    Tabelle7.Range("F1").Resize(UBound(data), UBound(data, 2)) = data
End Sub
Function Slice(DataArr, ByVal colNo As Long) As Variant()
'Purpose: return entire column data as 2-dim array and
'         transpose them here to get a "flat" 1-dim array of column data
With Application
    Slice = .Transpose(.Index(DataArr, 0, colNo))
End With
End Function

Caveat: The repeated transformation of data in two steps can be time consuming for larger data sets.


Workaround

Therefore I'd prefer the basic approach in the cited post via ►array arguments in the Application.Index() function, but by inserting a (e.g. temporary) column to the physical data range first and eventually by rearranging the columns including the newly added extra data (last position) at any new position (e.g. here on top).

Sub DelSwitchAndInsert()
'Auth: https://stackoverflow.com/users/6460297/t-m
'[0]add other array data as last column to existing range
    Sheet1.Range("E1:E4") = Application.Transpose(Array(1, 2, 3, 4))
'[1]get data
    Dim data: data = Tabelle7.Range("A1:E4")
'[2]reorder via Array(1, 4, 2), i.e. get 1st column, 4th and 2nd column omitting the 3rd one
    data = Application.Index(data, Evaluate("row(1:" & UBound(data) & ")"), Array(UBound(data, 2), 1, 4, 2))
'[3]write to any target
    Sheet2.Range("A1").Resize(UBound(data), UBound(data, 2)) = data
End Sub

Addendum to workaround in response to recent comments //Edit/2020-07-07

A flexible example following the workaround logic for insertion of vertical extra single-column data at any given "column" number could be as follows; I don't pretend this to be neither the best method nor the best way to code:

    InsCol data, extra, 3        ' insertion e.g. as new 3rd "column"
Sub InsertExtraData()
'Purpose:  insert a single-column array (2-dim, 1-based)
    '[0]define extra array (or slice AND transpose from other data source)
        Dim extra: extra = Application.Transpose(Array(100, 200, 300, 400))   ' example data
    '[1]get data (or existing 2-dim array)
        Dim data: data = Sheet1.Range("A1:D4")
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    '[2]insert extra as >>3rd<< column
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        InsCol data, extra, 3
    '[3]write to any target
        Sheet2.Range("A1").Resize(UBound(data), UBound(data, 2)) = data
End Sub
Sub InsCol(data, extra, Optional ByVal colNo As Long = 1)
With Sheets.Add
    '[0]add data to temporary range
    .Range("B1").Resize(UBound(data), UBound(data, 2)) = data
    .Range("B1").Offset(0, UBound(data, 2)).Resize(UBound(extra) - LBound(extra) + 1, 1) = extra
    '[1]get data
        data = .Range("B1").Resize(UBound(data), UBound(data, 2) + 1)
    '[2]reorder via Array(5, 1, 2, 3, 4)
        data = Application.Index(data, Evaluate("row(1:" & UBound(data) & ")"), getColNums(data, colNo))
    '[3]delete temporary sheet
        Application.DisplayAlerts = False: .Delete
        Application.DisplayAlerts = True
End With
End Sub
Function getColNums(main, Optional ByVal colNo As Long = 1) As Variant()
    'c.f. : https://stackoverflow.com/questions/53727578/joining-two-arrays-in-vba/60082345#60082345
    'Purp.: return ordinal element counters of combined 0-based 1-dim arrays
    Dim i&, n&: n = UBound(main) + 1    ' +1 as one column, +1 from 0-based~>one-based
    ReDim tmp(0 To n - 1)               ' redim to augmented size (zero-based)
    If colNo > n Then colNo = n
    If colNo < 1 Then colNo = 1
    For i = 0 To colNo - 1: tmp(i) = i + 1: Next i
    tmp(colNo - 1) = n
    For i = colNo To UBound(tmp): tmp(i) = i: Next i
    getColNums = tmp        ' return combined elem counters,  e.g. Array(1,2, >>5<< ,3,4)
End Function

T.M.
  • 9,436
  • 3
  • 33
  • 57
  • 1
    Very nice! Voted it Up. `Insert slices` works like a charm, solving my "dilema". I am trying to transform `DelSwitchAndInsert` in a way to **only** insert another column (the Extra one of the previous Sub), without any reordering, but I do not get it.... – FaneDuru Jul 04 '20 at 13:22
  • Does this help? - The basic idea of the workaround is to `[0]` *add/append* the extra data as new column right to an existing range or to fill a temporary or hidden target range with any existing array plus the most right extra data , `[1]` include the new "last" column into the extended array assignment and `[2]` insert the most right column number `Array(UBound(data, 2)` to any wished position in the current columns array argument `Array(1,2,3,4)` ~> e.g. `Array(UBound(data,2), 1, 2, 3, 4)` for a final rearrangement as new first column. – T.M. Jul 04 '20 at 18:55
  • It helped, of course! But my (declared) interest is connected to inserting of **a new built column** between the existing ones. As `Insert slices` does. If I am not missing anything, the second one only switches or inserts **existing columns**. I could not adapt it to accept a new created (column) array on the mecanism of the previous `Sub`. I mean, inserting of `Extra` array... But this does not mean that everything presented is not extremely interesting... :) I like using arrays and until now I couldn't insert a column in a compact way. – FaneDuru Jul 04 '20 at 19:33
  • If I will also succed to adapt the second one to insert a column from outside, I will declare myself extremely satisfied, too! :) – FaneDuru Jul 04 '20 at 19:34
  • I appreciate your interest. - I am not quite sure, however to understand you correcty: the result is the same, it inserts extra data at any wanted column position, but with the additional step via a temporary range reassigning the combined data A:D plus an added temporary E column to the right (moved only thereafter via `Application.Index). Because of this additional step I consider this working (btw faster) approach as workaround to the original question. *I'd have liked to find an abbreviated way here, too* :-) @FaneDuru – T.M. Jul 06 '20 at 19:24
  • Edited a flexibilized example following the workaround logic (`InsCol data, extra, 3` inserts e.g. a 3rd column), hopefully helpful :-) @FaneDuru – T.M. Jul 07 '20 at 14:04