Split and write in cell in VBA - excel

I get some unclean data from a text file, and in the text file, I process it as per the data in the VBA script, which gives the following output.
This is the Raw String.
1* student 1*2018-01-01*1*1234122
2* student 2*2017-01-01*1*54654654234
3* student 3*2015-01-01*4*54234654654
4* student 4*2012-01-01*5*546542345654
I wanted the output in different cells, as mentioned below in any open worksheet in excel.
Roll No
Student Name
Date of Birth
class
phone
1
student 1
2018-01-01
1
1234122
2
student 2
2017-01-01
1
54654654234
3
student 3
2015-01-01
4
54234654654
4
student 4
2012-01-01
5
546542345654
I tried various sources but couldn't get the expected output.
Any help will be appreciated.

Please, try the next way. It assumes that the whole text you show is in a single cell, not in different linens. But the above way will also deal with a single row:
Sub extractTextDelimSep()
Dim x As String, arr, arrLine, arrFin, cols As Long, i As Long, j As Long
x = "1* student 1*2018-01-01*1*1234122" & vbCr & _
"2* student 2*2017-01-01*1*54654654234" & vbCr & _
"3* student 3*2015-01-01*4*54234654654" & vbCr & _
"4* student 4*2012-01-01*5*546542345654"
arr = Split(x, vbCr) 'split the rows
cols = UBound(Split(arr(0), "*")) + 1 'determine the columns number per row
ReDim arrFin(1 To UBound(arr) + 1, 1 To cols) 'ReDim the final array
For i = 0 To UBound(arr)
arrLine = Split(Replace(arr(i), " ", ""), "*")
For j = 0 To UBound(arrLine)
arrFin(i + 1, j + 1) = arrLine(j)
Next j
Next i
Range("B2").Resize(UBound(arrFin), UBound(arrFin, 2)).Value = arrFin
End Sub
Instead using x as a built string you my have the string in a cell and you can use x = ActiveCell.value.
It is easy to transform the above code in a function, too...
Edited:
You can use the next function:
Function extractTextDelimSep(x As String) As Variant
Dim arr, arrLine, arrFin, cols As Long, i As Long, j As Long
arr = Split(x, vbCr)
cols = UBound(Split(arr(0), "*")) + 1
ReDim arrFin(1 To UBound(arr) + 1, 1 To cols)
For i = 0 To UBound(arr)
arrLine = Split(Replace(arr(i), " ", ""), "*")
For j = 0 To UBound(arrLine)
arrFin(i + 1, j + 1) = arrLine(j)
Next j
Next i
extractTextDelimSep = arrFin
End Function
If everything is in the same cell (in column A:A), you should call it in the next way:
Sub TestextractTextDelimSep()
Dim x As String, arr
x = ActiveCell.Value
arr = extractTextDelimSep(x)
'drop the array content at once:
Range("B" & ActiveCell.row).Resize(UBound(arr), UBound(arr, 2)).Value = arr
End Sub
If the string is distributed one line of a (different) cell, you can use:
Sub TestextractTextDelimSepRange()
Dim lastR As Long, arr, i As Long
lastR = Range("A" & rows.count).End(xlUp).row
For i = 1 To lastR
arr = extractTextDelimSep(Range("A" & i).Value)
Range("B" & i).Resize(1, UBound(arr, 2)).Value = arr
Next i
End Sub
For this last case, in Excel 365 it can be used as UDF function, being called from the cell as formula:
=extractTextDelimSep(A1) 'in A1 should be the line to be split

Related

Split cell values with additional text

