Transform Excel data into custom format - vba

I'm attempting to transform a large Excel input table into a custom format, also as an Excel table. Visually this is what I need to accomplish:
The psuedo-code I've come up with to solve the problem goes as follows:
Pseudo code:
// Column A is of higher level than column B
Initialize dictionary
Read the entire spreadsheet into system memory
While(sheet has records) {
Loop through spreadsheet records top-down
Start at cell A1
Look to the immediate right of column A
if(A:B not already in dictionary){
Dictionary>> Append B as child of A in dictionary // Must find correct entry and append value
}
Move to cell A+1
}
Once sheet is out of records {
Move one column to the right
Repeat while method
Do this until entire column is null
}
while (dictionary has records) {
Key = Column A value
List of values = Column B value
Save values as new Excel sheet
}
end
I'm not sure if there are libraries that exist that would accomplish what I need here, I can use whatever language offers a solution.
Appreciate any input from you all.

Stuff the 'Raw Data' into a two dimensional variant array and cycle through each rank, building the children of the parent or sub-parent as the case may be.
Sub collate_family_values()
Dim v As Long, w As Long, vVALs As Variant
Dim sPAR As String, sTMP As String
With ActiveSheet '<-set this worksheet reference properly!
.Columns("f:g").EntireColumn.Delete
.Cells(1, 6) = "Output Data"
.Cells(2, 6).Resize(1, 2) = .Cells(2, 1).Resize(1, 2).Value
vVALs = Application.Transpose(.Range(.Cells(3, 1), .Cells(Rows.Count, 4).End(xlUp)).Value)
For w = LBound(vVALs, 1) To UBound(vVALs, 1) - 1
sTMP = ChrW(8203)
sPAR = vVALs(w, LBound(vVALs, 2))
For v = LBound(vVALs, 2) To UBound(vVALs, 2)
If Not CBool(InStr(1, sTMP, ChrW(8203) & vVALs(w + 1, v) & ChrW(8203), vbTextCompare)) Then
sTMP = sTMP & vVALs(w + 1, v) & ChrW(8203)
End If
If sPAR <> vVALs(w, Application.Min(v + 1, UBound(vVALs, 2))) Or v = UBound(vVALs, 2) Then
.Cells(Rows.Count, 6).End(xlUp).Offset(1, 0).Resize(1, 2) = _
Array(sPAR, Replace(Mid(sTMP, 2, Len(sTMP) - 2), ChrW(8203), ", "))
sTMP = ChrW(8203)
If v < UBound(vVALs, 2) Then sPAR = vVALs(w, v + 1)
End If
Next v
Next w
End With
End Sub
I moved the 'Output Dat' results to the right of the 'Raw Data' due to an unknown number of rows to adjust for.
    

Related

Code checking/helping

