Generalizing increasing number of nested loop algorithm - vb.net

Sorry for the terrible title, but I have no clue on how to generalize (or simplify) my loop case here.
I have a program that iterates to a sequence of integer, for example dimension=1 to 5.
In each iteration, there will be a main loop, and inside the main loop, there will be a nested loop. The number of the nested loop will be [dimension].
For example, in dimension=1, there is a For loop. In dimension=2, there is a For loop inside a For loop. And so on.
Is there any possible way to simplify the algorithm? currently I'm manually write totally different code for each value of [dimension]. Imagine if dimension=1 to 100? I'll be dead.
Here's my piece of program (written in VB.NET)
for dimension=2
Dim result(2) As Integer
For i = 0 To 1
For j = 0 To 1
result(0)=i
result(1)=j
Next
Next
For dimension=3
Dim result(3) As Integer
For i = 0 To 1
For j = 0 To 1
For k = 0 To 1
result(0)=i
result(1)=j
result(2)=k
Next
Next
Next
For dimension=4
Dim result(4) As Integer
For i = 0 To 1
For j = 0 To 1
For k = 0 To 1
For l = 0 To 1
result(0)=i
result(1)=j
result(2)=k
result(3)=l
Next
Next
Next
Next
And so on..
Any suggestion?
Thanks!

There are plenty of solutions:
Recursion
Idk, if vb.net supports methods, but if it does, this would probably be the simplest:
void nestedLoop(int lower , int upper , int remaining_loops , int[] values)
if(remaining_loops == 0)
//process values list
else
for int i in [lower , upper)
values[remaining_loops] = i
nestedLoop(lower , upper , remaining_loops - 1)
Integer Transformation
In theory, a number can be represented by any radix:
d_i * radix ^ i + d_i-1 * radix ^ (i - 1) ... + d_0 * radix ^ 0
Consider each digit the value of one of the nested loops:
for int i in [0 , max)
for int j in [0 , max)
for int k in [0 , max)
...
Could be represented by a 3-digit number with radix max, where d_0 = i, d_1 = j, etc.. Basically how each digit is mapped to one of the values can be arbitrary and will only affect the order of the output.
void nestedLoops(int upper , int dimension)
for int i in [0 , pow(upper , dimension))
int[] values
int digit_sub = 1
int tmp = i
for int j in [0 , dimension)
values[j] = tmp % dimension
tmp /= dimension
//all values of the loops are now in values
//process them here
There would be a few other options aswell, but these are the most common.

Please do note that when you do
Dim result(2) As Integer
You are actually declaring an array of 3 elements see this question for why. It's a subtle difference in VB.NET
That being said, I'll assume that you meant to declare an array of only 2 elements. If this is the case then you could build and call a recursive function like this
LoopOver(result)
Sub LoopOver(ByRef array() As Integer, ByVal Optional level As Integer = 0)
If array.Length = level Then
Return
Else
array(level) = 1
LoopOver(array, level + 1)
End If
End Sub
This recursive function will call itself (i.e., it will loop) for as many times as the array's size.

Related

Looking for a fast way, to represent the value of an integer, by a number in the range 0 to 3 (without branching?)

Given that val is some random integer,
and number the possible outcome:
if the value is less then &H100 ; the number is 0
if the value is less then &H10000 ; the number is 1
if the value is less then &H1000000 ; the number is 2
else ; the number is 3
I've got this:
If (val And &HFFFF0000) = 0 Then
If (val And &HFF00) = 0 Then
num = 0
Else
numb = 1
End If
ElseIf (**val** And &HFF000000) = 0 Then
numb = 2
Else
numb = 3
End If
I believe to remember that I could achieve this with a simple calculation, but I can not
wrap my head around it...
cheers..
Jhonny
edit:--- after reaction of video.baba ---
Here is half a solution:
The result is a number from 0 to 7, of witch the bits represent a non-zero byte.
A lookuptable could be used to translate it to a 2-bit value.
val >>= 8 ' move to the right, so the first byte can hold identification-bits
val += &H3FF0000 'set a bit in the first byte, if the second one is not zero
val = val And &H400FFFF
val += &H1FFFF00 'set a bit in the first byte, if the third one is not zero
val = val And &H60000FF
val += &HFFFFFF'set a bit in the first byte, if the fourth one is not zero
val >>= 24 'put the result in the last byte
number=lookuptable(val)
have not tested it for speed yet, but it feels over-complicated?
Do you mean something like:
Select Case Value
Case < &H100
Number = 0
Case < &H10000
Number = 1
Case < &H1000000
Number = 2
Case Else
Number = 3
End Select