I have hundreds of rows data in column A, and I want to split every 10 rows with adding text to make partition of data with excel vba.
Example:
|Col-A |Col-B
|D00112|00053
|D00112|00261
|D00112|00548
|etc...|etcXX
|D00112|00XXX ---row 500th
Output:
|Col-A |Col-B
|D00112-A|00053
|D00112-A|00261
|D00112-A|00548
|etc.. |etcXX
|D00112-B|xxxxx ---row 11th
|D00112-B|xxxxx
|etc.. |xxxxx
|D00112-C|xxxxx ---row 20th
|D00112-C|xxxxx
|etc |xxxxx
I have tried something like this:
Dim wrk As Workbook
Dim sht As Worksheet
Dim trg As Worksheet
Set wrk = ActiveWorkbook
Set sht = wrk.Worksheets(1)
For i = 2 To 10
If sht.Range("A" & i).Value > 0 Then
sht.Range("A" & i).Value = "D00112-A"
End If
Next i
For j = 11 To 20
If sht.Range("A" & j).Value > 0 Then
sht.Range("B" & j).Value = "D00112-B"
End If
Next j
for etc..
next etc
is there possible way to make this looping code looks simple and faster?
this code takes long time for executing
Please, try using the next code. It should be very fast, processing an array, working only in memory and drop the processed result at once. But, as I said in my above comment, the alphabet can be used as you show only up to 260 rows. The next code uses the next characters returned from the incremented ASCII code of the previous one:
Sub SplitColumn()
Dim sh As Worksheet, lastR As Long, arr
Dim i As Long, k As Long, initL As Long
Set sh = ActiveSheet 'use here the sheet you need
lastR = sh.Range("A" & sh.rows.count).End(xlUp).row 'the last row in A:A
arr = sh.Range("A2:A" & lastR).Value2 'place the range in an array for faster iteration
initL = Asc("A") 'extract ASCII code from letter A
For i = 1 To UBound(arr)
arr(i, 1) = arr(i, 1) & "-" & Chr(initL)
k = k + 1: If k = 10 Then k = 0: initL = initL + 1
Next i
'drop the array content back (at once):
sh.Range("A2").Resize(UBound(arr), 1).Value2 = arr
End Sub
If you need to handle letters in a different way, try to define an algorithm to be applied...
Edited:
Please, test the next version. It adds numbers (from 0 to 9) at each letter, increasing the range 100 times:
Sub SplitColumnComplex()
Dim sh As Worksheet, lastR As Long, arr
Dim i As Long, k As Long, j As Long, initL As Long
Set sh = ActiveSheet 'use here the sheet you need
lastR = sh.Range("A" & sh.rows.count).End(xlUp).row 'the last row in A:A
arr = sh.Range("A2:A" & lastR).Value2 'place the range in an array for faster iteration
initL = Asc("A") 'extract ASCII code from letter A
For i = 1 To UBound(arr)
arr(i, 1) = arr(i, 1) & "-" & Chr(initL) & j 'add the letter plus a digit (from 0 to 9)
k = k + 1
If k Mod 10 = 0 Then j = j + 1 'at each 10 rows change the number
If k = 100 Then initL = initL + 1: j = 0: k = 0 'at each 100 rows change letter and reinitialize all variables
Next i
'drop the array content back (at once):
sh.Range("A2").Resize(UBound(arr), 1).Value2 = arr
End Sub

Trying to group numbers in vba

