Sequential numbers on column with a maximum range Excel VBA - arrays

I'm completely new to VBA and I'm been trying to make basic problems to practice.
I just wanna fill a column with sequential numbers from 1 to N. The N number will be a value on an specific cell.
So the N value is on the C4 cell for example is 5 , and I wanna output from B2 - BN = 1,2,3,4,5
I have this code based on kinda similar questions and my knowledge of cycles but I can't make it work...
Sub ejemplo()
Dim total() As Variant
maximo = Range("C4").Value
For i = 1 To maximo
total(i) = i
Next i
total = Application.WorksheetFunction.Transpose(total)
Range("B7:B").Value = total
End Sub
The error that sometimes pop out is "out of range" on the total(i) = i line, I really don't know what's happening...

Array to Worksheet
In all three cases instead of For i = ... you can use: For i = LBound(total) To UBound(total).
Transpose is limited to a maximum of 65536 items, so study the
third solution which doesn't use it.
The Code
Option Explicit
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Writes the numbers from 1 to "maximo" to the column range
' starting with cell "B7".
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' 1D array (0-based, 'one-row')
Sub ejemplo1()
Dim total() As Variant
Dim maximo As Long
Dim i As Long
maximo = Range("C4").Value
ReDim total(maximo - 1)
For i = 0 To maximo - 1
total(i) = i + 1
Next i
total = Application.Transpose(total)
Range("B7").Resize(UBound(total)).Value = total
End Sub
' 1D array (1-based, 'one row')
Sub ejemplo2()
Dim total() As Variant
Dim maximo As Long
Dim i As Long
maximo = Range("C4").Value
ReDim total(1 To maximo)
For i = 1 To maximo
total(i) = i
Next i
total = Application.Transpose(total)
Range("B7").Resize(UBound(total)).Value = total
End Sub
' 2D array (1-based, 'one column')
Sub ejemplo3()
Dim total() As Variant
Dim maximo As Long
Dim i As Long
maximo = Range("C4").Value
ReDim total(1 To maximo, 1 To 1)
For i = 1 To maximo
total(i, 1) = i
Next i
Range("B7").Resize(UBound(total)).Value = total
End Sub

Pretty close:
Sub ejemplo()
Dim total As Variant
maximo = Range("C4").Value
ReDim total(1 To maximo) As Long
For i = 1 To maximo
total(i) = i
Next i
total = Application.WorksheetFunction.Transpose(total)
Range("B7").Resize(maximo, 1).Value = total
End Sub
NOTE:
the ReDim statement
the statement with Resize
EDIT#1:
If you are using Excel 365, then the code can be reduced to a single line:
Sub NoLoops()
Range("B7").Formula2 = "=SEQUENCE(" & Range("C4").Value & ",1,1,1)"
End Sub

The standard method for counting is
i = i + 1
As you repeatedly call this function i counts up. Applied to your problem, the loop For i = 1 To maximo would count the numbe of loops but it doesn't give the initial i, the number to start from. Therefore, what you need is this:-
Dim MyNumber As Integer
Dim i As Integer
MyNumber = 0
For i = 1 To maximo
MyNumber = MyNumber + 1
Next i
The next task is to define the cells to write to. It's basically the same logic. You need a point to start from, say B2.
Dim StartCell As Range
Set StartCell = Range("B2")
And now you can put it all together.
Dim Maximo As Integer
Dim StartCell As Range
Dim MyNumber As Integer
Dim i As Integer
Maximo = Range("C4").Value
MyNumber = 0
Set StartCell = Range("B2")
For i = 1 To maximo
MyNumber = MyNumber + 1
StartCell.Offset(0, i - 1).Value = MyNumber
Next i

I'd always recommend to at least be explicit about your Worksheet reference. I'd use a With statement making use of the sheet's CodeName.
Furthermore, I'd like to add another answer that creates an array through Evaluate(). While this function has a limit of 255 chars, in this exercise that would never be at risk.
Sub Test()
Dim total As Variant
With Sheet1
total = .Evaluate("ROW(1:" & .[C4] & ")")
.Range("B7").Resize(UBound(total)).Value = total
End With
End Sub
Once you are comfortable with what you are looking at here, you can do this in one shot without (IMO) ruining readability:
With Sheet1
.Range("B7").Resize(.[C4]).Value = .Evaluate("ROW(1:" & .[C4] & ")")
End With

