Selecting wrong range - excel

I am trying to quickly calculate a long lists unique values given several filters.
Rwave2 generates a range that starts past the end of Rwave, somehow taking values from the original "Export" range.
The first value of s = 44928 and e = 85991 and second value s = 1 and e = 2388. However the second range starts at 89855 of the original "Export" cell Range and 2388 values after. Not 1-2388 of the Rwave range.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim KeyCells As Range
Set KeyCells = Range("B4")
If Not Application.Intersect(KeyCells, Range(Target.Address)) _
Is Nothing Then
Dim wave As String
wave = CStr(Range("B4").Value)
Dim Rwave As Range
Dim s, e As Long
'Separate by Wave
s = Search_Start(Sheets("Export").Range("A:A"), "A", wave)
e = Search_End(Sheets("Export").Range("A:A"), "A", wave, s)
Set Rwave = Sheets("Export").Range(Sheets("Export").Cells(s, "A"), Sheets("Export").Cells(e, "G"))
Sheets("TestSheet1").UsedRange.ClearContents
Rwave.Copy Sheets("TestSheet1").Range("A1")
For i = 6 To 56
'Separate by Zone
Dim Rwave2 As Range
s = Search_Start(Rwave, "B", CStr(Sheets("Sheet1").Cells(i, "B")))
e = Search_End(Rwave, "B", CStr(Sheets("Sheet1").Cells(i, "B")), s)
Set Rwave2 = Rwave.Range(Rwave.Cells(s, "A"), Rwave.Cells(e, "G"))
Sheets("TestSheet2").UsedRange.ClearContents
Rwave2.Copy Sheets("TestSheet2").Range("A1")
'Create an array of only the unique locations
Dim tmp, cell As String
Dim arr() As String
tmp = "|"
For j = 1 To Rwave2.Rows.Count
'Only count the locations on the right level
If Rwave2.Cells(j, "C") = Sheets("Sheet1").Cells(i, "C") Then
cell = Rwave2.Cells(j, "D")
If (cell <> "") And (InStr(tmp, cell) = 0) Then
tmp = tmp & cell & "|"
End If
End If
Next j
If Len(tmp) > 0 Then tmp = Left(tmp, Len(tmp) - 1)
arr = Split(tmp, "|")
Cells(i, "M") = UBound(arr) - LBound(arr)
Next i
End If
End Sub
Function Search_Start(r As Range, c As String, y As String) As Double
For i = 1 To r.Rows.Count
If InStr(r.Cells(i, c), y) <> 0 Then
Search_Start = i
Exit Function
End If
Next i
Search_Start = 1
End Function
Function Search_End(r As Range, c As String, y As String, s As Variant) As Double
For i = s To r.Rows.Count
If InStr(r.Cells(i, c), y) = 0 Then
Search_End = i - 1
Exit Function
End If
Next i
Search_End = r.Rows.Count
End Function

Think I see what's going on now.
The code below will search column C of the range B5:D10.
Because the searched range starts on column B - then column C is the third column, which is column D when looking at the whole worksheet.
Similarly you're counting rows within your range. If worksheet cell D7 contains the word Yellow then it will return i=3 as that's the third row in your range.
Sub Test()
Debug.Print Search_Start(Sheet3.Range("B5:D10"), "C", "Yellow")
End Sub
Function Search_Start(r As Range, c As String, y As String) As Double
Dim i As Long
For i = 1 To r.Rows.Count
Debug.Print r.Cells(i, c).Address
If InStr(r.Cells(i, c), y) <> 0 Then
Search_Start = i
Exit Function
End If
Next i
Search_Start = 1
End Function
To return the correct number use Search_Start = r.Cells(i, c).Row.

Related

VBA code that concatenates "unique" values

