0

I have a huge range (80 K rows).
the values on that range are already sorted (from smallest to largest).
with the purpose to speed my code and also learning:
How to split that range into (4) even sequenced Arrays with condition that LBound / UBound is not duplicated on the other Arrays?
Note:that range has a lot of duplicate values ( I cannot delete it ,to preserve consistency of the other data).
In advance, thanks for any helpful comments ,Ideas and answers.
enter image description here

Sub Split_Range_into_4_even_sequenced_Arrays()
 
    Dim ws As Worksheet: Set ws = ActiveSheet
    Dim LastRow As Long: LastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
 
   'Putting the whole range on one array,leads to the code takes a very long time to finish
     Dim wholearr:  wholearr = ws.Range("A1:A" & LastRow).Value2 '80K rows
      'My Code
 
   'The desired answer:Pseudocode
 
   Dim arr1, arr2, arr3, arr4         'Split the whole range into (4) arrays to make the code faster
    arr1 = ws.Range("A1:A" & UBound(arr1)).Value2                     '1st quarter of the range
       'My Code
    arr2 = ws.Range("A" & LBound(arr2) & ":A" & UBound(arr2)).Value2  '2nd quarter of the range
       'My Code
    arr3 = ws.Range("A" & LBound(arr3) & ":A" & UBound(arr3)).Value2  '3rd quarter of the range
       'My Code
    arr4 = ws.Range("A" & LBound(arr4) & ":A" & UBound(arr4)).Value2  '4th quarter of the range
       'My Code
End Sub
Waleed
  • 847
  • 1
  • 4
  • 18
  • Could you explain what *"LBound / UBound is not duplicated on the other Arrays"* means? – VBasic2008 May 08 '22 at 08:01
  • @VBasic2008 ,kindly see my screenshot – Waleed May 08 '22 at 08:15
  • @waleed Just to be clear, do you ask how to get arrays with **bounds equal to the first and last row numbers** of the range, from where you load data? – Vitalizzare May 08 '22 at 12:52
  • I realize you have 80000 rows of data, but to simplify my understanding of your goal does this describe what you wish to do with the four arrays with just forty rows? `Dim array1(1 to 10)` `Dim array2(11 to 20)` `Dim array3(21 to 30)` `Dim array4(31 to 40)` And then rows 1 to 40 of the data range would be copied into the four arrays with just 10 rows in each array? – Excel Hero May 08 '22 at 15:24
  • @Excel Hero , there's a misunderstanding of my question and two persons voted to close. To save your time on preparing a lost answer, I will post again with more clearer info and I will mention you (if you don't mind) – Waleed May 08 '22 at 16:12
  • Yes, that's ok. – Excel Hero May 08 '22 at 16:15

2 Answers2

1

Approach via jagged array

As your intention is to get 4 even structured arrays out of a given range (of e.g. 12 column elements), I'd suggest to calculate the needed number of rows per partial array first (see section b) in example code), where

  • 12 is OP's rows count,
  • -1 provides for correct increments,
  • \ executes an integer division,
  • 4 is the (constant) number of partial arrays needed,
  • +1 will include also elements when rows count has division rests.
    elems = (12 - 1) \ 4 + 1                ' 11\4 + 1 = 2 + 1 ~~> 3            

instead of splitting an existing wholearray and to use a jagged array (aka as array of arrays) for better readibility (especially to avoid declaring more than 4 different variables arr1 .. arrN).

Btw Coding arr2 = ws.Range("A" & LBound(arr2) & ":A" & UBound(arr2)).Value2 etc would reference each partial range by "A1:A3" (assuming lower boundaries at 1).

Sub SplitRange()
    Const parts As Long = 4

    With Sheet1                               ' << e.g. project's sheet Code(Name)
    'a) Define needed range
        Dim lastRow As Long
        lastRow = .Cells(.Rows.Count, 1).End(xlUp).Row
        Dim rng As Range
        Set rng = .Range("A1:A" & lastRow)    ' << e.g. single column range
       'Set rng = .Range("A2:B" & lastRow)    ' << e.g. multicolumn range starting in row 2

    'b) calculate needed number of elements of each partial array
        Dim elems As Long
        elems = (rng.Rows.Count - 1) \ parts + 1
        
    'c) Provide for a jagged array (aka array of arrays)
        'Single elements need a double indexation: jagged(part)(r,c)
        Dim jagged(1 To parts)

    'd) assign partial datafield arrays to jagged array    
    Dim part As Long
        For part = 1 To parts
            'get start row
            Dim start As Long
            start = (part - 1) * elems + rng.Row ' Rng.Row considers top cell
            'get (partial) source range
            Dim src As Range
            Set src = .Cells(start, rng.Column).Resize(elems, rng.Columns.Count)
            'assign 1-based 2-dim datafield to jagged array part 1..4
            jagged(part) = src.Value2
    
            'Debug.Print part, "Row " & start & ".." & ( start + elems - 1), src.Address
    
        Next part
    End With

