Hvordan kan jeg skjære en rekke i Excel VBA?

stemmer
25

Hvilken funksjon kan jeg bruke i Excel VBA til å skjære en array?

Publisert på 06/10/2008 klokken 14:54
kilden bruker
På andre språk...                            


8 svar

stemmer
47

Application.WorksheetFunction.Index (matrise, rad, kolonne)

Hvis du angir en nullverdi for rad eller kolonne, så får du hele kolonnen eller raden som er angitt.

Eksempel:

Application.WorksheetFunction.Index (matrise, 0, 3)

Dette vil gi deg hele tredje kolonnen.

Hvis du angir både rad og kolonne som ikke er null, så får du bare den spesifikke element. Det er ingen enkel måte å få en mindre bit enn en komplett rad eller kolonne.

Begrensning : Det er en grense for matrisen størrelse som WorksheetFunction.Indexkan håndtere hvis du bruker en nyere versjon av Excel. Dersom arrayhar mer enn 65.536 rader eller 65.536 kolonner, så det kaster en "Type mismatch" feil. Hvis dette er et problem for deg, så ser dette mer komplisert svar som ikke er underlagt de samme begrensningene.

Her er funksjonen jeg skrev å gjøre all min 1D og 2D slicing:

Public Function GetArraySlice2D(Sarray As Variant, Stype As String, Sindex As Integer, Sstart As Integer, Sfinish As Integer) As Variant

' this function returns a slice of an array, Stype is either row or column
' Sstart is beginning of slice, Sfinish is end of slice (Sfinish = 0 means entire
' row or column is taken), Sindex is the row or column to be sliced
' (NOTE: 1 is always the first row or first column)
' an Sindex value of 0 means that the array is one dimensional 3/20/09 ljr

Dim vtemp() As Variant
Dim i As Integer

On Err GoTo ErrHandler

Select Case Sindex
    Case 0
        If Sfinish - Sstart = UBound(Sarray) - LBound(Sarray) Then
            vtemp = Sarray
        Else
            ReDim vtemp(1 To Sfinish - Sstart + 1)
            For i = 1 To Sfinish - Sstart + 1
                vtemp(i) = Sarray(i + Sstart - 1)
            Next i
        End If
    Case Else
        Select Case Stype
            Case "row"
                If Sfinish = 0 Or (Sstart = LBound(Sarray, 2) And Sfinish = UBound(Sarray, 2)) Then
                    vtemp = Application.WorksheetFunction.Index(Sarray, Sindex, 0)
                Else
                    ReDim vtemp(1 To Sfinish - Sstart + 1)
                    For i = 1 To Sfinish - Sstart + 1
                        vtemp(i) = Sarray(Sindex, i + Sstart - 1)
                    Next i
                End If
            Case "column"
                If Sfinish = 0 Or (Sstart = LBound(Sarray, 1) And Sfinish = UBound(Sarray, 1)) Then
                    vtemp = Application.WorksheetFunction.Index(Sarray, 0, Sindex)
                Else
                    ReDim vtemp(1 To Sfinish - Sstart + 1)
                    For i = 1 To Sfinish - Sstart + 1
                        vtemp(i) = Sarray(i + Sstart - 1, Sindex)
                    Next i
                End If
        End Select
End Select
GetArraySlice2D = vtemp
Exit Function

ErrHandler:
    Dim M As Integer
    M = MsgBox("Bad Array Input", vbOKOnly, "GetArraySlice2D")

End Function
Svarte 06/10/2008 kl. 14:57
kilden bruker

stemmer
1

Du kan bruke en kombinasjon av rader, kolonner, offset og Endre størrelse egenskaper for å få en undergruppe av et område.

For eksempel hvis du har et område som er 5 kolonner og 3 rader:

Set rng = Range("A1:E3")

Du kan få noen undergruppe av hensiktsmessig å kombinere de ovennevnte egenskaper. For eksempel, hvis du ønsker å få lengst til høyre 3 celler på andre rad (dvs. "C2: E2" i eksempelet ovenfor), kan du gjøre noe sånt som:

   Set rngSubset = rng.Rows(2).Offset(0, rng.Columns.Count - 3).Resize(1, 3)