I'm trying to group a number from E column starting with 1, the result should be like as below:
Column
E I
1 1-52
. 54-56
. 58-59
.
52
54
55
56
58
59
And I start to write like this:
Sub Group_Numbers()
Dim a As Variant, b As Variant
Dim i As Long, k As Long
Range("I1") = Range("E1")
k = 1
a = Range("E1", Range("E" & Rows.Count).End(xlUp)).Value
ReDim b(1 To UBound(a), 1 To 1)
For i = 2 To UBound(a)
If a(i, 1) <> Val(a(i - 1, 1)) + 1 Then
k = k + 1
b(k, 1) = a(i, 1)
Else
b(k, 1) = Split(b(k, 1), "-")(0) & -a(i, 1)
End If
Next i
Range("I2").Resize(l).Value = b
End Sub
However, it prompts an error 9 subscript out of range. Hope to get help right here.
Thanks a lot!
I would do the following
Option Explicit
Public Sub Example()
Dim ws As Worksheet ' define worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim LastRow As Long
LastRow = ws.Cells(ws.Rows.Count, "E").End(xlUp).Row
Dim Data() As Variant ' read input data into array
Data = ws.Range("E1", "E" & LastRow).Value2
Dim OutData() As Variant ' define output array
ReDim OutData(1 To UBound(Data, 1), 1 To 1) As Variant
Dim iOut As Long
iOut = 1
Dim StartVal As Long
StartVal = Data(1, 1) ' initialize start value of a group
Dim iRow As Long
For iRow = 2 To UBound(Data, 1) ' loop through values
' check if value is previous value +1
If Data(iRow, 1) <> Data(iRow - 1, 1) + 1 Then
' if not write output from StartVal to previos value
OutData(iOut, 1) = StartVal & "-" & Data(iRow - 1, 1)
iOut = iOut + 1
' and set curent value as new group start
StartVal = Data(iRow, 1)
End If
Next iRow
' close last group
OutData(iOut, 1) = StartVal & "-" & Data(iRow - 1, 1)
' write array back to cells
ws.Range("I1").Resize(RowSize:=iOut).NumberFormat = "#" 'format cells as text so `1-2` does not get converted into date.
ws.Range("I1").Resize(RowSize:=iOut).Value2 = OutData
End Sub
Alternative via Excel's Filter() function (vers. MS 365)
Disposing of the new dynamic array features you can profit from a worksheet-related formula evaluation via a tabular filter upon the data range rows compared with the same range shifted by 1 resulting in an array of endRows numbers. This is the base for a results array which joins start and end values.
The following code allows to define a flexible source range, as the evaluation takes care of the actual start row in the indicated data column.
Example call //edited responding to comment
Sub Grouping()
'0) get data
Dim src As Range
Set src = Sheet1.Range("E1:E59") ' change to your needs
Dim data As Variant
If src.Rows.Count > 1 Then ' is the usual case
data = src.Value2 ' get 2-dim datafield array
Else ' a one liner is the exception
ReDim data(1 To 1, 1 To 1) ' create 2-dim by ReDim
data(1, 1) = Application.Index(src, 1, 1)
End If
'1a)prepare formula evaluation of endRows
Dim EndPattern As String
EndPattern = "=LET(data,$,FILTER(ROW(OFFSET(data,1,0))-" & src.Row & ",ABS(OFFSET(data,1,0)-data)>1))"
EndPattern = Replace(EndPattern, "$", src.Address(False, False))
'1b)evaluate formula
Dim endRows: endRows = src.Parent.Evaluate(EndPattern)
'~~~~~~~~~~~~~~
'2) get results
'~~~~~~~~~~~~~~
Dim results: results = getResults(data, endRows) '<< Help function getResults
'3) write to any target
With Sheet1.Range("I1")
.Resize(UBound(results), 1) = results
End With
End Sub
Help function getResults() //added responding to comment
The usual result of an evaluation is a 1-based 2-dim array with two exceptions code has to provide for:
a) non-findings (which would result only in a returned error value),
b) only a single return value (which is a 1-dim array).
Not enough with these exceptions, the tricky comparison of identical endRows blocks - being shifted by 1 row - makes it necessary to check for the actual last row number if not comprised in endRows. - Imo this might have been the commented issue by #TecLao.
Function getResults(ByRef data, ByRef endRows)
'Purpose: combine value ranges
Dim results As Variant
Dim n As Long: n = UBound(data)
'a) no end row returned by shift-formula evaluation
If IsError(endRows) Then ReDim endRows(1 To 1): endRows(1) = n
'b) one end row found
If Application.WorksheetFunction.CountA(endRows) = 1 Then
ReDim results(1 To IIf(endRows(1) < n, 2, 1), 1 To 1)
'write results
results(1, 1) = "'" & data(1, 1) & "-" & data(endRows(1), 1)
If UBound(results) = 2 Then
results(2, 1) = _
"'" & data(endRows(1) + 1, 1) & _
"-" & _
data(n, 1)
End If
'c) several end rows found
Else
Dim increment As Long
If endRows(UBound(endRows), 1) < n Then increment = 1
'write results
ReDim results(1 To UBound(endRows) + increment, 1 To 1)
results(1, 1) = "'" & data(1, 1) & "-" & data(endRows(1, 1), 1)
Dim i As Long
For i = 2 To UBound(endRows)
results(i, 1) = _
"'" & _
data(endRows(i - 1, 1) + 1, 1) & _
"-" & _
data(endRows(i, 1), 1)
Next
If increment Then
results(i, 1) = "'" & data(endRows(i - 1, 1) + 1, 1) & "-" & data(n, 1)
End If
End If
'function return
getResults = results
End Function