End Sub


T.M.
  • 9,436
  • 3
  • 33
  • 57
  • I tried on a real workbook and found that `LBound` item is found on two arrays (it should not), Is your answer code consider this point. – Waleed May 08 '22 at 11:48
  • @Waleed What do you mean by `LBound` item? Could you give an example with the actual range and tell me where you get an issue? - If you mean to get the first item of any of the 4 partial arrays (which contain 1-based 2-dim datafield arrays) you should reference `jagged(part)(1,1)`. So each partial array starts with row index 1. – T.M. May 08 '22 at 15:48
  • there's a misunderstanding of my question and two persons voted to close. To save your time on preparing a lost answer, I will post again with more clearer info and I will mention you (if you don't mind) – Waleed May 08 '22 at 16:14
1

If you expect to produce an array and then to use its bounds for the initialization as in the next line, it's wrong:

arr = ws.Range("A" & LBound(arr) & ":A" & UBound(arr)).Value2

You'll get a brand new array with bounds from 1 to Ubound - Lbound + 1 instead of previous one.

As for the main question, I suggest to use a generator. I can see two ways: 1) to get sequence of (lower, upper) bound pairs, or 2) to get bounds one by one as lower1, upper1, lower2, upper2, .... I like the second approach:

Function GetQuarter(Optional MaxUBound)
' Generate a sequence of MaxUBound quoters starting from 1
' where MaxUbound is an integer number.
' Return the next bound when MaxUbound is missing.
' Example:
' GetQuarter(700) = 1
' GetQuarter = 175
' GetQuarter = 176
' GetQuarter = 350
' GetQuarter = 351
' ...
Const Parts = 4
Static Iter As Long
Static Max As Long
Static StepForward As Long
    If IsMissing(MaxUBound) Then
        Iter = Iter + StepForward
        StepForward = IIf(StepForward, 0, 1)
    Else
        Max = MaxUBound
        Iter = 0
        StepForward = 1
    End If
    GetQuarter = Int(Iter * Max / Parts + StepForward)
End Function

To start the generator call it with a parameter LastRow. To get the next bound you call it without a parameter. Here's an example, how it can be used:

Sub Test()
Dim arr1, arr2, arr3, arr4
Dim FirstRow&, LastRow&
Dim Source As Range
    FirstRow = 10
    LastRow = 20
    Set Source = Columns("A").Rows(FirstRow & ":" & LastRow)
    With Source
        arr1 = .Rows(GetQuarter(.Rows.Count) & ":" & GetQuarter).Value2
        arr2 = .Rows(GetQuarter & ":" & GetQuarter).Value2
        arr3 = .Rows(GetQuarter & ":" & GetQuarter).Value2
        arr4 = .Rows(GetQuarter & ":" & GetQuarter).Value2
    End With
    Stop
End Sub
Vitalizzare
  • 4,496
  • 7
  • 13
  • 32
  • I used this `Debug.Print UBound(arr*)` to know upper bound or the arrays, But UBound is the same for all arrays. `*` means the number of array – Waleed May 08 '22 at 12:15
  • @waleed You have to `Redim Preserve Arr(lower to upper)` if it's important for the other parts of your code. – Vitalizzare May 08 '22 at 12:18
  • I tried your answer as it is, I just changed `FirstRow & LastRow` – Waleed May 08 '22 at 12:24
  • @waleed Is your question "How to apply `Redim` in this case"? – Vitalizzare May 08 '22 at 12:29
  • Yes,How to apply Redim in this case? My aim to make every array has not duplicates items with the other arrays. – Waleed May 08 '22 at 13:09
  • 1
    @waleed Sorry, can't help. I used `Redim Preserve` to add space for new elements. Now I see that it works if only you keep the first index untouched and start from it, which is not what I expected. It seems impossible to change the indexing other than to create a new array with appropriate one and to initialize its elements one by one. May be here you'll find some inspiration [Initialize Entire Array without Looping](https://stackoverflow.com/questions/19336987/vba-excel-initialize-entire-array-without-looping). BTW why do you need non-overlapping indexing? – Vitalizzare May 08 '22 at 13:49
  • 1
    No problem and thanks for all your efforts, actually I post this question, because when I put the whole range into one array that caused excel to hang, because excessive use of `union` , that why I need to split it, but the logic of my code depends on that values must be in one group even if it was duplicates. – Waleed May 08 '22 at 14:10