Removing Duplicates from 2d array and updating it in VBA - vba

I have a 2d array holding values like the following:
array - JK(K, NC)
"K" Stores Total No of Items
"NC" Stores Items
I need to remove the duplicates From "NC"
and also update "K" (i.e the total items) after removing the duplicates.
4 - 5, 6, 7, 5
6 - 7, 6, 9, 10, 11, 7
4 - 8, 7, 15, 8
9 - 12, 15, 16, 12, 17, 18, 19, 20, 16
3 - 26, 27, 26
3 - 20, 19, 20
6 - 21, 33, 33, 34, 35, 21
8 - 19, 33, 34, 18, 38, 39, 40, 34
5 - 39, 40, 38, 43, 40
6 - 41, 44, 44, 45, 46, 41
3 - 20, 19, 20
6 - 21, 33, 33, 34, 35, 21
8 - 19, 33, 34, 18, 38, 39, 40, 34

Here is a Solution based on the Entries and Code from #tigeravatar and #Jeeped with you could have find here on Stack overflow, so a big thanks to this guys.
Removing Duplicate values from a string in Visual Basic
and
Multidimensional Arrays with For Loops VBA
Sub Test()
Dim strArray(8, 1) As String
Dim newString As String
strArray(0, 0) = "4"
strArray(0, 1) = "5 6 7 5"
strArray(1, 0) = "6"
strArray(1, 1) = "7 6 9 10 11 7"
strArray(2, 0) = "4"
strArray(2, 1) = "8 7 15 8"
strArray(3, 0) = "9"
strArray(3, 1) = "12 15 16 12 17 18 19 20 16"
strArray(4, 0) = "4"
strArray(4, 1) = "5 6 7 5"
strArray(5, 0) = "6"
strArray(5, 1) = "7 6 9 10 11 7"
strArray(6, 0) = "9"
strArray(6, 1) = "12 15 16 12 17 18 19 20 16"
For i = 0 To UBound(strArray, 1)
newString = DeDupeString(strArray(i, 1), " ")
strArray(i, 0) = UBound(Split(newString, " ")) + 1
strArray(i, 1) = newString
Next i
End Sub
Function DeDupeString(ByVal sInput As String, Optional ByVal sDelimiter As String = ",") As String
Dim varSection As Variant
Dim sTemp As String
varSection = Split(sInput, sDelimiter)
For Each varSection In Split(sInput, sDelimiter)
If InStr(1, sDelimiter & sTemp & sDelimiter, sDelimiter & varSection & sDelimiter, vbTextCompare) = 0 Then
sTemp = sTemp & sDelimiter & varSection
End If
Next varSection
DeDupeString = Mid(sTemp, Len(sDelimiter) + 1)
End Function

You could use a function, something like this
Function RemoveDupes(strInput As String) As Variant()
' Uses Microsoft Scripting Runtime referece
Dim arrSplit() As String
Dim lngCounter As Long
Dim dicDupeCheck As New Scripting.dictionary
arrSplit = Split(strInput, Chr(32))
For lngCounter = 0 To UBound(arrSplit) - 1
If Not dicDupeCheck.Exists(arrSplit(lngCounter)) Then
dicDupeCheck.Add arrSplit(lngCounter), arrSplit(lngCounter)
End If
Next lngCounter
RemoveDupes = Array(dicDupeCheck.Count, Join(dicDupeCheck.Items(), " "))
Erase arrSplit
End Function
This will then be used as follows
RemoveDupes("12 15 16 12 17 18 19 20 16")(0) will give the count, and RemoveDupes("12 15 16 12 17 18 19 20 16")(1) will give the non-dupe output.
Or set an array to removedupes and use that, so arr=RemoveDupes("12 15 16 12 17 18 19 20 16") then OriginalArray(x)=arr(0) & " - " & arr(1)

Related

Returning non-empty row numbers using VBA