Du kan deretter bryte opp dette i en VBA-funksjon.

Svarte 06/10/2008 kl. 15:26
kilden bruker

stemmer
4

To ting, VBA tillater ikke rekke å ha klippet så uansett hva du bruker, må du rulle din egen. Men siden dette er bare for Excel, kan du bruke bygge i regnearkfunksjonen indeksen for matrise slicing.

Sub Test()
    'All example return a 1 based 2D array.
    Dim myArr As Variant 'This var must be generic to work.
    'Get whole range:
    myArr = ActiveSheet.UsedRange
    'Get just column 1:
    myArr = WorksheetFunction.Index(ActiveSheet.UsedRange, 0, 1)
    'Get just row 5
    myArr = WorksheetFunction.Index(ActiveSheet.UsedRange, 5, 0)
End Sub
Svarte 02/06/2009 kl. 02:51
kilden bruker

stemmer
2

Lance løsning har en feil i at det ikke respektere en offset start verdi med en sub-Arry av uspesifisert lengde, fant jeg også hvordan det fungerer ganske forvirrende. Jeg har en (forhåpentligvis) mer gjennomsiktig løsning nedenfor.

Public Function GetSubTable(vIn As Variant, Optional ByVal iStartRow As Integer, Optional ByVal iStartCol As Integer, Optional ByVal iHeight As Integer, Optional ByVal iWidth As Integer) As Variant
    Dim vReturn As Variant
    Dim iInRowLower As Integer
    Dim iInRowUpper As Integer
    Dim iInColLower As Integer
    Dim iInColUpper As Integer
    Dim iEndRow As Integer
    Dim iEndCol As Integer
    Dim iRow As Integer
    Dim iCol As Integer

    iInRowLower = LBound(vIn, 1)
    iInRowUpper = UBound(vIn, 1)
    iInColLower = LBound(vIn, 2)
    iInColUpper = UBound(vIn, 2)

    If iStartRow = 0 Then
        iStartRow = iInRowLower
    End If
    If iStartCol = 0 Then
        iStartCol = iInColLower
    End If

    If iHeight = 0 Then
        iHeight = iInRowUpper - iStartRow + 1
    End If
    If iWidth = 0 Then
        iWidth = iInColUpper - iStartCol + 1
    End If

    iEndRow = iStartRow + iHeight - 1
    iEndCol = iStartCol + iWidth - 1

    ReDim vReturn(1 To iEndRow - iStartRow + 1, 1 To iEndCol - iStartCol + 1)

    For iRow = iStartRow To iEndRow
        For iCol = iStartCol To iEndCol
            vReturn(iRow - iStartRow + 1, iCol - iStartCol + 1) = vIn(iRow, iCol)
        Next
    Next

    GetSubTable = vReturn
End Function
Svarte 21/09/2011 kl. 16:39
kilden bruker

stemmer
12

Nedenfor er en rask metode for å skjære Excel variant arrays. Mesteparten av dette ble satt sammen ved hjelp av informasjon fra dette utmerkede nettstedet http://bytecomb.com/vba-reference/

I hovedsak bestemmelses matrisen er ferdigbygget som en tom 1d eller 2d variant og føres til under med kildegruppen og element indeks som skal skjæres opp. På grunn av måten arrays er lagret i minnet er det mye raskere å skjære en kolonne enn en rad som minnet oppsettet gjør at en enkelt blokk som skal kopieres.

Den gode ting om dette er det skalerer godt utover rad grense Excel.

skriv bildebeskrivelse her

Option Explicit

#If Win64 Then
    Public Const PTR_LENGTH As Long = 8
    Public Declare PtrSafe Function GetTickCount Lib "kernel32" () As Long
    Public Declare PtrSafe Sub Mem_Copy Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)
    Private Declare PtrSafe Function VarPtrArray Lib "VBE7" Alias "VarPtr" (ByRef Var() As Any) As LongPtr
    Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    Private Declare PtrSafe Sub FillMemory Lib "kernel32" Alias "RtlFillMemory" (Destination As Any, ByVal Length As Long, ByVal Fill As Byte)