I'm trying to write a VBA code that will take values from a selection and concatenate the cell values with a line seperator. I also wish to not include and duplicates.
Ex. as follows:
Say I have a data set like the below. I would like to type =ConcatenateUnique(A1:B2,",") and have it return One,Two,Three
Column A
Column B
One
Two
Three
One
I tried the below, although I'm aware if it did work it would only return Two,Three
Function CONCATENATEUNIQUE(Ref As Range, Separator As String) As String
Dim Cell As Range
Dim Result As String
For Each Cell In Ref
If WorksheetFunction.CountIf(Ref, Cell.Value) <= 1 Then
Result = Result & Cell.Value & Separator
End If
Next Cell
CONCATENATEMULTIPLE = Left(Result, Len(Result) - 1)
End Function
For this type of task a Scripting Dictionary is useful (but note this won't work on a Mac):
Function UniqueList(rng As Range, Optional sep As String = ",")
Dim arr, r As Long, c As Long, v, dict As Object
If rng.Count = 1 Then 'handle single-cell case
UniqueList = rng.Value
Exit Function
End If
arr = rng.Value 'get values into an array
Set dict = CreateObject("Scripting.Dictionary")
For r = 1 To UBound(arr, 1)
For c = 1 To UBound(arr, 2)
v = arr(r, c)
If Not IsError(v) Then
If Len(v) > 0 Then dict(v) = True
End If
Next c
Next r
UniqueList = Join(Application.Transpose( _
Application.Transpose(dict.Keys)), sep)
End Function

VBA find duplicate in selected cells and count them

I read many post on this forum regarding my problem, but cant find solutions.
I have a table with different number of cells, with duplicate value.
I would like to count duplicates and show in another columns.
Source table where I mark a few cell:
I would like to receive such output
A have a part of code, but whatever I select, it counts the last cell
Dim rng, rngTarget, rngTargetName As Range
Set rngTarget = Range("D7")
Set items = CreateObject("Scripting.Dictionary")
For Each rng In Selection
If Not items.exists(rng.Value) Then
items.Add rng.Value, 1
rngTarget.Value = items(rng.Value)
rngTargetName = rng
Else
items(rng.Value) = items(rng.Value) + 1
rngTarget.Value = items(rng.Value)
rngTargetName = rng
End If
Next
What i missing?
First enter this in a Standard Module:
Public Function unikue(rng As Range)
Dim arr, c As Collection, r As Range
Dim nCall As Long, nColl As Long
Dim i As Long
Set c = New Collection
nCall = Application.Caller.Count
On Error Resume Next
For Each r In rng
If r.Value <> "" Then
c.Add r.Text, CStr(r.Text)
End If
Next r
On Error GoTo 0
nColl = c.Count
If nCall > nColl Then
ReDim arr(1 To nCall, 1 To 1)
For i = 1 To nCall
arr(i, 1) = ""
Next i
Else
ReDim arr(1 To nColl, 1 To 1)
End If
For i = 1 To nColl
arr(i, 1) = c.Item(i)
Next i
unikue = arr
End Function
The above UDF() will return a list of the unique, non-blank, items in a block of cells.
Then in a set of cells in column, say F starting at F5 , array-enter:
=unikue(A1:D3)
In G5 enter:
=COUNTIF($A$1:$D$3,F5)
and copy downward:
With Excel 365, there is a no-VBA solution.
Thanks Gary's for help, but ...
i completed my version of code and now works as expected - i can select few cell and count duplicates.
My working code:
Dim rng As Range
Dim var As Variant
Dim i As Integer
i = 0
Set D = CreateObject("Scripting.Dictionary")
For Each rng In Selection
If rng <> "" Then
If D.exists(rng.Value) Then
D(rng.Value) = D(rng.Value) + 1
Else
D.Add rng.Value, 1
End If
End If
Next
For Each var In D.Keys
Range("C" & (i + 18)) = var
Range("E" & (i + 18)) = D(var)
i = i + 1
Next
Set D = Nothing

How to count the total number of specific words in a cell and do the same for other cells as well using VBA?

How do I count the total number of "alt" and "first" that appeared in a cell and do the same for other cells as well while ignoring empty cells in the process? For instance, if a cell has first, first, alt, first, first, first, it should give me firstcounter = 5 (where firstcounter is the total count for first) and altcounter= 1(altcounter is the total count for alt). After that I can use the value of firstcounter and altcounter found to concatenate them into a string as shown in column B in the form of "first-" & firstcounter, "alt-"& altcounter.
Dim ia As Long
Dim lastrow2 As Long
Dim ws1 As Worksheet
Set ws1 = ThisWorkbook.Sheets(1)
lastrow2 = ws1.Range("A" & ws1.Rows.count).End(xlUp).Row
For ia = 2 To lastrow2
Dim arr() As Variant
' Split the string to an array
arr = Split(ws1.Cells(ia, "A"), ",").Value
'what should i do after split
Enter the following into a code module...
Function CountWords$(r)
Dim a&, f&, w
For Each w In Split(r, ",")
If w = "alt" Then a = a + 1
If w = "first" Then f = f + 1
Next
If (a + f) Then CountWords = "first-" & f & ",alt-" & a
End Function
Then in cell B2 enter this formula:
=CountWords(A2)
Now copy it downwards as far as you need.
Update
To use the above function from VBA without entering formulas in the worksheet you can do it like this...
Sub Cena()
Dim i&, v
With [a2:a8]
v = .Value2
For i = 1 To UBound(v)
v(i, 1) = CountWords(v(i, 1))
Next
.Offset(, 1) = v
End With
End Sub
Function CountWords$(r)
Dim a&, f&, w
For Each w In Split(r, ",")
If w = "alt" Then a = a + 1
If w = "first" Then f = f + 1
Next
If (a + f) Then CountWords = "first-" & f & ",alt-" & a
End Function
Update #2
In response to your questions in the comments, you can use this variation instead...
Sub Cena()
Dim i&, v
With [a2].Resize(Cells(Rows.Count, 1).End(xlUp).Row - 1)
v = .Value2
For i = 1 To UBound(v)
v(i, 1) = CountWords(v(i, 1))
Next
.Cells = v
End With
End Sub
Function CountWords$(r)
Dim a&, f&, w
For Each w In Split(r, ",")
If w = "alt" Then a = a + 1
If w = "first" Then f = f + 1
Next
If (a + f) Then CountWords = "first-" & f & ",alt-" & a
End Function
In order to make this independent from the words alt and first and whitespaces in the string I would use the following functions
Option Explicit
'Add a reference to Microsoft VBScript Regular Expressions 5.5
Public Function RemoveWhiteSpace(target As String) As String
With New RegExp
.Pattern = "\s"
.MultiLine = True
.Global = True
RemoveWhiteSpace = .Replace(target, vbNullString)
End With
End Function
'Add a reference to Microsoft Scripting Runtime
Function CountWordsA(rg As Range) As String
On Error GoTo EH
Dim dict As Dictionary
Set dict = New Dictionary
Dim vDat As Variant
vDat = RemoveWhiteSpace(rg.Value)
vDat = Split(vDat, ",")
Dim i As Long
For i = LBound(vDat) To UBound(vDat)
If dict.Exists(vDat(i)) Then
dict(vDat(i)) = dict(vDat(i)) + 1
Else
dict.Add vDat(i), 1
End If
Next i
Dim vKey As Variant
ReDim vDat(1 To dict.Count)
i = 1
For Each vKey In dict.Keys
vDat(i) = vKey & "-" & dict(vKey)
i = i + 1
Next vKey
CountWordsA = Join(vDat, ",")
Exit Function
EH:
CountWordsA = ""
End Function
Sub TestIt()
Dim rg As Range
Set rg = Range("A2:A8")
Dim sngCell As Range
For Each sngCell In rg
sngCell.Offset(, 1) = CountWordsA(sngCell)
Next sngCell
End Sub
More about dictionaries and regular expressions
Alternative using Filter() function
This demonstrates the use of the Filter() function to count words via function UBound():
Function CountTerms() (usable also in formulae)
Function CountTerms(ByVal WordList As String, Optional TermList As String = "first,alt", Optional DELIM As String = ",") As String
'Purpose: count found terms in wordlist and return result as list
'[1] assign lists to arrays
Dim words, terms
words = Split(WordList, DELIM): terms = Split(TermList, DELIM)
'[2] count filtered search terms
Dim i As Long
For i = 0 To UBound(terms)
terms(i) = terms(i) & "-" & UBound(Filter(words, terms(i), True, vbTextCompare)) + 1
Next i
'[3] return terms as joined list, e.g. "first-5,alt-1"
CountTerms = Join(terms, ",")
End Function
Example call (due to comment) & help function getRange()
In order to loop over the entire range and replace the original data with the results list:
Sub ExampleCall()
'[1] get range data assigning them to variant temporary array
Dim rng As Range, tmp
Set rng = getRange(Sheet1, tmp) ' << change to sheet's Code(Name)
'[2] loop through array values and get counts
Dim i As Long
For i = 1 To UBound(tmp)
tmp(i, 1) = CountTerms(tmp(i, 1))
Next i
'[3] write to target (here: overwriting due to comment)
rng.Offset(ColumnOffset:=0) = tmp
End Sub
Function getRange(mySheet As Worksheet, tmp) As Range
'Purpose: assign current column A:A data to referenced tmp array
With mySheet
Set getRange = .Range("A2:A" & .Cells(.Rows.Count, 1).End(xlUp).Row)
tmp = getRange ' assign range data to referenced tmp array
End With
End Function

How to Loop Through Rows of Data Values, That Are Pipe Delimited, and Check If Any of the Values Match the Previous Row

I have a data set that is only 1 column and is pipe delimited
7033 | 6010 | 873
6040 | 888
6017 | 6040 | 567
I am looking for the most efficient way (preferably VBA) to loop through all the rows in the column, and determine if any of the data in row N is equal to row N-1. I would like to be able to insert a value in the adjacent column that flags when this does occur with a "Y" or a "N".
In the example above, Row 2 does not match Row 1 but Row 3 matches Row 2 because of the value "6040"
Any advise or suggestions would be greatly appreciated
Any recommendation
I assume that your data are in column A of Sheet1. This code can be optimized more, but right now it should be fast enough to handle hundreds of lines in seconds
Sub Main()
Dim i As Long
Dim j As Long
Dim str As String
Dim arr As Variant
Dim arr2 As Variant 'array of the result
Dim arrTemp As Variant
Dim rng As Range
'put everything into an array
Set rng = Worksheets("Sheet1").UsedRange.Columns(1)
arr = rng.Value
arr2 = arr
arr2(1, 1) = "" 'delete the first row since there won't be any value above it to compare
'loop thru the array rows and split the values of each element and compare it with the element above it
For i = 2 To UBound(arr, 1)
arrTemp = Split(arr(i, 1), " | ")
arr2(i, 1) = "No" 'assume there is no match at first
For j = 0 To UBound(arrTemp)
If InStr(arr(i - 1, 1), arrTemp(j)) > 0 Then
arr2(i, 1) = "Yes"
Exit For 'there was a match get out
End If
Next j
Next i
'paste the results
rng.Offset(0, 1).Value = arr2
End Sub
Output
Assuming that data is in A Column, Following code will give you the expected output.
Private Sub Test()
Dim i As Integer
For i = 4 To 2 Step -1
j = i - 1
PrevTempStr = Cells(j, "A")
CurTempStr = Cells(i, "A")
PreVal = Split(PrevTempStr, "|") 'Converts into array
CurVal = Split(CurTempStr, "|")
If MatchArray(CurVal, PreVal) Then 'Function to check if any of the element of current array matches with previous array
Cells(i, "B") = "Y" 'Update Value Y in B column if matches
Else
Cells(i, "B") = "N"
End If
Next
End Sub
Private Function MatchArray(Arg1 As Variant, Arg2 As Variant) As Boolean
For i = LBound(Arg1) To UBound(Arg1)
For j = LBound(Arg2) To UBound(Arg2)
If Trim(Arg1(i)) = Trim(Arg2(j)) Then
MatchArray= True
Exit Function
End If
Next j
Next i
MatchArray= False
End Function

The conditioning is not taken into account in a If statment with excel VBA

the 2 last conditions are not taken into account when the below code is exceuted. Can you help to understand why?
Also the "r" range retreive me the expected results + 1 row. I did not manage to change this. Any ideas?
Dim x As Range
Dim r As Range
Dim j As Range
Set r = Range("J2").End(xlDown)
Set j = Range("D2").End(xlDown)
For Each x In Range("J2", r)
If x.Value < 0 And j = ("long") Or j = ("") Then
x.Offset(1, 0).Select
ActiveCell.FormulaR1C1 = "=R[-1]C+RC[-1]"
End If
Next
End Sub
FYI: r range in your code will be the last used cell of column J starting from J2. There should be no empty cell in between.
try this:
Sub posted()
Dim x As Range
Dim r As Range
'Dim j As Range
Set r = Range("J2").End(xlDown)
'Set j = Range("D2").End(xlDown)
For Each x In Range("J2", r)
If (x.Value < 0) And ((x.Offset(, -6).value = "long") _
Or (x.Offset(, -6).value = "")) Then
'''''''''dont know if you want cell value by long or number format of the cell''''
'If (x.Value < 0) And ((x.Offset(, -6).NumberFormat = "Number") _
Or (x.Offset(, -6) = "")) Then
'''''''''
x.Offset(1, 0).Select
ActiveCell.FormulaR1C1 = "=R[-1]C+RC[-1]"
End If
Next
Set r = Nothing
End Sub

Resources