Related

editing array values VBA -- using Instr & Split -- Date output eccentricities

Have a 2d Array, need to search through one of the columns finding a string and deleting everything after it.
I have a list of dates, but the format it is currently in has a long Time value
after the date ending in 2019. I would like to find and replace 2019 + time
with just 2019.
Edit code
The date isn't stored as a date, for all intents and purposes it's a string that looks something like "****#### 2019 ######" and I am just looking for a method to remove everything after a value, (2019) .
Right now, it steps through it all nicely checks array value by value
but doesn't actually change anything.
Edit2
Found workable solution using Instr & Split functions.
BUT the weirdest bug crept in,
some dates appear fine in debug.print
eg : 11/06/2019 BUT after printing to a range 06/11/2019
13/06/2019 13/06/2019
Even if the format of the destination is pre-defined
Public Sub PrintArray(Data As Variant, Cl As Range)
Cl.Resize(UBound(Data, 1), UBound(Data, 2)) = Data
End Sub
Private Sub Test()
Dim Name_col As Integer
Dim Date_col As Integer
Dim Hours_col As Integer
Dim Department_col As Integer
Dim Data_row As Integer
Name_col = 1
Date_col = 2
Hours_col = 3
Department_col = 4
Data_row = 2
Dim i As Integer
Dim zom As Integer
Dim DirArray As Variant
Dim col As Integer
Dim LString As String
Dim LArray() As String
zom = 0
i = 2
col = 2
Dim X As Integer
Application.ScreenUpdating = False
Do While Sheets("Sheet2").Cells(i, 1).Value <> ""
i = i + 1
zom = zom + 1
Loop
Application.ScreenUpdating = True
NumberOfZombies = zom
Debug.Print "Number of zombies" & NumberOfZombies
Worksheets("Sheet2").Activate
DirArray = Sheets("Sheet2").Range(Cells(Data_row, Name_col), Cells(zom, Department_col)).Value
For rw = LBound(DirArray) To UBound(DirArray)
For col = LBound(DirArray) To UBound(DirArray, 2)
LString = DirArray(rw, col)
If InStr(LString, "2019") > 0 Then
LArray = Split(LString)
Debug.Print LArray(0)
DirArray(rw, col) = LArray(0)
End If
Debug.Print DirArray(rw, col)
Next
Next
PrintArray DirArray, Sheets("Sheet3").[A1]
End Sub

Check if Value is in an Array and if it's not add it to the end