#Else
    Public Const PTR_LENGTH As Long = 4
    Public Declare Function GetTickCount Lib "kernel32" () As Long
    Public Declare Sub Mem_Copy Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)
    Private Declare Function VarPtrArray Lib "VBE7" Alias "VarPtr" (ByRef Var() As Any) As LongPtr
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    Private Declare Sub FillMemory Lib "kernel32" Alias "RtlFillMemory" (Destination As Any, ByVal Length As Long, ByVal Fill As Byte)
#End If

Private Type SAFEARRAYBOUND
    cElements    As Long
    lLbound      As Long
End Type

Private Type SAFEARRAY_VECTOR
    cDims        As Integer
    fFeatures    As Integer
    cbElements   As Long
    cLocks       As Long
    pvData       As LongPtr
    rgsabound(0) As SAFEARRAYBOUND
End Type

Sub SliceColumn(ByVal idx As Long, ByRef arrayToSlice() As Variant, ByRef slicedArray As Variant)
'slicedArray can be passed as a 1d or 2d array
'sliceArray can also be part bound, eg  slicedArray(1 to 100) or slicedArray(10 to 100)
Dim ptrToArrayVar As LongPtr
Dim ptrToSafeArray As LongPtr
Dim ptrToArrayData As LongPtr
Dim ptrToArrayData2 As LongPtr
Dim uSAFEARRAY As SAFEARRAY_VECTOR
Dim ptrCursor As LongPtr
Dim cbElements As Long
Dim atsBound1 As Long
Dim elSize As Long

    'determine bound1 of source array (ie row Count)
    atsBound1 = UBound(arrayToSlice, 1)
    'get pointer to source array Safearray
    ptrToArrayVar = VarPtrArray(arrayToSlice)
    CopyMemory ptrToSafeArray, ByVal ptrToArrayVar, PTR_LENGTH
    CopyMemory uSAFEARRAY, ByVal ptrToSafeArray, LenB(uSAFEARRAY)
    ptrToArrayData = uSAFEARRAY.pvData
    'determine byte size of source elements
    cbElements = uSAFEARRAY.cbElements

    'get pointer to destination array Safearray
    ptrToArrayVar = VarPtr(slicedArray) + 8 'Variant reserves first 8bytes
    CopyMemory ptrToSafeArray, ByVal ptrToArrayVar, PTR_LENGTH
    CopyMemory uSAFEARRAY, ByVal ptrToSafeArray, LenB(uSAFEARRAY)
    ptrToArrayData2 = uSAFEARRAY.pvData

    'determine elements size
    elSize = UBound(slicedArray, 1) - LBound(slicedArray, 1) + 1
    'determine start position of data in source array
    ptrCursor = ptrToArrayData + (((idx - 1) * atsBound1 + LBound(slicedArray, 1) - 1) * cbElements)
    'Copy source array to destination array
    CopyMemory ByVal ptrToArrayData2, ByVal ptrCursor, cbElements * elSize

End Sub

Sub SliceRow(ByVal idx As Long, ByRef arrayToSlice() As Variant, ByRef slicedArray As Variant)
'slicedArray can be passed as a 1d or 2d array
'sliceArray can also be part bound, eg  slicedArray(1 to 100) or slicedArray(10 to 100)
Dim ptrToArrayVar As LongPtr
Dim ptrToSafeArray As LongPtr
Dim ptrToArrayData As LongPtr
Dim ptrToArrayData2 As LongPtr
Dim uSAFEARRAY As SAFEARRAY_VECTOR
Dim ptrCursor As LongPtr
Dim cbElements As Long
Dim atsBound1 As Long
Dim i As Long

    'determine bound1 of source array (ie row Count)
    atsBound1 = UBound(arrayToSlice, 1)
    'get pointer to source array Safearray
    ptrToArrayVar = VarPtrArray(arrayToSlice)
    CopyMemory ptrToSafeArray, ByVal ptrToArrayVar, PTR_LENGTH
    CopyMemory uSAFEARRAY, ByVal ptrToSafeArray, LenB(uSAFEARRAY)
    ptrToArrayData = uSAFEARRAY.pvData
    'determine byte size of source elements
    cbElements = uSAFEARRAY.cbElements

    'get pointer to destination array Safearray
    ptrToArrayVar = VarPtr(slicedArray) + 8 'Variant reserves first 8bytes
    CopyMemory ptrToSafeArray, ByVal ptrToArrayVar, PTR_LENGTH
    CopyMemory uSAFEARRAY, ByVal ptrToSafeArray, LenB(uSAFEARRAY)
    ptrToArrayData2 = uSAFEARRAY.pvData

    ptrCursor = ptrToArrayData + ((idx - 1) * cbElements)
    For i = LBound(slicedArray, 1) To UBound(slicedArray, 1)

        CopyMemory ByVal ptrToArrayData2, ByVal ptrCursor, cbElements
        ptrCursor = ptrCursor + (cbElements * atsBound1)
        ptrToArrayData2 = ptrToArrayData2 + cbElements
    Next i