can someone please look into my code and say me where is the mistake cause I got a type mismatch error message ? With this code I would like to delete all rows who which contain "0" in the respective cells.
I got the error message for the line where is standing: sn = Application.Index(sn, Application.Transpose(Split(Mid(c00, 2), "|")), [transpose(row(1:8))])
Also I had to declare the variable "c00" and I choosed "c00 As Variant". I don't know if it its correct. I would appreciate someone helping me to solve the problem.
Dim sn As Variant, c00 As Variant
sn = Sheets(1).UsedRange
For j = 1 To UBound(sn)
If sn(j, 4) & sn(j, 5) & sn(j, 6) & sn(j, 7) & sn(j, 8) & sn(j, 9) = "000000" Then c00 = c00 & "|" & j
Next
If c00 <> "" Then
sn = Application.Index(sn, Application.Transpose(Split(Mid(c00, 2), "|")), [transpose(row(1:8))])
Sheets(1).UsedRange.ClearContents
Sheets(1).Cells(1).Resize(UBound(sn), UBound(sn, 2)) = sn
End If
Original Code
Dim LR%
LR = Cells(Rows.Count, 3).End(xlUp).Row
Set Myrange = Range("D2:AO" & LR).SpecialCells(xlCellTypeBlanks) 'nur Leerzellen
Myrange.Formula = "0"
ActiveSheet.UsedRange
Set r = ActiveSheet.UsedRange
lastrow3 = r.Rows.Count + r.Row - 1
For j = lastrow3 To 1 Step -1
If (Cells(j, 4) = 0 And Cells(j, 5) = 0 And Cells(j, 6) = 0 And Cells(j, 7) = 0 And Cells(j, 8) = 0 And Cells(j, 9) = 0) Then
Rows(j).Delete
End If
Next j
Image w/ Error
Edit: the error was from attempting to use Application.Index on an array larger than the function size limit. Redirect to here for Q&A an on alternative option to Application.Index.
I'll break down my analysis of your code:
Application.Index(Array, Row_Number, Column_Number)
The code you currently have:
sn = Application.Index(sn, Application.Transpose(Split(Mid(c00, 2), "|")), [transpose(row(1:8))])
is saying that the parameters are:
Array: sn
Row_Number: Application.Transpose(Split(Mid(c00, 2), "|"))
Column_Number: [transpose(row(1:8))]
The Array section looks fine to me. The Row numbers will, I think(?), be the values for j which you collected in c00 (although the Application.Transpose may not be necessary Correction: it is in this scenario.). I have no idea what is going on with your Column_Number parameter....
Issues:
Application.Index keeps the selected columns/rows. However, your if statement selects the values of j where the rows are entirely 0, so instead of losing them, you would be keeping only those rows.
If your intention is to keep all the columns, you can just input 0 into the Column_Number parameter. Correction: this works when only selecting a single row to keep. If selecting multiple rows, all columns must be listed as well.
Corrected code:
Since this code does delete data, you should save a copy of the data before running this code on it.
Note: c00 can be a Variant; String also works. You will need to also copy over the fillA function, as well.
Dim sn As Variant, c00 As String
sn = Sheets(1).UsedRange
' Changed condition based on your post with previous code. (And negated)
For j = 1 To UBound(sn)
If Not ((Cells(j, 4) = 0 And Cells(j, 5) = 0 And Cells(j, 6) = 0 And Cells(j, 7) = 0 And Cells(j, 8) = 0 And Cells(j, 9) = 0)) Then c00 = c00 & "|" & j
Next
If c00 <> "" Then
' Corrected inputs for Application.Index, Added helper function "fillA".
sn = Application.Index(sn, Application.Transpose(Split(Mid(c00, 2), "|")), fillA(1, UBound(sn, 2) - LBound(sn, 2) + 1))
Sheets(1).UsedRange.ClearContents
Sheets(1).Cells(1).Resize(UBound(sn), UBound(sn, 2)) = sn
End If
Function fillA(min As Long, max As Long) As Variant
Dim var() As Variant, i As Long
ReDim var(1 To max - min + 1)
For i = min To max
var(i) = i + min - 1
Next i
fillA = var
End Function
Edit:
Realized this did not address your issue. I suspect that the error was from the [transpose(row(1:8))] you were inserting for the Column_Number parameter.
Maybe someone else has a simpler way of doing what I did with the fillA function (what I believe you were attempting).

Loading a VBA Dictionary which Sums Duplicate Keys