I'm trying to create an array with only unique values (Signal Names). For example my spreadsheet looks like this
Voltage
Voltage
Voltage
Current
Current
Current
etc....
I've got 32 signals however, I want this to work even if I don't know I have 32 signals explicitly i.e. 17 signals.
Signals("Voltage", "Current", "Etc....")
IN THE CODE BELOW
I realize I'm trying to ReDim an array within a loop and that's the problem. I'm just not able to think of another way of doing this. I would prefer to keep it as an array problem and not a dictionary or collection problem for now.
Public Sub Signals()
Dim myArray() As Variant
Dim Signals() As Variant
Dim element As Variant
Dim intA As Integer
WsName = ActiveSheet.Name
intRows = Sheets(WsName).Range("B2", Sheets(WsName).Range("B" & Sheets(WsName).Rows.Count).End(xlUp)).Rows.Count
intRows = intRows + 1
ReDim Signals(1)
Signals(1) = Sheets(WsName).Cells(4, 2).Value
For intA = 4 To intRows
For Each element In Signals()
If element <> Sheets(WsName).Cells(intA, 2) Then
ReDim Signals(UBound(Signals) + 1) 'This throws the error
Signals(UBound(Signals)) = Sheets(WsName).Cells(intA, 2).Value
End If
Next element
Next
End Sub
How the code doesn't work - RunTime Error '10' Array is temporarily fixed or locked.
I posted a solution to this issue using arrays in a similar question a couple days ago - using column B for your case, this would do the trick.
Aside from this solution, you have several problems in your current code - you're testing against each individual element in your current array without checking them all first, you're not using ReDim Preserve, and you need (0 to 0), not just a single (0) or (1). You're also naming your subroutine "Signals" while attempting to declare a variable "Signals" in the subroutine as well... That'll cause all kinds of issues.
Sub Test()
Dim list() As Variant
Dim inlist As Boolean
Dim n As Long, i As Long, j As Long, endrow As Long, colnum As Long
ReDim list(0 To 0)
inlist = False
j = 0
colnum = 2 'Column B in this case
endrow = Cells(Rows.Count, colnum).End(xlUp).Row
For n = 1 To endrow
For i = 0 To UBound(list)
If list(i) = Cells(n, colnum).Value Then
inlist = True
Exit For
End If
Next i
If inlist = False Then
ReDim Preserve list(0 To j)
list(j) = Cells(n, colnum).Value
j = j + 1
End If
inlist = False
Next n
For i = 0 To UBound(list)
Debug.Print list(i)
Next i
End Sub
Even simpler solution thanks to #user10829321's suggestions:
Sub Test()
Dim list() As Variant
Dim n As Long, i As Long, j As Long, endrow As Long, colnum As Long
ReDim list(0 To 0)
j = 0
colnum = 2 'Column B in this case
endrow = Cells(Rows.Count, colnum).End(xlUp).Row
For n = 1 To endrow
If IsError(Application.Match(Cells(n, colnum).Value, list, 0)) Then
ReDim Preserve list(0 To j)
list(j) = Cells(n, colnum).Value
j = j + 1
End If
Next n
For i = 0 To UBound(list)
Debug.Print list(i)
Next i
End Sub
An optional, if perhaps unwanted, solution using a scripting dictionary to give an array.
Public Function Signals(ByRef this_worksheet_range As excel.Range) As Variant()
Dim myArray() As Variant
Dim element As Variant
Dim interim_dic As Scripting.Dictionary
myArray = this_worksheet_range.values2
Set interim_dic = New Scripting.Dictionary
For Each element In myArray
If Not interim_dic.Exists(element) Then
interim_dic.Add Key:=element, Item:=element
End If
Next
Signals = interim_dic.Items
End Function

VBA - How to take a single column as input array then output the array removing all odd numbers

I have a very basic question, but would love to know how to do this. I want to write a function in VBA where I can highlight a column as an input, and then spit out the result somewhere else.
Thanks in advance :)
e.g. column A
--------
10
8
5
6
1
3
2
becomes:
column A
--------
10
8
6
2
I just did it from column a to b, but you probably want range as the current selection and a different output column.
Option Explicit
Sub filterlist()
Dim rng As Range
Set rng = Range("a1:a5")
Dim celluse As Range
Dim arr As Variant
For Each celluse In rng
If celluse.Value Mod 2 = 0 Then
If IsEmpty(arr) Then
arr = Array(celluse.Value)
Else
ReDim Preserve arr(UBound(arr) + 1)
arr(UBound(arr)) = celluse.Value
End If
End If
Next celluse
Dim i As Long
For i = 0 To UBound(arr)
Range("b" & i + 1) = arr(i)
Next i
End Sub
This code should do the trick.
You can enter as an array-formula directly to a sheet: {=RemoveOdds(A1:A7)} or as part of another procedure:
Sub Test()
RemoveOdds Selection
End Sub
Public Function RemoveOdds(Target As Range) As Variant
Dim vFinal() As Variant
Dim rCell As Range
Dim x As Long
ReDim vFinal(1 To Target.Cells.Count)
x = 1
For Each rCell In Target
If rCell Mod 2 = 0 Then
vFinal(x) = rCell.Value
x = x + 1
End If
Next rCell
'So missing values do not show up as 0 at bottom of array.
' Do While x <= Target.Cells.Count
' vFinal(x) = ""
' x = x + 1
' Loop
ReDim Preserve vFinal(1 To x - 1)
'RemoveOdds = vFinal 'Basic array - will place values horizontally on sheet.
RemoveOdds = Application.Transpose(vFinal) 'Will place values vertically on sheet.
End Function

