I get output from a program imported to Excel in the following format:
Item 1
1 10
2 10
3 20
5 20
8 30
13 30
Item 2
1 40
2 40
3 50
5 50
8 60
13 60
Item 3
1 50
2 50
3 40
5 40
8 30
13 30
Now, I want to create a table where the values for each item is placed next to each other as below:
Item 1 Item 2 Item 3
1 10 40 50
2 10 40 50
3 20 50 40
5 20 50 40
8 30 60 30
13 30 60 30
I can think of ways to do this using formulas with a combination of INDIRECT other functions, but I can see right away that it will be a huge pain. Is there a clever way of doing this?
My approach would be something like this:
=VLOOKUP($A6;indirect("A"&(6+G$5*$X$4):"D"&(30+G$5*$X$4));4;FALSE)
where my first lookup table is from A6:D30, the second from A32:D56. X4 contains the value 26 which is the number of rows for each Item, and G5:AA5 is 0, 1, 2 ....
I would place this besides the Item 1 list and drag it sideways and downwards. I think the procedure should work, but I get syntax error.
I don't have much experience writing VBA, but I'm capable of reading and understanding it.
UPDATE:
At Siddharth's request:
Can you check out this.
It assumes a fixed format as it is shown in your example.
It can be made dynamic, but then you need to customize the code.
Option Explicit
Sub test()
Dim oCollection As Collection
Dim oDict As Variant
Dim oItem As Object
Dim iCnt As Integer
Dim iCnt_B As Integer
Dim iCnt_items As Integer
Dim iCnt_records As Integer
Dim iID As Integer
Dim iValue As Integer
Dim strKey As Variant
'Nr of items
iCnt_items = 3
'Records per item
iCnt_records = 6
'This dictionary will store the items
Set oCollection = New Collection
'Store dictionaries in collection
For iCnt = 0 To iCnt_items - 1
Set oDict = CreateObject("Scripting.Dictionary")
For iCnt_B = 1 To iCnt_records
iID = ThisWorkbook.Sheets(1).Cells((iCnt * (iCnt_records) + (iCnt + 1) + iCnt_B), 1).Value
Debug.Print iID
iValue = ThisWorkbook.Sheets(1).Cells((iCnt * (iCnt_records) + (iCnt + 1) + iCnt_B), 2).Value
Debug.Print iValue
oDict.Add iID, iValue
Next iCnt_B
oCollection.Add oDict, "item " & iCnt
Next iCnt
'Write collection to sheet
iCnt = 0
For Each oItem In oCollection
iCnt = iCnt + 1
ThisWorkbook.Sheets(2).Cells(1, 1 + iCnt).Value = "item " & iCnt
iCnt_B = 0
For Each strKey In oItem.keys
iCnt_B = iCnt_B + 1
ThisWorkbook.Sheets(2).Cells(1 + iCnt_B, 1).Value = strKey
ThisWorkbook.Sheets(2).Cells(1 + iCnt_B, 1 + iCnt).Value = oItem(strKey)
Next
Next oItem
End Sub
Edit: sorry for interrupting the conversation -> I didn't follow up the comment section while programming.
Sidenote:
If the ranges you work with are dynamic, I would go with a dictionary.
The reason why I'm saying this is because the dictionary object uses indexing on its records.
The key - pair structure being: ID, value
allows you to directly access the values corresponding the given ID.
In your example you are working with a clear ID - value structure.
Using numeric id's would actually be the fastest.
Since I already worked on this... Here is another way..
Assumptions:
Data starts at row 5 in Sheet1
Output will be generated in Sheet2
Code:
The below code uses Collections and Formulas to achieve what you want.
Sub Sample()
Dim wsInput As Worksheet, wsOutput As Worksheet
Dim ColItems As New Collection, ColSubItems As New Collection
Dim lRow As Long, i As Long, N As Long
Dim itm
Set wsInput = ThisWorkbook.Sheets("Sheet1")
Set wsOutput = ThisWorkbook.Sheets("Sheet2")
With wsInput
lRow = .Range("B" & .Rows.Count).End(xlUp).Row
.Columns(1).Insert
.Range("A5:A" & lRow).Formula = "=IF(ISERROR(SEARCH(""Item"",B5,1)),A4,B5)"
For i = 5 To lRow
On Error Resume Next
If InStr(1, .Range("B" & i).Value, "item", vbTextCompare) Then
ColItems.Add .Range("B" & i).Value, CStr(.Range("B" & i).Value)
Else
ColSubItems.Add .Range("B" & i).Value, CStr(.Range("B" & i).Value)
End If
On Error GoTo 0
Next i
End With
With wsOutput
.Cells.ClearContents
N = 2
'~~> Create Header in Row 1
For Each itm In ColItems
.Cells(1, N).Value = itm
N = N + 1
Next
N = 2
'~~> Create headers in Col 1
For Each itm In ColSubItems
.Cells(N, 1).Value = itm
N = N + 1
Next
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
lcol = .Cells(1, .Columns.Count).End(xlToLeft).Column
j = 2
For i = 2 To lcol
.Range(.Cells(j, i), .Cells(lRow, i)).Formula = "=SUMIFS(" & _
wsInput.Name & _
"!C:C," & wsInput.Name & _
"!A:A," & .Name & _
"!$" & _
Split(.Cells(, i).Address, "$")(1) & _
"$1," & _
wsInput.Name & _
"!B:B," & _
.Name & _
"!A:A)"
Next i
.Rows("1:" & lRow).Value = .Rows("1:" & lRow).Value
End With
wsInput.Columns(1).Delete
End Sub
Screenshot:
This is what I have tried.
Sheet 1 contains the data. The result is generated in Sheet 2
Sub createTable()
Dim counter As Integer
Dim countRow As Integer
Dim flag As Boolean
Dim cellAddress As String
flag = True
countRow = 2
counter = 2
ThisWorkbook.Sheets("Sheet1").Activate
For Each cell In Range("a:a")
If counter = 2 Then
If InStr(1, cell.Value, "Item") Then
ThisWorkbook.Sheets("Sheet2").Activate
ActiveSheet.Cells(1, counter).Value = cell.Value
firstItem = cell.Value
counter = counter + 1
End If
Else
ThisWorkbook.Sheets("Sheet2").Activate
If InStr(1, cell.Value, "Item") Then
ThisWorkbook.Sheets("Sheet2").Activate
ActiveSheet.Cells(1, counter).Value = cell.Value
counter = counter + 1
flag = False
End If
If flag = True Then
Cells(cell.Row, cell.Column) = cell.Value
End If
End If
If cell.Value = vbNullString Then
Exit For
End If
Next cell
ThisWorkbook.Sheets("Sheet1").Activate
Application.CutCopyMode = False
Dim counteradd As Integer
counteradd = 2
For Each cell In Range("a:a")
v = cell.Value
If InStr(1, cell.Value, "Item") Then
If cell.Offset(1, 1).Select <> vbNullString Then
Range(Selection, Selection.End(xlDown)).Select
Selection.Copy
Sheets("Sheet2").Select
Cells(2, counteradd).Select
ActiveSheet.Paste
Application.CutCopyMode = False
counteradd = counteradd + 1
ThisWorkbook.Sheets("Sheet1").Activate
End If
End If
Next cell
End Sub
Related
i tried using dictionary but it only counts the repetition but i want to know the exact frequency of all datas in a column
what ive used is
Sub countThings()
Dim ws As Worksheet
Dim lastrow As Long, x As Long
Dim items As Object
Application.ScreenUpdating = False
Set ws = ActiveSheet
lastrow = ws.Range("B" & Rows.count).End(xlUp).Row
Set items = CreateObject("Scripting.Dictionary")
For x = 2 To lastrow
If Not items.exists(ws.Range("B" & x).Value) Then
items.Add ws.Range("B" & x).Value, 1
ws.Range("C" & x).Value = items(ws.Range("B" & x).Value)
Else
items(ws.Range("B" & x).Value) = items(ws.Range("B" & x).Value) + 1
ws.Range("C" & x).Value = items(ws.Range("B" & x).Value)
End If
Next x
items.RemoveAll
End Sub
and this gives me
[1: https://i.stack.imgur.com/Mhp5g.png][1]
but what i need is
[4: https://i.stack.imgur.com/UYOFu.png][4]
I think this is what you were after. Please try it.
Sub CountThings()
Dim Ws As Worksheet
Dim Items As Object ' Scripting.Dictionary
Dim Arr As Variant ' values in column B
Dim R As Long ' loop couner: Rows
Dim Key As Variant ' loop counter: dictionary keys
Set Items = CreateObject("Scripting.Dictionary")
Set Ws = ActiveSheet ' better: define tab by name
With Ws
' reading from the sheet is slow
' therefore read all items at once
Arr = .Range(.Cells(2, "B"), .Cells(.Rows.Count, "B").End(xlUp)).Value
' this is a 1-based 2-D array, like Arr([Rows], [Column])
' where column is always 1 because there's only 1 column
End With
For R = 1 To UBound(Arr)
If Items.Exists(Trim(Arr(R, 1))) Then
Items(Trim(Arr(R, 1))) = Items(Trim(Arr(R, 1))) + 1
Else
Items.Add Trim(Arr(R, 1)), 1
End If
Next R
ReDim Arr(1 To Items.Count, 1 To 2)
R = 0
For Each Key In Items.keys
R = R + 1
Arr(R, 1) = Key
Arr(R, 2) = Items(Key)
Next Key
' specify the top left cell of the target range
Ws.Cells(2, "C").Resize(UBound(Arr, 1), UBound(Arr, 2)).Value = Arr
Set Items = Nothing
End Sub
You need not Trim the Keys if you are sure that there can't be any stray leading or trailing blanks.
Your second picture doesn't need VBA. It can be produce by this formula, entered in C2 and copied down.
=COUNTIF($B$2:$B$13,$B2)
In fact, you can even do the job of my above code without VBA. Enter this formula in G2 of your sheet as an array formula (confirmed with CTL + SHIFT + ENTER if you don't have Excel 365), and the other one in H. Then copy both formulas down.
[G2] =IFERROR(INDEX($B$2:$B$13, MATCH(0, COUNTIF($G$1:G1, $B$2:$B$13), 0)), "")
[H2] =IF($G2<>"",COUNTIF($B$2:$B$13,$G2),"")
You need to assign values to column C after you have finished counting and therefore, need another loop:
Sub countThings()
Dim ws As Worksheet
Dim lastrow As Long, x As Long
Dim items As Object
Application.ScreenUpdating = False
Set ws = ActiveSheet
lastrow = ws.Range("B" & Rows.Count).End(xlUp).Row
Set items = CreateObject("Scripting.Dictionary")
For x = 2 To lastrow
If Not items.exists(ws.Range("B" & x).Value) Then
items.Add ws.Range("B" & x).Value, 1
Else
items(ws.Range("B" & x).Value) = items(ws.Range("B" & x).Value) + 1
End If
Next x
For x = 2 To lastrow
ws.Range("C" & x).Value = items(ws.Range("B" & x).Value)
Next x
items.RemoveAll
Set items = Nothing
End Sub
A simpler way to achieve what you want is to let excel do the counting for you like this:
Sub countThings2()
Dim sDataAddress As String
With ActiveSheet
sDataAddress = "$B$2:$B$" & .Cells(Rows.Count, "B").End(xlUp).Row
With .Range(sDataAddress).Offset(0, 1)
.Formula2 = "=COUNTIF(" & sDataAddress & ",B2)"
.Value = .Value
End With
End With
End Sub
i use table and 2 functions. not simple way but works :)
Sub Fx()
Dim str_Tab() As String, str_Text As String, str_Result As String
Dim int_Counter As Integer, int_TabItemCounter As Integer, int_LastRow As Integer
Dim rng_WorkRange As Range
int_LastRow = ActiveSheet.Range("A" & Rows.Count).End(xlUp).Row
Set rng_WorkRange = ActiveSheet.Range("A1:A" & int_LastRow)
For i = 1 To int_LastRow
str_Text = ActiveSheet.Range("A" & i)
If i > 1 Then
str_Result = IsInArray(str_Text, str_Tab)
If str_Result = -1 Then
int_TabItemCounter = UBound(str_Tab) - LBound(str_Tab)
ReDim str_Tab(int_TabItemCounter)
str_Tab(int_TabItemCounter) = str_Text
ActiveSheet.Range("B" & i) = CountThisItem(rng_WorkRange, str_Text)
Else
ActiveSheet.Range("B" & i) = CountThisItem(rng_WorkRange, str_Text)
End If ' If str_Result = -1
Else ' If i > 1
ReDim str_Tab(i)
str_Tab(i) = str_Text
ActiveSheet.Range("B" & i) = CountThisItem(rng_WorkRange, str_Text)
End If ' If i > 1
Next i
End Sub
function to check is text in table
Function IsInArray(stringToBeFound As String, arr As Variant) As Long
Dim i As Long
' default return value if value not found in array
IsInArray = -1
For i = LBound(arr) To UBound(arr)
If StrComp(stringToBeFound, arr(i), vbTextCompare) = 0 Then
IsInArray = i
Exit For
End If
Next i
End Function
function to count item in range
Function CountThisItem(CountingRange As Range, a As String) As Integer
Dim rng_FindRange As Range
Dim LA As String
Set rng_FindRange = CountingRange.Find(a, lookat:=xlWhole)
If Not rng_FindRange Is Nothing Then
LA = rng_FindRange.Address
CountThisItem = 1
Do
Set rng_FindRange = CountingRange.Find(a, lookat:=xlWhole, after:=rng_FindRange)
If Not rng_FindRange Is Nothing And rng_FindRange.Address <> LA Then CountThisItem = CountThisItem + 1
Loop While rng_FindRange.Address <> LA
Else
CountThisItem = 0
End If
End Function
I want to increment the decimal part of a number and restart numbering every time the number changes as below
1.00
1.01
1.02
1.03
1.04
1.05
2.00 'Restart With 2
2.01
3.00 'Restart With 3
3.01
3.02
3.03
I used the following Code
Sub AutoNumberDecimals()
Dim Rng, C As Range
Dim Lrow As Long
Dim i As Integer
Lrow = Cells(Rows.Count, 1).End(xlUp).Row
Set Rng = Worksheets("Union").Range("A2:A" & Lrow)
For Each C In Rng.Cells
If C.Value = "" And C.Offset(0, 1).Value = "" Then
C.Offset(1, 0).Value = C.Value + 0.01
Next C
End Sub
But It did not work
Appreciate your help
Thanks, Regards
I wrote this code. Make sure to add it in Sheet1 module (or similar sheet). It reacts when you enter a number in column 1 and it renumbers all numbers in that column. If you enter 1, it shows 1.00... if you enter 1 again, it will show 1.01. If you enter 2 you will have 2.00 etc...
Private ChangingValues As Boolean
Private Sub RenumFirstColumn()
Dim RowNo As Integer
Dim Major As Integer
Dim Minor As Integer
Dim CurrentValue As String
RowNo = 1
Major = 1
Minor = 0
Do
CurrentValue = CStr(Cells(RowNo, 1).Value)
If Int(Val(Left(CurrentValue, 1))) = Major Then
CurrentValue = CStr(Major) & "." & Format(Minor, "00")
Minor = Minor + 1
If Minor > 99 Then
MsgBox "To high value (> X.99)"
Exit Sub
End If
Else
Major = Val(Left(CurrentValue, 1))
Minor = 0
CurrentValue = CStr(Major) & "." & Format(Minor, "00")
Minor = Minor + 1
End If
Cells(RowNo, 1).NumberFormat = "#"
Cells(RowNo, 1).Value = CurrentValue
RowNo = RowNo + 1
Loop Until IsEmpty(Cells(RowNo, 1))
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 1 And ChangingValues = False Then
ChangingValues = True
RenumFirstColumn
ChangingValues = False
End If
End Sub
Hope it was what you were looking for
Try the next code, please. It uses maxIncr variable to set a maximum incrementing times:
Sub IncrementingRoots()
Dim sh As Worksheet, lastR As Long, maxIncr As Long
Dim NrI As Long, i As Long, j As Long
Set sh = ActiveSheet: maxIncr = 7
lastR = sh.Range("A" & Rows.count).End(xlUp).Row
For i = 2 To lastR + maxIncr
If sh.Range("A" & i).Value <> "" Then
NrI = sh.Range("A" & i).Value
For j = 1 To maxIncr
If sh.Range("A" & i + j).Value = Empty Then
sh.Range("A" & i + j).Value = sh.Range("A" & i + j - 1).Value + 0.01
Else
i = j + i - 1: Exit For
End If
Next
End If
If i > lastR Then Exit For
Next i
End Sub
And the next code is yours adapted to work. But impossible to procress the last number in the range, too, without something more (like maxIncr in my above code)...
Sub AutoNumberDecimals()
Dim sh As Worksheet, Rng As Range, C As Range, Lrow As Long, i As Long
Set sh = ActiveSheet 'Worksheets("Union")
Lrow = sh.cells(Rows.count, 1).End(xlUp).Row
Set Rng = sh.Range("A2:A" & Lrow)
For Each C In Rng.cells
If C.Value = "" And (C.Offset(1, 0).Value <> _
Int(C.Value Or C.Offset(1, 0).Value = "")) Then
C.Value = C.Offset(-1, 0).Value + 0.01
End If
Next C
End Sub
This uses DataSeries and NumberFormat to fill the cells.
This creates a random board, and isn't necessary to the main code.
Cells.Clear
Cells(1, 1) = 1 ' creates a random board
x = 2
For i = 2 To 20
If Rnd() > 0.8 Then
Cells(i, 1) = x
x = x + 1
End If
Next i
Cells(21, 1) = 0 ' terminates entries
Note that rather than determine the row column length using code, I have preset it to 21, although you can use the terminating 0.00 value to define a column length.
The main code:
Range("a:a").NumberFormat = "0.00"
For i = 1 To 21 ' loops through range
j = 0 ' finds local range
If Cells(i, 1) <> "" And Cells(i, 1) > 0 Then
Do
j = j + 1
Loop While Cells(i + j, 1) = ""
End If
Range(Cells(i, 1), Cells(i + j - 1, 1)).DataSeries Type:=xlLinear, Step:=0.01
i = i + j ' jumps to next entry
Next i
Each cell is formatted into the desired style. Then the loop finds a non-empty cell, and determines the associated local subrange by checking if the next cell down is empty or not, and continues until it isn't. Then the subrange is formatted using DataSeries with a Step of 0.01.
Ref:
https://learn.microsoft.com/en-us/office/vba/api/excel.range.dataseries
I am creating hierarchies and need to outline them in the format on the right-hand side. It would be a lot easier if I could simply outline the hierarchy in one column and automatically have it expand (left -> right in the sample).
A few considerations:
Within the first column, the start of a new hierarchy will always be the value 'A'
Hierarchies can range from 2-10 children in length
Any thoughts?
Type the letters in column A only, start each new sequence with the word HEADER. Then run the macro and the expansions should be created.
Sub expand()
Dim ws As Worksheet
Set ws = ActiveSheet
Dim cell As Range, cellHeader As Range
Dim irow As Integer, i As Integer
Dim iCount As Integer, iLast As Long
' find last row in col A
iLast = ws.Range("A" & Rows.Count).End(xlUp).Row
'scan down the sheet
For Each cell In ws.Range("A1:A" & iLast)
If UCase(cell) = "DIRECT" Then
' remember the header line
Set cellHeader = cell
With cellHeader
.BorderAround xlContinuous
.Font.Bold = True
End With
ElseIf Len(cell) > 0 Then
cell.BorderAround xlContinuous
' start of sequence
If cell = "A" Then
irow = 1
iCount = 0
End If
' add header value
With cellHeader.Offset(0, irow)
.Value = "L" & irow
.Font.Bold = True
.BorderAround xlContinuous
End With
' copy cell diagonally upwards
If irow > 1 Then
For i = 1 To irow - 1
cell.Offset(-i, i) = cell.Value
cell.Offset(-i, i).BorderAround xlContinuous
Next
End If
' check max children
iCount = iCount + 1
If iCount > 10 Then
MsgBox "Children count > 10", vbCritical, "Error"
Exit Sub
End If
irow = irow + 1
End If
Next
MsgBox "Expansion Complete", vbInformation
End Sub
You do not answer my questions and I cannot wait, anymore...
Please test the next code, which works based on the thowe assumptions: Your hierarchies in discussion have all the time a kind of header (Direct in column A:A and L1 in B:B). This, or an empty row sets the bottom part of the hierarchy.
Here's the code:
Sub HierarchyArrangeMultipleR()
Dim sh As Worksheet, i As Long, j As Long, lastR As Long, lastH As Long
Dim arrI As Variant, arrTr As Variant, colN As Long, k As Long, h As Long
Set sh = ActiveSheet 'please, use here your worksheet
lastR = sh.Range("A" & sh.Rows.count).End(xlUp).Row
For k = 1 To lastR
If lastH > 0 Then k = lastH + 1
If k >= lastR Then Exit For
Start:
If sh.Range("A" & k).Value = "Direct" And sh.Range("B" & k).Value = "L1" Then
For i = 1 To 10
If sh.Range("A" & k + i).Value = "Direct" Or _
sh.Range("A" & k + i).Value = Empty Then
lastH = k + i - 1: Exit For
End If
Next i
For h = 3 To lastH - k
sh.Cells(k, h) = "L" & h - 1
Next h
Else
k = k + 1: GoTo Start
End If
arrI = sh.Range("A" & k + 1 & ":A" & lastH).Value
ReDim arrTr(1 To UBound(arrI) - 1)
colN = 1
For i = k To lastH - 2
For j = 1 To UBound(arrTr) 'lastH - i + k - 2
arrTr(j) = arrI(j, 1)
Next j
colN = colN + 1
sh.Range(sh.Cells(k + 1, colN), sh.Cells(lastH + 1 - colN, colN)).Value = WorksheetFunction.Transpose(arrTr)
Next i
Erase arrTr
Next k
End Sub
I have product codes: (they are in C column of active sheet)
DO-001
DO-002
DO-003
DO-004
And I have big list of data: (they are in C column of "Sheet1")
41300100_DO-001_14215171
41300104_DO-001_14215173
K1_ISK_41300661_DO-002_13190369
NP_41533258_DO-003_14910884
DO-003_DD_44_ddd
And I want to count how many times do the product codes appear in the list of data. So the result for this case'd be: (result is H column of active sheet)
DO-001 2
DO-002 1
DO-003 2
DO-004
I have done this with this code:
Sub CountcodesPLC()
Dim i, j As Integer, icount As Integer
Dim ldata, lcodes As Long
icount = 0
lcodes = Cells(Rows.Count, 3).End(xlUp).Row
ldata = Worksheets("Sheet1").Cells(Rows.Count, 3).End(xlUp).Row
For i = 10 To lcodes
For j = 2 To ldata
If InStr(Worksheets("Sheet1").Range("C" & j), Range("C" & i)) <> 0 Then
icount = icount + 1
End If
Next j
If icount <> 0 Then
Range("H" & i).Value = icount
End If
icount = 0
Next i
End Sub
But I want to change it, so if the list of data contains some key words like "NP", "ISK", then not to count them, or if the first part of the data is the code then also not to count them, so the result for this example would be:
DO-001 2
DO-002
DO-003
DO-004
Also, I'll have around 1.000 product codes, and around 60.000 strings of data.
Will my code crash?
Seems your code is OK. But if you want to match only the first part of string (a'ka StartsWith), i'd change only this line:
If InStr(Worksheets("Sheet1").Range("C" & j), Range("C" & i)) <> 0 Then
to:
If Worksheets("Sheet1").Range("C" & j) Like Range("C" & i) & "*" Then
For further details, please see: Wildcard Characters used in String Comparisons
Use Dictionnary
Dim Dict As Scripting.Dictionary
Set Dict = New Scripting.Dictionary
Arr = Split("refer your text here", "_")
For I = LBound(Arr) To UBound(Arr)
If Dict.Exists(Arr(I)) Then
Dict(Arr(I)) = Dict(Arr(I)) + 1 'increment
Else
Dict.Add Arr(I), 1
End If
Next I
This may be OTT for the requirement but should work quite quickly.
Public Sub Sample()
Dim WkSht As Worksheet
Dim LngRow As Long
Dim AryLookup() As String
Dim VntItem As Variant
'We put what we want to search into an array, this makes it a lot quicker to search
Set WkSht = ThisWorkbook.Worksheets("Sheet1")
ReDim AryLookup(0)
LngRow = 1
Do Until WkSht.Range("A" & LngRow) = ""
If AryLookup(UBound(AryLookup, 1)) <> "" Then ReDim Preserve AryLookup(UBound(AryLookup, 1) + 1)
AryLookup(UBound(AryLookup, 1)) = Trim(UCase(WkSht.Range("A" & LngRow)))
LngRow = LngRow + 1
Loop
Set WkSht = Nothing
'Then we go down the list and check the array against each item
Set WkSht = ActiveSheet
LngRow = 1
Do Until WkSht.Range("A" & LngRow) = ""
WkSht.Range("B" & LngRow) = 0
For Each VntItem In AryLookup()
'This looks for the match without any of the exclusion items
If (InStr(1, VntItem, Trim(UCase(WkSht.Range("A" & LngRow)))) <> 0) And _
(InStr(1, VntItem, "NP") = 0) And _
(InStr(1, VntItem, "ISK") = 0) Then
WkSht.Range("B" & LngRow) = WkSht.Range("B" & LngRow) + 1
End If
Next
LngRow = LngRow + 1
Loop
Set WkSht = Nothing
MsgBox "Done"
End Sub
Basically, the 60,000 data strings will go into an array in memory, then the array will be searched against the 1,000 products. Searching in memory should be quick.
One thing I would raise is the exclusion method may produce false positives.
For example, excluding NP will exclude: -
NP_41533258_DO-003_14910884
NPA_41533258_DO-003_14910884
41533258_ANP_DO-003_14910884
You may want to think about the method overall.
Have you considered an array formula, not sure how it will perform vs code, but, you could do something along these lines, where list is in A and prod numbers in B
=SUM(IF(NOT(ISERR(SEARCH(B1 & "_",$A$1:$A$5,1)))*(ISERR(SEARCH("NP_",$A$1:$A$5,1))),1,0))
Where "NP" would be replaced by a range containing the exclusions, I've left as NP to show what's happening.
The code would be like this. But I don't know the speed.
Sub test()
Dim vDB, vLook, vSum(), Sum As Long
Dim Ws As Worksheet, dbWs As Worksheet
Dim s As String, sF As String, sCode As String
Dim i As Long, j As Long, n As Long
Set dbWs = Sheets("Sheet1")
Set Ws = ActiveSheet
With Ws
vLook = .Range("a1", .Range("a" & Rows.Count).End(xlUp))
End With
With dbWs
vDB = .Range("c1", .Range("c" & Rows.Count).End(xlUp))
End With
n = UBound(vLook, 1)
ReDim vSum(1 To n, 1 To 1)
For i = 1 To n
sF = Split(vLook(i, 1), "-")(0)
sCode = Replace(vLook(i, 1), sF, "")
Sum = 0
For j = 1 To UBound(vDB, 1)
s = vDB(j, 1)
If Left(s, Len(sF)) = sF Or InStr(s, "NP") Or InStr(s, "ISK") Then
Else
If InStr(s, sCode) Then
Sum = Sum + 1
End If
End If
Next j
If Sum > 0 Then
vSum(i, 1) = Sum
End If
Next i
Ws.Range("h1").Resize(n) = vSum
End Sub
I'm not sure if this title is descriptive enough.
So basically I have a number of vectors (about 50 of them), each containing several hundred values. Each vector is labelled with a number, and they look something like this:
Vector 1
Stim1 12
Stim5 36
Stim7 24
Stim10 4
... ...
That is, they have a number associated with a specific stimulus label (StimX). However, each vector is populated by a unique set of stimulus labels; some stimulus labels are shared between multiple vectors - however, each vector does not contain every stimulus label, and no one stimulus label is shared by every vector. So, for example, Vector 2 would look like this:
Vector 2
Stim2 28
Stim3 33
Stim5 9
Stim8 40
... ...
and
Vector 3
Stim4 50
Stim3 10
Stim7 4
Stim11 22
... ...
Furthermore, each vector has a variable number of values ... some have 200, others 300, etc.
What I want to do is create a macro that will populate a matrix based on these vector values. So the matrix would look something like:
Vector 1 Vector 2 Vector 3 ...
Stim1 12
Stim2 28
Stim3 33 10
Stim4 50
Stim5 36 9
Stim6
Stim7 24 4
Stim8 40
Stim9
Stim10 4
Stim11 22
...
I don't really know VBA that well, so I am sure this can be done pretty simply.
I have assumed that your Vector and Stim list in sheet1, and sheet2 will display your matrix.
Column A - vectors and stims
Column B - corresponding #s
This code will do the job:
Option Explicit
Sub cMatrix()
Dim i As Long
Dim j As Long
Dim cnt As Long
cnt = 2
Dim tmp As Long
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Dim arr() As String
Set ws1 = ThisWorkbook.Sheets(1)
Set ws2 = ThisWorkbook.Sheets(2)
' populate Y axis: list of stims
For i = 1 To ws1.Range("A" & Rows.Count).End(xlUp).Row
If StrComp(CStr(Left(ws1.Range("A" & i), 1)), "s", vbTextCompare) = 0 Then
ws2.Range("A" & cnt).Value = ws1.Range("A" & i).Value
cnt = cnt + 1
End If
Next i
' populate X axis: vectors
cnt = 2
For i = 1 To ws1.Range("A" & Rows.Count).End(xlUp).Row
If StrComp(CStr(Left(ws1.Range("A" & i), 1)), "v", vbTextCompare) = 0 Then
ws2.Cells(1, cnt).Value = ws1.Range("A" & i).Value
cnt = cnt + 1
End If
Next i
' fill array
ReDim arr(ws2.Range("A" & Rows.Count).End(xlUp).Row - 1)
For i = 2 To ws2.Range("A" & Rows.Count).End(xlUp).Row
arr(i - 2) = ws2.Range("A" & i).Value
ws2.Range("A" & i).ClearContents
Next i
' remove duplicate
Call RemoveDuplicate(arr)
' reprint stims
For i = LBound(arr) To UBound(arr)
ws2.Range("A" & i + 2).Value = arr(i)
Next i
' fill matrix
For cnt = 2 To ws2.Cells(1, Columns.Count).End(xlToLeft).Column
For i = 1 To ws1.Range("A" & Rows.Count).End(xlUp).Row
If StrComp(ws2.Cells(1, cnt).Value, ws1.Range("A" & i).Value, vbTextCompare) = 0 Then
j = i + 1
While StrComp(Left(ws1.Range("A" & j).Value, 1), "S", vbTextCompare) = 0
For tmp = 2 To ws2.Range("A" & Rows.Count).End(xlUp).Row
If (StrComp(ws2.Range("A" & tmp).Value, ws1.Range("A" & j).Value, vbTextCompare) = 0) Then
ws2.Cells(tmp, cnt).Value = ws1.Range("B" & j).Value
j = j + 1
End If
Next tmp
Wend
End If
Next i
Next cnt
End Sub
Public Sub RemoveDuplicate(ByRef StringArray() As String)
Dim LowBound As Long, UpBound As Long
Dim TempArray() As String, Cur As Long
Dim A As Long, B As Long
If (Not StringArray) = True Then Exit Sub
LowBound = LBound(StringArray)
UpBound = UBound(StringArray)
ReDim TempArray(LowBound To UpBound)
Cur = LowBound
TempArray(Cur) = StringArray(LowBound)
For A = LowBound + 1 To UpBound
For B = LowBound To Cur
If LenB(TempArray(B)) = LenB(StringArray(A)) Then
If InStrB(1, StringArray(A), TempArray(B), vbBinaryCompare) = 1 Then Exit For
End If
Next B
If B > Cur Then Cur = B: TempArray(Cur) = StringArray(A)
Next A
ReDim Preserve TempArray(LowBound To Cur)
StringArray = TempArray
End Sub
If you have any questions, please ask!