Home > Blockchain >  Fastest method to achieve this output matching using Excel VBA
Fastest method to achieve this output matching using Excel VBA

Time:01-06

I have a range with several series of dates and values

Input

enter image description here

Output

And i need this output, a series of dates ( using the min date and max date from input ).

If output date matches with the input date of a series then set the value of this day if not set a 0. I have tried all kind of loops but i have 40 series o dates and values ( 80 columns x 2000 rows ) and i can't get anything fast.

enter image description here

CodePudding user response:

Please, test the next code. You must take care that the format in the analyzed range to be the same as the one in the built range (dd/mm/yyyy). It returns the processed array in another sheet (sh1). I used the next sheet. If it is empty in your case, you can use the code as it is. There must not exist other records in the first row, except the last Valuex. The code can be adapted to search this header type, but it is not the object of the solution:

Sub CentralizeDateValues()
  Dim sh As Worksheet, sh1 As Worksheet, lastR As Long, rngD As Range, lastCol As Long, lastColL As String
  Dim arrD1, arrD2, arrGen, minD As Date, maxD As Date, i As Long, j As Long
  Dim arrOddCols, arrCols, strCols As String, NoD As Long, mtch, col As Long, StartTime As Date
  
  Set sh = ActiveSheet
  Set sh1 = sh.Next 'use here the sheet you need (where to return the processed range)
  
  lastR = sh.UsedRange.rows.Count                                        'last row
  lastCol = sh.cells(1, sh.Columns.Count).End(xlToLeft).Column   'last column
  'extract the odd columns number in an array:
  arrOddCols = Evaluate("TRANSPOSE(ROW(1:" & lastCol / 2 & ")*2-1)")
  Debug.Print Join(arrOddCols, "|"):     'just to visually check it. Comment the line after understanding what the above line does

  'obtain the columns letters array:
  ReDim arrCols(1 To UBound(arrOddCols))
  For i = 1 To UBound(arrOddCols)
        arrCols(i) = Split(cells(1, arrOddCols(i)).Address, "$")(1)
    Next i
  strCols = Join(arrCols, "1,") & "1": Debug.Print strCols        'just to visually check it.
  Set rngD = Intersect(sh.UsedRange, sh.Range(strCols).EntireColumn) ' build the range where to match max/min dates
  
 minD = WorksheetFunction.min(rngD)
 maxD = WorksheetFunction.Max(rngD)
 NoD = maxD - minD   1 'number the days in the range betweenthe min and max dates
 'build a continuous date array from min to max:
 arrD1 = Evaluate("TEXT(DATE(" & Year(minD) & "," & month(minD) & ",row(" & Day(minD) & ":" & NoD & ")),""dd/mm/yyyy"")")
 Debug.Print Join(Application.Transpose(arrD1), "|")               'just to visually check it.
 
 arrD2 = arrD1   'clone the built dates array
 ReDim Preserve arrD2(1 To UBound(arrD1), 1 To UBound(arrCols)   1) 'add the necessary columns for Values
 
 StartTime = Timer 'start the timer to count the time spent by the following code.

 arrGen = sh.Range("A2", sh.cells(lastR, lastCol)).Value: col = 1
 For i = 1 To UBound(arrGen)
     For j = 1 To UBound(arrGen, 2) - 1 Step 2 'iterate from two to two columns to check dates (as string) and extract values
        If arrGen(i, j) <> "" Then
            col = col   1
            mtch = Application.match(CStr(arrGen(i, j)), arrD1, True)
            If IsNumeric(mtch) Then
                arrD2(mtch, col) = arrGen(i, j   1)
            Else
               arrD2(mtch, col) = "strange..." 'the code reaches this line only if a mistake is in the Dates range...
            End If
        End If
     Next j
     col = 1 'reinitialize the variable to set the column where the value to be placed
 Next i
 'drop the processed array content at once
 sh1.Range("A2").Resize(UBound(arrD2), UBound(arrD2, 2)).Value = arrD2
 Sub CentralizeDateLongValues()
  Dim sh As Worksheet, sh1 As Worksheet, lastR As Long, rngD As Range, lastCol As Long, lastColL As String
  Dim arrD1, arrD2, arrGen, minD As Date, maxD As Date, i As Long, j As Long
  Dim arrOddCols, arrCols, strCols As String, NoD As Long, mtch, col As Long, StartTime As Date
  
  Set sh = ActiveSheet
  Set sh1 = sh.Next 'use here the sheet you need (where to return the processed range)
  
  lastR = sh.UsedRange.rows.Count                                        'last row
  lastCol = sh.cells(1, sh.Columns.Count).End(xlToLeft).Column   'last column
  'extract the odd columns number in an array:
  arrOddCols = Evaluate("TRANSPOSE(ROW(1:" & lastCol / 2 & ")*2-1)")
  Debug.Print Join(arrOddCols, "|"):     'just to visually check it. Comment the line after understanding what the above line does

  'obtain the columns letters array:
  ReDim arrCols(1 To UBound(arrOddCols))
  For i = 1 To UBound(arrOddCols)
        arrCols(i) = Split(cells(1, arrOddCols(i)).Address, "$")(1)
    Next i
  strCols = Join(arrCols, "1,") & "1": Debug.Print strCols        'just to visually check it.
  Set rngD = Intersect(sh.UsedRange, sh.Range(strCols).EntireColumn) ' build the range where to match max/min dates
  
 minD = WorksheetFunction.min(rngD)
 maxD = WorksheetFunction.Max(rngD)
 NoD = maxD - minD   1 'number the days in the range betweenthe min and max dates
   'build a continuous date array from long numbers, corespondent to min and max dates:
  arrD1 = Evaluate("row(" & CLng(minD) & ":" & CLng(maxD) & ")")
  'Debug.Print Join(Application.Transpose(arrD1), "|"): 'Stop

 arrD2 = arrD1   ''clone the built dates arary
 ReDim Preserve arrD2(1 To UBound(arrD1), 1 To UBound(arrCols)   1) 'add the necessary columns for Values
 
 StartTime = Timer 'start the timer to count the time spent by the following code.
 
 arrGen = sh.Range("A2", sh.cells(lastR, lastCol)).Value2: col = 1
 For i = 1 To UBound(arrGen)
     For j = 1 To UBound(arrGen, 2) - 1 Step 2 'iterate from two to two columns to check dates (as string) and extract values
        If arrGen(i, j) <> "" Then
            col = col   1
            mtch = Application.match(arrGen(i, j), arrD1, True)
            If IsNumeric(mtch) Then
                arrD2(mtch, col) = arrGen(i, j   1)
            Else
               arrD2(mtch, col) = "strange..." 'the code reaches this line only if a mistake is in the Dates range...
            End If
        End If
     Next j
     col = 1 'reinitialize the variable to set the column where the value to be placed
 Next i
 'drop the processed array content at once
 With sh1.Range("A2").Resize(UBound(arrD2), UBound(arrD2, 2))
    .Value2 = arrD2
    .Columns(1).NumberFormat = "dd/mm/yyyy"
 End With
 'put headers:
 Dim arrHd: arrHd = Application.Transpose(Evaluate("row(1:" & UBound(arrD2, 2) - 1 & ")"))
 arrHd = Split("Date|Value" & Join(arrHd, "|Value"), "|")
 sh1.Range("A1").Resize(1, UBound(arrHd)   1).Value = arrHd: sh1.Activate
 MsgBox "Ready..." & vbCrLf & _
             " (" & Format(Timer - StartTime, "00.00") & " seconds)"