Label a set of objects with (A->Z,AA->ZZ, AAA->ZZZ) in VBA

I have a set which has an unknown number of objects. I want to associate a label to each one of these objects. Instead of labeling each object with a number I want to label them with letters.
For example the first object would be labeled A the second B and so on.
When I get to Z, the next object would be labeled AA
AZ? then BA, BB, BC.
ZZ? then AAA, AAB, AAC and so on.
I'm working using Mapbasic (similar to VBA), but I can't seem to wrap my head around a dynamic solution. My solution assumes that there will be a max number of objects that the set may or may not exceed.
label = pos1 & pos2
Once pos2 reaches ASCII "Z" then pos1 will be "A" and pos2 will be "A". However, if there is another object after "ZZ" this will fail.
How do I overcome this static solution?
Basically what I needed was a Base 26 Counter. The function takes a parameter like "A" or "AAA" and determines the next letter in the sequence.
Function IncrementAlpha(ByVal alpha As String) As String
Dim N As Integer
Dim num As Integer
Dim str As String
Do While Len(alpha)
num = num * 26 + (Asc(alpha) - Asc("A") + 1)
alpha = Mid$(alpha, 2,1)
Loop
N = num + 1
Do While N > 0
str = Chr$(Asc("A") + (N - 1) Mod 26) & str
N = (N - 1) \ 26
Loop
IncrementAlpha = str
End Function
If we need to convert numbers to a "letter format" where:
1 = A
26 = Z
27 = AA
702 = ZZ
703 = AAA etc
...and it needs to be in Excel VBA, then we're in luck. Excel's columns are "numbered" the same way!
Function numToLetters(num As Integer) As String
numToLetters = Split(Cells(1, num).Address(, 0), "$")(0)
End Function
Pass this function a number between 1 and 16384 and it will return a string between A and XFD.
Edit:
I guess I misread; you're not using Excel. If you're using VBA you should still be able to do this will the help of an reference to an Excel Object Library.
This should get you going in terms of the logic. Haven't tested it completely, but you should be able to work from here.
Public Function GenerateLabel(ByVal Number As Long) As String
Const TOKENS As String = "ZABCDEFGHIJKLMNOPQRSTUVWXY"
Dim i As Long
Dim j As Long
Dim Prev As String
j = 1
Prev = ""
Do While Number > 0
i = (Number Mod 26) + 1
GenerateLabel = Prev & Mid(TOKENS, i, 1)
Number = Number - 26
If j > 0 Then Prev = Mid(TOKENS, j + 1, 1)
j = j + Abs(Number Mod 26 = 0)
Loop
End Function

vb.net array.sort() parameters why 1 short?