joining all cell value between ascending range

I am trying to figure out how to join every cell (normal text) in the D column between the range I have set up in the A column. While searching I came across variations of the concatenate, textjoin and other functions but could not seem to find the right option.
There are around 8000 values in my file(ID value in column A) so it should be automatically filled and not manual. since it would take weeks to complete...
i've added a visual representation of the problem
The solution would be to have all cells selected in column D between the value '1' and '2' in column A and joined togheter in the E column in the row where the the cell in the A column isn't blank.
I hope someone can help me with this problem.
Try this code, please. It should be very fost, using arrays and returns the built strings at once at the end:
Sub testJoinBetweenLimits()
Dim sh As Worksheet, arrInit As Variant, arrFin As Variant
Dim strInit As String, i As Long, j As Long, refRow As Long
Set sh = ActiveSheet 'use here your sheet
arrInit = sh.Range("A2:D" & sh.Range("D" & Cells.Rows.Count).End(xlUp).Row).value
ReDim arrFin(1 To 1, 1 To UBound(arrInit, 1))
For i = 1 To UBound(arrInit, 1)
If arrInit(i, 1) <> "" Then strInit = arrInit(i, 4): refRow = i: j = i + 1
Do While arrInit(j, 1) = ""
If arrInit(j, 4) <> "" Then
strInit = strInit & ", " & arrInit(j, 4)
Else
arrFin(1, j) = Empty
End If
j = j + 1
If j >= sh.Range("D" & Cells.Rows.Count).End(xlUp).Row Then
arrFin(1, refRow) = strInit
ReDim Preserve arrFin(1 To 1, 1 To refRow)
GoTo Ending
End If
Loop
i = j - 1
arrFin(1, refRow) = strInit: strInit = "": j = 0
Next i
Ending:
sh.Range("E2").Resize(UBound(arrFin, 2), 1).value = WorksheetFunction.Transpose(arrFin)
End Sub

Evenly Distributing Arrary Elements Across Multiple Columns in Excel VBA

