hello i worte a funnction that sums ahe abslute diffrence betwen two cells each time and then i subtact 70000 from the sum
i would like to to this in vba more aesthetic
70000-(IF(D2>0,ABS(D2-C2))+IF(E2>0,ABS(E2-D2))+IF(F2>0,ABS(F2-E2))+IF(G2>0,ABS(G2-F2))+IF(H2>0,ABS(H2-G2))+IF(I2>0,ABS(I2-H2))+IF(J2>0,ABS(J2-I2))+IF(K2>0,ABS(K2-J2))+IF(L2>0,ABS(L2-K2))+IF(M2>0,ABS(M2-L2))+IF(N2>0,ABS(N2-M2))+IF(O2>0,ABS(O2-N2)) )
Why not simply:
=70000-SUMPRODUCT(ABS(D2:O2-C2:N2),N(D2:O2>0))
Maybe a bit off-topic since you specifically ask for a VBA solution, but this formula-solution would also bring aesthetic and improves calculation:
=70000-
REDUCE(0, COLUMN(C:N),
LAMBDA(a, b,
LET(offset,INDEX(2:2,,b+1),
IF(offset>0,
a+ABS(offset-INDEX(2:2,,b)),
a))))
It loops through column C:N (b) in the row mentioned (row 2:2 in this case) and checks if the value offset 1 to the right (I used INDEX to not make it volatile, but named it offset).
If the value in that row/column+1 is greater than 0 than value a becomes a + ABS(the value in the row/column+1 - value in that row/column), otherwise a stays the same.
Edit:
For if your range might grow/decrease this may be a nice dynamic solution:
=70000-LET(range,C2:Z2,
cols,DROP(FILTER(COLUMN(range),range<>""),,-1),
REDUCE(0,cols,LAMBDA(a,b,LET(offset,INDEX(2:2,,b+1),IF(offset>0,a+ABS(offset-INDEX(2:2,,b)),a)))))
It checks for any values in the range C2:Z2 (Z could be expanded) and filters out the blanks.
Than it takes all columns in the range minus the last (for the offset calculation purpose).
Note that if there are gaps in your data this would filter those out as well.
First, based on your formula, you rather only add the absolute difference if the value subtracted from is >0. If this is what you want, then you would have something like this:
Sub SumAbsDiff()
Dim i As Integer
Dim sum As Double
'This loops from col D to col O
For i = 4 To 15 Step 1
If Cells(2, i).Value > 0 Then sum = sum + Abs(Cells(2, i).Value - Cells(2, i - 1).Value)
Next i
'Change this to the cell you would like to display the value
Cells(1, 1).Value = 70000 - sum
End Sub
Explanation:
Here, we are taking row 2, and then looping over from D until O. Using the loop, we absolute subtract each of them (D-C, E-D, etc) if the >0 condition satisfy. The result is then add to the sum variable (which initialize as 0 by default).
After the loop is done, we just simply use it to subtract from 70,000 and then write it to the cell that we wanted.
As a side note, if your original formula was wrong, and you actually want the sum between each of the absolute differences without the >0 condition, then removing the If ... Then would do the trick.
Sum Up Absolute Differences
Static (C2:O2)
Sub SumUpAbsStatic()
Const SRC_NAME As String = "Sheet1"
Const SRC_RANGE As String = "C2:O2"
Const DST_CELL As String = "B2"
Const INIT_VALUE As Double = 70000
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim ws As Worksheet: Set ws = wb.Sheets(SRC_NAME)
Dim Data(): Data = ws.Range(SRC_RANGE).Value
Dim pValue: pValue = Data(1, 1)
Dim cValue, c As Long, Total As Double
For c = 2 To UBound(Data, 2)
cValue = Data(1, c)
If IsNumeric(pValue) Then
If IsNumeric(cValue) Then
If cValue > 0 Then Total = Total + Abs(cValue - pValue)
End If
End If
pValue = cValue
Next c
Total = INIT_VALUE - Total
ws.Range(DST_CELL).Value = Total
End Sub
Dynamic (C2:LastColumn2)
Sub SumUpAbsolute()
Const SRC_NAME As String = "Sheet1"
Const SRC_FIRST_CELL As String = "C2"
Const DST_CELL As String = "B2"
Const INIT_VALUE As Double = 70000
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim ws As Worksheet: Set ws = wb.Sheets(SRC_NAME)
Dim srg As Range, cCount As Long
With ws.Range(SRC_FIRST_CELL)
Dim lCell As Range: Set lCell = .Resize(, ws.Columns.Count _
- .Column + 1).Find("*", , xlFormulas, , , xlPrevious)
If Not lCell Is Nothing Then
cCount = lCell.Column - .Column + 1
Set srg = .Resize(, cCount)
End If
End With
Dim Total As Double
If cCount > 1 Then
Dim Data(): Data = srg.Value
Dim pValue: pValue = Data(1, 1)
Dim cValue, c As Long
For c = 2 To cCount
cValue = Data(1, c)
If IsNumeric(pValue) Then
If IsNumeric(cValue) Then
If cValue > 0 Then Total = Total + Abs(cValue - pValue)
End If
End If
pValue = cValue
Next c
End If
Total = INIT_VALUE - Total
ws.Range(DST_CELL).Value = Total
End Sub
You can achieve this in VBA using a for loop:
Sub AbsoluteDifference(n As Double, startCell As String, outputCell As String)
' Store variables as double to account for large numbers and decimals
Dim sum As Double
sum = 0
'Range until the last filled cell
For Each i In Range(startCell, Range(startCell).End(xlToRight)).Cells
If i.Value > 0 Then
sum = sum + Abs(i.Value - i.Offset(0, -1).Value)
End If
Next i
' Save the value to outputCell
Range(outputCell).Value = n - sum
End Sub
' Run the Main sub to call AbsoluteDifference with parameters
Sub Main()
Call AbsoluteDifference(70000, "D2", "C3")
End Sub
This code produces an identical result to your function.
Related
I'm a VBA noob. I need help working out this filter:
My data has ~50,000 rows and 100 columns. The column I want to filter has values like TL-98.263138472% BD-1.736861528%. I want to filter out all the values in VBA where TL>90%. I can think of a long way of doing it - where I create a loop, break down each cell, then look at TL, then the 4 numbers next to it. But it sounds like it would take forever. Wondering if there's a faster/easier way to do it? Also wondering, if it's even worth it. If it would take even more than 2 seconds, then I would rather not do it with VBA.
I have not coded it yet, wanted to see if anyone has better ideas than what I came up with.
Thanks in advance! Adding an example of my data below:
Pretty fast in my tests:
Sub tester()
Dim ws As Worksheet, t
Dim i As Long, rng As Range, rngFilt As Range, arr, arrFilt
' For i = 2 To 50000 'create some dummy data
' Cells(i, "A") = "TL-" & 50 + (Rnd() * 60) & "% BD-1.736861528%"
' Next i
' [B2:CV50000].value="blah" 'fill rest of table
t = Timer
Set ws = ActiveSheet
If ws.FilterMode Then ws.ShowAllData
Set rng = ws.Range("A1:A" & ws.Cells(Rows.Count, "A").End(xlUp).Row) 'range of values to filter
Set rngFilt = rng.Offset(0, 110) 'a range off to the right to filter on
arr = rng.Value
arrFilt = rngFilt.Value 'for holding filtering flags
arrFilt(1, 1) = "Filter" 'column header
For i = 2 To UBound(arr, 1)
arrFilt(i, 1) = IIf(FilterOut(arr(i, 1)), "Y", "N")
Next i
rngFilt.Value = arrFilt
rngFilt.AutoFilter field:=1, Criteria1:="N"
Debug.Print Timer - t
End Sub
'does this value need to be filtered out?
Function FilterOut(v) As Boolean
Dim pos As Long
pos = InStr(v, "TL-")
If pos > 0 Then
v = Mid(v, pos + 3)
pos = InStr(v, "%")
If pos > 0 Then
v = Left(v, pos - 1)
'Debug.Print v
If IsNumeric(v) Then FilterOut = v > 90
End If
End If
End Function
This ran in <0.3 sec for me, on a 50k row X 100 col dataset
Filter Via Table Helper Column and String Parse
It you want to look into non VBA solutions, You could use a helper column to decide it it's worth filtering out.
First we need to find "TL-" in the string, then find "%" After that:
MID(A4,FIND("TL-",A4)+3,FIND("%",A4,FIND("TL-",A4)+3)-FIND("TL-",A4)-3)
This will just return us that value sub string, regardless or position.
Now we need to convert it into a value... and I'm told that --( ) isn't the correct way to convert a string to a value... but i keep using it and it keeps working.
Anyway, finally we test if that is larger than 90 like:
=IF(--(MID(A4,FIND("TL-",A4)+3,FIND("%",A4,FIND("TL-",A4)+3)-FIND("TL-",A4)-3))>90,"Remove","Keep")
Here's my example:
And the final result.
And Filtered:
Copy Values (Efficiently!?)
The Code
Option Explicit
Sub CopyData()
Dim T As Double: T = Timer
' Read Data: Write the values from the source range to an array.
' Define constants.
Const SRC_NAME As String = "Sheet1"
Const SRC_COLUMN As Long = 44
Const CRIT_STRING_LEFT As String = "TL-"
Const CRIT_VALUE_GT As Double = 90
Const DST_NAME As String = "Sheet2"
' Reference the workbook.
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Reference the source range.
Dim sws As Worksheet: Set sws = wb.Sheets(SRC_NAME)
Dim srg As Range: Set srg = sws.Range("A1").CurrentRegion
Dim srCount As Long: srCount = srg.Rows.Count
Dim cCount As Long: cCount = srg.Columns.Count
' Write to the array (practically this line uses up all the time).
Dim Data(): Data = srg.Value ' assumes at least two cells in 'srg'
Debug.Print "Read Data: " & Format(Timer - T, "0.000s")
T = Timer
' Modify Data: Write the critical values to the top of the array.
Dim cLen As Long: cLen = Len(CRIT_STRING_LEFT)
Dim dr As Long: dr = 1 ' skip headers
Dim sr As Long, c As Long
Dim cPos As Long, cNum As Double, cString As String
For sr = 2 To srCount ' skip headers
cString = CStr(Data(sr, SRC_COLUMN))
cPos = InStr(1, cString, CRIT_STRING_LEFT, vbTextCompare)
If cPos > 0 Then
cString = Right(cString, Len(cString) - cPos - cLen + 1)
cString = Replace(cString, "%", "")
cNum = Val(cString) ' 'Val' doesn't work with "!,#,#,$,%,&,^"
If cNum > CRIT_VALUE_GT Then ' 'Evaluate' is too slow!
dr = dr + 1
For c = 1 To cCount
Data(dr, c) = Data(sr, c)
Next c
End If
End If
Next sr
Debug.Print "Modify Data: " & Format(Timer - T, "0.000s")
T = Timer
' Write Data: Write the values from the array to the destination range.
If dr = 0 Then Exit Sub ' no filtered values
' Reference the destination range.
Dim dws As Worksheet: Set dws = wb.Sheets(DST_NAME)
Dim drg As Range: Set drg = dws.Range("A1").Resize(dr, cCount)
' Write to the range (practically this line uses up all the time).
drg.Value = Data
' Clear below
drg.Resize(dws.Rows.Count - drg.Row - dr + 1).Offset(dr).Clear
Debug.Print "Write Data: " & Format(Timer - T, "0.000s")
MsgBox "Data copied.", vbInformation
End Sub
The Result (Time Passed)
On a sample of 50k rows by 100 columns of data with 26k matches, the code finished in under 5s:
Read Data: 1.336s
Modify Data: 0.277s
Write Data: 3.375s
There were no blank cells and each cell in the criteria column contained the criteria string with a percentage hence it should be faster on your data. Your feedback is expected.
I am looking to count the occurrences of consecutive numbers in a column and cannot seem to find a logical way to calculate this within a loop.
My column of values is simply entries of 0 or 1. What I want to is count each time there is two 0's in a row, three 0's a row, four 0's in a row and so on. The maximum number of times I would expect a consecutive number is 15.
Ideally, I would like the output for each occurrence entered into a table.
I have provided a snapshot below of the column in question.
My attempts so far consist of looping through the column checking for two 0's in a row, starting at row 2 but this causes issues when I have more than two 0's in a row.
'Check for 2
Dim TwoCount, RowNo As Integer, LastRow As Long
LastRow = Sheets("Data").Range("A165536").End(xlUp).Row
TwoCount = 0
RowNo = 2
For i = 2 To LastRow
If Sheets("Data").Range("H" & RowNo).Value = 1 Then
RowNo = RowNo + 1
Else
If Sheets("Data").Range("H" & RowNo).Value = 0 Then
TwoCount = 1
RowNo = RowNo + 1
If Sheets("Data").Range("H" & RowNo).Value = 0 Then
TwoCount = 2
RowNo = RowNo + 1
If Sheets("Data").Range("H" & RowNo).Value = 1 Then
End If
End If
End If
End If
Next i
I welcome any suggestions to how I should approach this? Whether it's easier as a formula or array formula.
Desired output
Count Consecutive Occurrences
Option Explicit
Sub CountConsecutive()
' Source
Const sName As String = "Data"
Const sFirstCellAddress As String = "H1"
Const sCriteria As Variant = 0
' Destination
Const dName As String = "Data"
Const dFirstCellAddress As String = "J1"
Dim dHeaders As Variant
dHeaders = VBA.Array("Occurrences", "Number of Times")
' Workbook
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Write the values from the source column to an array.
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
Dim Data As Variant
Dim rCount As Long
With sws.Range(sFirstCellAddress)
Dim slCell As Range: Set slCell = .Resize(sws.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If slCell Is Nothing Then Exit Sub
rCount = slCell.Row - .Row + 1
If rCount < 2 Then Exit Sub
Data = .Resize(rCount).Value
End With
' Count the occurrences by using a dictionary.
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
Dim Key As Long
Dim r As Long
Dim cCount As Long
Dim MaxCount As Long
For r = 2 To rCount
Key = Data(r, 1)
If IsNumeric(Key) Then
If Key = sCriteria Then
cCount = cCount + 1
Else
If cCount > 0 Then
dict(cCount) = dict(cCount) + 1
If cCount > MaxCount Then MaxCount = cCount
cCount = 0
End If
End If
End If
Next r
If MaxCount = 0 Then Exit Sub
' Write the values from the dictionary to the array.
rCount = MaxCount + 1
ReDim Data(1 To rCount, 1 To 2)
Data(1, 1) = dHeaders(0)
Data(1, 2) = dHeaders(1)
For r = 2 To rCount
Data(r, 1) = r - 1
If dict.Exists(r - 1) Then
Data(r, 2) = dict(r - 1)
Else
Data(r, 2) = 0
End If
Next r
' Write the values from the array to the destination range.
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
With dws.Range(dFirstCellAddress).Resize(, 2)
.Resize(rCount).Value = Data
.Resize(dws.Rows.Count - .Row - rCount + 1).Offset(rCount).Clear
'.Font.Bold = True
'.EntireColumn.AutoFit
End With
'wb.save
MsgBox "Consecutive count created.", vbInformation
End Sub
COUNTING THE FREQUENCY OF CONSECUTIVE OCCURRENCES OF 0 IN A COLUMN
You may try this array formula as well,
• Formula used in cell L2
=SUMPRODUCT(--(FREQUENCY(
IF($H$2:$H$32=0,ROW($H$2:$H$32)),
IF($H$2:$H$32=1,ROW($H$2:$H$32)))=K2))
And Fill Down!
Note: Array formulas need to be entered by pressing CTRL + SHIFT + ENTER (not just ENTER). Hold down both the CTRL key and the SHIFT key then hit ENTER. If you are using Excel 2021 or O365 you can only press ENTER.
Imagine your numbers Win/Lose in column A then add in cell B3 (not B2 this will stay empty) the following formula and copy it down:
=IF(AND(A3=0,A3<>A4),COUNTIF($A$2:A3,A3)-SUM($B$2:B2),"")
Then to count them just use =COUNTIF(B:B,E2) in F2 and copy it down.
You can read this requirements in two ways as I see it:
You can count an occurence of 1,2,3 and 4 in a sequence of 4 zero's;
You can count only the max occurence of the above;
I went with the assumptions of the latter:
Formula in C1:
=LET(X,SEQUENCE(15),Y,LEN(TEXTSPLIT(CONCAT(IF(A2:A32," ",1)),," ",1)),VSTACK({"Occurences of 0","Number of Times"},HSTACK(X,BYROW(X,LAMBDA(a,SUM(--(Y=a)))))))
Important note:
It may not be best to rely on CONCAT() since depending on the amount of rows you want to concatenate, it may strike a character limit. Instead you could try something like:
=LET(X,SEQUENCE(15),Y,LEN(TEXTSPLIT(REDUCE("",A2:A32,LAMBDA(a,b,a&IF(b," ",1))),," ",1)),VSTACK({"Occurences of 0","Number of Times"},HSTACK(X,BYROW(X,LAMBDA(a,SUM(--(Y=a)))))))
Also, please note that ms365 is required for the above functions to run properly (and at time of writing VSTACK(), HSTACK() and TEXTSPLIT() are still in the insider's BETA-channels.
Hi I have a column of 0's and 1's I want to create a running total of the non 0 values un-till it reaches a cell value of 0. Once it hits zero it should, return an empty cell, reset to 0, and begin again from 1 at the next cell value of 1.
Any help would be appreciated, including what I might want to look at to help.
Editing with current solution:
Ive found this solution that works, how would I go about making this a function instead of using this Sub()?
Sub test()
Dim value As Integer
value = 0
For i = 1 To Range("Table2").Rows.Count
If ThisWorkbook.Worksheets("Sheet1").Range("Table2[Current Col]").Cells(i) = 0 Then
value = 0
Range("Table2[New Column]")(i) = ""
ElseIf ThisWorkbook.Worksheets("Sheet1").Range("Table2[Current Col]").Cells(i) = 1 Then
value = value + 1
Range("Table2[New Column]")(i) = value
End If
Next i
End Sub
Incrementing Groups
Use variables to avoid long unreadable lines.
Option Explicit
Sub test()
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim srg As Range: Set srg = ws.Range("Table2[Current Col]")
Dim drg As Range: Set drg = ws.Range("Table2[New Col]")
Dim sValue As Variant
Dim dValue As Variant
Dim iValue As Long
Dim i As Long
For i = 1 To srg.Cells.Count
' Read from source cell into a variable ('sValue').
sValue = srg.Cells(i).Value
' Test and write result to a variable ('dValue').
If IsNumeric(sValue) Then
If sValue = 1 Then
iValue = iValue + 1
dValue = iValue
End If
Else
iValue = 0
dValue = Empty
End If
' Write from the variable ('dValue') to the destination cell.
drg.Cells(i).Value = dValue
Next i
End Sub
As a UDF:
Function CountUp(rng As Range)
Dim arr, arrOut(), v As Long, i As Long
arr = rng.Columns(1).value
ReDim arrOut(1 To UBound(arr, 1), 1 To UBound(arr, 2))
v = 0
For i = 1 To UBound(arr, 1)
v = IIf(arr(i, 1) = 1, v + 1, 0)
arrOut(i, 1) = v
Next i
CountUp = arrOut
End Function
If your Excel version has the "autospill" feature then you can enter it as a normal function: if not then you need to select the whole output range and enter the formula using Ctrl+Shift+Enter
I'm trying to make a macro for where a user inputs a number and the even numbers are repeated in an array. I have got the code for repeating the numbers from 0-n (n being the number inputted). However, I don't know how to go about repeating the even numbers twice.
Sub Macro3()
For n = 1 To Worksheets("Sheet1").Cells(1, 2) + 1
Cells(2, 1 + n).Select
ActiveCell.FormulaR1C1 = (n - 1)
Next
End Sub
Below is the output
Current code vs what I really want
Write an Array of Integers
Writes an array of integers between 0 and the specified value in cell B1 to a row range starting from B2. Even numbers are written twice (one worksheet).
Initial Solution
This is a slow solution meant to be educational in understanding object variables (workbook-worksheet-range), ranges (Resize, Offset), loops,...
Option Explicit
Sub WriteArrayOfIntegersRange()
Const ProcTitle As String = "Write Array of Integers Range"
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim ws As Worksheet: Set ws = wb.Worksheets("Sheet1")
' Create a reference to the source cell.
Dim sCell As Range: Set sCell = ws.Range("B1")
' Write the value of the source cell to a variable.
Dim sValue As Variant: sValue = sCell.Value
Dim LastInteger As Long
' Validate the source cell value.
If IsNumeric(sValue) Then ' is a number
LastInteger = Abs(CLng(sValue)) ' positive ('Abs'), whole ('CLng')
Else ' is not a number
MsgBox "The value in cell '" & sCell.Address(0, 0) & "' ('" _
& sValue & "' is not a number.", vbCritical, ProcTitle
Exit Sub
End If
' Create a reference to the first destination cell.
Dim dCell As Range: Set dCell = ws.Range("B2"): dCell.Value = 0
Dim Size As Long: Size = 1
Dim n As Long
' Loop through the numbers and apply alternating row size (1 or 2)
' and column offset (2 or 1) before writing.
For n = 1 To LastInteger
Set dCell = dCell.Offset(, Size) ' define next first cell
Size = 2 - n Mod 2 ' calculate the size (Odd = 1, Even = 2)
dCell.Resize(, Size).Value = n ' write to the resized row range
Next n
' Clear the range to the right of the last cell to remove any previous data.
Dim crrg As Range
With dCell.Offset(, Size) ' define next first cell
' Define the range from the next first to the last worksheet cell
' in the row.
Set crrg = .Resize(, ws.Columns.Count - .Column + 1)
End With
crrg.Clear ' or crrg.ClearContents
MsgBox "Array of numbers written.", vbInformation, ProcTitle
End Sub
Using Arrays
This is a more advanced solution that utilizes the multi-purpose GetArrayOfIntegers function. By modifying the related constants (Function Parameters) in the following procedure, you can easily change the output.
Note that it returns the results in another worksheet (Sheet2).
The last procedure is created for anyone to quickly get a flavor of the GetArrayOfIntegers function. Just add a new workbook, add a new module and copy the codes to it. Modify the function parameters in the last procedure to get different results in the Immediate window (Ctrl+G).
Sub WriteArrayOfIntegers()
' Needs the 'GetArrayOfIntegers' function.
Const ProcTitle As String = "Write Array of Numbers"
' Source
Const sName As String = "Sheet1"
Const sCellAddress As String = "B1"
' Destination
Const dName As String = "Sheet2"
Const dfCellAddress As String = "B2"
' Function Parameters ' experiment with these five parameters
Const EvensCount As Long = 2
Const OddsCount As Long = 1
Const DoReturnRow As Boolean = True
Const IncludeZero As Boolean = True
Const IsZeroOdd As Boolean = True
' Workbook
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Create a reference to the source cell.
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
Dim sCell As Range: Set sCell = sws.Range(sCellAddress)
' Write the value of the source cell to a variable.
Dim sValue As Variant: sValue = sCell.Value
Dim LastInteger As Long
' Validate the source cell value.
If IsNumeric(sValue) Then ' is a number
LastInteger = Abs(CLng(sValue)) ' positive ('Abs'), whole ('CLng')
Else ' is not a number
MsgBox "The value in cell '" & sCell.Address(0, 0) & "' ('" _
& sValue & "' is not a number.", vbCritical, ProcTitle
Exit Sub
End If
' Return the result (an array) of the 'GetArrayOfIntegers' function.
Dim Data As Variant: Data = GetArrayOfIntegers( _
LastInteger, EvensCount, OddsCount, DoReturnRow, IncludeZero, IsZeroOdd)
' Without the constants it would be:
'Data = GetArrayOfIntegers(LastInteger, 2, 1, True, True, True)
If IsEmpty(Data) Then Exit Sub
Dim drCount As Long: drCount = UBound(Data, 1)
Dim dcCount As Long: dcCount = UBound(Data, 2)
' Create a reference to the first destination cell.
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
Dim dfCell As Range: Set dfCell = dws.Range(dfCellAddress)
' Clear all cells next to (to the right of) and below
' the first destination cell.
Dim dcrg As Range: Set dcrg = dfCell.Resize( _
dws.Rows.Count - dfCell.Row + 1, dws.Columns.Count - dfCell.Column + 1)
dcrg.Clear ' or dcrg.ClearContents
' Create a reference to the destination range.
Dim drg As Range: Set drg = dfCell.Resize(drCount, dcCount)
' Write the values from the array to the destination range.
drg.Value = Data
MsgBox "Array of numbers written.", vbInformation, ProcTitle
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Author: VBasic2008
' Dates: 20211101
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns an array of integers in a 2D one-based array.
' Remarks: The first element is always 0 or 1.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetArrayOfIntegers( _
ByVal LastInteger As Long, _
Optional ByVal EvensCount As Long = 1, _
Optional ByVal OddsCount As Long = 1, _
Optional ByVal DoReturnRow As Boolean = False, _
Optional ByVal IncludeZero As Boolean = False, _
Optional ByVal IsZeroOdd As Boolean = False) _
As Variant
Dim eoArr() As Long: ReDim eoArr(0 To 1)
eoArr(0) = EvensCount: eoArr(1) = OddsCount
Dim zCount As Long
If IncludeZero Then
If IsZeroOdd Then zCount = OddsCount Else zCount = EvensCount
End If
Dim iMod As Long: iMod = LastInteger Mod 2
Dim eCount As Long: eCount = Int(LastInteger / 2)
Dim oCount As Long: oCount = Int(LastInteger / 2) + iMod
Dim dtCount As Long
dtCount = eCount * EvensCount + oCount * OddsCount + zCount
Dim Data As Variant
Dim dt As Long: dt = 1
Dim n As Long
Dim r As Long
If DoReturnRow Then
ReDim Data(1 To 1, 1 To dtCount)
If zCount > 0 Then
For dt = 1 To zCount: Data(1, dt) = 0: Next dt
End If
For n = 1 To LastInteger
For r = 1 To eoArr(n Mod 2)
Data(1, dt) = n
dt = dt + 1
Next r
Next n
Else
ReDim Data(1 To dtCount, 1 To 1)
If zCount > 0 Then
For dt = 1 To zCount: Data(dt, 1) = 0: Next dt
End If
For n = 1 To LastInteger
For r = 1 To eoArr(n Mod 2)
Data(dt, 1) = n
dt = dt + 1
Next r
Next n
End If
GetArrayOfIntegers = Data
End Function
' This is an unrelated example to play with.
' Note that changing the fourth parameter will make no difference since
' the results are written to the Immediate window (Ctrl+G).
Sub GetArrayOfIntegersTEST()
' Needs the 'GetArrayOfIntegers' function.
Dim Data As Variant: Data = GetArrayOfIntegers(4, 3, 2, False, False, False)
Dim r As Long, c As Long
For r = 1 To UBound(Data, 1)
For c = 1 To UBound(Data, 2)
Debug.Print Data(r, c)
Next c
Next r
End Sub
Fast alternative via ArrayList
Working with an ArrayList (disposing btw of methods like .Sort,.Remove, .Insert, .Reverse) may be a convenient way to manipulate array data in a very readable way. It is not part of VBA, but can be accessed easily via late binding (referring to .Net library mscorlib.dll).
Option Explicit ' code module head
Sub DoubleEvenNumbersGreaterOne()
'a) define upper limit
Dim ws As Worksheet
Set ws = Sheet1 ' << change to project's sheet Code(Name)
Dim Limit As Long
Limit = ws.Range("B1")
'b) declare ArrayList
Dim arr As Object ' late bind .Net mscorlib.dll
Set arr = CreateObject("System.Collections.ArrayList")
'c) populate list array
arr.Add 0 ' start adding with zero
Dim i As Long
For i = 1 To Limit ' loop through sequence 1:Limit
arr.Add i ' add current number
If i Mod 2 = 0 Then arr.Add i ' additional even number
Next
'd) get array
Dim a As Variant: a = arr.ToArray ' change ArrayList object to VBA array
'Debug.Print Join(a, "|") ' optional check in VB Editor's immediate window
'e) write 0-based 1-dim array to ws (here: Sheet1) or declare another target worksheet (e.g. ws2)
With ws.Range("B2")
.EntireRow = vbNullString ' empty target row
.Resize(1, UBound(a) + 1) = a ' write values into correct number of cells
End With
End Sub
A formula oriented approach // late edit as of 11/1 2021
a) A first and very elementary way would be to
enter formula =COLUMN(A1)-INT((COLUMN(A1)+2)/3) into cell B2 and to
copy into the right neighbour cells as long as you get the wanted maximum
b) Refining this approach you can code the following udf accepting the wanted maximum as argument (note that I changed the flat Column reference to a vertical Row reference to simplify calculation of the actual maxima):
Function Sequ(ByVal maxNo As Long)
Dim myFormula As String
myFormula = Replace("=ROW(1:$)-INT((ROW(1:$)+2)/3)", "$", maxNo + maxNo \ 2 + 1)
Sequ = Application.Transpose(Evaluate(myFormula))
End Function
A direct formula entry of e.g. =Sequ(10) into B2 benefitting from the newer dynamic features of vers. 2019+/MS 365 would display all (row) elements automatically in a so called spill range without need of further inputs.
Using VBA to display results in VB Editor's immediate window could be coded as follows: Debug.Print Join(Sequ(10), "|") resulting in
0|1|2|2|3|4|4|5|6|6|7|8|8|9|10|10
or to assign the results to a variable that can be used in further code.
Your code is really ok, just add question is number even and one more variable to see where to write. Also just change n loop from 0:
Sub Macro3()
For n = 0 To Worksheets("Sheet1").Cells(1, 2)
a = a + 1
Cells(2, 2 + a).Select
ActiveCell.FormulaR1C1 = n
'check if number is even and check if a > 1 because we don't want to repeat 0
If n Mod 2 = 0 And a > 1 Then
a = a + 1
Cells(2, 2 + a).Select
ActiveCell.FormulaR1C1 = n
End If
Next
End Sub
Try this code
Sub Test()
Dim v, ws As Worksheet, i As Long, ii As Long, n As Long
Set ws = ThisWorkbook.Worksheets("Sheet1")
v = ws.Range("B1").Value
If Not IsNumeric(v) Or IsEmpty(v) Then MsgBox "Must Be Number", vbExclamation: Exit Sub
ReDim a(1 To (v / 2) + v)
For i = 1 To v
If i Mod 2 = 0 Then
For ii = 1 To 2
n = n + 1: a(n) = i
Next ii
Else
n = n + 1: a(n) = i
End If
Next i
Range("C2").Resize(, UBound(a)).Value = a
End Sub
I wish to have a VBA macro that will help me to edit values in the column 'C' for each row until the sum value has reached.
However, there are some criteria:
The value has to be smaller than the value in column 'B'
If the value in column 'B' is zero, then the value in column 'C' should be zero
For example:
I have certain values in column A for each row and I want the sum for Column C to be 10. Hence, the VBA will loop and iterate each row in column C and check if the number in Column B is greater than 0, if yes then it will add 1 to it. After going through each row, it will check the sum, if the sum has not reach the certain amount (in this case, it's 10), it will loop back again and add 1 to each row and stop when it reaches the sum.
Example output:
----------------------
Column B | Column C
----------------------
124 | 3
100 | 3
83 | 2
23 | 1
4 | 1
0 | 0
-----------------------
Code:
Sub Loop()
Dim Report As Worksheet
Set Report = Excel.ActiveSheet
Dim cell As Range
Dim i As Integer
Dim total As Integer
total = Range("C8").Value
Range("C2:C7").ClearContents
For total = 0 To 10
For Each cell In Range("C2:C12")
For i = 2 To 7
If Range("B" & i).Value > 0 Then
cell.Value = cell.Value + 1
If cell.Value > Range("B" & i).Value Then
cell.Value = cell.Value
End If
Else:
cell.Value = 0
End If
Next i
Next cell
total = total + Range("C8").Value
Next total
End Sub
However, the output that I got seems to be not my desire output and I got all zeros instead. I am a newbie to VBA :(, can anyone help me with this?
Below is my take on your problem but a couple of things to start...
VBA didn't much like the sub having the name loop as loop is a reserved word
I've used methods intersect and offset when working with ranges but there are multiple ways to skin this cat
Range("C2:C7").ClearContents will result in empty cells and while an empty cell will likely be considered 0 when trying to add 1 to the value, it would perhaps be better to give the cells an explicit value e.g. 0
There exists some cases where a total of, for example, 10 can't be reached and if not tested for and handled, the loop will run forever, never reaching 10
I'm not sure if msgbox is appropriate to your task but I popped it in anyway in case you haven't yet come across it
Sub SomeLoop()
Set totalCell = Range("C8")
Set editableColumnRange = Range("C2:C7")
'set all the editable cells to a default/initial value
editableColumnRange.Value = 0
totalAtEndOfLastLoop = 0
total = 0
Offset = 0
Do While total < 10
'set the current row to the current row offset past the top row of the editable cells
Set currentRowRange = editableColumnRange.Rows(1).EntireRow.Offset(Offset)
Set currentCCell = Intersect(currentRowRange, Range("C:C"))
Set currentBCell = Intersect(currentRowRange, Range("B:B"))
'implement the rules
If currentBCell.Value = 0 Then
currentCCell.Value = 0
ElseIf currentBCell.Value > currentCCell.Value + 1 Then
'to ensure b remains > c, we need to test b > c + 1
'so if c is incremented by 1, it remains less than b
currentCCell.Value = currentCCell.Value + 1
total = total + 1
End If
Offset = Offset + 1
If Offset >= editableColumnRange.Rows.Count Then
'we've got an offset which would task us past the data
'it's time to wrap around
'check if the total has changed otherwise we'll be stuck in the loop forever
If total > totalAtEndOfLastLoop Then
totalAtEndOfLastLoop = total
Offset = 0
Else
MsgBox "The total hasn't changed"
Exit Do
End If
End If
Loop
End Sub
Increment While Less Than
Adjust the values in the constants section and the worksheets (possibly workbooks).
Option Explicit
Sub doIncrement()
' Constants
Const sFirst As String = "B2"
Const dFirst As String = "C2"
Const iTotal As Long = 10
' Worksheets (could be different)
Dim sws As Worksheet: Set sws = ActiveSheet
Dim dws As Worksheet: Set dws = ActiveSheet
' Create a reference to the Source Range.
Dim srg As Range
With sws.Range(sFirst)
Dim lCell As Range
Set lCell = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If lCell Is Nothing Then Exit Sub
Set srg = .Resize(lCell.Row - .Row + 1)
End With
' Write values from Source Range to Source Array.
Dim rCount As Long: rCount = srg.Rows.Count
Dim sData As Variant
If rCount = 1 Then
ReDim sData(1 To 1, 1 To 1): sData = srg.Value
Else
sData = srg.Value
End If
' Define Destination Array.
Dim dData() As Long: ReDim dData(1 To rCount, 1 To 1) ' Long: all zeros
Dim r As Long
Dim dTotal As Long
' Loop through rows of Source Array and write to rows of Destination Array.
Do
For r = 1 To rCount
If sData(r, 1) - dData(r, 1) > 1 Then
dData(r, 1) = dData(r, 1) + 1
dTotal = dTotal + 1
If dTotal = iTotal Then
Exit Do
End If
End If
Next r
Loop
With dws.Range(dFirst)
' Write values from Destination Array to Destination Range.
.Resize(rCount).Value = dData
' Clear contents below.
.Resize(.Worksheet.Rows.Count - .Row - rCount + 1) _
.Offset(rCount).ClearContents
End With
End Sub