I'm sorting an array using code like this:
Array.Sort(arr, 0, intEndingPosition, New myIComparer)
I want the sorting to start with index 0 and end with index intEndingPosition. However, the last element arr(intEndingPosition) was left out and did not get sorted. Why?
intEndingPosition is calculated beforehand like this:
Dim StringOfConcern As String
Dim OneChar(65534), FrqOne(65534) As String
Dim CntNewOnes, CntRptOnes As Integer
Dim c As Char
Dim i, j As Integer
Dim isNew As Boolean
StringOfConcern = TextBox1.Text
OneChar(0) = CStr(StringOfConcern(0))
FrqOne(0) = 1
i = 0
j = 0
For Each c In StringOfConcern.Substring(1)
isNew = True
For j = 0 To i Step 1
If CStr(c) = OneChar(j) Then
isNew = False
FrqOne(j) += 1
Exit For
End If
Next j
If isNew = True Then
i += 1
OneChar(i) = CStr(c)
FrqOne(i) = 1
End If
Next c
CntNewOnes = i + 1
CntRptOnes = 0
For i = 0 To CntNewOnes - 1 Step 1
If FrqOne(i) > 1 Then CntRptOnes += 1
Next i
The sorting follows here. The code in my original question is only illustrative. The actual sorting is:
Array.Sort(FrqOne, OneChar, 0, CntNewOnes - 1)
Array.Reverse(FrqOne, 0, CntNewOnes - 1)
Array.Reverse(OneChar, 0, CntNewOnes - 1)
Note the method declaration for Array.Sort
Public Shared Sub Sort (
array As Array,
index As Integer,
length As Integer,
comparer As IComparer
)
The third parameter is the number of elements in the range to sort (length) not the end index as you suggest.
So let's assume for a minute that your intEndingPosition is 4. This means you're expecting to sort 5 elements i.e. elements at indices 0, 1, 2, 3, 4. However, the number 4 is the length and not the end index thus you're only sorting elements at indices 0, 1, 2, 3.
This explains why you're observing that the elements being sorted is one shorter than you expected.
Put it simply the third parameter should specify the length of elements to sort and not the end index.
Another Example:
Consider the Substring method of the String class:
Public Function Substring (
startIndex As Integer,
length As Integer
) As String
Then assume we have this piece of code:
Dim temp As String = "testing"
Dim result As String = temp.Substring(0, 4)
result is now a string containing 4 characters as 4 in the Substring call indicates the length that should be retrieved as opposed to the end index.
Had 4 been the end index then you'd expect result to contain 5 characters.

VBA: Testing for perfect cubes