first time poster, long time reader.
Apologies if this is hard to follow.
I have a spreadsheet which has a list of first names and last names. What I am wanting to do is take all of the first names which have the same last name and place them, evenly(ish) and separated by a comma, into the 3 reference columns in the same spreadsheet for example;
Example of Completed Sheet
I would like to do this in VBA because there are 200+ names and growing, and later the code will use this information to create and populate more workbooks.
So far, what I have works for all last names which have 3 or less first names (ie; one per column) but I cannot get it to work for last names where there are more than 3 first names.
My thought was to read all of the names into an array, split out the elements which have more than 3 names into another array, join these together separated by a comma, to then be transferred to the relevant column on the sheet.
However for some reason, I cannot get it to output more than one name into the column.
I have had a few attempts at this, but this is my latest attempt;
Private Sub cmdUpdate_Click()
Dim i As Long
Dim j As Long
Dim k As Long
Dim l As Long
Dim m As Long
Dim namesPerCol As Long
Dim strLastNameMatches As String
Dim arrNames() As String
Dim arrMultiNames(3) As String
Application.ScreenUpdating = False
With ActiveSheet
'Finds the last row with data in it
lngLastRow = .Cells(.Rows.count, "A").End(xlUp).Row
End With
'Sort the Columns
Columns("A:E").Sort key1:=Range("A1"), Header:=xlYes
'Loop through the LastNames
For i = 2 To lngLastRow
'Second loop through the LastNames
For j = 2 To lngLastRow
'If the last name matches
If Cells(i, 2).Value = Cells(j, 2).Value Then
'If the cell is empty then
If Range("C" & i).Value = "" Then
'Place the name in colA into colC
Range("C" & i).Value = Range("A" & j).Value
Else
'If the cell is not empty, then place a comma and space and then the value from colA
Range("C" & i).Value = Range("C" & i).Value & ", " & Range("A" & j).Value
End If
End If
Next j
Next i
For i = 2 To lngLastRow
strLastNameMatches = Range("C" & i).Value
arrNames = Split(strLastNameMatches, ", ")
If UBound(arrNames) > 2 Then
namesPerCol = UBound(arrNames) / 3
For l = 0 To 1
For k = LBound(arrNames) To namesPerCol
arrMultiNames(l) = arrNames(k) & ", "
Next k
Next l
For m = LBound(arrMultiNames) To UBound(arrMultiNames)
Select Case m
Case 0
Range("C" & i).Value = arrMultiNames(m)
Case 1
Range("D" & i).Value = arrMultiNames(m)
Case 2
Range("E" & i).Value = arrMultiNames(m)
End Select
Next m
Else
For j = LBound(arrNames) To UBound(arrNames)
Select Case j
Case 0
Range("C" & i).Value = arrNames(j)
Case 1
Range("D" & i).Value = arrNames(j)
Case 2
Range("I" & i).Value = arrNames(j)
End Select
Next j
End If
Next i
Application.ScreenUpdating = True
End Sub
Apologies for the poor quality coding, I will work on tiding it up once it is all working.
Any help I can get to get this code splitting out the names evenly across the three columns will be greatly appreciated
This task might be simpler if you could store your data into a more tree-like structure. There are many ways to do this; I've used the Collection object as it's easy to handle an unknown number of items. Basically, there are collections within a collection, ie one collection of first names for each last name.
The sample below uses very rudimentary distribution code (which is also hard-coded to a split of 3), but the point is that iterating through and down the tree is far simpler:
Dim lastList As Collection, firstList As Collection
Dim lastText As String, firstText As String
Dim data As Variant, last As Variant, first As Variant
Dim output() As Variant, dist(1 To 3) As Long
Dim str As String
Dim r As Long, c As Long, i As Long
'Read data into an array
With Sheet1
data = .Range(.Range("A1"), .Cells(.Rows.Count, "B").End(xlUp)).Value2
End With
'Create lists of unique lastnames containing the firstnames
Set lastList = New Collection
For r = 2 To UBound(data, 1)
firstText = CStr(data(r, 1))
lastText = CStr(data(r, 2))
Set firstList = Nothing
On Error Resume Next
Set firstList = lastList(lastText)
On Error GoTo 0
If firstList Is Nothing Then
Set firstList = New Collection
lastList.Add firstList, lastText
End If
firstList.Add firstText
Next
'Write results to sheet
ReDim output(1 To UBound(data, 1) - 1, 1 To 3)
For r = 2 To UBound(data, 1)
lastText = CStr(data(r, 2))
Set firstList = lastList(lastText)
'Calculate the distribution
dist(3) = firstList.Count / 3 'thanks #Comitern
dist(2) = dist(3)
dist(1) = firstList.Count - dist(2) - dist(3)
i = 1: c = 1: str = ""
For Each first In firstList
str = str & IIf(i > 1, ", ", "") & first
i = i + 1
If i > dist(c) Then
output(r - 1, c) = str
i = 1: c = c + 1: str = ""
End If
Next
Next
Sheet1.Range("C2").Resize(UBound(output, 1), UBound(output, 2)).Value = output

Divide a string in a single cell into several cells