End Sub

Eksempler på bruk:

Sub exampleUsage()
Dim sourceArr() As Variant
Dim destArr As Variant
Dim sliceIndex As Long

    On Error GoTo Err:

    sourceArr = Sheet1.Range("A1:D10000").Value2
    sliceIndex = 2 'Slice column 2 / slice row 2

    'Build target array
    ReDim destArr(20 To 10000) '1D array from row 20 to 10000
'    ReDim destArr(1 To 10000) '1D array from row 1 to 10000
'    ReDim destArr(20 To 10000, 1 To 1) '2D array from row 20 to 10000
'    ReDim destArr(1 To 10000, 1 To 1) '2D array from row 1 to 10000

    'Slice Column
    SliceColumn sliceIndex, sourceArr, destArr

    'Slice Row
    ReDim destArr(1 To 4)
    SliceRow sliceIndex, sourceArr, destArr

Err:
    'Tidy Up See ' http://stackoverflow.com/questions/16323776/copy-an-array-reference-in-vba/16343887#16343887
    FillMemory destArr, 16, 0

End Sub

Timings var på en gammel dual core CPU ved hjelp av følgende test

Sub timeMethods()
Const trials As Long = 10
Const rowsToCopy As Long = 1048576
Dim rng As Range
Dim Arr() As Variant
Dim newArr As Variant
Dim newArr2 As Variant
Dim t As Long, t1 As Long, t2 As Long, t3 As Long
Dim i As Long

    On Error GoTo Err

    'Setup Conditions 1time only
    Sheet1.Cells.Clear
    Sheet1.Range("A1:D1").Value = Split("A1,B1,C1,D1", ",") 'Strings
'    Sheet1.Range("A1:D1").Value = Split("1,1,1,1", ",") 'Longs
    Sheet1.Range("A1:D1").AutoFill Destination:=Sheet1.Range("A1:D" & rowsToCopy), Type:=xlFillDefault

    'Build source data
    Arr = Sheet1.Range("A1:D" & rowsToCopy).Value
    Set rng = Sheet1.Range("A1:D" & rowsToCopy)

    'Build target container
    ReDim newArr(1 To rowsToCopy)
    Debug.Print "Trials=" & trials & " Rows=" & rowsToCopy
    'Range
    t3 = 0
    For t = 1 To trials
        t1 = GetTickCount

            For i = LBound(newArr, 1) To UBound(newArr, 1)
                newArr(i) = rng(i, 2).Value2
            Next i

        t2 = GetTickCount
        t3 = t3 + (t2 - t1)
        Debug.Print "Range: " & t2 - t1
    Next t
    Debug.Print "Range Avg ms: " & t3 / trials

    'Array
    t3 = 0
    For t = 1 To trials
        t1 = GetTickCount

            For i = LBound(newArr, 1) To UBound(newArr, 1)
                newArr(i) = Arr(i, 2)
            Next i

        t2 = GetTickCount
        t3 = t3 + (t2 - t1)
        Debug.Print "Array: " & t2 - t1
    Next t
    Debug.Print "Array Avg ms: " & t3 / trials

    'Index
    t3 = 0
    For t = 1 To trials
        t1 = GetTickCount

            newArr2 = WorksheetFunction.Index(rng, 0, 2) 'newArr2 2d

        t2 = GetTickCount
        t3 = t3 + (t2 - t1)
        Debug.Print "Index: " & t2 - t1
    Next t
    Debug.Print "Index Avg ms: " & t3 / trials

    'CopyMemBlock
    t3 = 0
    For t = 1 To trials
        t1 = GetTickCount

            SliceColumn 2, Arr, newArr

        t2 = GetTickCount
        t3 = t3 + (t2 - t1)
        Debug.Print "CopyMem: " & t2 - t1
    Next t
    Debug.Print "CopyMem Avg ms: " & t3 / trials