I'm trying to write a simple function in VBA that will test a real value and output a string result if it's a perfect cube. Here's my code:
Function PerfectCubeTest(x as Double)
If (x) ^ (1 / 3) = Int(x) Then
PerfectCubeTest = "Perfect"
Else
PerfectCubeTest = "Flawed"
End If
End Function
As you can see, I'm using a simple if statement to test if the cube root of a value is equal to its integer portion (i.e. no remainder). I tried testing the function with some perfect cubes (1, 8, 27, 64, 125), but it only works for the number 1. Any other value spits out the "Flawed" case. Any idea what's wrong here?
You are testing whether the cube is equal to the double supplied.
So for 8 you would be testing whether 2 = 8.
EDIT: Also found a floating point issue. To resolve we will round the decimals a little to try and overcome the issue.
Change to the following:
Function PerfectCubeTest(x As Double)
If Round((x) ^ (1 / 3), 10) = Round((x) ^ (1 / 3), 0) Then
PerfectCubeTest = "Perfect"
Else
PerfectCubeTest = "Flawed"
End If
End Function
Or (Thanks to Ron)
Function PerfectCubeTest(x As Double)
If CDec(x ^ (1 / 3)) = Int(CDec(x ^ (1 / 3))) Then
PerfectCubeTest = "Perfect"
Else
PerfectCubeTest = "Flawed"
End If
End Function
#ScottCraner correctly explains why you were getting incorrect results, but there are a couple other things to point out here. First, I'm assuming that you are taking a Double as input because the range of acceptable numbers is higher. However, by your implied definition of a perfect cube only numbers with an integer cube root (i.e. it would exclude 3.375) need to be evaluated. I'd just test for this up front to allow an early exit.
The next issue you run into is that 1 / 3 can't be represented exactly by a Double. Since you're raising to the inverse power to get your cube root you're also compounding the floating point error. There's a really easy way to avoid this - take the cube root, cube it, and see if it matches the input. You get around the rest of the floating point errors by going back to your definition of a perfect cube as an integer value - just round the cube root to both the next higher and next lower integer before you re-cube it:
Public Function IsPerfectCube(test As Double) As Boolean
'By your definition, no non-integer can be a perfect cube.
Dim rounded As Double
rounded = Fix(test)
If rounded <> test Then Exit Function
Dim cubeRoot As Double
cubeRoot = rounded ^ (1 / 3)
'Round both ways, then test the cube for equity.
If Fix(cubeRoot) ^ 3 = rounded Then
IsPerfectCube = True
ElseIf (Fix(cubeRoot) + 1) ^ 3 = rounded Then
IsPerfectCube = True
End If
End Function
This returned the correct result up to 1E+27 (1 billion cubed) when I tested it. I stopped going higher at that point because the test was taking so long to run and by that point you're probably outside of the range that you would reasonably need it to be accurate.
For fun, here is an implementation of a number-theory based method described here . It defines a Boolean-valued (rather than string-valued) function called PerfectCube() that tests if an integer input (represented as a Long) is a perfect cube. It first runs a quick test which throws away many numbers. If the quick test fails to classify it, it invokes a factoring-based method. Factor the number and check if the multiplicity of each prime factor is a multiple of 3. I could probably optimize this stage by not bothering to find the complete factorization when a bad factor is found, but I had a VBA factoring algorithm already lying around:
Function DigitalRoot(n As Long) As Long
'assumes that n >= 0
Dim sum As Long, digits As String, i As Long
If n < 10 Then
DigitalRoot = n
Exit Function
Else
digits = Trim(Str(n))
For i = 1 To Len(digits)
sum = sum + Mid(digits, i, 1)
Next i
DigitalRoot = DigitalRoot(sum)
End If
End Function
Sub HelperFactor(ByVal n As Long, ByVal p As Long, factors As Collection)
'Takes a passed collection and adds to it an array of the form
'(q,k) where q >= p is the smallest prime divisor of n
'p is assumed to be odd
'The function is called in such a way that
'the first divisor found is automatically prime
Dim q As Long, k As Long
q = p
Do While q <= Sqr(n)
If n Mod q = 0 Then
k = 1
Do While n Mod q ^ k = 0
k = k + 1
Loop
k = k - 1 'went 1 step too far
factors.Add Array(q, k)
n = n / q ^ k
If n > 1 Then HelperFactor n, q + 2, factors
Exit Sub
End If
q = q + 2
Loop
'if we get here then n is prime - add it as a factor
factors.Add Array(n, 1)
End Sub
Function factor(ByVal n As Long) As Collection
Dim factors As New Collection
Dim k As Long
Do While n Mod 2 ^ k = 0
k = k + 1
Loop
k = k - 1
If k > 0 Then
n = n / 2 ^ k
factors.Add Array(2, k)
End If
If n > 1 Then HelperFactor n, 3, factors
Set factor = factors
End Function
Function PerfectCubeByFactors(n As Long) As Boolean
Dim factors As Collection
Dim f As Variant
Set factors = factor(n)
For Each f In factors
If f(1) Mod 3 > 0 Then
PerfectCubeByFactors = False
Exit Function
End If
Next f
'if we get here:
PerfectCubeByFactors = True
End Function
Function PerfectCube(n As Long) As Boolean
Dim d As Long
d = DigitalRoot(n)
If d = 0 Or d = 1 Or d = 8 Or d = 9 Then
PerfectCube = PerfectCubeByFactors(n)
Else
PerfectCube = False
End If
End Function
Fixed the integer division error thanks to #Comintern. Seems to be correct up to 208064 ^ 3 - 2
Function isPerfectCube(n As Double) As Boolean
n = Abs(n)
isPerfectCube = n = Int(n ^ (1 / 3) - (n > 27)) ^ 3
End Function

How to compare Strings for Percentage Match using vb.net?