End Sub
End Sub

It returns in "A1" of the next sheet the header and in "A2" the processed array.

Please, send some feedback after testing it. I am curious how much it takes for a big range. I tested it on a small range, but solution must run on any range...

Edited:

Please, test the following version. It uses a Long numbers array, corresponding to the necessary Dates range. This allows using value2 to create the global array, which allows a (little) faster iteration and does no need the CStr conversion. Not date format dependent, too:

Sub CentralizeDateLongValues()
  Dim sh As Worksheet, sh1 As Worksheet, lastR As Long, rngD As Range, lastCol As Long, lastColL As String
  Dim arrD1, arrD2, arrGen, minD As Date, maxD As Date, i As Long, j As Long
  Dim arrOddCols, arrCols, strCols As String, NoD As Long, mtch, col As Long, StartTime As Date
  
  Set sh = ActiveSheet
  Set sh1 = sh.Next 'use here the sheet you need (where to return the processed range)
  
  lastR = sh.UsedRange.rows.Count                                        'last row
  lastCol = sh.cells(1, sh.Columns.Count).End(xlToLeft).Column   'last column
  'extract the odd columns number in an array:
  arrOddCols = Evaluate("TRANSPOSE(ROW(1:" & lastCol / 2 & ")*2-1)")
  Debug.Print Join(arrOddCols, "|"):     'just to visually check it. Comment the line after understanding what the above line does

  'obtain the columns letters array:
  ReDim arrCols(1 To UBound(arrOddCols))
  For i = 1 To UBound(arrOddCols)
        arrCols(i) = Split(cells(1, arrOddCols(i)).Address, "$")(1)
    Next i
  strCols = Join(arrCols, "1,") & "1": Debug.Print strCols        'just to visually check it.
  Set rngD = Intersect(sh.UsedRange, sh.Range(strCols).EntireColumn) ' build the range where to match max/min dates
  
 minD = WorksheetFunction.min(rngD)
 maxD = WorksheetFunction.Max(rngD)
 NoD = maxD - minD   1 'number the days in the range betweenthe min and max dates
   'build a continuous date array from long numbers, corespondent to min and max dates:
  arrD1 = Evaluate("row(" & CLng(minD) & ":" & CLng(maxD) & ")")
  'Debug.Print Join(Application.Transpose(arrD1), "|"): 'Stop

 arrD2 = arrD1   ''clone the built dates arary
 ReDim Preserve arrD2(1 To UBound(arrD1), 1 To UBound(arrCols)   1) 'add the necessary columns for Values
 
 StartTime = Timer 'start the timer to count the time spent by the following code.
 
 arrGen = sh.Range("A2", sh.cells(lastR, lastCol)).Value2: col = 1
 For i = 1 To UBound(arrGen)
     For j = 1 To UBound(arrGen, 2) - 1 Step 2 'iterate from two to two columns to check dates (as string) and extract values
        If arrGen(i, j) <> "" Then
            col = col   1
            mtch = Application.match(arrGen(i, j), arrD1, True)
            If IsNumeric(mtch) Then
                arrD2(mtch, col) = arrGen(i, j   1)
            Else
               arrD2(mtch, col) = "strange..." 'the code reaches this line only if a mistake is in the Dates range...
            End If
        End If
     Next j
     col = 1 'reinitialize the variable to set the column where the value to be placed
 Next i
  'drop the processed array content at once
 Dim rngBlank As Range
 With sh1.Range("A2").Resize(UBound(arrD2), UBound(arrD2, 2))
    .Value2 = arrD2
    .Columns(1).NumberFormat = "dd/mm/yyyy"
    .EntireColumn.AutoFit
    .Borders(xlEdgeLeft).Weight = xlThin
    .Borders(xlEdgeTop).Weight = xlThin
    .Borders(xlEdgeBottom).Weight = xlThin
    .Borders(xlEdgeRight).Weight = xlThin
    .Borders(xlInsideVertical).Weight = xlThin
    .Borders(xlInsideHorizontal).Weight = xlThin
    .BorderAround Weight:=xlThick
    On Error Resume Next 'for the case (even imporbable) that no any blank cell will exist...
     Set rngBlank = .SpecialCells(xlCellTypeBlanks)
    On Error GoTo 0
 End With
 If Not rngBlank Is Nothing Then rngBlank.Value = 0
 'put headers:
 Dim arrHd: arrHd = Application.Transpose(Evaluate("row(1:" & UBound(arrD2, 2) - 1 & ")"))
 arrHd = Split("Date|Value" & Join(arrHd, "|Value"), "|")
 With sh1.Range("A1").Resize(1, UBound(arrHd)   1)
    .Value = arrHd
    .Font.Bold = True
    .EntireColumn.AutoFit
    .Borders(xlInsideVertical).Weight = xlThin
    .BorderAround Weight:=xlThick
 End With
 sh1.Activate
 MsgBox "Ready..." & vbCrLf & _
             " (" & Format(Timer - StartTime, "00.00") & " seconds)"
End Sub

Please, send some feedback after testing it...

  •  Tags:  
  • Related