I have a range I am looping through in VBA:
For Lrow = Firstrow To Lastrow Step 1
With .Cells(Lrow, "E")
If Not IsError(.Value) Then
End If
End With
Next Lrow
Within that if statement I need to load an array with each value only once
MB-NMB-ILA
MB-NMB-ILA
MB-NMB-STP
MB-NMB-STP
MB-NMB-WAS
MB-NMB-WAS
MB-NMB-WAS
So for the array I only want MB-NMB-ILA, MB-NMB-STP, and MB-NMB-WAS
Can anyone help me out, my brain isn't working right on a Monday! Thanks
You could use filter to test if something exists in the array.
Dim arr As Variant: arr = Array("test1", "test2", "test3")
If UBound(Filter(arr, "blah")) > -1 Then
Debug.Print "it is in the array"
Else
Debug.Print "it's not in the array"
End If
You could also use a collection and write a sub to add only unique items to the collection
Dim col As New Collection
Sub addIfUnique(sAdd As String)
Dim bAdd As Boolean: bAdd = True
If col.Count > 0 Then
Dim iCol As Integer
For iCol = 1 To col.Count
If LCase(col(iCol)) = LCase(sAdd) Then
bAdd = False
Exit For
End If
Next iCol
End If
If bAdd Then col.Add sAdd
End Sub
Private Sub Command1_Click()
Dim a As Integer
Dim b As Integer
For a = 1 To 10
addIfUnique "item " & a
For b = 1 To 10
addIfUnique "item " & b
Next b
Next a
For a = 1 To col.Count
Debug.Print col(a)
Next a
End Sub
Suppose I have the following in cell A1 to A5 and want an array of unique values i.e. {a,b,c,d}
A
1 "a"
2 "b"
3 "c"
4 "c"
5 "d"
The follow two pieces of code will help achieve this:
CreateUniqueArray - get val from each cell and add to array if not already in array
IsInArray - utility function to check if value in array by performing simple loop
I have to say that this is the brute force way and would welcome any improvements...
Sub Test()
Dim firstRow As Integer, lastRow As Integer, cnt As Integer, iCell As Integer
Dim myArray()
cnt = 0
firstRow = 1
lastRow = 10
For iCell = firstRow To lastRow
If Not IsInArray(myArray, Cells(iCell, 1)) Then
ReDim Preserve myArray(cnt)
myArray(cnt) = Cells(iCell, 1)
cnt = cnt + 1
End If
Next iCell
End Sub
Function IsInArray(myArray As Variant, val As String) As Boolean
Dim i As Integer, found As Boolean
found = False
If Not Len(Join(myArray)) > 0 Then
found = False
Else
For i = 0 To UBound(myArray)
If myArray(i) = val Then
found = True
End If
Next i
End If
IsInArray = found
End Function
Related
I'm trying to add a list of names from another worksheet that has duplicates. On the listbox, I want to have unique names, instead of duplicates. The following code is not sorting them for duplicates, it errors out. Any help is appreciated.
Dim intCount As Integer
Dim rngData As Range
Dim strID As String
Dim rngCell As Range
dim ctrlListNames as MSForms.ListBox
Set rngData = Application.ThisWorkbook.Worksheets("Names").Range("A").CurrentRegion
'declare header of strID and sort it
strID = "Salesperson"
rngData.Sort key1:=strID, Header:=xlYes
'Loop to add the salesperson name and to make sure no duplicates are added
For Each rngCell In rngData.Columns(2).Cells
If rngCell.Value <> strID Then
ctrlListNames.AddItem rngCell.Value
strID = rngCell.Value
End If
Next rngCell
Way 1
Use this to remove the duplicates
Sub Sample()
RemovelstDuplicates ctrlListNames
End Sub
Public Sub RemovelstDuplicates(lst As msforms.ListBox)
Dim i As Long, j As Long
With lst
For i = 0 To .ListCount - 1
For j = .ListCount - 1 To (i + 1) Step -1
If .List(j) = .List(i) Then
.RemoveItem j
End If
Next
Next
End With
End Sub
Way 2
Create a unique collection and then add it to the listbox
Dim Col As New Collection, itm As Variant
For Each rngCell In rngData.Columns(2).Cells
On Error Resume Next
Col.Add rngCell.Value, CStr(rngCell.Value)
On Error GoTo 0
Next rngCell
For Each itm In Col
ctrlListNames.AddItem itm
Next itm
Private Sub Workbook_Open()
Dim ctrlListNames As MSForms.ListBox
Dim i As Long
Dim j As Long
ctrlListNames.List = Application.ThisWorkbook.Worksheets("Names").Range("Salesperson").Value
With ctrlListNames
For i = 0 To .ListCount - 1
For j = .ListCount To (i + 1) Step -1
If .List(j) = .List(i) Then
.RemoveItem j
End If
Next
Next
End With
End Sub
And it says invalid property array index.
It says invalid property array index because the list gets shortened after the removal of entries. if we use FOR, the end value is static, therefore, we need to use DO while loop. Use the following code to remove duplicates.
Count = ListBox1.ListCount - 1
i = 0
j = 0
Do While i <= Count
j = i + 1
Do While j <= Count
If ListBox1.List(i) = ListBox1.List(j) Then
ListBox1.RemoveItem (j)
Count = ListBox1.ListCount - 1 'Need to update list count after each removal.
End If
j = j + 1
Loop
i = i + 1
Loop
Dear stackoverflow community
At work I have to write a macro which should be able to hide rows based on numbers in a column. Those can be multiple ones in one cell and the input should also allow to show more than one number at a time.
for example:
row 1: 20, 30, 15
row 2: 20
row 3: 13, 76
So if I enter 20, 30, it should only show rows 1 & 2)
I usually code with Java / c# and Im new to VBA, so Id really appreciate help:
My plan was to show a input box and split those numbers into an array.
Then i wanna go through each row with a for-Loop, in which i added two for each loops to check if any numbers equal. If not, hide row. If so, show and then i want to exit both for each loops and go to the next row. To exit nested loops, i tried using a do while boolean but it doesnt seem to work.
Right now it only shows the rows with all the input numbers (only row1 in example).
Sub SortingTest()
Dim numbers() As String
myNum = Application.InputBox("Enter BKPS (separate multiples by , )")
numbers = Split(myNum, ",", -1, compare)
'Userinput Vars
Dim row As Integer
row = 1
Dim saveNumber As String
'Looping Vars
Dim existingNum As String
Dim existingNumsArray() As String
Dim checkRows As Long
Dim saveElement As String
Dim done As Boolean
done = False
' Range("B3").Value = 10
' Saves the Input as Array:
For Each Element In numbers
saveNumber = Element
Cells(2, row).Value = saveNumber
row = row + 1
Next Element
Dim b As Integer
Do While done = False
For b = 1 To 100 'hardcoded, should be length of document. b == row;
existingNum = Cells(b, 3).Value
existingNumsArray = Split(existingNum, ",", -1, compare)
' loop thru input numbers
For Each Element In numbers
saveElement = Element
'loop thru given numbers
For Each inputElement In existingNumsArray
If saveElement <> inputElement Then
Rows(b).Hidden = True
ElseIf saveElement = inputElement Then
Rows(b).Hidden = False
done = True
Exit For
End If
Next
Next
Next
Loop
End Sub
Thank you very much for you answer. Yours hid all the rows, so i adjusted it to show them.
Option Explicit
Function ArrOr(a As Variant, b As Variant) As Boolean
Dim runner As Variant
ArrOr = True
If IsArray(a) Then
For Each runner In a
If ArrOr(runner, b) Then Exit Function
Next
Else
For Each runner In b
If Trim(a) = Trim(runner) Then Exit Function
Next
End If
ArrOr = False
End Function
Sub SortingBKPS()
Dim numbers As Variant, vars As Variant, i As Long, xRows As Range
numbers = Split(Application.InputBox("Enter BKPS (separate multiples by , )"), ",")
With Sheets("Sheet1")
vars = .Range("B1", .Cells(.Rows.Count, 2).End(xlUp)).Value2
For i = 2 To UBound(vars)
.Rows(i).EntireRow.Hidden = True
If ArrOr(Split(vars(i, 1), ","), numbers) Then
If xRows Is Nothing Then
Set xRows = .Rows(i)
Else
Set xRows = Union(xRows, .Rows(i))
End If
End If
Next
xRows.EntireRow.Hidden = False
End With
End Sub
By splitting it up it is very easy to do:
Option Explicit
Function ArrOr(a As Variant, b As Variant) As Boolean
Dim runner As Variant
ArrOr = True
If IsArray(a) Then
For Each runner In a
If ArrOr(runner, b) Then Exit Function
Next
Else
For Each runner In b
If Trim(a) = Trim(runner) Then Exit Function
Next
End If
ArrOr = False
End Function
Sub SortingTest()
Dim numbers As Variant, vars As Variant, i As Long, xRows As Range
numbers = Split(Application.InputBox("Enter BKPS (separate multiples by , )"), ",")
With Sheets("Sheet1")
vars = .Range("B1", .Cells(.Rows.Count, 2).End(xlUp)).Value2
For i = 1 To UBound(vars)
If ArrOr(Split(vars(i, 1), ","), numbers) Then
If xRows Is Nothing Then
Set xRows = .Rows(i)
Else
Set xRows = Union(xRows, .Rows(i))
End If
End If
Next
xRows.EntireRow.Hidden = True
End With
End Sub
by running this code line by line, it should be pretty much self explaining (also knowing you have already some knowledge in "coding")
Still, if you have any questions, just ask ;)
You can also do it the following way:
Sub SortingTest()
Dim numbers As Variant
Dim RangeCompare As Range
Dim MyRow As Integer
Dim NumFound As Boolean
numbers = Application.InputBox("Please,list the values in this format: " & _
vbCrLf & "{value, value, value, ...}", _
Default:="{#, #, #}", Type:=64)
For MyRow = 1 To Cells(Rows.Count, 1).End(xlUp).row
Set RangeCompare = Range(Cells(MyRow, 1), Cells(MyRow, Columns.Count).End(xlToLeft))
NumFound = False
For Each rCell In RangeCompare
For Each Element In numbers
If rCell = Element Then
NumFound = True
Exit For
End If
Next Element
If NumFound = True Then Exit For
Next rCell
If NumFound = False Then
Rows(MyRow).Hidden = True
End If
Next MyRow
End Sub
I think it's easy to understand but feel free to ask for explanation.
I am trying to make a queue which is able to show the first in first out concept. I want to have an array which works as a waiting list. The patients who come later will be discharged later. There is a limitation of 24 patients in the room the rest will go to a waiting list. whenever the room is empty the first patients from the waiting room (the earliest) goes to the room. Here is the code that I have come up with so far. Any help is greatly appreciated.
Dim arrayU() As Variant
Dim arrayX() As Variant
Dim arrayW() As Variant
Dim LrowU As Integer
Dim LrowX As Integer
Dim LrowW As Integer
'Dim i As Integer
Dim j As Integer
Dim bed_in_use As Integer
LrowU = Columns(21).Find(What:="*", LookIn:=xlValues, SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
LrowX = Columns(24).Find(What:="*", LookIn:=xlValues, SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
LrowW = Columns(23).Find(What:="*", LookIn:=xlValues, SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
ReDim arrayU(1 To LrowU)
ReDim arrayX(1 To LrowX)
ReDim arrayW(1 To LrowW)
For i = 3 To LrowU
arrayU(i) = Cells(i, 21)
Next i
i = 3
For i = 3 To LrowX
arrayX(i) = Cells(i, 24)
Next i
i = 3
j = 3
For r = 3 To LrowW
arrayW(r) = Cells(r, 23)
Next r
r = 3
i = 3
j = 3
For i = 3 To LrowX ' the number of bed in use is less than 24 (HH)
If bed_in_use >= 24 Then GoTo Line1
For j = 3 To LrowU
If bed_in_use >= 24 Then GoTo Line1
If arrayX(i) = arrayU(j) Then
If Wait_L > 0 Then
Wait_L = Wait_L - (24 - bed_in_use)
Else
bed_in_use = bed_in_use + 1
End If
End If
Next j
Line1:
For r = 3 To LrowW
If bed_in_use < 24 Then Exit For
If arrayX(i) = arrayW(r) Then
bed_in_use = bed_in_use - 1
Wait_L = Wait_L + 1
End If
Next r
Cells(i, "Y").Value = bed_in_use
Cells(i, "Z").Value = Wait_L
Next i
Easiest way to do this would be to implement a simple class that wraps a Collection. You could wrap an array, but you'd end up either having to copy it every time you dequeued an item or letting dequeued items sit in memory.
In a Class module (I named mine "Queue"):
Option Explicit
Private items As New Collection
Public Property Get Count()
Count = items.Count
End Property
Public Function Enqueue(Item As Variant)
items.Add Item
End Function
Public Function Dequeue() As Variant
If Count > 0 Then
Dequeue = items(1)
items.Remove 1
End If
End Function
Public Function Peek() As Variant
If Count > 0 Then
Peek = items(1)
End If
End Function
Public Sub Clear()
Set items = New Collection
End Sub
Sample usage:
Private Sub Example()
Dim q As New Queue
q.Enqueue "foo"
q.Enqueue "bar"
q.Enqueue "baz"
Debug.Print q.Peek '"foo" should be first in queue
Debug.Print q.Dequeue 'returns "foo".
Debug.Print q.Peek 'now "bar" is first in queue.
Debug.Print q.Count '"foo" was removed, only 2 items left.
End Sub
Would you not follow Comintern's "Class" approach (but I'd go with it!) you can stick to an "array" approach like follows
place the following code in any module (you could place it at the bottom of you code module, but you'd be better placing it in a new module to call, maybe, "QueueArray"...)
Sub Clear(myArray As Variant)
Erase myArray
End Sub
Function Count(myArray As Variant) As Long
If isArrayEmpty(myArray) Then
Count = 0
Else
Count = UBound(myArray) - LBound(myArray) + 1
End If
End Function
Function Peek(myArray As Variant) As Variant
If isArrayEmpty(myArray) Then
MsgBox "array is empty! -> nothing to peek"
Else
Peek = myArray(LBound(myArray))
End If
End Function
Function Dequeue(myArray As Variant) As Variant
If isArrayEmpty(myArray) Then
MsgBox "array is empty! -> nothing to dequeue"
Else
Dequeue = myArray(LBound(myArray))
PackArray myArray
End If
End Function
Sub Enqueue(myArray As Variant, arrayEl As Variant)
Dim i As Long
EnlargeArray myArray
myArray(UBound(myArray)) = arrayEl
End Sub
Sub PackArray(myArray As Variant)
Dim i As Long
If LBound(myArray) < UBound(myArray) Then
For i = LBound(myArray) + 1 To UBound(myArray)
myArray(i - 1) = myArray(i)
Next i
ReDim Preserve myArray(LBound(myArray) To UBound(myArray) - 1)
Else
Clear myArray
End If
End Sub
Sub EnlargeArray(myArray As Variant)
Dim i As Long
If isArrayEmpty(myArray) Then
ReDim myArray(0 To 0)
Else
ReDim Preserve myArray(LBound(myArray) To UBound(myArray) + 1)
End If
End Sub
Public Function isArrayEmpty(parArray As Variant) As Boolean
'http://stackoverflow.com/questions/10559804/vba-checking-for-empty-array
'assylias's solution
'Returns true if:
' - parArray is not an array
' - parArray is a dynamic array that has not been initialised (ReDim)
' - parArray is a dynamic array has been erased (Erase)
If IsArray(parArray) = False Then isArrayEmpty = True
On Error Resume Next
If UBound(parArray) < LBound(parArray) Then
isArrayEmpty = True
Exit Function
Else
isArrayEmpty = False
End If
End Function
then in your main sub you could go like this:
Option Explicit
Sub main()
Dim arrayU As Variant
Dim arrayX As Variant
Dim arrayW As Variant
Dim myVar As Variant
Dim j As Integer, i As Integer, R As Integer
Dim bed_in_use As Integer, Wait_L As Integer
Dim arrayXi As Variant
Const max_bed_in_use As Integer = 24 'best to declare a "magic" value as a constant and use "max_bed_in_use" in lieu of "24" in the rest of the code
'fill "queue" arrays
With ActiveSheet
arrayU = Application.Transpose(.Range(.cells(3, "U"), .cells(.Rows.Count, "U").End(xlUp))) 'fill arrayU
arrayX = Application.Transpose(.Range(.cells(3, "X"), .cells(.Rows.Count, "X").End(xlUp))) 'fill arrayX
arrayW = Application.Transpose(.Range(.cells(3, "W"), .cells(.Rows.Count, "W").End(xlUp))) 'fill arrayW
End With
'some examples of using the "queue-array utilities"
bed_in_use = Count(arrayU) 'get the number of elements in arrayU
Enqueue arrayU, "foo" ' add an element in the arrayU queue, it'll be placed at the queue end
Enqueue arrayU, "bar" ' add another element in the arrayU queue, it'll be placed at the queue end
bed_in_use = Count(arrayU) 'get the update number of elements in arrayU
Dequeue arrayU 'shorten the queue by removing its first element
myVar = Dequeue(arrayU) 'shorten the queue by removing its first element and storing it in "myvar"
bed_in_use = Count(arrayU) 'get the update number of elements in arrayU
MsgBox Peek(arrayU) ' see what's the first element in the queue
End Sub
My end result is to output the names in column A to column B in random order.
I have been researching but cant seem to find what I need.
So far I can kinda of randomise the numbers but its still giving me repeated numbers + the heading (A1).
I need it to skip A1 because this is the heading\title of the column and start at A2.
I assume once that is working correctly I add the randomNumber to a random name to Worksheets("Master Sheet").Cells(randomNumber, "B").Value ...something like that...?
OR if there is a better way of doing this let me know.
Sub Meow()
Dim CountedRows As Integer
Dim x As Integer
Dim i As Integer
Dim PreviousCell As Integer
Dim randomNumber As Integer
i = 1
PreviousCell = 0
CountedRows = Worksheets("Master Sheet").Range("A" & Rows.Count).End(xlUp).Row
If CountedRows < 2 Then
' If its only the heading then quit and display a messagebox
No_People_Error = MsgBox("No People entered or found, in column 'A' of Sheetname 'Master Sheet'", vbInformation, "Pavle Says No!")
Exit Sub
End If
Do Until i = CountedRows
randomNumber = Int((Rnd * (CountedRows - 1)) + 1) + 1
If Not PreviousCell = randomNumber Then
Debug.Print randomNumber
i = i + 1
End If
PreviousCell = randomNumber
Loop
Debug.Print "EOF"
End Sub
Here's a quick hack...
Sub Meow()
'On Error GoTo err_error
Dim CountedRows As Integer
Dim x As Integer
Dim i As Integer
Dim PreviousCell As Integer
Dim randomNumber As Integer
Dim nums() As Integer
PreviousCell = 0
CountedRows = Worksheets("Master Sheet").Range("A" & Rows.Count).End(xlUp).Row
ReDim nums(CountedRows - 1)
If CountedRows < 2 Then
' If its only the heading then quit and display a messagebox
No_People_Error = MsgBox("No People entered or found, in column 'A' of Sheetname 'Master Sheet'", vbInformation, "Pavle Says No!")
Exit Sub
End If
For i = 1 To CountedRows
rand:
randomNumber = randomNumbers(1, CountedRows, nums)
nums(i - 1) = randomNumber
Worksheets("Master Sheet").Range("B" & randomNumber) = Range("A" & i)
Next i
Exit Sub
err_error:
Debug.Print Err.Description
End Sub
Public Function randomNumbers(lb As Integer, ub As Integer, used As Variant) As Integer
Dim r As Integer
r = Int((ub - lb + 1) * Rnd + 1)
For Each j In used
If j = r Then
r = randomNumbers(lb, ub, used)
Else
randomNumbers = r
End If
Next
End Function
I've managed something similar previously using two collections.
Fill one collection with the original data and leave the other collection empty. Then keep randomly picking an index in the first collection, adding the value at that index to the second collection and delete the value from the original collection. Set that to loop until the first collection is empty and the second collection will be full of a randomly sorted set of unique values from your starting list.
***Edit: I've thought about it again and you don't really need the second collection. You can pop a random value from the first collection and write it directly to the worksheet, incrementing the row each time:
Sub Meow()
Dim lst As New Collection
Dim rndLst As New Collection
Dim startRow As Integer
Dim endRow As Integer
Dim No_People_Error As Integer
startRow = 2
endRow = Worksheets("Master Sheet").Cells(startRow, 1).End(xlDown).Row
If Cells(startRow, 1).Value = "" Then
' If its only the heading then quit and display a messagebox
No_People_Error = MsgBox("No People entered or found, in column 'A' of Sheetname 'Master Sheet'", vbInformation, "Pavle Says No!")
Exit Sub
End If
' Fill a collection with the original list
Dim i As Integer
For i = startRow To endRow
lst.Add Cells(i, 1).Value
Next i
' Create a randomized list collection
' Use i as a row counter
Dim rowCounter As Integer
rowCounter = startRow
Dim index As Integer
Do While lst.Count > 0
'Find a random index in the original collection
index = Int((lst.Count - 1 + 1) * Rnd + 1)
'Place the value in the worksheet
Cells(rowCounter, 2).Value = lst(index)
'Remove the value from the list
lst.Remove (index)
'Increment row counter
rowCounter = rowCounter + 1
Loop
End Sub
P.S. I hope there's an excellent story behind naming your sub Meow() :P
I have a string compressed into one cell. I need to separate each part of the string into their own cell, while copying the data from the same row.
Here is my example data:
A | B
Row1 ABC ABD ABE ABF | CODE1
Row2 BCA DBA EBA FBA | CODE2
Row3 TEA BEF | CODE3
The result would be:
A B
ABC CODE1
ABD CODE1
ABE CODE1
ABF CODE1
BCA CODE2
DBA CODE2
EBA CODE2
FBA CODE2
TEA CODE3
BEF CODE3
I have about 2000 rows and would literally take 30 years to use the text to column function for this. So I am trying to write a vba macro. I think I am making this harder than it needs to be. Any thoughts or pushes in the right direction would be appreciated. Thanks in advance for any help.
This will work, (but it's mighty inefficient unless you do it in an array... nevertheless for only 2000 rows, you won't even notice the lag)
Function SplitThis(Str as String, Delimiter as String, SerialNumber as Long) As String
SplitThis = Split(Str, Delimiter)(SerialNumber - 1)
End Function
Use it as
= SPLITTHIS("ABC EFG HIJ", " ", 2)
' The result will be ...
"EFG"
You will still need to put in a whole lot of extra error checking, etc. if you need to use it for a distributed application, as the users might put in values greater than the number of 'split elements' or get delimiters wrong, etc.
I like iterating over cells for problems like this post.
' code resides on input sheet
Sub ParseData()
Dim wksOut As Worksheet
Dim iRowOut As Integer
Dim iRow As Integer
Dim asData() As String
Dim i As Integer
Dim s As String
Set wksOut = Worksheets("Sheet2")
iRowOut = 1
For iRow = 1 To UsedRange.Rows.Count
asData = Split(Trim(Cells(iRow, 1)), " ")
For i = 0 To UBound(asData)
s = Trim(asData(i))
If Len(s) > 0 Then
wksOut.Cells(iRowOut, 1) = Cells(iRow, 2)
wksOut.Cells(iRowOut, 2) = s
iRowOut = iRowOut + 1
End If
Next i
Next iRow
MsgBox "done"
End Sub
Assuming your data is on the first sheet, this populates the second sheet with the formatted data. I also assume that the data is uniform, meaning there is the same type of data on every row until the data ends. I did not attempt the header line.
Public Sub FixIt()
Dim fromSheet, toSheet As Excel.Worksheet
Dim fromRow, toRow, k As Integer
Dim code As String
Set fromSheet = Me.Worksheets(1)
Set toSheet = Me.Worksheets(2)
' Ignore first row
fromRow = 2
toRow = 1
Dim outsideArr() As String
Dim insideArr() As String
Do While Trim(fromSheet.Cells(fromRow, 1)) <> ""
' Split on the pipe
outsideArr = Split(fromSheet.Cells(fromRow, 1), "|")
' Split left of pipe, trimmed, on space
insideArr = Split(Trim(outsideArr(0)), " ")
' Save the code
code = Trim(outsideArr(UBound(outsideArr)))
' Skip first element of inside array
For k = 1 To UBound(insideArr)
toSheet.Cells(toRow, 1).Value = insideArr(k)
toSheet.Cells(toRow, 2).Value = code
toRow = toRow + 1
Next k
fromRow = fromRow + 1
Loop
End Sub
Let me try as well using Dictionary :)
Sub Test()
Dim r As Range, c As Range
Dim ws As Worksheet
Dim k, lrow As Long, i As Long
Set ws = Sheet1 '~~> change to suit, everything else as is
Set r = ws.Range("B1", ws.Range("B" & ws.Rows.Count).End(xlUp))
With CreateObject("Scripting.Dictionary")
For Each c In r
If Not .Exists(c.Value) Then
.Add c.Value, Split(Trim(c.Offset(0, -1).Value))
End If
Next
ws.Range("A:B").ClearContents
For Each k In .Keys
lrow = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
If lrow = 1 Then i = 0 Else i = 1
ws.Range("A" & lrow).Offset(i, 0) _
.Resize(UBound(.Item(k)) + 1).Value = Application.Transpose(.Item(k))
ws.Range("A" & lrow).Offset(i, 1).Resize(UBound(.Item(k)) + 1).Value = k
Next
End With
End Sub
Above code loads all items in Dictionary and then return it in the same Range. HTH.
Here is an approach using a User Defined Type, Collection and arrays. I've been using this lately and thought it might apply. It does make writing the code easier, once you get used to it.
The user defined type is set in a class module. I called the type "CodeData" and gave it two properties -- Code and Data
I assumed your data was in columns A & B starting with row 1; and I put the results on the same worksheet but in columns D & E. This can be easily changed, and put on a different worksheet if that's preferable.
First, enter the following code into a Class Module which you have renamed "CodeData"
Option Explicit
Private pData As String
Private pCode As String
Property Get Data() As String
Data = pData
End Property
Property Let Data(Value As String)
pData = Value
End Property
Property Get Code() As String
Code = pCode
End Property
Property Let Code(Value As String)
pCode = Value
End Property
Then put the following code into a Regular module:
Option Explicit
Sub ParseCodesAndData()
Dim cCodeData As CodeData
Dim colCodeData As Collection
Dim vSrc As Variant, vRes() As Variant
Dim V As Variant
Dim rRes As Range
Dim I As Long, J As Long
'Results start here. But could be on another sheet
Set rRes = Range("D1:E1")
'Get Source Data
vSrc = Range("A1", Cells(Rows.Count, "B").End(xlUp))
'Collect the data
Set colCodeData = New Collection
For I = 1 To UBound(vSrc, 1)
V = Split(vSrc(I, 1), " ")
For J = 0 To UBound(V)
Set cCodeData = New CodeData
cCodeData.Code = Trim(vSrc(I, 2))
cCodeData.Data = Trim(V(J))
colCodeData.Add cCodeData
Next J
Next I
'Write results to array
ReDim vRes(1 To colCodeData.Count, 1 To 2)
For I = 1 To UBound(vRes)
Set cCodeData = colCodeData(I)
vRes(I, 1) = cCodeData.Data
vRes(I, 2) = cCodeData.Code
Next I
'Write array to worksheet
Application.ScreenUpdating = False
rRes.EntireColumn.Clear
rRes.Resize(rowsize:=UBound(vRes, 1)) = vRes
Application.ScreenUpdating = True
End Sub
Here is the solution I devised with help from above. Thanks for the responses!
Sub Splt()
Dim LR As Long, i As Long
Dim X As Variant
Application.ScreenUpdating = False
LR = Range("A" & Rows.Count).End(xlUp).Row
Columns("A").Insert
For i = LR To 1 Step -1
With Range("B" & i)
If InStr(.Value, " ") = 0 Then
.Offset(, -1).Value = .Value
Else
X = Split(.Value, " ")
.Offset(1).Resize(UBound(X)).EntireRow.Insert
.Offset(, -1).Resize(UBound(X) - LBound(X) + 1).Value = Application.Transpose(X)
End If
End With
Next i
Columns("B").Delete
LR = Range("A" & Rows.Count).End(xlUp).Row
With Range("B1:C" & LR)
On Error Resume Next
.SpecialCells(xlCellTypeBlanks).FormulaR1C1 = "=R[-1]C"
On Error GoTo 0
.Value = .Value
End With
Application.ScreenUpdating = True
End Sub