I am trying to use a dictionary to perform a lookup. I am getting some incorrect results because of duplicates in the data I am lookup up to. Below is the "formula version" of my lookup:
=IFERROR(VLOOKUP([#[Contract]],'Subs Summary'!I:P,8,FALSE),0)
The issue is that on the Subs Summary worksheet, the "Contract" (Column I) can have multiple lines with the same contract (and the Vloookup only pulls back the first line it finds the contract on). I want to perform the lookup via a dictionary and when a duplicate contract occurs, to SUM the values in column P (instead of only retrieving the first instance / line).
Below is my current Code for the dictionary loading and lookup:
Dim x, x2, y, y2()
Dim i As Long
Dim dict As Object
Dim LastRowTwo As Long, shtOrders As Worksheet, shtReport As Worksheet
Set shtOrders = Worksheets("Orders")
Set shtReport = Worksheets("Subs Summary")
Set dict = CreateObject("Scripting.Dictionary")
'get the lookup dictionary from Report
With shtReport
lastRow = .Range("A" & Rows.Count).End(xlUp).Row
x = .Range("I2:I" & lastRow).Value
x2 = .Range("P2:P" & lastRow).Value
For i = 1 To UBound(x, 1)
dict.Item(x(i, 1)) = x2(i, 1)
Next i
End With
'map the values
With shtOrders
lastRow = .Range("A" & Rows.Count).End(xlUp).Row
y = .Range("C2:C" & lastRow).Value 'looks up to this range
ReDim y2(1 To UBound(y, 1), 1 To 1) '<< size the output array
For i = 1 To UBound(y, 1)
If dict.exists(y(i, 1)) Then
y2(i, 1) = dict(y(i, 1))
Else
y2(i, 1) = "0"
End If
Next i
.Range("CM2:CM" & lastRow).Value = y2 '<< place the output on the sheet
End With
This code (I believe) is be performing the Vlookup correctly, but without handling the duplicates at all. I am trying to code a check if the key (in Column I) exists already in the dictionary, and if so, sum the line's value in Column P to already existing column P values for that contract/key. There are often times where a key/ contract will have 4 lines in the lookup page (Subs Summary).
Any input is greatly appreciated - I am fairly new to dictionaries and VBA in general, so it could be that my existing code has another issue / inefficiency. It does run without error and retrieves correct values except for duplicates as far as I can tell.
Cheers!
I was able to adapt my above posted code by adjusting / adding this portion:
If Not dict.exists(x(i, 1)) Then
dict.Add x(i, 1), x2(i, 1)
Else
dict.Item(x(i, 1)) = CDbl(dict.Item(x(i, 1))) + CDbl(x2(i, 1))
End If
Next i
SUMIFS did not end up working because there was duplicates on both the "Orders" worksheet as well as the "Subs Summary" worksheet. Perhaps there is a way to do this using only SUMIFS, but the code (shown below) in it's entirety, works great.
Dim x, x2, y, y2()
Dim i As Long
Dim dict As Object
Dim LastRowTwo As Long, shtOrders As Worksheet, shtReport As Worksheet
Set shtOrders = Worksheets("Orders")
Set shtReport = Worksheets("Subs Summary")
Set dict = CreateObject("Scripting.Dictionary")
'get the lookup dictionary from Report
With shtReport
lastRow = .Range("A" & Rows.Count).End(xlUp).Row
x = .Range("I2:I" & lastRow).Value
x2 = .Range("P2:P" & lastRow).Value
For i = 1 To UBound(x, 1)
If Not dict.exists(x(i, 1)) Then
dict.Add x(i, 1), x2(i, 1)
Else
dict.Item(x(i, 1)) = CDbl(dict.Item(x(i, 1))) + CDbl(x2(i, 1))
End If
Next i
End With
'map the values
With shtOrders
lastRow = .Range("A" & Rows.Count).End(xlUp).Row
y = .Range("C2:C" & lastRow).Value 'looks up to this range
ReDim y2(1 To UBound(y, 1), 1 To 1) '<< size the output array
For i = 1 To UBound(y, 1)
If dict.exists(y(i, 1)) Then
y2(i, 1) = dict(y(i, 1))
Else
y2(i, 1) = "0"
End If
Thanks all!
Next i
.Range("CM2:CM" & lastRow).Value = y2 '<< place the output on the sheet
End With

Paste one array (single dimension) with >100K item into a excel range

I posted the same question on StackOverflow thread
but I think here is the correct place to ask (if is not right, admin please to remove it).Every day I need to format date imported from AS400 (data, time,..).
Usualy (for some thousands of record) I use this code.
Public Sub Cfn_FormatDate(control As IRibbonControl)
Application.ScreenUpdating = False
Dim UR As Long, X As Long
Dim MyCol As Integer
MyCol = ActiveCell.Column
UR = Cells(Rows.Count, MyCol).End(xlUp).Row
For X = 2 To UR
If Not IsDate(Cells(X, MyCol)) Then
Select Case Len(Cells(X, MyCol))
Case 8
Cells(X, MyCol) = DateSerial(Left(Cells(X, MyCol), 4), Mid(Cells(X, MyCol), 5, 2), Right(Cells(X, MyCol), 2))
Case 6
Cells(X, MyCol) = DateSerial(Left(Cells(X, MyCol), 2), Mid(Cells(X, MyCol), 3, 2), Right(Cells(X, MyCol), 2))
End Select
End If
Next X
Columns(MyCol).NumberFormat = "DD/MM/YYYY;#"
Columns(MyCol).EntireColumn.AutoFit
Application.ScreenUpdating = True
End Sub
but if the records are are many more, the code posted code is not performing.
(ex 70K records were formatted / pasted in 18 seconds)
so I thought to using variables in an array and I wrote this code:
Sub ConvDate(c As Integer)
Application.ScreenUpdating = False
Dim lrw As Long, i As Long
Dim ArrVal As Variant
lrw = ActiveSheet().Range(Cells(1, c)).End(xlDown).Row
ReDim ArrVal(2 To lrw)
For i = 2 To lrw
If IsDate(Cells(i, c)) Then
ArrVal(i) = Cells(i, c)
Else
Select Case Len(Cells(i, c)) ' to check YYYYMMDD or YYMMDD
Case 8
ArrVal(i) = DateSerial(Left(Cells(i, c), 4), Mid(Cells(i, c), 5, 2), Right(Cells(i, c), 2))
Case 6
ArrVal(i) = DateSerial(Left(Cells(i, c), 2), Mid(Cells(i, c), 3, 2), Right(Cells(i, c), 2))
End Select
End If
NextX:
Next i
Range(Cells(2, c), Cells(lrw, c)) = ArrVal
Columns(c).NumberFormat = "DD/MM/YYYY;#"
Columns(c).EntireColumn.AutoFit
Application.ScreenUpdating = True
End Sub
It not work, all the cells (in the range) have the same result (Cells(2, c)).
a guy suggested me to change the code like:
ActiveSheet.Range(Cells(2, c), Cells(lrw, c)).Value = WorksheetFunction.Transpose(ArrVal)
this change is limiting , over 65536 records I get an error (runtime 13, type mismatch)
Ok, to summarise all the answers and comments:
As you have indicated in your question and as user85489 alludes, reading the values into an array, manipulating that same array, and writing it back to the sheet is vastly quicker than lopping cell by cell.
If you have an array whose 'row' dimension is not going to change. Then it might be fair to say that you're better off declaring a 2 dimensional array of size (1 to rows, 1 to columns). This way you can avoid having to transpose a 1 dimensional array at all.
Because as Gareth points out, Transpose() is limited to 65536 elements in a dimension.
Putting it all together, then, skeleton code for your post could be this:
Sub ConvertDates(colIndex As Long)
Dim v As Variant
Dim firstCell As Range
Dim lastCell As Range
Dim fullRange As Range
Dim i As Long
Dim dd As Integer
Dim mm As Integer
Dim yy As Integer
Dim dat As Date
'Define the range
With ThisWorkbook.Worksheets("Sheet1")
Set firstCell = .Cells(2, colIndex)
Set lastCell = .Cells(.Rows.Count, colIndex).End(xlUp)
Set fullRange = .Range(firstCell, lastCell)
End With
'Read the values into an array
v = fullRange.Value
'Convert the text values to dates
For i = 1 To UBound(v, 1)
If Not IsDate(v(i, 1)) Then
If Len(v(i, 1)) = 6 Then v(i, 1) = "20" & v(i, 1)
yy = CInt(Left(v(i, 1), 4))
mm = CInt(Mid(v(i, 1), 5, 2))
dd = CInt(Right(v(i, 1), 2))
dat = DateSerial(yy, mm, dd)
v(i, 1) = dat
End If
Next
'Write the revised array and format range
With fullRange
.NumberFormat = "DD/MM/YYYY;#"
.Value = v
.EntireColumn.AutoFit
End With
End Sub
You have come across the 32 bit limitation of the function Transpose which truncates your array to 65536.
You can use loop statement to populate the cells, else if you want to do it directly then define your array ArrVal like:
Redim ArrVal(1,Lrw) as variant
Flood the array with values and then, Offload it like
Range(Cells(2, c), Cells(lrw, c)) = ArrVal
Hopefully you get rid of the same value errors.

Optimization for Excel

Here is the Situation: In my Excel sheet I had a column with entries in the form 1-name. I wanted to remove the numbers, taking into account that the number can also be double digit. This by itself was not a Problem and I got it working, just the Performance is so bad. As it is now my program needs about half a second per cell entry.
My question: How can I improve the performance?
Here is the code:
Sub remove_numbers()
Dim YDim As Long
Dim i As Integer, l As Integer
Dim val As String
Dim s As String
YDim = Cells(Rows.Count, 5).End(xlUp).Row
For i = 8 To YDim
val = Cells(i, 5)
l = Len(val)
s = Mid(val, 2, 1)
If s = "-" Then
val = Right(val, l - 2)
Else
val = Right(val, l - 3)
End If
Cells(i, 5).Value = val
Next i
End Sub
Instead of using 3 different functions: Len(), Mid(), Right() you could use a Split() function which would have been much more efficient in this case.
Try the below code
Sub remove_numbers()
Application.ScreenUpdating = False
Dim i As Long
For i = 8 To Cells(Rows.Count, 5).End(xlUp).Row
Cells(i, 5) = Split(Cells(i, 5), "-")(1)
Next i
Application.ScreenUpdating = True
End Sub
My suggestion:
Sub remove_numbers()
Dim i As Integer, values() As Variant
values = Range(Cells(8, 5), Cells(Rows.Count, 5).End(xlUp).Row).Value
For i = LBound(values) To UBound(values)
values(i, 1) = Mid(values(i, 1), IIf(Mid(values(i, 1), 2, 1) = "-", 2, 3))
Next
Range(Cells(8, 5), Cells(Rows.Count, 5).End(xlUp).Row).Value = values
End Sub
Optimizations:
Perform all calculations in memory and them update entire range: this is a HUGE performance gain;
Condensed multiple commands into a single command;
Replaced Right(x, Len(x)-n) with Mid(x, n).
EDIT:
As suggested by #Mehow, you may also gain some performance using
values(i, 1) = Split(values(i, 1), "-", 2)(1)
instead of values(i, 1) = Mid(values(i, 1), IIf(Mid(values(i, 1), 2, 1) = "-", 2, 3))
You should manipulate the whole range values as an array and work directly with it in memory.
Something like :
Dim valuesOfRangeToModify() As Variant
Set valuesOfRangeToModify = Range(Cells(8, 5), Cells(Rows.Count, 5).End(xlUp)).Value
For Each cell In valuesOfRangeToModify
cell = ... // remove numbers
Next
Range(Cells(8, 5), Cells(Rows.Count, 5).End(xlUp)).Value = valuesOfRangeToModify
My VB is quite old so it probably has syntax errors but you get the idea.
This should give a huge boost.
For reference, here is an article full of interesting advices, see point #4 for more explanation of the solution given above :
http://www.soa.org/news-and-publications/newsletters/compact/2012/january/com-2012-iss42-roper.aspx
Also do not operate one cell at a time. Create a range of cells and transfer them into an array for processing. In the end the array can be used to replace the cells.
To tweak the answer from #mehow
Sub remove_numbers()
Dim i As Long, N as Long, r as Range
Set r = Range("B3") ' Whatever is the first cell in the column
N = Range(r, r.End(xlDown)).Rows.Count 'Count the rows in the column
Set r = r.Resize(N,1) ' Expand the range with all the cells
Dim values() as Variant
values = r.Value ' Collect all the values from the sheet
For i=1 to N
values(i,1) = Split( values(i,1), "-")(1)
Next i
r.Value = values 'Replace values to the sheet
End Sub
To make it more general you can add an argument to the procedure to pass a reference to the first cell in the column, like Sub remove_numbers(ByRef r as Range). There is no need to de-activate the screen as there is only one write operation in the end and you want the screen to update after that.

Excel/VBA Breakdown field

Bit of a complicated task I have to do but I will try and explain. I have an excel file with 23000 lines of data which I am importing into a website. Each one has a field like so:
Category | other data | other data 2
Foods/Dog/Treats Pre-Pack | 1223 | image.jpg
I need it to grab each line and add a new line below it for each "/" so turning the above into:
Category | other data | other data 2
[blank in original line] | 1223 | image.jpg
Foods | [blank field] | [blank field]
Foods/Dog | [blank field] | [blank field]
Foods/Dog/Treats Pre-Pack | [blank field] | [blank field]
So the script needs to add a new line for each category but keeping the original category in front of it. So turning category/category2/category 3 into 4 lines of: [blank] - category - category/category2 - category/category2/category 3
Does anyone know a way or script to do this?
Thanks, Simon
Note: The worksheet is called "test" and the category column starts at E2 and goes to E23521
I have the following script:
Sub test()
Dim a, i As Long, ii As Long, e, n As Long
Dim b(), txt As String, x As Long
With Range("a1").CurrentRegion
a = .Value
txt = Join$(Application.Transpose(.Columns(5).Value))
With CreateObject("VBScript.RegExp")
.Global = True
.Pattern = "/"
x = .Execute(txt).Count * 2
End With
ReDim b(1 To UBound(a, 1) + x, 1 To UBound(a, 2))
For i = 1 To UBound(a, 1)
If a(i, 5) <> "" Then
For Each e In Split(a(i, 5), "/")
n = n + 1
For ii = 1 To UBound(a, 2)
b(n, ii) = a(i, ii)
Next
b(n, 5) = Trim$(e)
Next
End If
Next
.Resize(n).Value = b
End With
End Sub
This seems to create a new row as I need it to but does not keep the slash structuring moving up with each one. And also dosnt add a blank line on all the new ones and make the original category value blank.
SOLVED:
Sub splitEmUp()
Dim splitter() As String 'this is storage space for the split function
Dim i As Integer ' main-loop for counter "which cell we are on"
Dim j As Integer ' splitter for-loop counter "which section of the split are we on"
Range("E2").Activate 'starting in cell e2 because row 1 is headers and category is located in the B column
For i = 0 To 24000 'from beginning to end i=0 means e2, i=1 means e3
ActiveCell.Offset(i, 0).Value = Replace(ActiveCell.Offset(i, 0).Value, " / ", "!##")
splitter = Split(ActiveCell.Offset(i, 0), "/") 'split the cell based on / and store it in splitter
If (UBound(splitter)) > 0 Then 'if a split occurred
ActiveCell.Offset(i, 0).Value = "" 'set the activecell to blank
Debug.Print i
ActiveCell.Offset(i + 1, 0).EntireRow.Insert shift:=xlDown, copyorigin:=xlFormatFromLeftOrAbove 'insert a new row and shift everything down
ActiveCell.Offset(i + 1, 0).Value = splitter(0) 'initialize the "Down" cells
ActiveCell.Offset(i + 1, 0).Value = Replace(ActiveCell.Offset(i + 1, 0).Value, "!##", " / ")
For j = 1 To UBound(splitter)
ActiveCell.Offset(i + j + 1).EntireRow.Insert shift:=xlDown, copyorigin:=xlFormatFromLeftOrAbove 'create another row if it needs to
ActiveCell.Offset(i + (j + 1), 0).Value = ActiveCell.Offset(i + j).Value & "/" & splitter(j) 'fill out the new row
ActiveCell.Offset(i + (j + 1), 0).Value = Replace(ActiveCell.Offset(i + (j + 1), 0).Value, "!##", " / ")
Next
i = i + UBound(splitter) + 1 'need to step I past the new cells
ReDim splitter(0)
Erase splitter 'erase and eliminate splitter to avoid carry over.
End If
Next
End Sub
This is what I came up with. Be sure to change the sheet names to fit your workbook. Also be sure to change the input range to fit your own input range of cells.
Function SplitAndWrite(inputCell As Range, TopOfOutputRange As Range, sep As String) As Range
Dim texts() As String, i As Integer, outputText As String
texts = Split(inputCell.Value, sep)
outputText = ""
TopOfOutputRange = "" 'your blank line
For i = LBound(texts) To UBound(texts)
outputText = outputText & sep & texts(i)
TopOfOutputRange.Offset(i + 1) = outputText
Next i
Set SplitAndWrite = TopOfOutputRange.Offset(UBound(texts) + 1)
End Function
Sub THEPOPULATOR()
Dim s3 As Worksheet, s4 As Worksheet
Set s3 = Sheets("Sheet1")
Set s4 = Sheets("Sheet2")
Dim inputrange As Range, c As Range, outputrange As Range
Set outputrange = s4.Range("A1")
Set inputrange = s3.Range(s3.Cells(2, 1), s3.Cells(2, 1).End(xlDown)) 'cells(2,1) = "A1". change this to your top input cell. then the second half will find the bottom cell on its own. This is the same as pressing Ctrl+down
For Each c In inputrange
s3.Range(c.Offset(0, 1), c.Offset(0, c.End(xlToRight).Column)).Copy outputrange.Offset(1, 1)
Set outputrange = SplitAndWrite(c, outputrange.Offset(1), "/")
Next c
End Sub
Here is an example from another solution How to split cell in a row with Excel, which I modified just a tiny bit to fit your situation:
Public Sub solutionJook()
Dim arr() As Variant
Dim arrSum() As Variant
Dim arrResult() As Variant
Dim arrTemp As Variant
Dim i As Long
Dim j As Long
Dim h As Long
Dim lngSplitColumn As Long
'input of array to seperate -> should cover all columns+rows of your data
arr = Range("A1:C2")
'specify which column has the values to be split up -> here this is the category column
lngSplitColumn = 2
'using the boundries of the given range,
'arrSum has now always the right boundries for the first dimension
ReDim Preserve arrSum(LBound(arr, 2) To UBound(arr, 2), 1 To 1)
'create the array with seperated A B C
For i = LBound(arr, 1) To UBound(arr, 1)
'use split to make Foods/Dog/Treats Pre-Pack into an array, using '\' (chr(92)) as indicator
arrTemp = Split(arr(i, lngSplitColumn), Chr(92))
'every value of arrTemp creates a new row
For j = LBound(arrTemp) To UBound(arrTemp)
'loop through all input columns and create the new row
For h = LBound(arr, 2) To UBound(arr, 2)
If h = lngSplitColumn Then
'setup the value of the splitted column
Dim k as long
arrSum(h, UBound(arrSum, 2)) = arrTemp(LBound(arrTemp))
for k = LBound(arrTemp)+1 to j
arrSum(h, UBound(arrSum, 2)) = arrSum(h, UBound(arrSum, 2)) & "\" & arrTemp(k) 'set Foods Foods/Dog Foods/Dog/Treats Pre-Pack
next k
Else
'setup the value of any other column
arrSum(h, UBound(arrSum, 2)) = arr(i, h) 'set Value of Column h
End If
Next h
ReDim Preserve arrSum(LBound(arr, 1) To UBound(arr, 2), _
LBound(arrSum, 2) To (UBound(arrSum, 2) + 1))
Next j
Next i
'clean up last empty row (not realy necessary)
ReDim Preserve arrSum(LBound(arr, 1) To UBound(arr, 2), _
LBound(arrSum, 2) To (UBound(arrSum, 2) - 1))
'setup transposed result array
ReDim arrResult(LBound(arrSum, 2) To UBound(arrSum, 2), _
LBound(arrSum, 1) To UBound(arrSum, 1))
'transpose the array
For i = LBound(arrResult, 1) To UBound(arrResult, 1)
For j = LBound(arrResult, 2) To UBound(arrResult, 2)
arrResult(i, j) = arrSum(j, i)
Next j
Next i
'specify target range
Range(Cells(1, 5), Cells(UBound(arrResult, 1), 4 + UBound(arrResult, 2))) = arrResult
End Sub
You might need to adapt the target range however.
Cells(1,5) -> E1 is the starting point of pasting