Err:
    'Tidy Up
    FillMemory newArr, 16, 0


End Sub
Svarte 19/07/2014 kl. 16:40
kilden bruker

stemmer
2

Her er en annen måte.

Dette er ikke flerdimensjonale men ville fungere enkelt rad og enkelt kolonne.

f og t parametere er null basert på.

Function slice(ByVal arr, ByVal f, ByVal t)
    slice = Application.Index(arr, Evaluate("Transpose(Row(" & f + 1 & ":" & t + 1 & "))"))
End Function
Svarte 26/11/2015 kl. 17:56
kilden bruker

stemmer
2

Her er en kjekk liten funksjon jeg skrev til delmengde A 2D array

Function Subset2D(arr As Variant, Optional rowStart As Long = 1, Optional rowStop As Long = -1, Optional colIndices As Variant) As Variant
    'Subset a 2d array (arr)
    'If rowStop = -1, all rows are returned
    'colIndices can be provided as a variant array like Array(1,3)
    'if colIndices is not provided, all columns are returned

    Dim newarr() As Variant, newRows As Long, newCols As Long, i As Long, k As Long, refCol As Long

    'Set the correct rowStop
    If rowStop = -1 Then rowStop = UBound(arr, 1)

    'Set the colIndices if they were not provided
    If IsMissing(colIndices) Then
        ReDim colIndices(1 To UBound(arr, 2))
        For k = 1 To UBound(arr, 2)
            colIndices(k) = k
        Next k
    End If

    'Get the dimensions of newarr
    newRows = rowStop - rowStart + 1
    newCols = UBound(colIndices) + 1
    ReDim newarr(1 To newRows, 1 To newCols)

    'Loop through each empty element of newarr and set its value
    For k = 1 To UBound(newarr, 2) 'Loop through each column
        refCol = colIndices(k - 1) 'Get the corresponding reference column
        For i = 1 To UBound(newarr, 1) 'Loop through each row
            newarr(i, k) = arr(i + rowStart - 1, refCol) 'Set the value
        Next i
    Next k

    Subset2D = newarr
End Function
Svarte 31/12/2015 kl. 15:52
kilden bruker

stemmer
1

Det er ingen direkte slicefunksjon for arrays, forskjellig fra mange andre nylige språk.

Men det er en kort kodebit svært nyttig for dette. Nedenfor en komplett løsning for 1D matriser:

'*************************************************************
'*                      Fill(N1,N2)
'* Create 1 dimension array with values from N1 to N2 step 1
'*************************************************************
Function Fill(N1 As Long, N2 As Long) As Variant
 Dim Arr As Variant
 If N2 < N1 Then
   Fill = False
   Exit Function
 End If
 Fill = WorksheetFunction.Transpose(Evaluate("Row(" & N1 & ":" & N2 & ")"))
End Function

'**********************************************************************
'*                        Slice(AArray, [N1,N2])
'* Slice an array between indices N1 to N2
'***********************************************************************
Function Slice(VArray As Variant, Optional N1 As Long = 1, Optional N2 As Long = 0) As Variant
 Dim Indices As Variant
 If N2 = 0 Then N2 = UBound(VArray)
 If N1 = LBound(VArray) And N2 = UBound(VArray) Then
   Slice = VArray
 Else
   Indices = Fill(N1, N2)
   Slice = WorksheetFunction.Index(VArray, 1, Indices)
 End If
End Function

For testing

Var V As Variant
V = Fill(100,109)
PrintArr(Slice(V,3,5))

'************************************************
'*                 PrintArr(VArr)
'* Print the array VARR
'**************************************************
Function PrintArr(VArray As Variant)
 Dim S As String
 S = Join(VArray, ", ")
 MsgBox (S)
End Function

Resultatene

102, 103, 104 
Svarte 27/04/2018 kl. 19:07
kilden bruker

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more