I was trying to automate an Excel file which has title in both A and B columns and I have to search each word from A within B and calculate the % by using the "no of words matched/total no of words (in column A)" formula.
I'm using the below code, however its not giving me the accurate % for which the title has repeated words (Duplicate words).
Sub percentage()
Dim a() As String, b() As String
Dim aRng As Range, cel As Range
Dim i As Integer, t As Integer
Set aRng = Range(Range("A1"), Range("A5").End(xlDown))
For Each cel In aRng
a = Split(Trim(cel), " ")
b = Split(Trim(cel.Offset(, 1)), " ")
d = 0
c = UBound(a) + 1
If cel.Value <> "" Then
If InStr(cel, cel.Offset(, 1)) Then
d = UBound(b) + 1
Else
For i = LBound(a) To UBound(a)
For t = LBound(b) To UBound(b)
If UCase(a(i)) = UCase(b(t)) Then
d = d + 1
End If
Next
Next
End If
End If
cel.Offset(0, 2).Value = (d / c)
Next
End Sub
If Title 1 : Really Nice pack with Nice print and Title 2 : Nice Print Nice pack then result should be 3/6 i.e. 67%.
But I'm getting a result as 100%.
Can anyone help me out please.
Titles are
Great job dud
Really Nice pack with Nice print
To give success and success process
Don’t eat too much. If you eat too much you will get sick
I have tried =noDuplicate(celladdress)
First, you should delete duplicate word in column B.
My function delete word and return array of word that not duplicate.
Function noDuplicate(ByVal str As String) As String()
Dim splitStr() As String
Dim result() As String
Dim i As Integer
Dim j As Integer
Dim k As Integer
Dim addFlag As Boolean
splitStr = Split(UCase(str), " ")
ReDim result(UBound(splitStr))
'
result(0) = splitStr(0)
k = 0
For i = 1 To UBound(splitStr)
addFlag = True
For j = 0 To k
If splitStr(i) = result(j) Then
addFlag = False
Exit For
End If
Next j
If addFlag Then
result(k + 1) = splitStr(i)
k = k + 1
End If
Next i
ReDim Preserve result(k)
noDuplicate = result
End Function
Then calculate the percentage of number of match word and number of word in column A.
Function percentMatch(ByVal colA As String, ByVal colB As String) As Double
Dim splitColA() As String
Dim splitColB() As String
Dim i As Integer
Dim j As Integer
Dim matchCount As Integer
splitColA = Split(UCase(colA), " ")
splitColB = noDuplicate(colB)
matchCount = 0
For i = 0 To UBound(splitColA)
For j = 0 To UBound(splitColB)
If splitColA(i) = splitColB(j) Then
matchCount = matchCount + 1
Exit For
End If
Next j
Next i
percentMatch = matchCount / (UBound(splitColA) + 1)
End Function
After add these two function, you can write your new code to below
Sub percentage()
Dim aRng As Range, cel As Range
Set aRng = Range(Range("A1"), Range("A5").End(xlDown))
For Each cel In aRng
cel.Offset(0, 2).Value = percentMatch(cel.Value, cel.Offset(0, 1).Value)
Next
End Sub
Note, I not protect for empty string in the function.
If you F8 through the code, you can see the problem.
The first Nice in column A loops through column B and counts 2 occurences.
Pack in column A loops through column B and counts 1 occurence.
The second Nice in column A loops through column B and counts 2 occurences.
Print in column A loops through column B and counts 1 occurence.
So you get a count of 6 against the 6 words in column A; 100%
If you add a random word to column A, you'll get 6 out of 7.
Related
The code provided below is intended to highlight cells where a negative word is within 5 words of a key word. For the sake of my benefactors privacy I will not provide the keywords I search for however, you can see the function of the code. You could put any word as a key word for testing including but not limited to "Micheal", "building", "damage", etc.
The code executes it's desired function to a degree however instead of highlighting the individual cells that met the requirement, it highlights the entire selection of cells below the first cell that meets the requirements. I feel the issue lies somewhere near the interior color index.
If anyone has any ideas to solve this issue than it'd be greatly appreciated.
Sub IdentifyCameraPresence()
Application.ScreenUpdating = False
Dim Rng As Range
Dim x As Double, y As Double, i As Double, p As Double
Dim Negatives As Variant, Keys As Variant, n As Double, k As Double 'keeps track of the location of negatives and key words in a cell
Dim NWords As Variant, KWords As Variant, m As Double, j As Double 'keeps track of the words that are negative and key
Dim Temp As Variant
Set Negatives = CreateObject("System.Collections.ArrayList")
Set Keys = CreateObject("System.Collections.ArrayList")
Set Rng = Selection
Set NWords = CreateObject("System.Collections.ArrayList")
NWords.Add "no"
NWords.Add "not"
Debug.Print NWords(0); NWords.Count
Set KWords = CreateObject("System.Collections.ArrayList")
KWords.Add "key1"
KWords.Add "key2"
KWords.Add "key3"
KWords.Add "key4"
KWords.Add "key5"
Debug.Print KWords(3)
Debug.Print "NWords"; NWords.Count; "KWords"; KWords.Count
For p = 1 To Rng.Rows.Count
' Debug.Print Rng.Cells(p, 1)
With Application.WorksheetFunction
Temp = Split(Rng(p, 1))
' For i = 0 To UBound(Temp)
' Debug.Print "word"; i; ":"; Temp(i)
' Next i
For i = 0 To UBound(Temp)
For m = 0 To NWords.Count - 1
If LCase(Temp(i)) = LCase(NWords(m)) Then Negatives.Add i
Next m
For j = 0 To KWords.Count - 1
If InStr(1, LCase(Temp(i)), LCase(KWords(j))) Then Keys.Add i
Next j
Next i
' For i = 0 To Negatives.Count - 1
' Debug.Print "Negative word"; i; ":"; Negatives(i)
' Next i
'
' For i = 0 To Keys.Count - 1
' Debug.Print "Key word"; i; ":"; Keys(i)
' Next i
'
''----------------------------PROBLEM IS HERE------------------------------------------------------------
For k = 0 To Keys.Count - 1
For n = 0 To Negatives.Count - 1
Debug.Print "Key"; Keys(k); "negative"; Negatives(n)
Debug.Print "In Color Index"
If Abs(Negatives(n) - Keys(k)) < 5 Then Rng.Cells(p, 1).Interior.ColorIndex = 35
Next n
Next k
''----------------------------PROBLEM IS HERE------------------------------------------------------------
'
End With
Next p
Application.ScreenUpdating = True
End Sub
Is there a way (vba code or excel trick) to manipulate a 2 columnar list so that I get a table with all potential combinations depending on a unique identifier in the first column?
E.g. I have one column with Company Names and another with Country Locations. What I need is every set if combinations of the countries per company (see screenshot attached).
This vba module should solve your problem.
Just copy the code to a new module, declare the input and output columns and the number of the first row of your list.
Note that the code will stop once it hits a line where the "Unique Identifier" Cell is empty.
Also, it requires that your list is sorted with respect to your "Unique Identifier".
If a Unique Identifier only appears once, it will still be written into the output list, but only once and with the outColNation2 staying empty in that row. If this is not desired and it should be left out entirely, just delete the commented if-statement.
Example Image of output
Also note, that a unique identifier can repeat at most 100 times. I assume none of them appears that often as that would create a ridiculously long output list.
Option Compare Text
Sub COMBINATIONS()
Dim i As Long, j As Long, k As Long, l As Long, n As Long
Dim arr(100) As String
Dim UI As String
Dim inColUI As Integer, inColNation As Integer
Dim outColUI As Integer, outColNation1 As Integer, outColNation2 As Integer
Dim FirstRowOfData As Integer
Dim YourWS As Worksheet
inColUI = 1 'Column of the "Unique Identifier"
inColNation = 2 'Column of the "Nations" in your example
outColUI = 4
outColNation1 = 5 'output columns
outColNation2 = 6
FirstRowOfData = 2 'First Row of data
Set YourWS = Application.Worksheets("Sheet1") 'Put in your Worksheet Name here.
i = FirstRowOfData
n = FirstRowOfData
With YourWS
Do Until .Cells(i, inColUI) = ""
j = 0
UI = .Cells(i, inColUI)
Do Until .Cells(i - 1, inColUI) <> .Cells(i, inColUI) And j > 0 Or .Cells(i, inColUI) = ""
arr(j + 1) = .Cells(i, inColNation)
i = i + 1
j = j + 1
Loop
If j = 1 Then '<- remove this if-statement and the following marked lines if single appearing UIs should be omitted entirely
.Cells(n, outColUI) = UI '<---
.Cells(n, outColNation1) = arr(1) '<---
n = n + 1 '<---
Else '<---
For k = 1 To j
For l = 1 To j
If arr(k) <> arr(l) Then
.Cells(n, outColUI) = UI
.Cells(n, outColNation1) = arr(k)
.Cells(n, outColNation2) = arr(l)
n = n + 1
End If
Next l
Next k
End If '<---
Loop
End With
End Sub
Edit: cleaned up the code a little bit
Something like the following shows how to iterate through 2 ranges of cells
Dim Rng1 as Range, Rng2 as Range
Dim SrcCell as Range, OthrCell as Range
Dim FullList as string
Rng1 = Range("A1:A12")
Rng2 = Range("B1:B12")
FullList = ""
For Each SrcCell in Rng1
For Each OthrCell in Rng2
FullList = IIF(FullList="","",FullList & vbCrLf) & SrcCell.Value & OthrCell.Value
Next OthrCell
Next srcCell
The FullList string now contains all the combinations but you may require something else. Only intended to give you a start
You need to add code yourself to filter out duplicates
You can do the following (see code below). As another commentee mentioned, when there is only one record of company vs country, it will not show in the output.
The solutions is based on creating a dictionary, each entry is a company and the value is a comma separated string of countries. After the dictionary is created, the dictionary is looped, and a list of countries is then iterated over a nested loop. If the index of the outer loop is the same as the inner index of the loop then the loop is skipped i.e. that would be a Country 1 vs Country 1 combination. Otherwise is added to the output list.
Columns A,B is input and columns D,E,F is output.
Option Explicit
Public Sub sCombine()
Dim r As Range, dest As Range
Dim d As New Dictionary
Dim key As Variant
Dim countries() As String
Dim i As Integer, j As Integer
On Error GoTo error_next
Set r = Sheet1.Range("A1")
Set dest = Sheet1.Range("D:F")
dest.ClearContents
Set dest = Sheet1.Range("D1")
While r.Value <> ""
If d.Exists(r.Value) Then
d(r.Value) = d(r.Value) & "," & r.Offset(0, 1)
Else
d.Add r.Value, r.Offset(0, 1).Value
End If
Set r = r.Offset(1, 0)
Wend
For Each key In d.Keys
countries = Split(d(key), ",")
For i = LBound(countries) To UBound(countries)
For j = LBound(countries) To UBound(countries)
If i <> j Then
dest.Value = key
dest.Offset(0, 1).Value = countries(i)
dest.Offset(0, 2).Value = countries(j)
Set dest = dest.Offset(1, 0)
End If
Next j
Next i
Next key
Exit Sub
error_next:
MsgBox Err.Description
End Sub
I'm currently programming an Excel Function which should return the average of the last 5 non-empty positions of an array. To do that I want to go through the array while inside the function as follows:
Function AVERAGE_LAST_5(rng As Range) As Long
Dim x As Integer, i As Integer, j As Integer, sum As Integer
Dim myArr() As Variant
myArr() = Application.Transpose(Application.Transpose(rng))
x = rng.Count
i = 0:: j = 0:: sum = 0
For i = x To 1 Step -1
If myArr(x).Value <> 0 Then
sum = sum + myArr(x)
j = j + 1
Else
End If
If j = 5 Then Stop
x = x - 1
Next
AVERAGE_LAST_5 = sum / 5
End Function
Problem: the for loop doesn't work, when reaching the first if the program aborts.
Does anyone has had the same problem?
Can anyone help me with it?
myarr will be a two-dimensional array, and not a range. You will need to provide both dimensions:
If isarray(myarr) then
for i = ubound(myarr,1) to lbound(myarr,1) step -1
for j = ubound(myarr,2) to lbound (myarr,2) step -1
if myarr(i,j) <> 0 then
K=k+1
Mysum = mysum + myarr(I,j)
Endif
Next j
Next i
Else ‘ single value
mysum =myarr(I,j)
Endif
Arrays Are Faster
Final Version (Hopefully)
This version additionally has the NumberOfLastValues argument (Required) so you can choose how many values will be summed up and it is shortened with the GoSub...Return statement since the If statement is the same for by rows and by columns.
For some other details look in the First Version below.
Usage
In VBA:
Sub LastAverage()
Debug.Print AvgLast(Range("B4:G14"), 5)
End Sub
In Excel:
=AvgLast(B4:G14,5)
Function AvgLast(SearchRange As Range, ByVal NumberOfLastValues As Long, _
Optional ByVal Row_0_Column_1 As Integer = 0) As Double
Dim vntRange As Variant ' Range Array
Dim i As Long ' Range Array Rows Counter
Dim j As Integer ' Range Array Columns Counter
Dim k As Long ' Values Counter
Dim dblSum As Double ' Values Accumulator
If SearchRange Is Nothing Then Exit Function
vntRange = SearchRange.Value
If Row_0_Column_1 = 0 Then
' By Row
For i = UBound(vntRange) To 1 Step -1
For j = UBound(vntRange, 2) To 1 Step -1
GoSub Calc
Next
Next
Else
' By Column
For j = UBound(vntRange, 2) To 1 Step -1
For i = UBound(vntRange) To 1 Step -1
GoSub Calc
Next
Next
End If
TiDa:
If k > 0 Then
AvgLast = dblSum / k
End If
Exit Function
Calc:
If vntRange(i, j) <> "" And IsNumeric(vntRange(i, j)) Then
k = k + 1
dblSum = dblSum + vntRange(i, j)
If k = NumberOfLastValues Then GoTo TiDa
End If
Return
End Function
First Version
It will return the average if there is at least 1 value and at most 5 values, otherwise it will return 0.
The Row_0_Column_1 arguments parameter is by default 0 and means that the search is done by row (first loop). If it is 1, then the search is done by column (second loop).
The basics are that the range is pasted (depsited) into an array and then the array is searched for existing 'numeric' values and not "" values that are summed up and when reaching the fifth value it 'jumps' out of the loop and divides the sum by 5.
Function AvgLast5(SearchRange As Range, Optional Row_0_Column_1 As Integer = 0) _
As Double
Dim vntRange As Variant ' Range Array
Dim i As Long ' Range Array Rows Counter
Dim j As Integer ' Range Array Columns Counter
Dim k As Long ' Values Counter
Dim dblSum As Double ' Values Accumulator
If SearchRange Is Nothing Then Exit Function
vntRange = SearchRange.Value
If Row_0_Column_1 = 0 Then
' By Row
For i = UBound(vntRange) To 1 Step -1
For j = UBound(vntRange, 2) To 1 Step -1
If vntRange(i, j) <> "" And IsNumeric(vntRange(i, j)) Then
k = k + 1
dblSum = dblSum + vntRange(i, j)
If k = 5 Then GoTo TiDa
End If
Next
Next
Else
' By Column
For j = UBound(vntRange, 2) To 1 Step -1
For i = UBound(vntRange) To 1 Step -1
If vntRange(i, j) <> "" And IsNumeric(vntRange(i, j)) Then
k = k + 1
dblSum = dblSum + vntRange(i, j)
If k = 5 Then GoTo TiDa
End If
Next
Next
End If
TiDa:
If k > 0 Then
AvgLast5 = dblSum / k
End If
End Function
after a couple of tough days at work I've finally got some time to improve my function taking your tips on board.
I've made some changes to enable the function to deal either with 1-Row or 1-Column Ranges. Basic Error handling was also added and a function discripton is as well available (under the FX Excel button).
Feel free to comment and/or use the code.
Here the result:
Function AVERAGE_LAST_N(rng As Range, N As Integer)
Dim NrN As Integer, NrR As Integer, NrC As Integer
Dim i As Integer, j As Integer
Dim sum As Double
Dim myArr As Variant
NrN = rng.Count 'Number of array positions
NrR = rng.Rows.Count 'Number of Rows in the array
NrC = rng.Columns.Count 'Number of Rows in the array
i = 0:: j = 0:: sum = 0 'Counters
'####################################################'
'## Transpose Range into array if row or if column ##'
'####################################################'
If rng.Rows.Count > 1 And rng.Columns.Count = 1 Then 'Transpose a Column Range into an Array
myArr = Application.Transpose(rng)
ElseIf rng.Rows.Count = 1 And rng.Columns.Count > 1 Then 'Transpose a Row Range into an Array
myArr = Application.Transpose(Application.Transpose(rng))
ElseIf rng.Rows.Count > 1 And rng.Columns.Count > 1 Then 'Retunrs an Error if Range is a Matrix *ERR_002*
AVERAGE_LAST_N = "ERR_002"
Exit Function
End If
'####################################################'
'## Transpose Range into array if row or if column ##'
'####################################################'
'################'
'## Start Main ##'
'################'
For i = NrN To 1 Step -1
If IsNumeric(myArr(NrN)) Then
sum = sum + myArr(NrN)
j = j + 1
End If
If j = N Then Exit For
NrN = NrN - 1
Next
AVERAGE_LAST_N = sum / N
'##############'
'## End Main ##'
'##############'
'####################'
'## Error Debuging ##'
'####################'
If j < N Then
AVERAGE_LAST_N = "ERR_001"
Exit Function
End If
'####################'
'## Error Debuging ##'
'####################'
End Function
Sub DescribeFunction()
Dim FuncName As String
Dim FuncDesc As String
Dim Category As String
Dim ArgDesc(1) As String
FuncName = "AVERAGE_LAST_N"
FuncDesc = "Returns the average of the last N non-empty values in the selected Range"
Category = 14 'Text category
ArgDesc(0) = "Range that contains the values" & Chr(10) & _
"ERR_001 - There are not enought non-empty or null values in the range" & Chr(10) & _
"ERR_002 - Selected range is a matrix and not a row or column range"
ArgDesc(1) = "Dimention of the sample" & Chr(10) & _
"ERR_001 - There are not enought non-empty or null values in the range" & Chr(10) & _
"ERR_002 - Selected range is a matrix and not a row or column range"
Application.MacroOptions _
Macro:=FuncName, _
Description:=FuncDesc, _
Category:=Category, _
ArgumentDescriptions:=ArgDesc
End Sub
'#######################################################################################
' ###############################################
' ############# Error DB ##############
' ###############################################
'
'
' ERR_001 - There are not enought non-empty values in the range
' ERR_002 - Selected range is a matrix and not a row or column range
'
Rafa
I'm trying to write code that extracts X consecutive numbers from text.
For example, if I want to extract 5 consecutive numbers in my text:
Cell A1: dsuad28d2hr 22222222 11111 d33d11103
Cell B2: 11111 (wanted)
I could make it work for texts with only 5 numbers but the problem is if my text contains other consecutive numbers higher than 5.
Sub ExtractNum2()
Dim Caract() As String
Dim i As Integer
Dim j As Integer
Dim z As Integer
Dim cont As Integer
Dim goal As Integer
Dim Protocolo() As String
Dim cel As String
Dim lin As Long
lin = Range("A1", Range("A1").End(xlDown)).Rows.Count 'Repeat for each line
For z = 1 To lin
cel = Cells(z, 1)
ReDim Caract(Len(cel))
ReDim Protocolo(Len(cel))
cont = 0
For i = 1 To Len(cel)
Caract(i) = Left(Mid(cel, i), 1)
If IsNumeric(Caract(i)) Then 'Character check
cont = cont + 1
Protocolo(cont) = Caract(i)
'If Not IsNumeric(Caract(6)) And cont = 5 Then**
If cont = 5 '
Dim msg As String
For j = 1 To 5
msg = msg & Protocolo(j)
Next j
Cells(z, 2) = msg 'fills column B
msg = ""
End If
Else
cont = 0
End If
Next i
Next z 'end repeat
End Sub
I'm trying to use:
If Not IsNumeric(Caract(6)) And cont = 5 Then
But it is not working, my output is: B2: 22222 but I want 11111.
What am I missing?
EDIT
Sorry i wasnt clear. I want to extract X numbers with 6>x>4 (x=5). I dont want 22222 since it has 8 consecutive numbers and 11111 has 5 in my example.
UDF:
Function GetNum(cell)
With CreateObject("VBScript.RegExp")
.Pattern = "\b(\d{5})\b"
With .Execute(cell)
If .Count > 0 Then GetNum = .Item(0).SubMatches(0)
End With
End With
End Function
UPDATE:
If you want to return error (say, #N/A) instead of callee's default data type, you could write the following:
Function GetNum(cell)
With CreateObject("VBScript.RegExp")
.Pattern = "\b(\d{5})\b"
With .Execute(cell)
If .Count > 0 Then
GetNum = .Item(0).SubMatches(0)
Else
GetNum = CVErr(xlErrNA)
End If
End With
End With
End Function
I tried this with a Cell containing "Yjuj 525211111x5333332s5" to test whether 2 consecutive 5 characters get catch, and it worked great.
Sub Macro_Find_Five()
Dim str As String
Dim tmp As String
Dim cntr As Integer
Dim result As String
str = Sheet1.Cells(1, 1).Value
tmp = ""
cntr = 1
col = 2
result = ""
'For Loop for tracing each charater
For i = 1 To Len(str)
'Ignore first starting character
If i > 1 Then
'If the last character matches current character then
'enter the if condition
If tmp = Mid(str, i, 1) Then
'concatenate current character to a result variable
result = result + Mid(str, i, 1)
'increment the counter
cntr = cntr + 1
Else
'if the previous character does not match
'reset the cntr to 1
cntr = 1
'as well initialize the result string to "" (blank)
result = ""
End If
End If
'if cntr matches 5 i.e. 5 characters traced enter if condition
If cntr = 5 Then
'adding to next column the result found 5 characters same
Sheet1.Cells(1, col).Value = result
'increment the col (so next time it saves in next column)
col = col + 1
'initializing the variables for new search
cntr = 1
tmp = ""
result = ""
End If
'stores the last character
tmp = Mid(str, i, 1)
'if first character match concatenate.
If cntr = 1 Then
result = result + Mid(str, i, 1)
End If
Next i
End Sub
I was trying to automate an Excel file which has title in both A and B columns and I have to search each word from A within B and calculate the % by using the "no of words matched/total no of words (in column A)" formula.
I'm using the below code, however its not giving me the accurate %. Can anyone help me out please.
Sub percentage()
Dim a() As String
Dim b() As String
Dim aRng As Range
Dim cel As Range
Dim i As Integer, t As Integer, clm As Integer
Set aRng = Range(Range("A1"), Range("A65536").End(xlDown))
For Each cel In aRng
a = Split(cel, " ")
b = Split(cel.Offset(, 1), " ")
d = 0
clm = 2
C = UBound(a)
If cel.Value <> "" Then
For i = LBound(a) To UBound(a)
For t = LBound(b) To UBound(b)
If UCase(a(i)) = UCase(b(t)) Then
clm = 2
Do While True
If UCase(cel.Offset(, clm)) = UCase(a(i)) Then
Exit Do
End If
If cel.Offset(, clm) = "" Then
'cel.Offset(, clm) = a(i)
Exit Do
End If
clm = clm + 1
Loop
d = d + 1
End If
Next
Next
cel.Offset(0, 2).Value = (d / c)
End If
Next
End sub
Please make the changes. c = UBound(a) + 1 'change here
Multiply by 100 and use rounding function cel.Offset(0, 2).Value = (d / c)
Your program code should work correctly.
******EDIT On 28-09-2015 *********
Another version of program considered a better approach by me for the OP's question.
**** EDIT 29-09-2015 ******
Retesting of program for different combination of words in the string revealed that this update of program is not giving consistent results in all type of situations, Hence Update of program on 28-09-2015 stands withdrawn.
Sub percentage_rev()
Dim a() As String, b() As String
Dim aRng As Range, cel As Range
Dim i As Integer, t As Integer
Set aRng = Range(Range("A1"), Range("A5").End(xlDown))
For Each cel In aRng
a = Split(Trim(cel), " ")
b = Split(Trim(cel.Offset(, 1)), " ")
d = 0
c = UBound(a) + 1 'change here
If cel.Value <> "" Then
If InStr(cel, cel.Offset(, 1)) Then
d = UBound(b) + 1
Else
For i = LBound(a) To UBound(a)
For t = LBound(b) To UBound(b)
If UCase(a(i)) = UCase(b(t)) Then
d = d + 1
End If
Next
Next
End If
End If
cel.Offset(0, 2).Value = (d / c) * 100 'multiply by 100 for percentage
Next
End Sub