EDIT: I Have figured out what I needed and placed the new portions of code in asterisks
I am using VBA in Access 2003 and have the following question:
I have some code that does a mailmerge to create labels, and it works fine on its own. I have been asked to see if it is possible to have the labels run down the cells and then across. Right now they fill up across then run down to the next row. If it is possible, would it be best to do it before the mailmerge executes, or do I do it in the loop that I have following the mailmerge? It is very important that the "5660 EasyPeel Address Labels" formatting stays the same. I have placed my coding below.
With oDoc.MailMerge
With .Fields
.Add oApp.Selection.Range, "FullAddress"
oApp.Selection.TypeParagraph
End With
Set oAutoText = oApp.NormalTemplate.AutoTextEntries.Add("MyLabelLayout", oDoc.Content)
oDoc.Content.Delete
.MainDocumentType = wdMailingLabels
.OpenDataSource Name:=strTxtFile, ConfirmConversions:=False, AddToRecentFiles:=False
oApp.MailingLabel.CreateNewDocument Name:="5660 Easy Peel Address Labels", Address:="", Autotext:="MyLabelLayout"
.Destination = wdSendToNewDocument
.Execute
oAutoText.Delete
'Loop through and add cell padding to each table.
intTblCount = oApp.ActiveDocument.Tables.Count
Do Until intTblCount = 0
DoEvents
oApp.ActiveDocument.Tables(intTblCount).TopPadding = PixelsToPoints(5, True)
DoEvents
oApp.ActiveDocument.Tables(intTblCount).Range.Select
With Selection
.Font.Size = 9
.Font.Name = "Janson Text LT Std"
.ParagraphFormat.LineSpacing = LinesToPoints(0.8)
.ParagraphFormat.SpaceBefore = 5
.ParagraphFormat.SpaceAfter = 5
.MoveRight wdCharacter, 5
End With
***Call TransposeTable***
DoEvents
intTblCount = intTblCount - 1
Loop
End With
*** Sub TransposeTable() 'Edited Sub from the Sub Written By Helmut Weber, MVP WordVBA
Dim C As Long ' column
Dim R As Long ' row
Dim x As Long ' just a counter
Dim y As Long ' just a counter
Dim z As Long ' just a counter
'Dim d As Long ' just a counter
Dim STempo As String
Dim sArr() As String
Dim NoGoArray As Variant: NoGoArray = Array(2, 4, 7, 9, 12, 14, 17, 19, 22, 24, 27, 29, 32, 34, 37, 39, 42, 44, 47, 49)
With Selection
.Collapse
With .Tables(1)
C = .Columns.Count
R = .Rows.Count
ReDim sArr(1 To .Range.Cells.Count)
For x = 1 To UBound(sArr)
If IsInArray(x, NoGoArray) Then
GoTo LoopContinue
Else
STempo = .Range.Cells(x).Range.Text
STempo = Left(STempo, (Len(STempo) - 2))
sArr(x) = STempo
'GoTo LoopContinue
End If
LoopContinue:
Next
'--------------------------
' Transpose Table Content
'--------------------------
C = .Columns.Count
R = .Rows.Count
x = 0
For y = 1 To C
For z = 1 To R
If IsOdd(y) Then
x = x + 1
If IsInArray(x, NoGoArray) Then
x = x + 1
.Cell(z, y).Range.Text = sArr(x)
Else
.Cell(z, y).Range.Text = sArr(x)
End If
End If
Next
Next
End With
End With
Erase NoGoArray
End Sub
Public Function IsOdd(ByVal Number As Long) As Boolean
IsOdd = (Number Mod 2 = 1)
End Function
Private Function IsInArray(valToBeFound As Variant, arr As Variant) As Boolean
'DEVELOPER: Ryan Wells (wellsr.com)
'DESCRIPTION: Function to check if a value is in an array of values
'INPUT: Pass the function a value to search for and an array of values of any data type.
'OUTPUT: True if is in array, false otherwise
Dim element As Variant
On Error GoTo IsInArrayError: 'array is empty
For Each element In arr
If element = valToBeFound Then
IsInArray = True
Exit Function
End If
Next element
Exit Function
IsInArrayError:
On Error GoTo 0
IsInArray = False
End Function ***
Related
I am given N lists of M items that will be physically realized (someone actually has to put items (names abbreviated here,) in physical bins.) Then the bins are emptied, if necessary, and re-used, working left-to-right. There is a real cost to putting a different item in a bin than what was in it before. I rearrange the lists to minimize changes, manually. Software can do it faster, and more reliably in an optimum way. The whole thing happens in Excel (then paper, then in a factory.) I wrote some VBA, a brute-force affair, that did really well with some examples. But not all. If I knew the family of optimization that this is, I could code it, even if I just pass something to a DLL. But multiple searches online have not succeeded. I tried several phrasings. It's not a traveling S.., knapsack, etc. It seems similar to the Sequence Alignment problem from Bioinformatics. Someone recognize it? Let's hear it, Operations Research people.
As it turns out, the naive solution just needed tweaking. Look at a cell. Try to find the same letter in the column to it's right. If you find one, swap it with whatever it to the right of that cell now. Work your way down. The ColumnsPer parameter accounts for the real-world use, where each column has an associated list of numbers and the grid columns alternate labels, numbers, labels, ...
Option Explicit
Public Const Row1 As Long = 4
Public Const ColumnsPer As Long = 1 '2, when RM, %
Public Const BinCount As Long = 6
Public Const ColCount As Long = 6
Private Sub reorder_items_max_left_to_right_repeats(wksht As Worksheet, _
col1 As Long, maxBins As Long, maxRecipes As Long, ByVal direction As Integer)
Dim here As Range
Set here = wksht.Cells(Row1, col1)
here.Activate
Dim cond
For cond = 1 To maxRecipes - 1
Do While WithinTheBox(here, col1, direction)
If Not Adjacent(here, ColumnsPer).Value = here.Value Then
Dim there As Range
Set there = Matching_R_ange(here, direction)
If Not there Is Nothing Then swapThem Adjacent(here, ColumnsPer), there
End If
NextItemDown:
Set here = here.Offset(direction, 0)
here.Activate
'Debug.Assert here.Address <> "$AZ$6"
DoEvents
Loop
NextCond:
Select Case direction
Case 1
Set here = Cells(Row1, here.Column + ColumnsPer)
Case -1
Set here = Cells(Row1 + maxBins - 1, here.Column + ColumnsPer)
End Select
here.Activate
Next cond
End Sub
Function Adjacent(fromHereOnLeft As Range, colsRight As Long) As Range
Set Adjacent = fromHereOnLeft.Offset(0, colsRight)
End Function
Function Matching_R_ange(fromHereOnLeft As Range, _
ByVal direction As Integer) As Range
Dim rowStart As Long
rowStart = Row1
Dim colLook As Long
colLook = fromHereOnLeft.Offset(0, ColumnsPer).Column
Dim c As Range
Set c = Cells(rowStart, colLook)
Dim col1 As Long
col1 = c.Column
Do While WithinTheBox(c, col1, direction)
Debug.Print "C " & c.Address
If c.Value = fromHereOnLeft.Value _
And c.Row <> fromHereOnLeft.Row Then
Set Matching_R_ange = c
Exit Function
Else
Set c = c.Offset(1 * direction, 0)
End If
DoEvents
Loop
'returning NOTHING is expected, often
End Function
Function WithinTheBox(ByVal c As Range, ByVal col1 As Long, ByVal direction As Integer)
Select Case direction
Case 1
WithinTheBox = c.Row <= Row1 + BinCount - 1 And c.Row >= Row1
Case -1
WithinTheBox = c.Row <= Row1 + BinCount - 1 And c.Row > Row1
End Select
WithinTheBox = WithinTheBox And _
c.Column >= col1 And c.Column < col1 + ColCount - 1
End Function
Private Sub swapThem(range10 As Range, range20 As Range)
'Unlike with SUB 'Matching_R_ange', we have to swap the %s as well as the items
'So set temporary range vars to hold %s, to avoid confusion due to referencing items/r_anges
If ColumnsPer = 2 Then
Dim range11 As Range
Set range11 = range10.Offset(0, 1)
Dim range21 As Range
Set range21 = range20.Offset(0, 1)
'sit on them for now
End If
Dim Stak As Object
Set Stak = CreateObject("System.Collections.Stack")
Stak.push (range10.Value) 'A
Stak.push (range20.Value) 'BA
range10.Value = Stak.pop 'A
range20.Value = Stak.pop '_ Stak is empty now, can re-use
If ColumnsPer = 2 Then
Stak.push (range11.Value)
Stak.push (range21.Value)
range11.Value = Stak.pop
range21.Value = Stak.pop
End If
End Sub
I have a Database with many columns, one of them containing Names.
My vb.net software acts as telegram server and waits for the user to send its full name.
The database could have its name spelled differently, for example "Marco Dell'Orso" could be spelled "Marco Dellorso" or "Marco Dell Orso" od "Dell Orso Marco" or whatever. The user could also misspell his name and invert two letters.. for esample "MaCRo Dell'Orso"
I would need a way to return the 5 rows that are the most similar to the words used in the query. What would be the best way? I was thinking of splitting the name on whitechars and then use LIKE in the query with the single words, but that does not work with mistyped words.
EDIT:
My current plan is to that if the database contains more than one or less then one rows with the exact name, then split the input into the single words and return all strings that contain ANY of the input words. this should reduce the rows to analyze from 42000 to a few hundred. Once I have these few hundred lines, i could run a Levenshtein function on the rows and return the 5 most matching..
Is this a good idea?
Solved it this way by combining my custom function with a premade Levenshtein function from this link: How to calculate distance similarity measure of given 2 strings? . I assign a score for each single word that appears in the other wordcomplex. then I add a score based on the Levenshtein comparison of each word to another. works great:
Public Class Form1
Private Sub TextBox1_KeyUp(sender As Object, e As KeyEventArgs) Handles TextBox1.KeyUp
calc()
End Sub
Private Sub TextBox2_KeyUp(sender As Object, e As KeyEventArgs) Handles TextBox2.KeyUp
calc()
End Sub
Sub calc()
Label1.Text = compare(TextBox1.Text, TextBox2.Text)
End Sub
Public Function compare(source As String, target As String) As Integer
Dim score As Double
Dim sourcewords As String() = source.Split(New Char() {" "c, "'"c, "`"c, "´"c})
Dim targetwords As String() = target.Split(New Char() {" "c, "'"c, "`"c, "´"c})
For Each s In sourcewords
If target.Contains(s) Then score = score + 1
For Each t In targetwords
score = score + 1 / (DamerauLevenshteinDistance(s, t, 100) + 1)
Next
Next
For Each s In targetwords
If source.Contains(s) Then score = score + 1
For Each t In sourcewords
score = score + 1 / (DamerauLevenshteinDistance(s, t, 100) + 1)
Next
Next
Return score
End Function
''' <summary>
''' Computes the Damerau-Levenshtein Distance between two strings, represented as arrays of
''' integers, where each integer represents the code point of a character in the source string.
''' Includes an optional threshhold which can be used to indicate the maximum allowable distance.
''' </summary>
''' <param name="source">An array of the code points of the first string</param>
''' <param name="target">An array of the code points of the second string</param>
''' <param name="threshold">Maximum allowable distance</param>
''' <returns>Int.MaxValue if threshhold exceeded; otherwise the Damerau-Leveshteim distance between the strings</returns>
Public Shared Function DamerauLevenshteinDistance(source As String, target As String, threshold As Integer) As Integer
Dim length1 As Integer = source.Length
Dim length2 As Integer = target.Length
' Return trivial case - difference in string lengths exceeds threshhold
If Math.Abs(length1 - length2) > threshold Then
Return Integer.MaxValue
End If
' Ensure arrays [i] / length1 use shorter length
If length1 > length2 Then
Swap(target, source)
Swap(length1, length2)
End If
Dim maxi As Integer = length1
Dim maxj As Integer = length2
Dim dCurrent As Integer() = New Integer(maxi) {}
Dim dMinus1 As Integer() = New Integer(maxi) {}
Dim dMinus2 As Integer() = New Integer(maxi) {}
Dim dSwap As Integer()
For i As Integer = 0 To maxi
dCurrent(i) = i
Next
Dim jm1 As Integer = 0, im1 As Integer = 0, im2 As Integer = -1
For j As Integer = 1 To maxj
' Rotate
dSwap = dMinus2
dMinus2 = dMinus1
dMinus1 = dCurrent
dCurrent = dSwap
' Initialize
Dim minDistance As Integer = Integer.MaxValue
dCurrent(0) = j
im1 = 0
im2 = -1
For i As Integer = 1 To maxi
Dim cost As Integer = If(source(im1) = target(jm1), 0, 1)
Dim del As Integer = dCurrent(im1) + 1
Dim ins As Integer = dMinus1(i) + 1
Dim [sub] As Integer = dMinus1(im1) + cost
'Fastest execution for min value of 3 integers
Dim min As Integer = If((del > ins), (If(ins > [sub], [sub], ins)), (If(del > [sub], [sub], del)))
If i > 1 AndAlso j > 1 AndAlso source(im2) = target(jm1) AndAlso source(im1) = target(j - 2) Then
min = Math.Min(min, dMinus2(im2) + cost)
End If
dCurrent(i) = min
If min < minDistance Then
minDistance = min
End If
im1 += 1
im2 += 1
Next
jm1 += 1
If minDistance > threshold Then
Return Integer.MaxValue - 1
End If
Next
Dim result As Integer = dCurrent(maxi)
Return If((result > threshold), Integer.MaxValue, result)
End Function
Private Shared Sub Swap(Of T)(ByRef arg1 As T, ByRef arg2 As T)
Dim temp As T = arg1
arg1 = arg2
arg2 = temp
End Sub
End Class
One way is to use the build-in soundex function of MySQL.
SELECT SOUNDEX(name) FROM table;
Or, the better way, there are a few MySQL-functions on the web implementing DoubleMetaphone. I think this is what you are searching:
GitHub
I'm using Join(Lookupset) to find unique group values which returns a sequence number. This is my function:
Join(LookupSet(Fields!itemId.Value & Fields!UseByDate.Value & Fields!rackId.Value
, Fields!itemId.Value & Fields!UseByDate.Value & Fields!rackId.Value
, Fields!CustomerSeqNo.Value
, "PickingList"), ",")
The problem is on some items there are multiple transactions. I want to remove the duplicates.
I found a blog http://blogs.msdn.com/b/bobmeyers/archive/2012/06/18/creating-short-lists-using-the-lookupset-function.aspx but could not get SSRS Report Builder to reference Linq assembly. My issue is
How can I just show the unique values?
You don't need Linq, but you do still need custom code (in BIDS go to Report -> Report Properties -> Code)
You can put a RemoveDuplicates function in here, something like this:
Public Shared Function RemoveDuplicates(m_Array As Object()) As String()
System.Array.Sort(m_Array)
Dim k As Integer = 0
For i As Integer = 0 To m_Array.Length - 1
If i > 0 AndAlso m_Array(i).Equals(m_Array(i - 1)) Then
Continue For
End If
m_Array(k) = m_Array(i)
k += 1
Next
Dim unique As [String]() = New [String](k - 1) {}
System.Array.Copy(m_Array, 0, unique, 0, k)
Return unique
End Function
To use it in your Join:
Join(Code.RemoveDuplicates(LookupSet(...)),",")
I agree with #user3697615 that Report Code is best. However, I prefer to build it straight into a string:
public shared function JoinDistinct(
dups as object(),
delimiter as string
) as string
dim result as string = ""
system.array.sort(dups)
for i as integer = 0 to dups.length - 1
if i <> 0 then result += delimiter
if i = 0 orElse dups(i) <> dups(i-1) then result += dups(i)
next i
return result
end function
This way, we eliminate one nested function on the call:
=Code.JoinDistinct(LookupSet(...), ",")
If you're like me, you also want the elements in order based on frequency (descending order).
I created the following VisualBasic code to do so
Public Shared Function RemoveDuplicates(dataset As Object()) As String()
Dim unique As New System.Collections.Generic.List(Of String)
Dim frequency As New System.Collections.Generic.List(Of Integer)
For i As Integer = 0 To dataset.Length - 1
Dim index As Integer = -1
For j As Integer = 0 To unique.Count - 1
If dataset(i).Equals(unique(j)) Then
index = j
Exit For
End If
Next
If index < 0 Then
unique.Add(dataset(i))
frequency.Add(1)
Else
frequency(index) += 1
End If
Next
Dim uniqueArray As [String]() = unique.ToArray()
Array.Sort(frequency.ToArray(), uniqueArray)
Array.Reverse(uniqueArray)
return uniqueArray
End Function
This is based off others' answers where the SSRS expression is the following
Join(Code.RemoveDuplicates(LookupSet(...)),",")
Note: I learned VisualBasic in about an hour to solve this problem, so my algorithm probably isn't the most efficient.
I liked pwilcox's idea, so I wrote this one which filters out null and blank values.
Public Function JoinDistinct(arr As Object(), delimiter As String) As String
System.Array.Sort(arr)
Dim result As String = String.Empty
Dim lastvalue As String = String.Empty
For i As Integer = 0 To arr.Length - 1
If Not arr(i) Is Nothing And arr(i) <> lastvalue And arr(i) <> String.Empty Then
If result = String.Empty Then
result = arr(i)
Else
result = result + delimiter + arr(i)
End If
End If
lastvalue = arr(i)
Next
Return result
End Function
Usage:
=Code.JoinDistinct(LookupSet(...), ",")
I have a loop I created to check if the values entered match an ordering, depending on the augment passed. So for example the ordering constraint must be
"SU", "M", "TU", "W", "TH", "F", "SA"
therefore if the user enters the following inputs
"SU,M,TU,SA" this is correct
however if the user enters
"SU,TH,M" this is incorrect since M should come before TH
The coding has been implemented and works fine however i don't find this was the best way of coding it, can anyone help me code it more efficiently?
Function validExDays(exDays As String)
Dim found As Boolean
found = False
If Len(exDays) >= 1 And Not IsNull(exDays) Then
Dim NumOfCommas As Integer
NumOfCommas = InstrCount(exDays, ",")
Dim days(0 To 7) As String
days(0) = ","
days(1) = "SU"
days(2) = "M"
days(3) = "TU"
days(4) = "W"
days(5) = "TH"
days(6) = "F"
days(7) = "SA"
Dim i, j, k, l, m, o, p, q As Integer
i = 1
j = 1
k = 1
l = 1
m = 1
o = 1
p = 1
q = 1
Do While i <= 7
If NumOfCommas = 0 Then
'One day input check
If i = 1 Then
Do While j <= 7
If UCase(exDays) = days(j) Then
found = True
Exit Do
End If
j = j + 1
Loop
End If
End If
'Two day input check
j = 1
If NumOfCommas = 1 Then
If found = False And i = 2 Then
Do While j <= 7
Do While k <= 7
If UCase(exDays) = days(j) + days(0) + days(k) Then
found = True
Exit Do
End If
k = k + 1
Loop
If found = False Then
j = j + 1
k = j
Else
Exit Do
End If
Loop
End If
End If
'Three day input check
So the string value entered can be "SU,M,F" or "SU,F" or any other combination but whatever items are included must be in the correct order.
The following code is a bit more compact. It uses the Split() function to break out the components, and uses a Dictionary object to hold the index values of each valid component
Option Compare Database
Option Explicit
Public Function IsValidExDays(exDays As String) As Boolean
Dim rtn As Boolean
Dim valueArray() As String, valueItem As Variant
Dim maxValue As Integer
Dim dict As Object ' Scripting.Dictionary
rtn = True
Set dict = CreateObject("Scripting.Dictionary")
dict.Add "SU", 1
dict.Add "M", 2
dict.Add "TU", 3
dict.Add "W", 4
dict.Add "TH", 5
dict.Add "F", 6
dict.Add "SA", 7
maxValue = 0
valueArray = Split(exDays, ",")
For Each valueItem In valueArray
If dict.Exists(valueItem) Then
If dict(valueItem) > maxValue Then
maxValue = dict(valueItem)
Else
rtn = False
Exit For
End If
Else
rtn = False
Exit For
End If
Next
Set dict = Nothing
IsValidExDays = rtn
End Function
Since you are in Access, why don't you make an entry form with checkboxes that will always return the parameters in the order you expect?
I have written a function interpolate_part_size that will not recalculate unless I click in the Excel cell and press Enter - which then gives the correct result. The purpose of the function is to interpolate a value comparing the current row of the active cell, with a reference row.
I have tried Application.Volatile but then it returns the same results on each and every row for the same argument, which is not correct. Using F9 to recalculate doesn't work either.
Any suggestions?
Function interpolate_part_size(pp) As Double
'--- Subroutine to interpolate size
Dim part_size_high As Double
Dim part_size_low As Double
Dim pp_high As Double
Dim pp_low As Double
Dim low_index As Integer
Dim high_index As Integer
Dim current_row As Integer
With Application.WorksheetFunction
current_row = ActiveCell.Row
' match on the range of cells in the current row
high_index = .Match(pp, Worksheets("DATA").Range(Cells(current_row, 4), Cells(current_row, 29)), -1)
low_index = high_index + 1
pp_high = .Index(Worksheets("DATA").Range(Cells(current_row, 4), Cells(current_row, 29)), 1, high_index)
pp_low = .Index(Worksheets("DATA").Range(Cells(current_row, 4), Cells(current_row, 29)), 1, low_index)
part_size_high = .Index(Worksheets("DATA").Range("PSD"), 1, high_index)
part_size_low = .Index(Worksheets("DATA").Range("PSD"), 1, low_index)
End With
If pp_high = pp_low Then
interpolate_part_size = part_size_low
Else:
interpolate_part_size = (pp - pp_low) / (pp_high - pp_low) * (part_size_high - part_size_low) + part_size_low
End If
End Function