I have a spreadsheet like this, and I would like to have a function that returns the list of row numbers non-empty cells in column B. In this case, it should return "2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 22, 23, 25, 26)
How do I do this in VBA?
Function GetEmptyCount()
Dim arr(), x&, cell
With Range("B1:B" & Cells(Rows.Count - 1, "B").End(xlUp).Row)
For Each cell In .SpecialCells(xlCellTypeBlanks).Cells
x = x + 1
ReDim Preserve arr(1 To x)
arr(x) = cell.Row
Next
End With
GetEmptyCount = arr
End Function
Sub Test()
Dim x, c
x = GetEmptyCount()
For Each c In x: MsgBox c: Next
End Sub
You can check the length of the cell value something like
IF(Length(Cell) > 0 THEN
// Include the row
ELSE
// skip the row

application-defined or object-defined error Ubound

there I have an excel VBA code that retrieves its records from an external file by month and set it according to the column heading.
However, i have an error in of application-defined or object-defined error in of the line .Range("A6").Resize(n, 23) = b
does anyone know why
code:
Sub zz()
Dim arr, c, b(), n&
Application.ScreenUpdating = False
Worksheets("Sheet2").Range("A6").AutoFilter
Workbooks.Open "C:\Users\sophia.tan\Desktop\MasterPlanData.xlsx", 0, 1
arr = Sheets("Excel").UsedRange
ActiveWorkbook.Close 0
c = Array(0, 2, 13, 14, 7, 8, 11, 1, 9, 10, 16, 17, 20, 22, 15, 30, 27, 28, 29, 3, 4, 30)
d = Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23)
ReDim b(1 To UBound(arr), 1 To 23)
For i = 2 To UBound(arr)
If arr(i, 12) >= DateSerial(Year:=2017, Month:=11, Day:=1) And arr(i, 12) <= DateSerial(Year:=2017, Month:=11, Day:=31) Then
n = n + 1
For j = 1 To UBound(c)
b(n, d(j)) = arr(i, c(j))
Next
End If
Next
With Worksheets("Sheet2")
.Range("A6:T" & Rows.Count).CurrentRegion.AutoFilter field:=1, Criteria1:="<>OFM"
.Range("A6:T" & Rows.Count).CurrentRegion.SpecialCells(xlCellTypeVisible).AutoFilter field:=13, Criteria1:="<>Collar & Cuff"
.Range("A6:T" & Rows.Count).CurrentRegion.Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow.Delete
.Range("A6").Resize(n, 23) = b
.Range("A6").CurrentRegion.Sort key1:=Range("G6"), order1:=xlAscending, Header:=xlYes
.Range("A6").Select
End With
Application.ScreenUpdating = 1
End Sub
Your determination on n is subjective to the If statement. However, any unfilled values in the 'rows' of b will be vbnullstrings and will produce truly blank cells.
.Range("A6").Resize(ubound(b, 1), ubound(b, 2)) = b
Alternately,
For i = 2 To UBound(arr)
If arr(i, 12) >= DateSerial(Year:=2017, Month:=11, Day:=1) And arr(i, 12) <= DateSerial(Year:=2017, Month:=11, Day:=31) Then
n = n + 1
For j = 1 To UBound(c)
b(n, d(j)) = arr(i, c(j))
Next
End If
Next
b = application.transpose(b)
redim preserve b(lbound(b, 1) to ubound(b, 1), lbound(b, 2) to n)
b = application.transpose(b)
.Range("A6").Resize(n, 23) = b
You can only adjust the last rank of an array with ReDim when using the preserve parameter.
Try
.Range("A6").Resize(n, 23).Value = b

excel vba for loop changing steps

I'm trying to write a program that averages daily values into monthly values. The difficulty I found was that each month have different days of the month so I figured I had to change the steps in my loop. My program runs fine but the logic to the correct steps are a bit off.
lastrow2 = Range(Sheets("Daily").Cells(5, 3), Sheets("Daily").Cells(5, 3).End(xlDown)).Count
lastcol2 = Range(Sheets("Daily").Cells(5, 3), Sheets("Daily").Cells(5, 3).End(xlToRight)).Count
irow2 = 5
cnt = 5
Set wb = ActiveWorkbook
For j2 = 3 To lastcol1 + 3 'number of columns in the dataset
nextmo:
If Sheets("Daily").Cells(i2, 2) = 1 Or Sheets("Daily").Cells(i2, 2) = 60 Or Sheets("Daily").Cells(i2, 2) = 121 Or Sheets("Daily").Cells(i2, 2) = 182 Or Sheets("Daily").Cells(i2, 2) = 213 Or Sheets("Daily").Cells(i2, 2) = 274 Or Sheets("Daily").Cells(i2, 2) = 335 Then
daysofthemo = 31
ElseIf Sheets("Daily").Cells(i2, 2) = 91 Or Sheets("Daily").Cells(i2, 2) = 152 Or Sheets("Daily").Cells(i2, 2) = 244 Or Sheets("Daily").Cells(i2, 2) = 305 Then
daysofthemo = 30
ElseIf Sheets("Daily").Cells(i2, 2) = 32 Then
daysofthemo = 28
End If
For i2 = cnt To lastrow1 + 5 Step daysofthemo 'number of rows in the dataset
'Will now take the average of one month and paste onto worksheet called "monthly"
Set myRange2 = Range(wb.Worksheets("Daily").Cells(i2, j2), wb.Worksheets("Daily").Cells(i2 + daysofthemo - 1, j2))
If Application.WorksheetFunction.Count(myRange2) > 0 Then
wb.Worksheets("Monthly").Cells(irow2, j2).Value = Application.WorksheetFunction.Average(myRange2)
End If
irow2 = irow2 + 1
cnt = i2 + daysofthemo
goto nextmo
Next i2
Next j2
I have msgbox before and after the if statement.
The output should be:
274, 31, 305, 30, 335, 31, 1, 31, and so on.
But right now the output is: 274, 31, 305, 30, 336, 30, 2, 33, and so on.
Just thought of this new code, tested but still not right.
intDaysInMonth=DateDiff("d", DateSerial(Year(dtDate), Month(dtDate), 1), WorksheetFunction.EoMonth(dtDate, 0)) + 1