how to fill data from worksheet into 2D array

if i have data filled in worksheet like shown in image, i want to create a 2D array and fill it with data in such way of the selected cells in the image, i.e to take the 1st value and skip the next two values and so on till the end of the array and by same way in columns
i made a solution which delete the intermediate rows and columns but for large array (example of 1000*1000),it takes a lot of time that is why i thought in another way to create array with the above criteria.
this is the code i used for deleting the intermediate rows and columns:
Sub Sorting()
Dim LastRow As Long
LastRow = sh.Range("A1", sh.Range("A1").End(xlDown)).rows.count
For cntr = 1 To LastRow / 3
rows(cntr + 1 & ":" & cntr + 2).EntireRow.Delete
Next
Dim LastColumn As Long
LastColumn = sh.Range("A1").CurrentRegion.Columns.count
K = LastColumn
For cntr = 1 To K / 3
Columns(cntr + 1).EntireColumn.Delete
Columns(cntr + 1).EntireColumn.Delete
Next
End Sub enter code here
Something like this should work for you:
Sub tgr()
Dim ws As Worksheet
Dim aData As Variant
Dim aResults() As Variant
Dim lRow As Long, lCol As Long
Dim i As Long, j As Long
Dim lRowInterval As Long
Dim lColInterval As Long
Set ws = ActiveWorkbook.ActiveSheet
lRowInterval = 3
lColInterval = 3
aData = ws.Range("A1").CurrentRegion
ReDim aResults(1 To Int(UBound(aData, 1) / lRowInterval), 1 To Int(UBound(aData, 2) / lColInterval))
i = 0
For lRow = 1 To UBound(aData, 1) Step lRowInterval
i = i + 1
j = 0
For lCol = 1 To UBound(aData, 2) Step lColInterval
j = j + 1
aResults(i, j) = aData(lRow, lCol)
Next lCol
Next lRow
'Do what you want with the array aResults here
End Sub

Get values from union of non-contiguous ranges into array with VBA with a simple command (no loops)

