Excel Range to CSVrangeoutput - split range into groups of 41 entries - excel
Im not sure exactly how to explain this in a google search so im not sure if anyone else has asked this.
I have a vba function that takes a range and turns it into a string of comma separated values.
It works like a charm.
Now i want it to only output the first 41 entries, switch down a row and output the next 41 entries in the range.
I cant quite wrap my head around it, it feels like a simple loop but i cant quite get there.
I found the csvrange macro online somewhere :)
Function csvRange(myRange As Range)
Dim csvRangeOutput
Dim entry As Variant
For Each entry In myRange
If Not IsEmpty(entry.Value) Then
csvRangeOutput = csvRangeOutput & entry.Value & ","
End If
Next
csvRange = Left(csvRangeOutput, Len(csvRangeOutput) - 1)
End Function
Input range would look like this
Desired output would look like this, one string located in column B each group of 41 values separated on a row, offsetting 1 down each time the function hits the next nr 42.
Something like this:
Option Explicit
Public Sub test()
Debug.Print csvRange(Selection, 41)
End Sub
Public Function csvRange(ByVal myRange As Range, ByVal Columns As Long) As String
Dim csvRangeOutput
Dim iCol As Long
Dim Entry As Variant
For Each Entry In myRange
If Not IsEmpty(Entry.Value) Then
iCol = iCol + 1
csvRangeOutput = csvRangeOutput & Entry.Value
If iCol = Columns Then
csvRangeOutput = csvRangeOutput & vbCrLf
iCol = 0
Else
csvRangeOutput = csvRangeOutput & ","
End If
End If
Next
csvRange = Left$(csvRangeOutput, Len(csvRangeOutput) - 1)
End Function
will turn this data
into comma separated values with 41 columns
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41
42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82
83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123
124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140
Alternative
Public Sub Convert()
Const ColCount As Long = 41
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim LastRow As Long
LastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
Dim iRow As Long
For iRow = 1 To LastRow Step ColCount
ws.Cells(iRow \ ColCount + 1, "B").Value = "'" & Join((WorksheetFunction.Transpose(ws.Range("A" & iRow).Resize(RowSize:=IIf(iRow + ColCount - 1 > LastRow, WorksheetFunction.Max(LastRow Mod ColCount, 2), ColCount)).Value)), ",")
Next iRow
End Sub
Please, test the next code. It will do what (I understood) you need, for as many records you have in column A:A. It should be fast, using arrays and working in memory. The single iteration is for the necessary number of range slices:
Private Sub testStringCSVArray()
Dim sh As Worksheet, arr, nrSlices As Long, LastRow As Long, rngF As Range
Dim rngStart As Range, i As Long, k As Long, h As Long, arrFin
Set sh = ActiveSheet
LastRow = sh.Range("A1").End(xlDown).row
LastRow = sh.Range("A" & rows.count).End(xlUp).row 'last row of A:A
arr = sh.Range("A1:A" & LastRow).Value 'put the range in an array
nrSlices = UBound(arr) \ 41 'determine the number of necessary slices
ReDim arrFin(nrSlices + 1)
Set rngStart = sh.Range("B" & UBound(arr) + 2) 'set the cell where the result to be returned
For i = 1 To nrSlices + 1
arrFin(h) = CStr(Join(Application.Transpose(Application.Index(arr, _
Evaluate("row(" & k + 1 & ":" & IIf(i <= nrSlices, 41 + k, UBound(arr)) & ")"), 1)), ","))
k = k + 41: h = h + 1
Next i
'Format the range where the processed data will be returned and drop the processed data array:
With rngStart.Resize(h, 1)
.NumberFormat = "#"
.Value = WorksheetFunction.Transpose(arrFin)
End With
End Sub
In order to avoid deleting of the already processed data, in case of whishing to run the code twice or more times, the processed data will be returned in column B:B, two rows down from the last cell in column A:A. If after testing, the code proves to be reliable and no need to run it one more time, Set rngStart = sh.Range("B" & UBound(arr) + 2) can be modified in Set rngStart = sh.Range("A" & UBound(arr) + 2).
Without preliminary formatting as text the area where the data will be dropped, Excel changes the NumberFormat in "scientific", when the comma delimited string contains (only) numbers of three digits each. It looks to consider the comma as a thousands separator...
Related
Split words from column and re-join based on criteria from an array
I have a column "D" in my spreadsheet that contains a list of software to install. The list is very long and I only want a few applications to install. Here are a few examples: Row2: License-E3; Minitab 17; Minitab 18; Proficy Historian 7.0; ; Row3: License-E3; Attachmate Reflection for UNIX and OpenVMS 14.0; Perceptive Content Desktop Client; Row4: License-E1; Avaya one-X® Communicator; PipelineBillingInterfaceSystemClient-V2_0; ; SAP-GUI-3Apps; Minitab 18 So, in the first example, I want column D row 2 to just say : License-E3,Minitab 18 Row 3 to say : License-E3,Reflection And 4 to say : License-E1,Minitab 18 The rows are auto filtered based on the User Id column, which is Column A in this sheet. The commented section is basically what I want to do. Here is my code so far: Sub FilterSoftware() Dim cl As Range, rng As Range, Lastrow As Integer, sSoft() As String, i As Long Dim vSoft As Variant, sNew As String, j As Long, sNewSoft() As String vSoft = Array("License-E3", "License-E1", "Reflection", "Minitab 18", "RSIGuard", "Java") Dim Ws As Worksheet: Set Ws = Sheet1 With Ws Lastrow = .Range("D" & .Rows.Count).End(xlUp).Row End With Set rng = Range("D2:D" & Lastrow) For Each cl In rng.SpecialCells(xlCellTypeVisible) sSoft = Split(cl, ";") For i = LBound(sSoft) To UBound(sSoft) If Not sSoft(i) = " " Then For j = LBound(vSoft) To UBound(vSoft) sNewSoft = Split(vSoft(j), " ") Debug.Print Trim$(sSoft(i)) Debug.Print Trim$(vSoft(j)) 'if sSoft(i) contains any words from vSoft(j) 'Join vSoft(j) with comma delimiter until full 'and overwrite in column D Next j End If Next i Next cl End Sub
Please, use the next adapted code. It will return in the next column, only for testing reason. If it returns what you need, you can change cl.Offset(0, 1).Value = Join(sNew, ",") with cl.Value = Join(sNew, ","): Sub FilterSoftware() Dim cl As Range, rng As Range, Lastrow As Long, sSoft Dim vSoft, sNew, i As Long, j As Long, t As Long vSoft = Array("License-E3", "License-E1", "Reflection", "Minitab 18", "RSIGuard", "Java") Dim Ws As Worksheet: Set Ws = ActiveSheet ' Sheet1 Lastrow = Ws.Range("D" & Ws.rows.count).End(xlUp).row Set rng = Range("D2:D" & Lastrow) ReDim sNew(UBound(vSoft)) 'redim the array to a dimension to be sure it will include all occurrences For Each cl In rng.SpecialCells(xlCellTypeVisible) sSoft = Split(cl, ";") For i = LBound(sSoft) To UBound(sSoft) If Not sSoft(i) = "" Then 'for cases of two consecutive ";" For j = LBound(vSoft) To UBound(vSoft) If InStr(1, sSoft(i), vSoft(j), vbTextCompare) > 0 Then sNew(t) = vSoft(j): t = t + 1: Exit For End If Next j End If Next i If t > 0 Then ReDim Preserve sNew(t - 1) 'keep only the array filled elements cl.Offset(0, 1).Value = Join(sNew, ",") 'put the value in the next column (for testing reason) ReDim sNew(UBound(vSoft)): t = 0 'reinitialize the variables End If Next cl End Sub
Comparing two lists with different lengths
I want to compare two ID lists with different lengths. The first list is longer and has Values, while the second has no Values. When the ID's match, it should paste the Value in the first list to the appropriate place beside list 2. Sub compareList() Dim v1, v2, v4, v3() Dim i As Long Dim j As Long v1 = Range("A2", Range("A" & Rows.Count).End(xlUp)).Value v2 = Range("B2", Range("B" & Rows.Count).End(xlUp)).Value v4 = Range("D2", Range("D" & Rows.Count).End(xlUp)).Value ReDim v3(1 To 4) For i = LBound(v1) To UBound(v1) If IsError(Application.Match(v1(i, 1), v4, 0)) Then j = j + 1 Else v3(j) = v2(i, 1) End If Next i Range("E2").Resize(i) = Application.Transpose(v3) End Sub It gives me an out of index error, or pastes the value in the order it reads it (without paying attention to the match).
If you do not like Vlookup and need some VBA code, please test the next code: Sub compareList() Dim sh As Worksheet, lastR As Long, lastR2 As Long, i As Long, j As Long, arr, arrFin Set sh = ActiveSheet lastR = sh.Range("A" & rows.count).End(xlUp).row lastR2 = sh.Range("D" & rows.count).End(xlUp).row arr = sh.Range("A2:B" & lastR).Value arrFin = sh.Range("D2:E" & lastR2).Value For i = 1 To UBound(arrFin) For j = 1 To UBound(arr) If arrFin(i, 1) = arr(j, 1) Then arrFin(i, 2) = arr(j, 2): Exit For Next j Next i sh.Range("D2:E" & lastR2).Value = arrFin End Sub
Just continuing on and referring to #FaneDuru stating If you don't like Vlookup and need some VBA code: 1) Example code using Match() Sub compareListTM() 'define arrays using help function getRange() Dim arr: arr = getRange(Sheet1.Range("A:A")).Value Dim data: data = getRange(Sheet1.Range("B:B")).Value Dim arrFin: arrFin = getRange(Sheet1.Range("D:D")).Value Dim ret: ret = Application.Match(arrFin, arr, 0) ' Match() items all at once :-) Dim i As Long For i = 1 To UBound(ret) If Not IsError(ret(i, 1)) Then ret(i, 1) = data(ret(i, 1), 1) Else ret(i, 1) = vbNullString End If Next i Sheet1.Range("E2").Resize(UBound(ret), 1).Value = ret End Sub If, however you could give VLookUp a try: 2) Example code using worksheetfunction Sub compareList2() Dim results results = WorksheetFunction.VLookup( _ getRange(Sheet1.Range("D:D")), _ getRange(Sheet1.Range("A:B")), _ 2, False) 'write results Sheet1.Range("E2").Resize(UBound(results), 1).Value = results End Sub Help function getRange() used in both examples A way to avoid repeated lastRow, Range definitions in main code. I don't pretend this function to be perfect in any way, it just meets the necessary requirements for above procedures kept as short as possible. Function getRange(ColRange As Range, _ Optional ByVal SearchColumn As Variant = 1, _ Optional ByVal StartRow As Long = 2) As Range 'Author : https://stackoverflow.com/users/6460297/t-m 'Purpose: calculate lastrow of a given search column (default: 1st column of ColRange) and ' return ColRange resized to calculated lastrow (considering optional StartRow argument) 'Par. 1 : assumes that ColRange is passed as ENTIRE COLUMN(S) range object, e.g. Range("X:Y") 'Par. 2 : a) a numeric SearchColumn argument refers to the ColRange's column index ' (even outside ColRange, can be negative or higher than columns count in ColRange!) ' b) a literal SearchColumn argument refers to the worksheet column as indicated (e.g. "B") 'Example: getRange(Sheet1.Range("X:Y")) ... calculates lastrow of 1st column in colRange (i.e. in X) ' getRange(Sheet1.Range("X:Y"), "B") ... calculates lastrow of column B in worksheet '~~~~~~ '1) get columns in ColRange Dim StartColumn As Long: StartColumn = ColRange.Columns(1).Column Dim LastColumn As Long: LastColumn = ColRange.Columns(ColRange.Columns.Count).Column With ColRange.Parent ' i.e. the worksheet '2) change numeric search column number to letter(s) If IsNumeric(SearchColumn) Then If SearchColumn + StartColumn - 1 < 1 Then ' cols left of StartColumn must be at least "A" SearchColumn = "A" Else ' get literal column name, e.g. column "D" SearchColumn = Split((.Columns(SearchColumn + StartColumn - 1).Address(, 0)), ":")(0) End If End If '3) get last row of SearchColumn Dim lastRow As Long: lastRow = .Range(SearchColumn & .Rows.Count).End(xlUp).Row If lastRow < StartRow Then lastRow = StartRow ' avoid findings lower than start row '4) return data range as function result Set getRange = .Range(.Cells(StartRow, StartColumn), .Cells(lastRow, LastColumn)) End With End Function
Is there ability to split cells while retaining the values of adjacent columns?
The IDs column in the first table contains multiple values in each cell that needs to be split. However, the unique issue is to retain both [name] and [description] info by ID into a new table. . The following VBA code performs the transpose paste option. This is what I am starting with to split cells with Chr(10), or new line as the delimiter: Sub splitText() 'splits Text active cell using ALT+10 char as separator Dim splitVals As Variant Dim totalVals As Long splitVals = Split(ActiveCell.Value, Chr(10)) totalVals = UBound(splitVals) Range(Cells(ActiveCell.Row, ActiveCell.Column + 1), Cells(ActiveCell.Row, ActiveCell.Column + 1 + totalVals)).Value = splitVals End Sub Other than this, I am still searching for ideas.
Maybe this will help: Sub splitText() 'splits Text active cell using ALT+10 char as separator Dim splitVals As Variant Dim lngRow As Long, lngEl As Long With Sheet2 'Range A2:A5 For lngRow = 5 To 2 Step -1 splitVals = Split(.Range("A" & lngRow).Value, Chr(10)) 'the first value .Range("A" & lngRow).Value = splitVals(0) 'remaining values For lngEl = 1 To UBound(splitVals) .Rows(lngRow + lngEl).Insert .Range("A" & lngRow + lngEl).Value = splitVals(lngEl) .Range("B" & lngRow + lngEl & ":C" & lngRow + lngEl).Value = .Range("B" & lngRow & ":C" & lngRow).Value Next lngEl Next lngRow End With End Sub Change Sheet Code/Name and Range as necessary. Before: After:
It's a bit more involved than your solution because you have to insert the correct number of rows below the targeted cell and then copy the IDs and the other data into the new rows. Here's an example to help you along. There's a little "trickery" I'm using when I calculate the offset value. I'm doing this because you can assume that all arrays from the Split function will begin indexing at 0, but my personal habit is to write code that can work with either a 0 or 1 lower bound. Calculating and using an offset makes it all work for the loops and indexes. Option Explicit Sub test() SplitText ActiveCell End Sub Sub SplitText(ByRef idCell As Range) Dim splitVals As Variant Dim totalVals As Long splitVals = Split(idCell.Value, Chr(10)) If LBound(splitVals) = -1 Then '--- the split character wasn't found, so exit Exit Sub End If Dim offset As Long offset = IIf(LBound(splitVals) = 0, 1, 0) totalVals = UBound(splitVals) + offset Dim idSheet As Worksheet Set idSheet = idCell.Parent Dim idRow As Long idRow = idCell.Row '--- insert the number of rows BELOW the idCell to hold all ' the split values Dim i As Long For i = 1 To totalVals - 1 idSheet.Rows(idRow + 1).Insert Next i '--- now add the IDs to all the rows and copy the other columns down Const TOTAL_COLUMNS As Long = 3 Dim j As Long Dim startIndex As Long startIndex = LBound(splitVals) + offset For i = startIndex To totalVals idCell.Cells(i, 1) = splitVals(i - offset) For j = 2 To TOTAL_COLUMNS idCell.Cells(i, j) = idCell.Cells(1, j) Next j Next i End Sub
Excel VBA parse column, extract all substrings
I'm trying to parse a column that contains data in the following format in each cell - pull: test1 or pull: test2|pull: test3|..... or other: blah... I only want a grab each "Pull: test" and place 1 in each row in a new worksheet like below, and ignore any parts of the cell that don't begin with "pull: " - pull: test1 pull: test2 pull: test3 ... What I have so far just pulls the entire column and pastes into the same spreadsheet, I'm not sure how to separate the items in each cell into their own rows. I also can't get it to pull to a different worksheet correctly either (commented out my attempt) Sub InStrDemo() Dim lastrow As Long Dim i As Integer, icount As Integer 'Sheets.Add.Name = "TEST" lastrow = ActiveSheet.Range("A10000").End(xlUp).Row For i = 1 To lastrow If InStr(1, LCase(Range("E" & i)), "pull:") <> 0 Then icount = icount + 1 'Sheets("TEST").Range("A" & icount & ":E" & icount) = Worksheets("SearchResults").Range("A" & i & ":E" & i).Value Range("L" & icount) = Range("E" & i).Value End If Next i End Sub
Untested, written on mobile. Option Explicit Sub testDemo() Dim sourceSheet as worksheet Set sourceSheet = ActiveSheet ' would be more reliable to qualify the workbook and worksheet by name' Dim outputSheet as worksheet Set outputSheet = thisworkbook.worksheets.add Dim lastRow As Long lastrow = sourceSheet.Range("A10000").End(xlUp).Row ' I assume column E needs to be parsed' Dim arrayOfValues() as variant arrayOfValues = sourceSheet.range("E1:E" & lastRow) Dim rowIndex as long Dim columnIndex as long Dim splitString() as string Dim cumulativeOffset as long Dim toJoin(0 to 1) as string toJoin(0) = "pull: test" ' Might speed up string concatenation below' Dim outputArray() as string With outputsheet.range("A1") ' The first row you want to start stacking from' For rowIndex = 1 to lastRow ' Single dimensional, 0-based array' splitString = VBA.strings.split(vba.strings.lcase$(arrayOfValues(rowIndex,1)), "pull: test",-1, vbbinarycompare) Redim outputArray(1 to (ubound(splitString)+1), 1 to 1) For columnIndex = lbound(splitString) to ubound(splitString) toJoin(1) = splitString(columnIndex) Outputarray(columnIndex+1,1) = VBA.strings.join(toJoin, vbnullstring) Next columnIndex 'Instead of splitting upon a delimiter, then prepending the delimiter to each array element (as is done above), you could repeatedly call instr(), use mid$() to extract the sub-string, then increase the argument passed to the "Start" parameter in instr() (effectively moving from start to end of the string) -- until instr() returns 0. Then move on to the next string in the outer loop.' .offset(cumulativeOffset,0).resize(Ubound(outputArray, 1), 1).value2 = outputArray cumulativeOffset = cumulativeOffset + ubound(splitString) Next rowIndex End Sub
Excel copy cell values X times with increasing numbers in the end
I have a similar task as in there: Copy value N times in Excel But mine is a bit more complex. So, I have this kind of sheet: A B dog-1.txt 3 cat-1.txt 2 rat-1.txt 4 cow-1.txt 1 The final result needs to be the following: A dog-1.txt dog-2.txt dog-3.txt cat-1.txt cat-2.txt rat-1.txt rat-2.txt rat-3.txt rat-4.txt cow-1.txt As you see it doesn't only multiply the cell content X times taken from column B, but it also increases the number in file name the same number of times with 1 step increase. How could I achieve that?
Try the following (tried and tested): Sub Extend() Dim Rng As Range, Cell As Range Dim WS As Worksheet, NewCell As Range Dim Dict As Object, NewStr As String Set WS = ThisWorkbook.Sheets("Sheet1") 'Modify as necessary. Set Rng = WS.Range("A1:A5") 'Modify as necessary. Set Dict = CreateObject("Scripting.Dictionary") For Each Cell In Rng If Not Dict.Exists(Cell.Value) Then Dict.Add Cell.Value, Cell.Offset(0, 1).Value End If Next Cell Set NewCell = WS.Range("C1") 'Modify as necessary. For Each Key In Dict For Iter = 1 To CLng(Dict(Key)) NewStr = "-" & Iter & ".txt" NewStr = Mid(Key, 1, InStrRev(Key, "-") - 1) & NewStr NewCell.Value = NewStr Set NewCell = NewCell.Offset(1, 0) Next Iter Next Key End Sub Screenshot (after running): The logic here is to get each name from the first column, store it as a dictionary key, then get the value beside it and store that that as the key-value. We then iterate inside each of the dictionary's keys, where we use the key-value as the upperbound of the iteration. During each iteration, we modify the string to change its number to the "current digit" of the iteration. We choose C1 as the initial target cell. Every iteration, we offset it one (1) row below to accommodate the new/next iteration. Let us know if this helps.
Tested , is this what u wanted :) ? (Working fine in my system) Sub teststs() Dim erange As Range Dim lrow As Integer Dim cnt As Integer Dim rnt As Integer Dim str As String Dim lrow2 As Integer With ActiveSheet lrow = .Range("A" & Rows.Count).End(xlUp).Row ' finding the last row For Each erange In .Range("A1:A" & lrow) ' loop though each each cell in the A column cnt = erange.Offset(0, 1).Value rnt = Mid(erange.Value, InStr(erange.Value, "-") + 1, 1) For i = 1 To cnt 'Looping to cnt times With Sheets("Sheet2") lrow2 = .Range("A" & Rows.Count).End(xlUp).Row + 1 str = Replace(erange.Value, rnt, i, InStr(erange.Value, "-") + 1) .Range("A" & lrow2).Value = Left(erange.Value, InStr(erange.Value, "-")) & str End With Next i Next erange End With End Sub