I am banging my head against the wall for a while now trying different techniques.
None of them are working well.
I have two strings.
I need to compare them and get an exact percentage of match,
ie. "four score and seven years ago" TO "for scor and sevn yeres ago"
Well, I first started by comparing every word to every word, tracking every hit, and percentage = count \ numOfWords. Nope, didn't take into account misspelled words.
("four" <> "for" even though it is close)
Then I started by trying to compare every char in each char, incrementing the string char if not a match (to count for misspellings). But, I would get false hits because the first string could have every char in the second but not in the exact order of the second. ("stuff avail" <> "stu vail" (but it would come back as such, low percentage, but a hit. 9 \ 11 = 81%))
SO, I then tried comparing PAIRS of chars in each string. If string1[i] = string2[k] AND string1[i+1] = string2[k+1], increment the count, and increment the "k" when it doesn't match (to track mispellings. "for" and "four" should come back with a 75% hit.) That doesn't seem to work either. It is getting closer, but even with an exact match it is only returns 94%. And then it really gets screwed up when something is really misspelled. (Code at the bottom)
Any ideas or directions to go?
Code
count = 0
j = 0
k = 0
While j < strTempName.Length - 2 And k < strTempFile.Length - 2
' To ignore non letters or digits '
If Not strTempName(j).IsLetter(strTempName(j)) Then
j += 1
End If
' To ignore non letters or digits '
If Not strTempFile(k).IsLetter(strTempFile(k)) Then
k += 1
End If
' compare pair of chars '
While (strTempName(j) <> strTempFile(k) And _
strTempName(j + 1) <> strTempFile(k + 1) And _
k < strTempFile.Length - 2)
k += 1
End While
count += 1
j += 1
k += 1
End While
perc = count / (strTempName.Length - 1)
Edit: I have been doing some research and I think I initially found the code from here and translated it to vbnet years ago. It uses the Levenshtein string matching algorithm.
Here is the code I use for that, hope it helps:
Sub Main()
Dim string1 As String = "four score and seven years ago"
Dim string2 As String = "for scor and sevn yeres ago"
Dim similarity As Single =
GetSimilarity(string1, string2)
' RESULT : 0.8
End Sub
Public Function GetSimilarity(string1 As String, string2 As String) As Single
Dim dis As Single = ComputeDistance(string1, string2)
Dim maxLen As Single = string1.Length
If maxLen < string2.Length Then
maxLen = string2.Length
End If
If maxLen = 0.0F Then
Return 1.0F
Else
Return 1.0F - dis / maxLen
End If
End Function
Private Function ComputeDistance(s As String, t As String) As Integer
Dim n As Integer = s.Length
Dim m As Integer = t.Length
Dim distance As Integer(,) = New Integer(n, m) {}
' matrix
Dim cost As Integer = 0
If n = 0 Then
Return m
End If
If m = 0 Then
Return n
End If
'init1
Dim i As Integer = 0
While i <= n
distance(i, 0) = System.Math.Max(System.Threading.Interlocked.Increment(i), i - 1)
End While
Dim j As Integer = 0
While j <= m
distance(0, j) = System.Math.Max(System.Threading.Interlocked.Increment(j), j - 1)
End While
'find min distance
For i = 1 To n
For j = 1 To m
cost = (If(t.Substring(j - 1, 1) = s.Substring(i - 1, 1), 0, 1))
distance(i, j) = Math.Min(distance(i - 1, j) + 1, Math.Min(distance(i, j - 1) + 1, distance(i - 1, j - 1) + cost))
Next
Next
Return distance(n, m)
End Function
Did not work for me unless one (or both) of following are done:
1) use option compare statement "Option Compare Text" before any Import declarations and before Class definition (i.e. the very, very first line)
2) convert both strings to lowercase using .tolower
Xavier's code must be correct to:
While i <= n
distance(i, 0) = System.Math.Min(System.Threading.Interlocked.Increment(i), i - 1)
End While
Dim j As Integer = 0
While j <= m
distance(0, j) = System.Math.Min(System.Threading.Interlocked.Increment(j), j - 1)
End While