Loop through list of products and output onto different spreadsheet

I have a workbook (C:\DOORS.xlsx) with the following data:
A B C D
100 ... Type A Description Remarks
102 ... Type B Description Remarks
103 ... Type C Description Remarks
I need to create a loop that goes thru each row that outputs the data onto a different workbook (C:\QT.xlsx). It needs to be able to make sure the values for Door and Description cannot be more than 55 characters long. If they are more than 55 characters in length then it needs to move the remainder onto the next row without cutting a word in half. Also if Remarks is blank then this is where the description should go.
Output would look like this on QT.xlsx:
'Starting at cell D18
A B C D
18 Door: 100, 100, 103, 104, 105,
19 106, 107, 108, 110, 107
20 Type A
21 Remarks A 'Text Should Be Bold
22 This is a really long description
23 and needs to fit in this space by
24 being 55 characters long and does
25 cut a word in half.
26
27 Door: 102, 100, 103, 104,
28 Type B
29 Remarks B 'Text Should Be Bold
30 Description
31
32 Door: 103, 100, 103, 104,
33 Type C
34 Description 'This is a blank cell in DOORS.xlsx
35
I'm still learning VBA and I'm new to looping. Not sure where to start but any help is greatly appreciated. Thanks in advance.
EDIT This should work as you need it. However, it is a huge change, if there are questions, just write a comment. :)
Option Explicit
Sub GetTheData()
Dim MyWSSource As Worksheet
Dim MyWSTarget As Worksheet
Dim sArr As Variant
Dim i As Long, j As Long, k As Byte, iLines As Long
Application.ScreenUpdating = False 'will automatically set to true after the sub ends
Set MyWSSource = Workbooks.Open("C:\DOORS.xlsx").Sheets(1) 'set your source wb+sheet
Set MyWSTarget = Workbooks.Open("C:\QT.xlsx").Sheets(1) 'set your target wb+sheet
iLines = MyWSSource.Cells(Rows.Count, 1).End(xlUp).Row 'get the last line to be processed
j = 18 'set the first line to output
For i = 1 To iLines
For k = 1 To 4
If Len(MyWSSource.Cells(i, Array(1, 2, 4, 3)(k - 1)).Value) Then 'if cell is empty it will be skipped
If k = 1 Then
' ---------- new lines start ----------
MyWSTarget.Cells(j, 2).Value = Len(MyWSSource.Cells(i, 1).Value) - Len(Replace(MyWSSource.Cells(i, 1).Value, ",", "")) + 1 'new line for count in b
If Left(MyWSSource.Cells(i, 3).Value, 4) = "Pair" Then 'case sensitive
'If LCase(Left(MyWSSource.Cells(i, 3).Value, 4)) = "pair" Then 'not case sensitive
MyWSTarget.Cells(j, 3).Value = "PR"
Else
MyWSTarget.Cells(j, 3).Value = "EA"
End If
' ---------- new lines end ----------
sArr = CropText("Door: " & MyWSSource.Cells(i, 1).Value) 'sets the "Door: " for column A
Else
sArr = CropText(MyWSSource.Cells(i, Array(1, 2, 4, 3)(k - 1)).Value)
'the "Array(1, 2, 4, 3)(k - 1)" switches col C and D cus you want A->B->D->C
End If
If k = 3 Then MyWSTarget.Cells(j, 4).Font.Bold = True 'bolt Remark-line
MyWSTarget.Cells(j, 4).Value = sArr(0): j = j + 1 'input text and goto next line
While Len(sArr(1))
sArr = CropText(CStr(sArr(1)))
If k = 3 Then MyWSTarget.Cells(j, 4).Font.Bold = True 'bolt Remark-line
MyWSTarget.Cells(j, 4).Value = sArr(0): j = j + 1 'input text and goto next line
Wend
End If
Next
j = j + 1 'adds an empty line after each dataset
Next
MyWSSource.Parent.Close 0 'close your source (discard changes -> no changes made)
MyWSTarget.Parent.Close 1 'close your target (save changes)
End Sub
Public Function CropText(a As String) As Variant
Dim b As String, i As Long
If Len(a) > 55 Then
For i = 0 To 55
If Mid(a, 56 - i, 1) = " " Then
CropText = Array(Left(a, 55 - i), Mid(a, 57 - i))
Exit Function
End If
Next
CropText = Array(Left(a, 55), Mid(a, 56)) 'new line -> see *NOTE
Else
CropText = Array(a, "")
End If
End Function
CropText(string) will split the text into 2 parts (the first is shorter than 56 characters and the second will be all thats left) *NOTE: If the text string has more than 55 characters without a space, it will be cut at the 55th character!
How it looks for me: (without the column B/C change)
Input: (DOORS.xlsx)
Output: (QT.xlsx)
A B C D
18 10 EA Door: 100, 100, 103, 104, 105,
19 106, 107, 108, 110, 107
20 Type A
21 Remarks A 'Text Should Be Bold
22 This is a really long description
23 and needs to fit in this space by
24 being 55 characters long and does
25 cut a word in half.
26
27 4 PR Door: 102, 100, 103, 104
28 Type B
29 Remarks B 'Text Should Be Bold
30 PAIR Description
31
32 3 EA Door: 103, 100, 103, 104
33 Type C
34 Description 'This is a blank cell in DOORS.xlsx
35