I have the following (on the surface of it, simple) task:
Copy the values from a number of columns on a spreadsheet into a 2D array using VBA.
To make life more interesting, the columns are not adjacent, but they are all of the same length. Obviously one could do this by looping over every element in turn, but that seems very inelegant. I am hoping there is a more compact solution - but I struggle to find it.
Here are some attempts of what I would consider "a simple approach" - for simplicity, I am putting the range as A1:A5, D1:D5 - a total of 10 cells in two ranges.
Private Sub testIt()
Dim r1, r2, ra, rd, rad
Dim valString, valUnion, valBlock
Set r1 = Range("A1:A5")
Set r2 = Range("D1:D5")
valString = Range("A1:A5,D1:D5").Value
valUnion = Union(r1, r2).Value
valBlock = Range("A1:D5").Value
End Sub
When I look at each of these variables, the first two have dimension (1 To 5, 1 To 1) while the last one has (1 To 5, 1 To 4). I was expecting to get (1 To 5, 1 To 2) for the first two, but that was not the case.
I would be happy if I could loop over the data one column at the time, and assign all the values in one column to one column in the array - but I could not figure out how to do that either. Something like
cNames = Array("A", "D")
ci = 1
For Each c in columnNames
vals( , ci) = Range(c & "1:" & c & "5").Value
ci = ci + 1
Next c
But that's not the right syntax. The result I want to get would be achieved with
cNames = Array("A", "D")
ci = 1
For Each c in columnNames
For ri = 1 To 5
vals(ri , ci) = Range(c & "1").offset(ri-1,0).Value
Next ri
ci = ci + 1
Next c
But that's pretty ugly. So here is my question:
Is it possible to get the values of a "composite range" (multiple non-contiguous blocks) into an array - either all at once, or a columns at a time? If so, how do I do it?
For extra bonus points - can anyone explain why the arrays returned in testIt() are dimensioned Base 1, whereas my VBA is set to Option Base 0? In other words - why are they not (0 To 4, 0 To 0)? Is this just one more inconsistency on the part of Microsoft?
Provided each area in rng has the same number of rows then this should work.
Function ToArray(rng) As Variant()
Dim arr() As Variant, r As Long, nr As Long
Dim ar As Range, c As Range, cnum As Long, rnum As Long
Dim col As Range
nr = rng.Areas(1).Rows.Count
ReDim arr(1 To nr, 1 To rng.Cells.Count / nr)
cnum = 0
For Each ar In rng.Areas
For Each col In ar.Columns
cnum = cnum + 1
rnum = 1
For Each c In col.Cells
arr(rnum, cnum) = c.Value
rnum = rnum + 1 'EDIT: added missing line...
Next c
Next col
Next ar
ToArray = arr
End Function
Usage:
Dim arr
arr = ToArray(Activesheet.Range("A1:A5,D1:D5"))
Debug.Print UBound(arr,1), UBound(arr,2)
As for why array from rng.Value are 1-based instead of zero-based, I'd guess it's because that maps more readily to actual row/column numbers on the worksheet than if it were zero-based. The Option Base x setting is ignored
It is possible to accomplish what you want if you're willing to add a hidden worksheet. I used Excel 2010 and created two worksheets (Sheet1 / Sheet2) to test my findings. Below is the code:
Private Sub TestIt()
' Src = source
' Dst = destination
' WS = worksheet
Dim Data As Variant
Dim SrcWS As Excel.Worksheet
Dim DstWS As Excel.Worksheet
' Get a reference to the worksheet containing the
' source data
Set SrcWS = ThisWorkbook.Worksheets("Sheet1")
' Get a reference to a hidden worksheet.
Set DstWS = ThisWorkbook.Worksheets("Sheet2")
' Delete any data found on the hidden worksheet
DstWS.UsedRange.Columns.EntireColumn.Delete
' Copy the non-contiguous range into the hidden
' worksheet.
SrcWS.Range("A1:A5,D1:D5").Copy DstWS.Range("A1")
' Now all of the data can be stored in a variable
' as a 2D array because it will be contiguous on
' the hidden worksheet.
Data = DstWS.UsedRange.Value
End Sub
Tim,
Thanks for your sample code. I had some problems with it and had to rewrite some portions of it. It wasn't counting through the rows and columns correctly. I have test this and it is working 100%
Function ToArray(rng As Range) As Variant()
Dim arr() As Variant, r As Long, nr As Long
Dim ar As Range, c As Range, cnum As Long, rnum As Long
Dim col As Range
Dim lastrow As Integer
Dim saverow() As Integer
Dim lastcolumn As Integer
Dim templastcolumn As Integer
For i = 1 To rng.Areas.Count
templastcolumn = (rng.Areas(i).Column + rng.Areas(i).CountLarge) - 1
If lastrow <> rng.Areas(i).Row Then
nr = nr + rng.Areas(i).Rows.Count
lastrow = rng.Areas(i).Row
End If
If lastcolumn < templastcolumn Then lastcolumn = templastcolumn
Next i
ReDim arr(1 To nr, 1 To lastcolumn)
ReDim saverow(1 To lastrow)
cnum = 0
rnum = 0
lastrow = 0
For Each ar In rng.Areas
If lastrow <> ar.Row Then
lastrow = ar.Row
cnum = 0
End If
For Each col In ar.Columns
cnum = cnum + 1
For Each c In col.Cells
If saverow(c.Row) = 0 Then
rnum = rnum + 1
saverow(c.Row) = rnum
End If
arr(saverow(c.Row), cnum) = c.value
Next c
Next col
Next ar
ToArray = arr
End Function
Sub TestCopyArray()
Dim arr As Variant
arr = ToArray(ThisWorkbook.Sheets("MSS").Range("B1:D2,G1:J2,B4:D4,B6:D6"))
ThisWorkbook.Sheets("Sheet1").Range("A1").Resize(UBound(arr, 1), UBound(arr, 2)) = arr
End Sub

Resources