Writing from an array to a range - vba

I am working on a list and doing all the calculations on VBA however when i want to write my list to the predefined range i get nothing. The following is a an example of the code i'm using. I am not posting the actual code because it's long however this example has the same problem.
Option Explicit
Sub readArray()
Dim CoGrade() As Variant
Dim LastRow As Integer
Dim NPSeQuedan() As Variant
Dim SeQuedanRng As Range
'erases information from arrays if there was any
Erase CoGrade
Erase NPSeQuedan
'-------------------------------------------------------------------------
'find the last row on the data i want to read
LastRow = Range("b10000").End(xlUp).Row
'the relevant data starts on row 34
ArrayRows = LastRow - 34 + 1
'redifines the variables with the total numbers of stocks in the portfolio
ReDim CoGrade(ArrayRows, 1)
ReDim NPSeQuedan(ArrayRows, 1)
'reads each relevant number into its proper variable
CoGrade = Range(Cells(34, 2), Cells(LastRow, 2))
'' test
Set SeQuedanRng = Range(Cells(34, 13), Cells(34 + ArrayRows - 1,
13))
For a = 1 To ArrayRows
NPSeQuedan(a, 1) = CoGrade(a, 1)
Next
SeQuedanRng.Value = NPSeQuedan
'''
end sub

Here is another solution (though #SJR 's idea of using 1-dimensional arrays is good). I added various points about your original code in the comments to the code:
Sub readArray()
Dim CoGrade As Variant 'Don't bother with ()
Dim LastRow As Long 'Integer risks overflow
Dim A As Long, ArrayRows As Long 'you use these -- so declare it
Dim NPSeQuedan As Variant 'etc.
Dim SeQuedanRng As Range
'erases information from arrays if there was any
'Erase CoGrade -- VBA is garbage collected and these have just been declared, so 100% pointless
'Erase NPSeQuedan
'-------------------------------------------------------------------------
'find the last row on the data i want to read
LastRow = Cells(Rows.Count, "B").End(xlUp).Row 'why hard-wire in 10000?
'the relevant data starts on row 34
ArrayRows = LastRow - 34 + 1
'redifines the variables with the total numbers of stocks in the portfolio
'ReDim CoGrade(ArrayRows, 1) -- pointless
ReDim NPSeQuedan(1 To ArrayRows, 1 To 1) 'this is important for what you are doing
'reads each relevant number into its proper variable
CoGrade = Range(Cells(34, 2), Cells(LastRow, 2)).Value
'' test
Set SeQuedanRng = Range(Cells(34, 13), Cells(34 + ArrayRows - 1, 13))
For A = 1 To ArrayRows
NPSeQuedan(A, 1) = CoGrade(A, 1)
Next
SeQuedanRng.Value = NPSeQuedan 'works now!
End Sub

You can do it like this, which incorporates several of the comments made by John Coleman.
Sub readArray()
Dim CoGrade As Variant
Dim LastRow As Long, ArrayRows as Long, a as Long
Dim NPSeQuedan() As Variant
Dim SeQuedanRng As Range
'find the last row on the data i want to read
LastRow = Range("b10000").End(xlUp).Row
'the relevant data starts on row 34
ArrayRows = LastRow - 34 + 1
'redifines the variables with the total numbers of stocks in the portfolio
ReDim NPSeQuedan(1 To ArrayRows)
'reads each relevant number into its proper variable
CoGrade = Range(Cells(34, 2), Cells(LastRow, 2))
Set SeQuedanRng = Range(Cells(34, 13), Cells(34 + ArrayRows - 1, 13))
For a = 1 To ArrayRows
NPSeQuedan(a) = CoGrade(a, 1)
Next
SeQuedanRng.Value = Application.Transpose(NPSeQuedan)
End Sub

Related

Reverse a selection of data from a specific column if value in a cell meets a criteria

I have a form where users enter the name of a project and the type of transaction.
I have written a macro that returns a selection of data from a table based on the name of the project the user entered, and it works perfectly.
Now I need to add in a function that reverses the order of that same list if the user enters a specific transaction type, it reverses the order of the same list of data.
For example, if type A returns:
Bob
Jerry
Andrew
Jeff
Then type B would reverse that order and return:
Jeff
Andrew
Jerry
Bob
The VBA I wrote for the first portion, to return the list based on project name is:
Sub finddata()
Dim projectName As String
Dim transactionType As String
Dim finalRow As Integer
Dim i As Integer
Sheets("Template_Test").Range("G10:I38").ClearContents
projectName = Sheets("Template_Test").Range("E10").Value
finalRow = Sheets("Project_Structure").Range("A20000").End(xlUp).Row
transactionType = Sheets("Template_Test").Range("E14").Value
For i = 2 To finalRow
Sheets("Project_Structure").Activate
If Cells(i, 1) = projectName Then
Sheets("Project_Structure").Range(Cells(i, 2), Cells(i, 4)).Copy
Sheets("Template_Test").Activate
Sheets("Template_Test").Range("G100").End(xlUp).Offset(1, 0).PasteSpecial xlPasteFormulasAndNumberFormats
End If
Next i
Sheets("Template_Test").Range("E10").Select
End Sub
I can get the selection to reverse order using the built in vba function strReverse and a specific range, but my data is not a consistent length of cells - sometimes it's 6 names and sometimes it's 15 - and I can't figure out how to get it to adjust the length it needs to reverse without including blank cells underneath the range.
Here is a method using the .Reverse method of ArrayList object
Option Explicit
Public Sub ReverseAList()
Dim ws As Worksheet, arr(), i As Long, aList As Object, lastRow As Long
Set ws = ThisWorkbook.Worksheets("Sheet1")
Set aList = CreateObject("System.Collections.ArrayList")
With ws
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row 'whichever column is required to determine last row. Assumes there are headers in row1
If lastRow = 2 Then arr(0) = .Range("A2").Value
arr = .Range("A2:A" & lastRow).Value
For i = LBound(arr, 1) To UBound(arr, 1)
aList.Add arr(i, 1)
Next i
aList.Reverse
For i = 0 To aList.Count - 1
arr(i + 1, 1) = aList(i)
Next
.Cells(2, 2).Resize(aList.Count, 1) = arr
End With
End Sub
Data and output
Same thing re-writing a sub by Ryan Wells as a function:
Public Sub ReverseAList2()
Dim ws As Worksheet, arr(), i As Long, aList As Object, lastRow As Long
Set ws = ThisWorkbook.Worksheets("Sheet1")
With ws
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row 'whichever column is required to determine last row. Assumes there are headers in row1
If lastRow = 2 Then arr(0) = .Range("A2").Value
arr = .Range("A2:A" & lastRow).Value
.Cells(2, 2).Resize(UBound(arr), 1) = ReverseArray(arr)
End With
End Sub
Public Function ReverseArray(vArray As Variant) As Variant
Dim vTemp As Variant, i As Long, iUpper As Long, iMidPt As Long
iUpper = UBound(vArray, 1)
iMidPt = (UBound(vArray, 1) - LBound(vArray, 1)) \ 2 + LBound(vArray)
For i = LBound(vArray) To iMidPt
vTemp = vArray(iUpper, 1)
vArray(iUpper, 1) = vArray(i, 1)
vArray(i, 1) = vTemp
iUpper = iUpper - 1
Next i
ReverseArray = vArray
End Function

How to randomly select number of rows based on conditions in Excel?

I want to randomly select 50 rows from one sheet and pasting them in a separate workbook for data sampling. I don't know how to do it because first, I'm new to VBA, I want to learn something new and second, I tried searching this on Google but no accurate answer found.
So what's on my mind is this:
I'll get first the number of rows in that worksheet. I've already
done it with this one line of code:
CountRows = ActiveSheet.Cells(ActiveSheet.Rows.Count, "A").End(xlUp).Row
Get a random number from 1 to CountRows uniquely. The random numbers should be incremental (1,5,7,20,28,30,50 and no backward counting). Then grab that row, create a new workbook if not yet open and paste it there.
How can I achieve this process? I have no idea how to start this.
First, generate an array of 50 unique numbers between 1 and CountRows, using this routine:
' Generate a sorted array(0 to count-1) numbers between a and b inclusive
Function UniqueRandom(ByVal count As Long, ByVal a As Long, ByVal b As Long) As Long()
Dim i As Long, j As Long, x As Long
ReDim arr(b - a) As Long
Randomize
For i = 0 To b - a: arr(i) = a + i: Next
If b - a < count Then UniqueRandom = arr: Exit Function
For i = 0 To b - a 'Now we shuffle the array
j = Int(Rnd * (b - a))
x = arr(i): arr(i) = arr(j): arr(j) = x ' swap
Next
' After shuffling the array, we can simply take the first portion
ReDim Preserve arr(0 To count - 1)
'sorting, probably not necessary
For i = 0 To count - 1
For j = i To count - 1
If arr(j) < arr(i) Then x = arr(i): arr(i) = arr(j): arr(j) = x ' swap
Next
Next
UniqueRandom = arr
End Function
Now you can use the above routine to generate random, unique and sorted indexes and copy the corresponding rows. Here's an example:
Sub RandomSamples()
Const sampleCount As Long = 50
Dim lastRow As Long, i As Long, ar() As Long, rngToCopy As Range
With Sheet1
lastRow = .Cells(.Rows.count, "A").End(xlUp).row
ar = UniqueRandom(sampleCount, 1, lastRow)
Set rngToCopy = .Rows(ar(0))
For i = 1 To UBound(ar)
Set rngToCopy = Union(rngToCopy, .Rows(ar(i)))
Next
End With
With Workbooks.Add
rngToCopy.Copy .Sheets(1).Cells(1, 1)
.SaveAs ThisWorkbook.path & "\" & "samples.xlsx"
.Close False
End With
End Sub
Following code will do what you need.
Sub Demo()
Dim lng As Long
Dim tempArr() As String
Dim srcWB As Workbook, destWB As Workbook
Dim rng As Range
Dim dict As New Scripting.Dictionary
Const rowMax As Long = 100 'maximum number of rows in source sheet
Const rowMin As Long = 1 'starting row number to copy
Const rowCopy As Long = 50 'number of rows to copy
Dim intArr(1 To rowCopy) As Integer, rowArr(1 To rowCopy) As Integer
Set srcWB = ThisWorkbook
'get unique random numbers in dictionary
With dict
Do While .Count < rowCopy
lng = Rnd * (rowMax - rowMin) + rowMin
.Item(lng) = Empty
Loop
tempArr = Split(Join(.Keys, ","), ",")
End With
'convert random numbers to integers
For i = 1 To rowCopy
intArr(i) = CInt(tempArr(i - 1))
Next i
'sort random numbers
For i = 1 To rowCopy
rowArr(i) = Application.WorksheetFunction.Small(intArr, i)
If rng Is Nothing Then
Set rng = srcWB.Sheets("Sheet1").Rows(rowArr(i))
Else
Set rng = Union(rng, srcWB.Sheets("Sheet1").Rows(rowArr(i)))
End If
Next i
'copy random rows, change sheet name and destination path as required
Set destWB = Workbooks.Add
With destWB
rng.Copy destWB.Sheets("Sheet1").Range("A1")
.SaveAs Filename:="D:\Book2.xls", FileFormat:=56
End With
End Sub
Above code uses Dictionary so you have to add reference to Microsoft Scripting Runtime Type Library. In Visual Basic Editor, go to Tools->References and check "Microsoft Scripting Runtime" in the list.
Let me know if anything is not clear.

excel vba loop through worksheets fails

I don't know why this function doesn't loop through the worksheets , what am I missing ?
I've gone through Almost every resource I can find both on stack overflow and Google but could not find an answer that I could implement.
I've tried looping through worksheet numbers however that didn't work so I am now attempting to loop through worksheet names. This also does not work.
I'm pretty sure it's a small error but I could not find the cause after days of searching.
Sub CreateUniquesList()
Dim WS_Count As Integer 'number of WorkSheets
Dim Sheet As Integer 'WorkSheet number
Dim Uniques() As String 'Array of all unique references
Dim UniquesLength As Integer
Dim size As Integer 'number of items to add to Uniques
Dim Row As Integer 'row number
Dim Column As Variant 'column number
Dim Columns As Variant
Dim blanks
Dim LastRow As Integer
Dim i As Integer
Dim wks As Variant, wksNames() As String
WS_Count = ActiveWorkbook.Worksheets.Count
ReDim wksNames(WS_Count - 1)
i = 0
For Each wks In Worksheets
wksNames(i) = wks.Name
i = i + 1
Next
Columns = Array(3, 4, 8, 11, 12, 17, 18)
ReDim Uniques(0)
Uniques(0) = "remove this item"
WS_Count = ActiveWorkbook.Worksheets.Count
' For Sheet = 1 To WS_Count
For Each wks In wksNames
For Each Column In Columns
' LastRow = ActiveWorkbook.Worksheets(Sheet).Cells(Rows.Count, Column).End(xlUp).Row
' size = WorksheetFunction.CountA(Worksheets(Sheet).Columns(Column)) - 1
LastRow = ActiveWorkbook.Worksheets(wks).Cells(Rows.Count, Column).End(xlUp).Row
size = WorksheetFunction.CountA(Worksheets(wks).Columns(Column)) - 1
UniquesLength = UBound(Uniques) - LBound(Uniques) + 1
ReDim Preserve Uniques(UniquesLength + size - 1)
blanks = 0
i = 1
For Row = LastRow To 2 Step -1
If Cells(Row, Column).Value <> "" Then
Uniques(UniquesLength + i - 1 - blanks) = Cells(Row, Column).Value
Else
blanks = blanks + 1
End If
i = i + 1
Next Row
Next Column
Next wks
' Next Sheet
'remove first unique element
For i = 1 To UBound(Uniques)
Uniques(i - 1) = Uniques(i)
Next i
ReDim Preserve Uniques(UBound(Uniques) - 1)
End Sub
I took a look at the code and have rewritten a fair portion of it as I don't think a lot of it was necessary (probably leftover from your attempts to make things work). Try this, and if you don't understand any of it, post a comment and I'll explain further.
Sub CreateUniquesList()
Dim Uniques() As String 'Array of all unique references
Dim Row As Integer 'row number
Dim Column As Variant 'column number
Dim Columns As Variant
Dim LastRow As Integer
Dim wks As Worksheet
Columns = Array(3, 4, 8, 11, 12, 17, 18)
ReDim Uniques(0)
For Each wks In ThisWorkbook.Worksheets
For Each Column In Columns
LastRow = wks.Cells(wks.Rows.Count, Column).End(xlUp).Row
For Row = LastRow To 2 Step -1
If wks.Cells(Row, Column).Value <> "" Then
Uniques(UBound(Uniques)) = wks.Cells(Row, Column).Value ' set the last element of the array to the value
ReDim Preserve Uniques(UBound(Uniques)+1) ' increment the size of the array
End If
Next Row
Next Column
Next wks
' lose the last element of the array as it's one larger than it needs to be
ReDim Preserve Uniques(UBound(Uniques) - 1)
End Sub
Try this
WS_Count = ActiveWorkbook.Worksheets.Count
' For Sheet = 1 To WS_Count
For Each wks In Worksheets
For Each Column In Columns
'LastRow = ActiveWorkbook.Worksheets(Sheet).Cells(Rows.Count,column).End(xlUp).Row
'size = WorksheetFunction.CountA(Worksheets(Sheet).Columns(Column)) - 1
LastRow = ActiveWorkbook.Worksheets(wks.Name).Cells(Rows.Count,Column).End(xlUp).Row
size = WorksheetFunction.CountA(Worksheets(wks.Name).Columns(Column)) - 1
UniquesLength = UBound(Uniques) - LBound(Uniques) + 1
ReDim Preserve Uniques(UniquesLength + size - 1)
blanks = 0
i = 1
For Row = LastRow To 2 Step -1
If Cells(Row, Column).Value <> "" Then
Uniques(UniquesLength + i - 1 - blanks) = Cells(Row, Column).Value
Else
blanks = blanks + 1
End If
i = i + 1
Next Row
Next Column
Next wks

VBA script to count string, insert rows, copy row, split cell

The department that provides me a spreadsheet to be used in my database now includes multiple text in a cell. In order to link to that data I have to turn it into multiple rows. Example: LC123/LC463/LC9846 needs to have the entire row copied with just one "LC" string in each row-
cell1 cell2 LC123
cell1 cell2 LC463
cell1 cell2 LC9846
I tried these two subroutines but obviously it failed
Sub InSert_Row()
Dim j As Long
j = InputBox(=SUM(LEN(ActiveCell)-LEN(SUBSTITUTE(ActiveCell,"LC",""))-1)
ActiveCell.EntireRow.Copy
ActiveCell.Offset(j).EntireRow.Insert Shift:=xlDown
End Sub
Sub SplitAndTranspose()
Dim N() As String
N = Split(ActiveCell, Chr(10))
ActiveCell.Resize(UBound(N) + 1) = WorksheetFunction.Transpose(N)
End Sub
The 2nd subroutine will split and copy but it doesn't insert rows, it writes over the rows below it.
'In memory' method
Inserting rows as necessary would be perhaps the most simple to understand, but the performance of making thousands of seperate row inserts would not be good. This would be fine for a one off (perhaps you only need a one-off) and should only take a minute or two to run but I thought what the heck and so wrote an approach that splits the data in memory using a collection and arrays. It will run in the order of seconds.
I have commented what it is doing.
Sub ProcessData()
Dim c As Collection
Dim arr, recordVector
Dim i As Long, j As Long
Dim rng As Range
Dim part, parts
'replace with your code to assign the right range etc
Set rng = ActiveSheet.UsedRange
j = 3 'replace with right column index, or work it out using Range.Find etc
arr = rng.Value 'load the data
'Process the data adding additional rows etc
Set c = New Collection
For i = 1 To UBound(arr, 1)
parts = Split(arr(i, j), "/") 'split the data based on "/"
For Each part In parts 'loop through each "LC" thing
recordVector = getVector(arr, i) 'get the row data
recordVector(j) = part 'replace the "LC" thing
c.Add recordVector 'add it to our results collection
Next part
Next i
'Prepare to dump the data back to the worksheet
rng.Clear
With rng.Parent
.Range( _
rng.Cells(1, 1), _
rng.Cells(1, 1).Offset(c.Count - 1, UBound(arr, 2) - 1)) _
.Value = getCollectionOfVectorsToArray(c)
End With
End Sub
'Helper method to return a vector representing our row data
Private Function getVector(dataArray, dataRecordIndex As Long)
Dim j As Long, tmpArr
ReDim tmpArr(LBound(dataArray, 2) To UBound(dataArray, 2))
For j = LBound(tmpArr) To UBound(tmpArr)
tmpArr(j) = dataArray(dataRecordIndex, j)
Next j
getVector = tmpArr
End Function
'Helper method to return an array from a collection of vectors
Function getCollectionOfVectorsToArray(c As Collection)
Dim i As Long, j As Long, arr
ReDim arr(1 To c.Count, LBound(c(1), 1) To UBound(c(1), 1))
For i = 1 To c.Count
For j = LBound(arr, 2) To UBound(arr, 2)
arr(i, j) = c(i)(j)
Next j
Next i
getCollectionOfVectorsToArray = arr
End Function
Edit:
Alternative "Range Insert" method.
It will be slower (although I made the number of discrete insert and copy operations be based on original row count, not some recursive sweep so it is not too bad) but is simpler to understand and so to perhaps tweak if needed. It should run in the order of a couple of minutes.
Sub ProcessData_RangeMethod()
Dim rng As Range
Dim colIndex As Long
Dim parts
Dim currRowIndex As Long
'replace with your code to assign the right range etc
Set rng = ActiveSheet.UsedRange
colIndex = 3 'replace with right column index, or work it out using Range.Find etc
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
currRowIndex = 1
Do Until currRowIndex > rng.Rows.Count
parts = Split(rng.Cells(currRowIndex, colIndex), "/")
If UBound(parts) > 0 Then
rng.Range(rng.Cells(currRowIndex + 1, 1), rng.Cells(currRowIndex + UBound(parts), rng.Columns.Count)).Insert xlShiftDown
rng.Rows(currRowIndex).Copy rng.Range(rng.Cells(currRowIndex + 1, 1), rng.Cells(currRowIndex + UBound(parts), rng.Columns.Count))
rng.Range(rng.Cells(currRowIndex, colIndex), rng.Cells(currRowIndex + UBound(parts), colIndex)).Value = Application.Transpose(parts)
End If
currRowIndex = currRowIndex + 1 + UBound(parts)
Loop
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub

how to insert a row before pasting an array

I currently have an array which I populate and paste in a sheet named "T1" using a macro. My current macro uses the rowcount function to determine the used rows and pastes the array from the next available row.
The problem I am having is that when I paste this array multiple times, the arrays need to be spaced by a row so that i can differentiate different submissions. This is what I have so far, and I was hoping someone could help me with this:
Sub CopyData()
Dim Truearray() As String
Dim cell As Excel.Range
Dim RowCount1 As Integer
Dim i As Integer
Dim ii As Integer
Dim col As Range
Dim col2 As Range
i = 0
ii = 2
RowCount1 = DHRSheet.UsedRange.Rows.Count
Set col = DHRSheet.Range("I1:I" & RowCount1)
For Each cell In col
If cell.Value = "True" Then
Dim ValueCell As Range
Set ValueCell = Cells(cell.Row, 3)
ReDim Preserve Truearray(i)
Truearray(i) = ValueCell.Value
Dim siblingCell As Range
Set siblingCell = Cells(cell.Row, 2)
Dim Siblingarray() As String
ReDim Preserve Siblingarray(i)
Siblingarray(i) = DHRSheet.Name & "$" & siblingCell.Value
i = i + 1
End If
Next
Dim RowCount2 As Integer
RowCount2 = DataSheet.UsedRange.Rows.Count + 1
For ii = 2 To UBound(Truearray)
DataSheet.Cells(RowCount2 + ii, 2).Value = Truearray(ii)
Next
For ii = 2 To UBound(Siblingarray)
DataSheet.Cells(RowCount2 + ii, 1).Value = Siblingarray(ii)
Next
DataSheet.Columns("A:B").AutoFit
MsgBox ("Data entered has been successfully validated & logged")
End Sub
If you Offset two rows from the bottom cell, you will leave a blank row of separation. You should also consider filling the whole array as base 1 and writing it to DataSheet in one shot.
Sub CopyData2()
Dim rCell As Range
Dim aTrues() As Variant
Dim rRng As Range
Dim lCnt As Long
'Define the range to search
With DHRSheet
Set rRng = .Range(.Cells(1, 9), .Cells(.Rows.Count, 9).End(xlUp))
End With
'resize array to hold all the 'trues'
ReDim aTrues(1 To Application.WorksheetFunction.CountIf(rRng, "True"), 1 To 2)
For Each rCell In rRng.Cells
If rCell.Value = "True" Then
lCnt = lCnt + 1
'store the string from column 2
aTrues(lCnt, 1) = DHRSheet.Name & "$" & rCell.Offset(0, -7).Value
'store the value from column 3
aTrues(lCnt, 2) = rCell.Offset(0, -6).Value
End If
Next rCell
'offset 2 from the bottom row to leave a row of separation
With DataSheet.Cells(DataSheet.Rows.Count, 1).End(xlUp).Offset(2, 0)
'write the stored information at one time
.Resize(UBound(aTrues, 1), UBound(aTrues, 2)).Value = aTrues
End With
End Sub