looping through multiple arrays to transfer information between excel workbooks

I have written some code that populates a preformatted worksheet in an another workbook, from another preformatted worksheet. They include merged cells and all other things nasty, and for whatever reason cannot be changed.
So, I have written the following
Sub test()
Dim wbkCurrent As Workbook
'Dim wbk3Mth As Workbook
Dim wbk6Mth As Workbook
Set wbkCurrent = ThisWorkbook
Set wbk6Mth = Workbooks.Open("C:\newbook.xlsm")
newbook.Sheets("Mon 1").Activate
Call assignArrays
End Sub
Sub assignArrays
Call moveValues(32, 3, 7, 8)
Call moveValues(32, 5, 23, 6)
Call moveValues(32, 65, 15, 8)
Call moveValues(32, 56, 31, 5)
Call moveValues(32, 57, 31, 11)
Call moveValues(32, 15, 39, 4)
Call moveValues(32, 16, 39, 5)
Call moveValues(32, 17, 39, 6)
Call moveValues(32, 18, 39, 7)
Call moveValues(32, 30, 39, 10)
Call moveValues(32, 31, 39, 11)
Call moveValues(32, 32, 39, 12)
Call moveValues(32, 33, 39, 13)
Call moveValues(32, 7, 7, 21)
Call moveValues(32, 9, 23, 19)
Call moveValues(32, 66, 15, 21)
Call moveValues(32, 59, 31, 18)
Call moveValues(32, 60, 31, 24)
Call moveValues(32, 20, 39, 17)
Call moveValues(32, 21, 39, 18)
Call moveValues(32, 22, 39, 19)
Call moveValues(32, 23, 39, 20)
Call moveValues(32, 35, 39, 23)
Call moveValues(32, 36, 39, 24)
Call moveValues(32, 37, 39, 25)
Call moveValues(32, 38, 39, 26)
Call moveValues(32, 11, 7, 34)
Call moveValues(32, 13, 23, 32)
Call moveValues(32, 67, 15, 34)
Call moveValues(32, 62, 31, 31)
Call moveValues(32, 63, 31, 37)
Call moveValues(32, 25, 39, 30)
Call moveValues(32, 26, 39, 31)
Call moveValues(32, 27, 39, 32)
Call moveValues(32, 28, 39, 33)
Call moveValues(32, 40, 39, 36)
Call moveValues(32, 41, 39, 37)
Call moveValues(32, 42, 39, 38)
Call moveValues(32, 43, 39, 39)
End Sub
Sub moveValues(tRow, tCol, rRow, rCol)
'trow is row in this workbook, tcol is column in this workbook, rRow & rCol are the same for the other workbook
ActiveSheet.Cells(rRow, rCol).Value = ThisWorkbook.Sheets("Results").Cells(tRow, tCol).Value
tRow = tRow + 1
rRow = rRow + 1
ActiveSheet.Cells(rRow, rCol).Value = ThisWorkbook.Sheets("Results").Cells(tRow, tCol).Value
tRow = tRow + 1
rRow = rRow + 1
ActiveSheet.Cells(rRow, rCol).Value = ThisWorkbook.Sheets("Results").Cells(tRow, tCol).Value
tRow = tRow + 1
rRow = rRow + 1
ActiveSheet.Cells(rRow, rCol).Value = ThisWorkbook.Sheets("Results").Cells(tRow, tCol).Value
tRow = tRow + 1
rRow = rRow + 1
ActiveSheet.Cells(rRow, rCol).Value = ThisWorkbook.Sheets("Results").Cells(tRow, tCol).Value
tRow = tRow + 1
rRow = rRow + 1
ActiveSheet.Cells(rRow, rCol).Value = ThisWorkbook.Sheets("Results").Cells(tRow, tCol).Value
End Sub
This works fine, and writes all the data out. Problem is, I need this to run starting where
trow = 2,12,22,32,42,52
Now I could write this all out manually, but it would mean that going in and changing it later would be a nightmare. So, I had the idea of using a = 2,12,22,32 etc and then having
call moveValues(a, 3, 7, 8)
However this means a bumps up a digit through the moveValues subroutine, and needs resetting each time.
I have one idea to solve this using arrays, but that has its own issues.
I replaced the module assignArrays with
Sub assignArrays()
'row in this workbook
Dim array1(5)
array1(5) = Array(2, 12, 22, 32, 42, 52)
'E
Dim array2(12)
array2(12) = Array(3, 5, 65, 56, 57, 15, 16, 17, 18, 30, 31, 32, 33)
'U
Dim array2_1(12)
array2_1(12) = Array(7, 9, 66, 59, 60, 20, 21, 22, 23, 35, 36, 37, 38)
'R
Dim array2_2(12)
array2_2(12) = Array(11, 13, 67, 62, 63, 25, 26, 27, 28, 40, 41, 42, 43)
'row in report
Dim array3(12)
array3(12) = Array(7, 23, 15, 31, 31, 39, 39, 39, 39, 39, 39, 39, 39) 'constant in each array 1
'column in report
Dim array4(12)
array4(12) = Array(8, 6, 8, 5, 11, 4, 5, 6, 7, 10, 11, 12, 13) '+13 for each third
Dim v1, v2, v3, v4 As Integer
For a = 0 To 5
v1 = array1(a)
For b = 0 To 12
v3 = array3(b)
For c = 0 To 12
v4 = array4(c)
For d = 0 To 12
v2 = array2(d)
Call moveValues(v1, v2, v3, v4)
Next d
Next c
For c = 0 To 12
v4 = array4(c) + 13
For d = 0 To 12
v2 = array2(d)
Call moveValues(v1, v2, v3, v4)
Next d
Next c
For c = 0 To 12
v4 = array4(c) + 26
For d = 0 To 12
v2 = array2(d)
Call moveValues(v1, v2, v3, v4)
Next d
Next c
Next b
Next a
End Sub
This dies with a 1004 error on the first line of moveValues. Any ideas to fix either solution?
You are not dealing with arrays properly.
Dim array1(5) 'Array with 5 dimension
array1(5) = Array(2, 12, 22, 32, 42, 52) 'Write all this content to the fifth position
The proper way to do that is:
Dim array1(5) As Integer
array1(0) = 2
array1(1) = 12
array1(2) = 22
array1(3) = 32
array1(4) = 42
array1(5) = 52
If you want to rely on one line, you can do:
Dim array1
array1 = Array(2, 12, 22, 32, 42, 52) 'In this case, it starts from 0 -> pretty unconventional (bear in mind that the array above is dimensioned from 1)
---- UPDATE
What your code delivers:
Dim array1(5)
array1(5) = Array(2, 12, 22, 32, 42, 52)
Dim test1 As Integer: test1 = array1(0) '-> 0
Dim test2 As Integer: test2 = array1(1) '-> 0
Dim test3 As Integer: test3 = array1(2) '-> 0
Dim test4 As Integer: test4 = array1(3) '-> 0
Dim test5 As Integer: test5 = array1(4) '-> 0
Dim test6 As Integer: test6 = array1(5) 'ERROR
What my code delivers:
Dim array1
array1 = Array(2, 12, 22, 32, 42, 52)
Dim test1 As Integer: test1 = array1(0) '-> 2
Dim test2 As Integer: test2 = array1(1) '-> 12
Dim test3 As Integer: test3 = array1(2) '-> 22
Dim test4 As Integer: test4 = array1(3) '-> 32
Dim test5 As Integer: test5 = array1(4) '-> 42
Dim test6 As Integer: test6 = array1(5) '-> 52