I have some data (mixed data types in column A). How can I split each data type into another column?
I mean numbers to be in column, string in column, dates in column and so on
This is my try till now but I didn't get all the results as expected
Sub Test()
Dim a, b(), dic As Object, i As Long, k As Long, ii As Long, n As Long
a = Range("A1:A10").Value
Set dic = CreateObject("Scripting.Dictionary")
For i = LBound(a) To UBound(a)
If Not dic.Exists(VarType(a(i, 1))) Then
dic.Item(VarType(a(i, 1))) = Empty
ReDim Preserve b(UBound(a, 1), k)
k = k + 1
End If
n = 0
Do Until b(i - 1, k - 1) <> Empty
b(i - 1, k - 1) = a(i, 1)
Loop
Next i
Range("J1").Resize(UBound(b, 1), UBound(b, 2)).Value = b
End Sub
There are a number of things I would do differently in your code
using meaningful names for the variables
specifying the worksheet instead of depending on the implied ActiveSheet
clearing the results area on the worksheet
early binding of the dictionary object
dynamic determination of the range to be processed
etc
but the below code modifies your original code minimally, to obtain the output I think you want, based on your screenshot
Sub Test()
Dim a, b(), dic As Object, i As Long, k As Long, ii As Long, n As Long, v
Dim dataType As String
a = Range("A1:A10").Value
ReDim b(1 To UBound(a))
'first create the dictionary with the datatypes
'since you are maintaining the entries in the same rows,
' add an empty array as the item
Set dic = CreateObject("Scripting.Dictionary")
dic.Add Key:="number", Item:=b
dic.Add Key:="date", Item:=b
dic.Add Key:="string", Item:=b
dic.Add Key:="logical", Item:=b
'Add the values to the correct dictionary item
' at the correct spot in the array
For i = LBound(a) To UBound(a)
Select Case VarType(a(i, 1))
Case 2 To 6
dataType = "number"
Case 7
dataType = "date"
Case 8
dataType = "string"
Case 11
dataType = "logical"
Case Else
dataType = ""
End Select
If dataType <> "" Then
v = dic(dataType)
v(i) = a(i, 1)
dic(dataType) = v
End If
Next i
'Next create output array
ReDim b(1 To UBound(a), 1 To dic.Count)
k = 0
For Each v In dic.Keys
k = k + 1
For i = 1 To UBound(dic(v))
b(i, k) = dic(v)(i)
Next i
Next v
Range("J1").Resize(UBound(b, 1), UBound(b, 2)).Value = b
End Sub
Edit:
If, as you indicate in your comments, you don't want to set up the data types initially, you can also do that at the time of creation of the dictionary object. Using the same algorithm of storing the item as an array of the same size as the number of rows in the data base:
Sub Test()
Dim a, b(), dic As Object, i As Long, k As Long, ii As Long, n As Long, v
Dim dataType As Long
a = Range("A1:A10").Value
ReDim b(1 To UBound(a))
Set dic = CreateObject("Scripting.Dictionary")
'Add the values to the correct dictionary item
' at the correct spot in the array
For i = LBound(a) To UBound(a)
dataType = VarType(a(i, 1))
If a(i, 1) <> "" Then
If Not dic.Exists(dataType) Then
ReDim b(UBound(a))
b(i) = a(i, 1)
dic.Add Key:=dataType, Item:=b
Else
b = dic(dataType)
b(i) = a(i, 1)
dic(dataType) = b
End If
End If
Next i
'Next create output array
ReDim b(1 To UBound(a), 1 To dic.Count)
k = 0
For Each v In dic.Keys
k = k + 1
For i = 1 To UBound(dic(v))
b(i, k) = dic(v)(i)
Next i
Next v
Range("J1").Resize(UBound(b, 1), UBound(b, 2)).Value = b
End Sub
Related
I'm trying to print the contents of a dictionary of arrays in one fell swoop to an Excel sheet.
The dictionary structure may be something like this:
dict(company_name) = employee
Where employee is an array of three values, e.g., name, surname, and age.
As long as the items are single value, I can print the dictionary with a statement like
Cells(1, 1).Resize(dict.Count, 1).Value2 = Application.Transpose(dict.Keys)
Cells(1, 2).Resize(dict.Count, 1).Value2 = Application.Transpose(dict.Items)
I cannot come up with a solution when I have array as item.
You've got an error. dict.Keys - it's an array of the keys! You can't set the cell value as array
You need to set the string variable and collect all keys in it
Dim str1 as String
Dim str2 as String
For i=1 to count 'qty of elements in dictionary
str1=str1 & dict.Keys()(i)
str2=str2 & dict.Items()(i)
Next i
Here is the link to the article about dictionaries
http://www.snb-vba.eu/VBA_Dictionary_en.html
With a loop you can do something like this - should still be fast.
Sub DictOutput()
Dim dict As Object, i As Long, r As Long, cols As Long, col As Long, arr, data, k
Set dict = CreateObject("scripting.dictionary")
'load some test data
For i = 1 To 100
dict.Add "Key_" & Format(i, "000"), Split("A,B,C,D", ",")
Next i
arr = dict.Items()(0) 'get the first value
cols = 1 + (UBound(arr) - LBound(arr)) 'number of items in array (assumed all the same size)
ReDim data(1 To dict.Count, 1 To (1 + cols)) 'size the output array
r = 0
For Each k In dict 'loop and fill the output array
r = r + 1
data(r, 1) = k
arr = dict(k)
i = 2
For col = LBound(arr) To UBound(arr) 'loop array and populate output row
data(r, i) = arr(col)
i = i + 1
Next col
Next k
'put the data on the sheet
ActiveSheet.Range("A1").Resize(UBound(data, 1), UBound(data, 2)).Value = data
End Sub
I have the following data
0/3, 1/1, 3/4
1/3, 3/2, 6/2
12/1, 3/6, 3/4
3/4, 0/3, 1/1 'will be considered is duplicate with the first data
Is there any way to find and remove duplicate data like this?
My current method is to split into 3 strings based on "," then check with the following condition.
'I got each String value by mid command.
'FrstStr1: First String of String 1
'SecStr1: Second String of String 1
'ThrStr1: Third String of String 1
'FrstStr2: First String of String 2
'SecStr2: Second String of String 2
'ThrStr2: Third String of String 2
if (FrstStr1 = FrstStr2 and SecStr1 = SecStr2 and ThrStr1 = ThrStr2) or
(FrstStr1 = FrstStr2 and SecStr1 = ThrStr2 and ThrStr1 = SecStr2) or
() or () .... then
I listed 6 possible cases and put them into if condition like above.
Make Array by Spliting data with delimiter comma.
And Sorting Array by function.
Ceck duplicated data by Dictionary.
## Code ##
Sub test()
Dim vR(), vDB
Dim dic As Object
Dim v As Variant
Dim s As String
Dim i As Long, n As Long
Set dic = CreateObject("Scripting.Dictionary")
vDB = Range("a1").CurrentRegion
For i = 1 To UBound(vDB, 1)
v = Split(vDB(i, 1), ",")
s = newArray(v)
If dic.exists(s) Then
Else
dic.Add s, s
n = n + 1
ReDim Preserve vR(1 To n)
vR(n) = vDB(i, 1)
End If
Next i
If n Then
Range("e1").Resize(n) = WorksheetFunction.Transpose(vR)
End If
End Sub
Function newArray(v As Variant)
Dim temp As String
Dim r As Integer, i As Integer, j As Integer
r = UBound(v)
For i = LBound(v) To r - 1
For j = i + 1 To r
v(i) = Trim(v(i))
v(j) = Trim(v(j))
If v(i) > v(j) Then
temp = v(j)
v(j) = v(i)
v(i) = temp
End If
Next j
Next i
newArray = Join(v, ",")
End Function
Image
expoliting Dictionary and ArrayList objects could lead to a very compact (and maintanable) code:
Sub RemoveDuplicatedDataWithoutCountingOrder()
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
Dim j As Long
Dim key As String
Dim datum As Variant, couple As Variant
For Each datum In Range("A1").CurrentRegion.Value
key = vbNullString
With CreateObject("System.Collections.SortedList")
For Each couple In Split(Replace(datum, " ", vbNullString), ",")
.Add couple, 0
Next
For j = 0 To .Count - 1
key = key & .getkey(j)
Next
If Not dict.exists(key) Then dict.Add key, datum
End With
Next
Range("C1").Resize(dict.Count) = Application.Transpose(dict.items)
End Sub
I'm new to VBA and was surprised that there isn't a function to insert elements in an array (my previous question). So I rethought my approach a bit.
On screen I have the following example table 'allActualWeights'. There are a lot of blanks (no weight value) that I want to get rid of (the table is different everytime). So the end result should be 'actualWeights'.
In my code I tried the following:
Option Base 1
Dim allActualWeights
allActualWeights = Range("A6:E29").Value
Dim actualWeights
actualWeights = allActualWeights
For Index = 1 To 24
If allActualWeights(Index, 2) <> 0 Then
ReDim actualWeights(Index, 5)
actualWeights(Index, 1) = allActualWeights(Index, 1)
actualWeights(Index, 2) = allActualWeights(Index, 2)
actualWeights(Index, 3) = allActualWeights(Index, 3)
actualWeights(Index, 4) = allActualWeights(Index, 4)
actualWeights(Index, 5) = allActualWeights(Index, 5)
End If
Next Index
Range("G6:K29") = actualWeights
But I'm not getting the results I hoped for.
What am I doing wrong, or is there a better approach?
Here's one approach:
Sub Tester()
Dim allActualWeights, actualweights(), i As Long, n As Long, c As Long
Dim rngSource As Range
Set rngSource = ActiveSheet.Range("A6:E29")
With rngSource
allActualWeights = .Value
'size the output array # of rows to count of values in ColB
ReDim actualweights(1 To Application.CountA(.Columns(1)), _
1 To .Columns.Count)
End With
n = 1
For i = LBound(allActualWeights, 1) To UBound(allActualWeights, 1)
If Len(allActualWeights(i, 2)) > 0 Then
For c = LBound(allActualWeights, 2) To UBound(allActualWeights, 2)
actualweights(n, c) = allActualWeights(i, c)
Next c
n = n + 1 'next output row
End If
Next i
'put the array on the sheet
Range("G6").Resize(UBound(actualweights, 1), UBound(actualweights, 2)) = actualweights
End Sub
This should do it and is easily maintainable...
Sub ActualWeights()
Dim c&, i&, j&, n&, a, b
With [a6:e29] '<-- allActualWeights
a = .Value2
n = UBound(a) - Application.CountBlank(.Offset(, 1).Resize(, 1))
ReDim b(1 To n, 1 To UBound(a, 2))
For i = 1 To UBound(a)
If a(i, 2) Then
c = c + 1
For j = 1 To UBound(a, 2)
b(c, j) = a(i, j)
Next
End If
Next
.Offset(, 6).Resize(n) = b
End With
End Sub
I have two lists of Data. List A and B both contain letter grades. I want to compare the data and if both lists have the same letter, I want to move that letter to list C that is blank to start with. If the two lists do not have the same letter, keep the letter where it is. I want to use 2 arrays to store the data and then create three new arrays for new list a,b, and c. Here is what I have so far.
Sub example1()
Dim ListA As Range, ListB As Range, ListC As Range
Range("H4:H10").Name = "ListA"
Range("I4:I6").Name = "ListB"
Range("J4", Range("J4").End(xlDown)).Name = "ListC"
Dim A(1 To 7), B(1 To 3), i As Integer, j As Integer
For i = 1 To 7 'stores data in listA in array A
A(i) = Range("ListA").Cells(i)
Next
For j = 1 To 3 'stores data in listB in array B
B(j) = Range("ListB").Cells(j)
Next
'select first from ListA and then compare data to listB
' if it is not found, stop and go to next item
'if it IS found, put in list C
Dim isfound As Boolean, letter As Variant, C(1 To 7), k As Integer
For i = 1 To 7
isfound = False
For j = 1 To 3
If A(i) = B(j) Then
isfound = True
letter = A(i)
Exit For
End If
Next
For k = 1 To 7
C(k) = Range("ListC").Cells(k) 'this is the part I am stuck on. How
do I get data to paste over to List C?
If isfound = True Then
C(k) = A(i) 'this says it will be equal to A(i) value if it is
found.
End If
Next
Next
End Sub
Something like this would work:
Sub example1()
Dim ListA, ListB, ListC(), i As Long, n As Long, m
ListA = Range("H4:H10").Value
ListB = Range("I4:I8").Value
ReDim ListC(1 To UBound(ListA, 1), 1 To 1) 'size the "dups" array
n = 1
For i = 1 To UBound(ListA, 1)
m = Application.Match(ListA(i, 1), ListB, 0) '<< check for match
If Not IsError(m) Then '<< have a duplicate
ListC(n, 1) = ListA(i, 1) 'add to ListC
ListA(i, 1) = "" '(optional) remove from original lists...
ListB(m, 1) = ""
n = n + 1
End If
Next i
'print to sheet...
Range("K4").Resize(UBound(ListA, 1)).Value = Compact(ListA)
Range("L4").Resize(UBound(ListB, 1)).Value = Compact(ListB)
Range("M4").Resize(UBound(ListC, 1)).Value = Compact(ListC)
End Sub
'remove empty array locations...
Function Compact(arr)
Dim rv(), p As Long, i As Long
ReDim rv(1 To UBound(arr, 1), 1 To 1)
For i = 1 To UBound(arr, 1)
If Len(arr(i, 1)) > 0 Then
p = p + 1
rv(p, 1) = arr(i, 1)
End If
Next i
Compact = rv
End Function
This assumes listA/B each contains unique values (no repeats within one list)
Column A contains the labels or outcome value, Columns B-N contain varying lengths of comma separated values, but range for each column is the same (i.e., 1-64). The goal is to covert to a new table with Column A representing the value range (1-64) and Columns B-N the labels/outcome from the original table.
A semi-related solution was sought here, but without use of macros.
I will let you to modify this code,
Sub splitThem()
Dim i As Long, j As Long, k As Long, x As Long
x = 1
Sheets.Add.Name = "newsheet"
For j = 2 To Cells(1, Columns.Count).End(xlToLeft).Column
For i = 1 To Cells(Rows.Count, 2).End(xlUp).Row
If Cells(i, j) <> "" Then
For k = 1 To Len(Cells(i, j)) - Len(Replace(Cells(i, j), ",", "")) + 1
Sheets("newsheet").Cells(x, j) = Cells(i, 1)
x = x + 1
Next k
End If
Next i
x = 1
Next j
End Sub
Try this code.
Sub test()
Dim vDB, vR()
Dim vSplit, v As Variant
Dim Ws As Worksheet
Dim i As Long, n As Long, j As Integer, c As Integer
vDB = Range("a2").CurrentRegion
n = UBound(vDB, 1)
c = UBound(vDB, 2)
ReDim vR(1 To 64, 1 To c)
For i = 1 To 64
vR(i, 1) = i
Next i
For i = 2 To n
For j = 2 To c
vSplit = Split(vDB(i, j), ",")
For Each v In vSplit
vR(v, j) = vDB(i, 1)
Next v
Next j
Next i
Set Ws = Sheets.Add '<~~ replace your sheet : Sheets(2)
With Ws
For i = 1 To c
.Range("b1")(1, i) = "COND" & i
Next i
.Range("a2").Resize(64, c) = vR
End With
End Sub