I have data that I need to split into individual points. My macro charts the data, as a scatter plot, with: Column A as the title of the chart, Column B as the X axis, and Columns C and D as the Y axis. What I need is for when the Product ID has more than 1 number listed to split the numbers out into their own rows and keep the columns B, C, and D the same for each row created form the original. So for row 167, I would want 3 rows (001,002,003) each with packaging, 200, and 100, in B, C, and D respectively. I am not sure where to begin. I tried to build a macro but, I immediately got tripped up when I tried to record a "Find" Formula to run on the data. Any help would be greatly appreciated.
Column A: 001, 002, 003 // Column B:packaging // Column C:200 // Column D:100
Sorry I couldn't post a screenshot of my data, the forum won't let me. If you have any questions please let me know, I will be sure to check in frequently.
Thanks in advance.
I worte this VERY quickly and without much care for efficiency, but this should do the trick:
Sub SplitUpVals()
Dim i As Long
Dim ValsToCopy As Range
Dim MaxRows As Long
Dim ValToSplit() As String
Dim CurrentVal As Variant
MaxRows = Range("A1").End(xlDown).Row
For i = 1 To 10000000
ValToSplit = Split(Cells(i, 1).Value, ",")
Set ValsToCopy = Range("B" & i & ":D" & i)
For Each CurrentVal In ValToSplit
CurrentVal = Trim(CurrentVal)
Cells(i, 1).Value = CurrentVal
Range("B" & i & ":D" & i).Value = ValsToCopy.Value
Cells(i + 1, 1).EntireRow.Insert
i = i + 1
MaxRows = MaxRows + 1
Next
Cells(i, 1).EntireRow.Delete
If i > MaxRows Then Exit For
Next i
End Sub
As a note, make sure there's no data in cells beneath your data as it might get deleted.
You will need to parse the data in column A. I would do this by splitting the string in to an array, and then iterate over the array items to add/insert additional rows where necessary.
Without seeing your worksheet, I would probably start with something like this, which will split your cell value from column A in to an array, and then you can iterate over the items in the array to manipulate the worksheet as needed.
Sub TestSplit()
Dim myString as String
Dim myArray() as String
Dim cell as Range
Dim i as Long
For each cell in Range("A2",Range("A2").End(xlDown))
myString = cell.Value
myArray = Split(myString, ",") '<-- converts the comma-delimited string in to an array
For i = lBound(myArray) to uBound(myArray)
If i >= 1 Then
'Add code to manipulate your worksheet, here
End If
Next
Next
End Sub
This is a better solution (now that I had more time :) ) - Hope this does the trick!
Sub SplitUpVals()
Dim AllVals As Variant
Dim ArrayIndex As Integer
Dim RowLooper As Integer
AllVals = Range("A1").CurrentRegion
Range("A1").CurrentRegion.Clear
RowLooper = 1
For ArrayIndex = 1 To UBound(AllVals, 1)
ValToSplit = Split(AllVals(ArrayIndex, 1), ",")
For Each CurrentVal In ValToSplit
CurrentVal = Trim(CurrentVal)
Cells(RowLooper, 1).Value = CurrentVal
Cells(RowLooper, 2).Value = AllVals(ArrayIndex, 2)
Cells(RowLooper, 3).Value = AllVals(ArrayIndex, 3)
Cells(RowLooper, 4).Value = AllVals(ArrayIndex, 4)
RowLooper = RowLooper + 1
Next
Next ArrayIndex
End Sub
Sub DivideData()
'This splits any codes combined into the same line, into their own separate lines with their own separate data
Dim a, b, txt As String, e, s, x As Long, n As Long, i As Long, ii As Long
With Range("a1").CurrentRegion
a = .Value
txt = Join$(Application.Transpose(.Columns(1).Value))
x = Len(txt) - Len(Replace(txt, ",", "")) + .Rows.Count
ReDim b(1 To x * 2, 1 To UBound(a, 2))
For i = 1 To UBound(a, 1)
For Each e In Split(a(i, 1), ",")
If e <> "" Then
For Each s In Split(e, "-")
n = n + 1
For ii = 1 To UBound(a, 2)
b(n, ii) = a(i, ii)
Next
b(n, 1) = s
Next
End If
Next
Next
With .Resize(n)
.Columns(1).NumberFormat = "#"
.Value = b
End With
End With
End Sub

Resources