I'm writing a code that checks if a word has multiple of the same letters in it, so I split each letter into an array and wrote this code. the "correctGuesses" variable is supposed to be the number of duplicate letters. The Array contains the strings ("H, E, L, L ,O").
Dim newCharArray() As Char = wordArray(rndNumber).ToCharArray
ReDim Preserve charToString_2(newCharArray.Length - 1)
Dim cBoolean As Boolean = False
For i As Integer = 0 To (newCharArray.Length - 1) Step 1
charToString_2(i) = newCharArray(i)
MsgBox(charToString_2(i))
Next
For j As Integer = 0 To (charToString_2.Length - 1) Step 1
For b As Integer = 0 To (charToString_2.Length - 1) Step 1
MsgBox("Is " & charToString_2(j) & " = " & charToString_2(b) & "?")
If j = b Then
MsgBox(j & " is equal to " & b & ", continuing.")
Exit For
End If
If CStr(charToString_2(b)) = CStr(charToString_2(b)) Then
MsgBox("Yes, +1")
correctGuesses += 1
charToString_2(b) = "Replaced"
cBoolean = True
End If
MsgBox("No, Continuing.")
Next
Next
The first if statement works, so whenever j = b, it exits and proceeds. But then the next loop, it checks if "E" is equal to "H", and it returns true! I have no idea why!
Your algorithm is almost there. You can tweak it a little.
Dim stringtoCheck As String = wordArray(rndNumber)
For j As Integer = 0 To (stringtoCheck.Length - 2)
For b As Integer = j+1 To (stringtoCheck.Length - 1)
If stringtoCheck.chars(b) = stringtoCheck.chars(j) Then
correctGuesses += 1
cBoolean = True
End If
Next
Next
This provides counts for the different characters in a string. Casing is shown.
Dim wordToCheck As String = "heLlo racecar" 'note L and l
Dim lettercounts As New Dictionary(Of Char, Integer)
For Each c As Char In wordToCheck ' .ToUpperInvariant '.ToLowerInvariant
If Not lettercounts.ContainsKey(c) Then
Dim ct As Integer = wordToCheck.Count(Function(ch) ch = c)
lettercounts.Add(c, ct)
End If
Next
'show the counts
For Each ltrct As KeyValuePair(Of Char, Integer) In lettercounts
Debug.WriteLine(String.Format("{0} {1}", ltrct.Key, ltrct.Value))
Next
Related
I want to remove characters from a string based on the position. i use the below code and i manage to:
Insert all word letters in an array
Loop the array and replace the letters that i want with nothing
BUT i dont know have to remove nothing from the array, recreate array index and create the word again without those characters.
'Create an array with all letters
For j = 1 To Len(SheetName2)
ReDim Preserve arrWord(k)
arrWord(k) = Mid(SheetName2, j, 1)
k = k + 1
Next j
Counter = (Len(SheetName2) - 31) * 2
'Loop array and replace with nothing
For w = Counter - 1 To 0 Step -2
arrWord(w) = ""
Next w
You don't need an array of the characters. Just read every second character into a new string.
Public Sub OutputEverySecondChar()
Dim TestString As String
TestString = "abcdefghij"
Dim OutputString As String
Dim iChar As Long
For iChar = 1 To Len(TestString) Step 2
OutputString = OutputString & Mid$(TestString, iChar, 1)
Next iChar
Debug.Print OutputString 'output is: acegi
End Sub
Or even better as re-useable function:
Public Sub test()
Debug.Print ReturnEveryXcharcter(InputString:="abcdefghij", FirstChar:=1, xChar:=2, CharLength:=1)
End Sub
Public Function ReturnEveryXcharcter(InputString As String, FirstChar As Long, xChar As Long, CharLength As Long) As String
Dim iChar As Long
For iChar = FirstChar To Len(InputString) Step xChar
ReturnEveryXcharcter = ReturnEveryXcharcter & Mid$(InputString, iChar, CharLength)
Next iChar
End Function
So the function is even more flexible and you can eg use the following to delete every 3ʳᵈ character (beginning with the first):
Debug.Print ReturnEveryXcharcter(InputString:="abcdefghij", FirstChar:=2, xChar:=3, CharLength:=2)
'output will be: bcefhi
Edit according comment:
Then loop until it is less then 31 characters:
Public Sub test()
Dim SheetName As String
SheetName = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
Do
SheetName = ReturnEveryXcharcter(InputString:=SheetName, FirstChar:=1, xChar:=2, CharLength:=1, LengthLimit:=31)
Loop While Len(SheetName) > 31
Debug.Print SheetName
End Sub
My solution:
If Len(SheetName1) > 31 Then
SheetName2 = Replace(SheetName1, " ", "")
'Third check - Remove letter by letter starting from the beginning
If Len(SheetName2) > 31 Then
Counter = (Len(SheetName2) - 31) * 2
Part_1 = Left(SheetName2, Counter)
Part_2 = Mid(SheetName2, Counter + 1, Len(SheetName2) - (Counter))
Semi_Part = ""
For j = 1 To Counter Step 2
'Use only Part_1 because it ll be the same in all occurance
Semi_Part = Semi_Part + Mid(Part_1, j, 1)
Next j
FinalSheetname = Semi_Part & Part_2
Else
FinalSheetname = SheetName2
End If
Else
FinalSheetname = SheetName1
End If
Just for interest, you can also do this with a worksheet function if you have one of the more recent versions of Excel (O365 or 2016+) that has the CONCAT function.
The "trick" is to create an array that includes the positions of the characters you wish to retain.
In the case of deleting every even space positioned character (retaining the odd positions):
=CONCAT(MID(A1,(ROW(INDEX($A:$A,1,1):INDEX($A:$A,ROUNDUP(LEN(A1)/2,0),1))-1)*2+1,1))
Since this is an array formula, you need to "confirm" it by holding down ctrl + shift while hitting enter. If you do this correctly, Excel will place braces {...} around the formula as observed in the formula bar
I need help trying to create a hang man game. I somehow cant display correct letters properly.I'm using a intGuessed array and a word array. compare the two and display the correct letters and "*" for incorrect letters.
code:
For I As Integer = 0 To 9
Console.WriteLine(“Enter a letter please: ”)
strInput = Console.ReadLine()
Console.WriteLine()
Console.WriteLine()
If [Char].TryParse(strInput, charInput) Then
For G As Integer = 0 To word.Length - 1
Select Case strInput
Case word(G)
ReDim Preserve aryGuessed(G + 1)
If Not aryGuessed.Contains(charInput) Then
aryGuessed(intGuessed) = charInput
intGuessed += 1
End If
For A As Integer = 0 To aryGuessed.Length - 1
For B As Integer = 0 To word.Length - 1
If aryGuessed(A) = word(B) Then
Console.Write(word(B))
End If
Next
Next
If G >= word.Length Then
Console.Write("correct")
End If
Case Else
Console.Write("*")
If G >= word.Length Then
bolwrong = True
End If
End Select
Console.WriteLine()
Next
End If
If bolwrong = True Then
intScore += 1
Console.WriteLine("incorrect")
Console.WriteLine("your score is now " + intScore.ToString())
Console.ReadLine()
bolwrong = False
End If
Next
Rather than looping lots, I would suggest the following code. I've shuffled things around a bit to make more sense, but the main changes are that I've created a string with the same number of '*' as the original word, and replaced a chunk of code with something simpler and more efficient.
ultimately most of your For .. Select Case .. End Case .. Next block can be replaced with ..
For G As Integer = 0 To word.Length - 1
If word(G) = charInput Then
guessedWord = guessedWord.Remove(G, 1).Insert(G, strInput)
boolWrong = False
End If
Next
And the guess/completion checking logic can be move toward the end of the outer loop.
This is what your final code would look like ..
Dim guessedWord As New String("*"c, word.Length)
For I As Integer = 0 To 9
Console.WriteLine(“Enter a letter please: ”)
strInput = Console.ReadLine()
Console.WriteLine()
Console.WriteLine()
boolWrong = True
If Char.TryParse(strInput, charInput) Then
aryGuessed(I) = strInput
For G As Integer = 0 To word.Length - 1
If word(G) = charInput Then
guessedWord = guessedWord.Remove(G, 1).Insert(G, strInput)
boolWrong = False
End If
Next
Console.WriteLine()
Console.WriteLine(guessedWord)
ReDim Preserve aryGuessed(aryGuessed.Count)
End If
If guessedWord = word Then
Console.Write("Correct!")
Exit For
End If
If boolWrong = True Then
intScore += 1
Console.WriteLine("incorrect")
Console.WriteLine("your score is now " + intScore.ToString())
End If
Next
Incidentally, this line
guessedWord = guessedWord.Remove(G, 1).Insert(G, strInput)
is a rather neat way of accomplishing the removal of a character and inserting a character all on one line
Its the same as writing
guessedWord = guessedWord.Remove(G, 1)
guessedWord = guessedWord.Insert(G, strInput)
Through much research I have figured out a code to truncate sentances stored in cells to 100 characters or less, and add the excess to a second string. I have been really struggling trying to turn this into a function.
I would like to have the function accept a range of (1 column by various rows) OR, if that isn't possible, an Array of the same range values. Also there should be a way to set the number of characters that each output string can hold, output as an array of strings.
i.e. wordWrap(Input 'range or array', maxLength as integer) output of wordWrap will be an array of the results
Here is my current code:
Sub wordWrap()
'This procedure is intended to check the character length of a string and truncate all the words over 100 characters
'To a second string. (basically a word wrap)
Dim sumCount As Integer, newCount As Integer, i As Integer
Dim newString As String, newString2 As String
Dim words As Variant
Dim lenwords(0 To 1000) As Variant
Dim myRange As Range
sumCount = 0
newCount = 0
newString = ""
newString2 = ""
With Range("Q:Q")
.NumberFormat = "#"
End With
Set myRange = Range("B3")
words = Split(myRange.Value, " ")
For i = 0 To UBound(words)
lenwords(i) = Len(words(i))
Range("Q3").Offset(i, 0) = CStr(words(i)) 'DEBUG
Range("R3").Offset(i, 0) = lenwords(i) 'DEBUG
If sumCount + (lenwords(i) + 1) < 100 Then
sumCount = sumCount + (lenwords(i) + 1)
newString = newString & " " & words(i)
Else
newCount = newCount + (lenwords(i) + 1)
newString2 = newString2 & " " & words(i)
End If
Next
'DEBUG
Range("S3") = CStr(newString)
Range("T3") = Trim(CStr(newString2))
Range("S4") = Len(newString)
Range("T4") = Len(newString2)
ActiveSheet.UsedRange.Columns.AutoFit
End Sub
So if a range of ("B2:B6")or equivalent array are entered at max 100 characters:
c = wordWrap(Range("B2:B6"),100)
Basically what this should do is count the length of each cell(or element) and truncate any extra words that make the string over 100 characters and concatenate them to the front of the next element in the output array to the next element of the output array. If that would put that element over 100 characters, then do the same process again until all of the elements contain sentence strings less then 100 characters long. It should add an extra element at the end to fit any leftover words.
I have been tearing out my hair trying to get this to work. I could use the advice of the experts.
Any help appreciated.
Example asked for:
http://s21.postimg.org/iywbgy307/trunc_ex.jpg
The ouput should be into an array, though, and not directly back to the worksheet.
The function:
Function WordWrap(ByVal Rng As Range, Optional ByVal MaxLength As Long = 100) As String()
Dim rCell As Range
Dim arrOutput() As String
Dim sTemp As String
Dim OutputIndex As Long
Dim i As Long
ReDim arrOutput(1 To Evaluate("CEILING(SUM(LEN(" & Rng.Address(External:=True) & "))," & MaxLength & ")/" & MaxLength) * 2)
For Each rCell In Rng.Cells
If Len(Trim(sTemp & " " & rCell.Text)) > MaxLength Then
OutputIndex = OutputIndex + 1
arrOutput(OutputIndex) = Trim(Left(sTemp & " " & rCell.Text, InStrRev(Left(sTemp & " " & rCell.Text, MaxLength), " ")))
sTemp = Trim(Mid(sTemp & " " & rCell.Text, Len(arrOutput(OutputIndex)) + 2))
For i = 1 To Len(sTemp) Step MaxLength
If Len(sTemp) < MaxLength Then Exit For
OutputIndex = OutputIndex + 1
arrOutput(OutputIndex) = Trim(Left(sTemp, InStrRev(Left(sTemp, MaxLength), " ")))
sTemp = Trim(Mid(sTemp, Len(arrOutput(OutputIndex)) + 2))
Next i
Else
OutputIndex = OutputIndex + 1
arrOutput(OutputIndex) = Trim(sTemp & " " & rCell.Text)
sTemp = ""
End If
Next rCell
OutputIndex = OutputIndex + 1
arrOutput(OutputIndex) = sTemp
ReDim Preserve arrOutput(1 To OutputIndex)
WordWrap = arrOutput
Erase arrOutput
End Function
The call:
Sub tgr()
Dim arrWrapped() As String
arrWrapped = WordWrap(Range("B2:B6"), 100)
MsgBox Join(arrWrapped, Chr(10) & Chr(10))
End Sub
Instead of a msgbox, you could output it to a sheet, or do whatever else you wanted.
going to say you get passed a string, and want to return an array
performance might be slow with this approach
dim words(1) as variant
dim lastSpace as Integer
dim i as Integer
words(1) = Cells(1, 1)
while(Len(words(UBound(words) - 1)) > 100) 'check if the newest array is > 100 characters
Redim words(UBound(words) + 1)
'find the last space
for i = 0 to 100
if(words(i) = " ") Then
lastSpace = i
EndIF
Next
words(UBound(words) - 1) = Mid(words(UBound(words) - 2), lastSpace) 'copy words after the last space before the 100th character
words(UBound(words) - 2) = Left(words(UBound(words) - 2), 100 - lastSpace) 'copy the words from the beginning to the last space
Wend
Not sure if this will compile/run but it should give you the general idea
I think this is my first question in this forum, so excuse me if I miss following some rules. I am trying to write a VBA algorithm to compute the Shapley-Shubik index. This index requires to compute all the permutations of a sequence of numbers (which represent the votes in a parliament, congress, etc.). After some thorough research I understood that one must use a recursive algorithm to perform such thing.
My idea is to create a matrix in vba where each element is stored separately, and each row contains a different permutation. That is the only way I can subsequently perform computations and retrieve the correct label values needed to compute such index.
The problem is I cannot understand how to revert back to the previous levels once I reach the last level of recursion.
(EDIT) Eventually, I was able to come up with a solution. I am posting the results below, since I have seen that it has been asked for. I should warn though, this is a very inefficient code, and it does not work with more than 7 players. The reason for this is because vba is not able to deal with the extremely big matrix created by this code, so the program just crashes with an overflow error.
However, in have not been particularly smart in writing this code, this means it should be pretty easy to modify the code in order to make it work for a bigger number of players. Basically, instead of using the permutation function to create a matrix, one just needs to compute the pivotal player in each specific permutation, then use an array to "store" the frequencies. Unfortunately, I did not have time to modify the code, as I am currently working on other projects, though somewhat related, using Matlab instead.
Here it is the function I have assembled:
Public Function ShapleyShubik( _
Votes As Range, _
Coalitions As Range, _
Candidate As String, _
Threshold As Double) As Double
'
'------------------------------------------------------
' by Sim1
' This function computes the Shapley-Shubik Power Index
' For a specified coalition among the available ones
'------------------------------------------------------
'
Dim Labels() As String
Dim Powers() As Double
Dim Interval As Variant
Dim MatLabels() As String
Dim MatPowers() As Integer
Dim Calc() As String
Dim Total As Integer
Dim ii As Integer
'Convert Labels Range
Interval = ToArray(Coalitions)
ReDim Labels(1 To UBound(Interval)) As String
For ii = 1 To UBound(Interval)
Labels(ii) = CStr(Interval(ii))
Next
'Convert Powers Range
Interval = ToArray(Votes)
ReDim Powers(1 To UBound(Interval)) As Double
For ii = 1 To UBound(Interval)
Powers(ii) = CInt(Interval(ii))
Next
SShubCalc Powers, Labels, Calc, Threshold, Total
'Compute Index
ShapleyShubik = (UBound(Filter(Calc, Candidate, True)) + 1) / Total
End Function
Private Function SShubCalc( _
ByRef Powers() As Double, _
ByRef Labels() As String, _
ByRef Pivotal() As String, _
ByVal bar As Double, _
ByRef Righe As Integer) As Boolean
On Error GoTo Error_line
Dim Colonne As Integer
Dim MatNum() As Double
Dim MatStr() As String
Dim Threshold As Integer
Dim Somma() As Double
Dim perfsum() As Boolean
Dim PivPos() As Integer
Dim Addend() As Double
Dim v() As Variant
' Define Size Variables
Colonne = UBound(Powers)
Righe = Factorial(Colonne)
'Generate Matrix of Permutations
MatrPerm Powers, MatNum, Labels, MatStr
'Provide Vector Sums and Check Threshold
With Application.WorksheetFunction
Threshold = .Sum(.index(MatNum, 1))
End With
'Control for unanimity
If (Threshold * bar) < (Threshold - 1) Then
Threshold = Round(Threshold * bar, 0) + 1
End If
'Initialize Arrays
ReDim perfsum(1 To Righe)
ReDim PivPos(1 To Righe)
ReDim Pivotal(1 To Righe)
For ii = 1 To Colonne
'First Iteration
If ii = 1 Then
v = Application.WorksheetFunction.index(MatNum, 0, ii)
ToDoubleArray Somma, v
Else:
v = Application.WorksheetFunction.index(MatNum, 0, (ii))
ToDoubleArray Addend, v
SumVector Somma, Somma, Addend
End If
For j = 1 To Righe
If Somma(j) >= Threshold And perfsum(j) = False Then
PivPos(j) = ii
perfsum(j) = True
End If
Next j
Next ii
'Transfer PivoPos to Labels
For ii = 1 To Righe
Pivotal(ii) = MatStr(ii, PivPos(ii))
Next ii
SShubCalc = True
Exit Function
Error_line:
SShubCalc = False
End Function
Private Function nextPerm(s As String)
' inspired by http://stackoverflow.com/questions/352203/generating-permutations-lazily
' this produces the "next" permutation
' it allows one to step through all possible iterations without having to have them
' all in memory at the same time
Dim L As Integer, ii As Integer, jj As Integer
Dim c() As Byte, temp As Byte
L = Len(s)
If StrComp(s, "**done**") = 0 Or StrComp(s, "") = 0 Then
nextPerm = ""
Exit Function
End If
' convert to byte array... more compact to manipulate
ReDim c(1 To L)
For ii = 1 To L
c(ii) = Asc(Mid(s, ii, 1))
Next ii
' find the largest "tail":
For ii = L - 1 To 1 Step -1
If c(ii) < c(ii + 1) Then Exit For
Next ii
' if we complete the loop without break, ii will be zero
If ii = 0 Then
nextPerm = "**done**"
Exit Function
End If
' find the smallest value in the tail that is larger than c(ii)
' take advantage of the fact that tail is sorted in reverse order
For jj = L To ii + 1 Step -1
If c(jj) > c(ii) Then
' swap elements
temp = c(jj)
c(jj) = c(ii)
c(ii) = temp
Exit For
End If
Next jj
' now reverse the characters from ii+1 to the end:
nextPerm = ""
For jj = 1 To ii
nextPerm = nextPerm & Chr(c(jj))
Next jj
For jj = L To ii + 1 Step -1
nextPerm = nextPerm & Chr(c(jj))
Next jj
'Debug.Print nextPerm
End Function
Private Function Factorial(dblNumber As Integer) As Integer
Dim dblCtr As Double
Dim dblResult As Double
dblResult = 1 'initializes variable
For dblCtr = 1 To dblNumber
dblResult = dblResult * dblCtr
Next dblCtr
Factorial = dblResult
End Function
Private Function SumVector(ByRef Result() As Double, ByRef Vec1() As Double, ByRef Vec2() As Double)
Dim temp As Integer
Dim tempuno As Integer
Dim ii As Integer
If LBound(Vec1) = 0 Then
temp = UBound(Vec2)
ReDim Preserve Vec1(1 To (temp + 1))
End If
If LBound(Vec2) = 0 Then
tempuno = UBound(Vec2)
ReDim Preserve Vec2(1 To (temp + 1))
End If
If temp <> tempuno Then
Exit Function
End If
ReDim Preserve Result(1 To UBound(Vec1))
'Debug.Print Vec1(1, 1)
For ii = 1 To UBound(Vec1)
Result(ii) = Vec1(ii) + Vec2(ii)
Next ii
End Function
Private Function ToDoubleArray( _
ByRef DoubleArray() As Double, _
ByRef VariantArray() As Variant)
If LBound(VariantArray) = 0 Then
ReDim Preserve VariantArray(1 To (UBound(VariantArray) + 1))
End If
ReDim DoubleArray(1 To UBound(VariantArray))
For ii = 1 To UBound(VariantArray)
DoubleArray(ii) = VariantArray(ii, 1)
Next ii
End Function
Private Function MatrPermStr( _
ByRef VecInput() As String, _
ByRef MatOutput() As String)
Dim Sequence As String
Dim StrPerm As String
Dim Colonne As Integer
Dim Righe As Integer
Dim ii As Integer
Dim j As Integer
' Size Variables
Colonne = UBound(VecInput)
Righe = Factorial(Colonne)
ReDim MatOutput(1 To Righe, 1 To Colonne) As String
'Start With an Empty Sequence
Sequence = ""
'Create Sequence with defined Length
For ii = 1 To Colonne
Sequence = Sequence & ii
Next ii
'Assign the permutation to the array
For j = 1 To Righe
If j = 1 Then
StrPerm = Sequence
Else
StrPerm = nextPerm(StrPerm)
End If
For ii = 1 To Colonne
MatOutput(j, ii) = VecInput(Mid(StrPerm, ii, 1))
Next ii
Next j
End Function
Private Function MatrPerm( _
ByRef VecInput() As Double, _
ByRef MatOutput() As Double, _
ByRef VecInputStr() As String, _
ByRef MatOutputStr() As String)
Dim Sequence As String
Dim StrPerm As String
Dim Colonne As Integer
Dim Righe As Integer
Dim ii As Integer
Dim j As Integer
Dim t As Integer
' Size Variables
Colonne = UBound(VecInput)
Righe = Factorial(Colonne)
ReDim MatOutput(1 To Righe, 1 To Colonne)
ReDim MatOutputStr(1 To Righe, 1 To Colonne)
'Start With an Empty Sequence
Sequence = ""
'Create Sequence with defined Length
For ii = 1 To Colonne
Sequence = Sequence & ii
Next ii
'Assign the permutation to the array
For j = 1 To Righe
If j = 1 Then
StrPerm = Sequence
Else
StrPerm = nextPerm(StrPerm)
End If
For ii = 1 To Colonne
MatOutput(j, ii) = VecInput(Mid(StrPerm, ii, 1))
MatOutputStr(j, ii) = VecInputStr(Mid(StrPerm, ii, 1))
Next ii
Next j
End Function
Private Function ToArray(ByRef someRange As Range) As Variant
Dim someValues As Variant
With someRange
If .Cells.Count = 1 Then
ReDim someValues(1 To 1)
someValues(1) = someRange.Value
ElseIf .Rows.Count = 1 Then
someValues = Application.Transpose(Application.Transpose(someRange.Value))
ElseIf .Columns.Count = 1 Then
someValues = Application.Transpose(someRange.Value)
Else
MsgBox "someRange is mutil-dimensional"
End If
End With
ToArray = someValues
End Function
Private Sub DescribeShapShub()
Dim FuncName As String
Dim FuncDesc As String
Dim Category As String
Dim ArgDesc(1 To 4) As String
FuncName = "SHAPLEYSHUBIK"
FuncDesc = "Returns Shapley-Shubik power index for a given player, given the other players' votes"
Category = 3 'Math category
ArgDesc(1) = "Range containing the player's votes (Only selected votes will be considered in the computation)"
ArgDesc(2) = "Range containing the player's names (must have the same length as ""Votes"")"
ArgDesc(3) = "Cell or String containing the player for which to compute the index"
ArgDesc(4) = "Cell or Number containing the voting threshold (e.g. 0.5 for 50%)"
Application.MacroOptions _
Macro:=FuncName, _
Description:=FuncDesc, _
Category:=Category, _
ArgumentDescriptions:=ArgDesc
End Sub
Sorry if some variables are in Italian. Also, some parts of the code have been retrieved here and there in some specialised forums, so I take no credit for the specific commands, just for the assembling :)
One last request: if anyone is able to improve this code, please share it so everybody can use it.
I am not going to answer your question exactly; but I would like to offer you a nice little function that will help solve your bigger problem. This function generates the "next" permutation of a string - where the string can contain numbers or letters, and "next" is in a lexicographical sense (see [this discussion](Generating permutations lazily
)).
What can you do with it? Well, when you want to compute anything "over all possible permutations", having a function that gives you "just the next permutation" will keep your code readable (it takes away an awful lot of housekeeping!). You can then simply say (this is pseudocode):
// initialize stuff
firstPerm = "1234"
np = nextPerm(firstPerm)
// loop over all permutations
while not np equals "done"
// update calculations on np
np = nextPerm(np)
wend
// report your results
Here is the function. It seemed to behave itself for me - even when I have multiple identical characters in the string, or a mixture of letters and numbers. Note that it treats A and a as distinct... Also note that it returns the string "done" when it is done. Obviously, if you happen to pass it the string "doen" as input, it will return "done" although it isn't done... Try to avoid that!
Function nextPerm(s As String)
' inspired by https://stackoverflow.com/questions/352203/generating-permutations-lazily
' this produces the "next" permutation
' it allows one to step through all possible iterations without having to have them
' all in memory at the same time
Dim L As Integer, ii As Integer, jj As Integer
Dim c() As Byte, temp As Byte
L = Len(s)
If StrComp(s, "**done**") = 0 Or StrComp(s, "") = 0 Then
nextPerm = ""
Exit Function
End If
' convert to byte array... more compact to manipulate
ReDim c(1 To L)
For ii = 1 To L
c(ii) = Asc(Mid(s, ii, 1))
Next ii
' find the largest "tail":
For ii = L - 1 To 1 Step -1
If c(ii) < c(ii + 1) Then Exit For
Next ii
' if we complete the loop without break, ii will be zero
If ii = 0 Then
nextPerm = "**done**"
Exit Function
End If
' find the smallest value in the tail that is larger than c(ii)
' take advantage of the fact that tail is sorted in reverse order
For jj = L To ii + 1 Step -1
If c(jj) > c(ii) Then
' swap elements
temp = c(jj)
c(jj) = c(ii)
c(ii) = temp
Exit For
End If
Next jj
' now reverse the characters from ii+1 to the end:
nextPerm = ""
For jj = 1 To ii
nextPerm = nextPerm & Chr(c(jj))
Next jj
For jj = L To ii + 1 Step -1
nextPerm = nextPerm & Chr(c(jj))
Next jj
End Function
You can test it simply by adding it to a VBA module in your spreadsheet, and saving the workbook with .xlsm extension. Then you can type =nextPerm("abcd") in cell A1, and it should give you the next permutation - "abdc". Typing =nextPerm(A1) in A2 will compute the one after that, etc. You could copy all the way down the spreadsheet, and get every value.
If you copy the cells to a range that goes beyond the last permutation, it will return "**done**" as value for the first time this happens; and when you feed it "**done**" as input, it will return blank. This makes it obvious where things stop.
Take a look at this function -- it will list all possible permutations of a set of numbers using recursion.
http://www.vb-helper.com/howto_permute.html
It's for VB6 but it should be basically working in the Excel's implementation of VBA too.
Anyway, I know I shouldn't be responding to other comments here in the answer, I'm really sorry. It's just that the author Simone S said "If anyone is interested in using the resulting function just ask me", however, there's no way to contact the person other than this. Simone, please, I've been looking for a Shapley-Shubik algorithm for hours. Could you please point me to the description of how to compute the index or the resulting function?
When I run this code it show a dialog box that says 'Mismatch Type' at line For i = 0 To UBound(arffArray). What is the problem with my code?
Public Function processFile()
Dim i, j, posRelation, temp, att, data, flag
Dim strRelation
Dim strAtt
Dim strData
strRelation = "#relation"
strAtt = "#attribute"
strData = "#data"
att = 0
data = 0
For i = 0 To UBound(arffArray)
If (InStr(arffArray(i), strRelation)) Then
temp = Replace(Mid(arffArray(i), 11, Len(arffArray(i))), "'", "")
RelationName = temp
ElseIf (InStr(arffArray(i), strAtt)) Then
flag = parseAtt(att, arffArray(i))
If (Not flag) Then
processFile = flag
Exit Function
End If
att = att + 1
ElseIf (InStr(arffArray(i), strData)) Then
data = readTheRest(i)
i = UBound(arffArray) 'end the loop
totalData = data
End If
Next
'get the list of class name
Dim tmpClassAttr
tmpClassAttr = attArray(1, UBound(attArray, 2))
For i = 0 To UBound(tmpClassAttr)
ReDim Preserve classArray(i)
classArray(i) = Trim(tmpClassAttr(i))
Next
processFile = True
End Function
'------------------------------------------------------------
'Function: parseAtt(num, attrData, ByVal m As MineKnow)
'require:
' >#num -> current attribute counter
' >#attrData -> current attribute declaration
'Raises: error if reading non numeric data/attribute
'Return: boolean parseAtt TRUE/FALSE, TRUE if parse successfully or otherwise
'Effect: parsing file content to:
' > attributes
'------------------------------------------------------------
Private Function parseAtt(num, attrData)
Dim temp, i, j, strAtt, temp2, pos, atVal
ReDim Preserve attArray(2, num)
'possible type of declarations
'#attribute outlook {sunny, rainy, overcast}
'#attribute outlook {sunny,rainy,overcast}
'attribute pos = 12
'get the attribute name first get the pos of "{"
pos = InStr(1, attrData, "{", 1)
If (pos = 0) Then
error = "---->Nominal attribute only." & vbCrLf & "---->" & attrData
parseAtt = False
Exit Function
Else
strAtt = Trim(Mid(attrData, 12, pos - 12))
atVal = Mid(attrData, pos + 1, Len(attrData) - (pos + 1))
atVal = Replace(atVal, "'", "")
atVal = Replace(atVal, "''", "")
atVal = Replace(atVal, "}", "")
atVal = Replace(atVal, " ", "")
temp = Split(atVal, ",")
attArray(0, num) = strAtt
attArray(1, num) = temp
parseAtt = True
End If
End Function
The biggest problem with your code is that you don't declare the type of your variables!!
Statements like
Dim i
will just declare the variable i as type Variant, which is definitely not what you want.
You actually want i to be declared as an Integer. You accomplish that by explicitly specifying the type at the point of declaration:
Dim i As Integer
Since the For loop is expecting an iterator index variable with type Integer, this should stop it from choking up that error.
Likewise, strings should always be declared explicitly as String types:
Dim strRelation As String
Note that when you declare multiple variables on a single line (which you probably shouldn't do, for clarity reasons), you need to make sure that you specify the type for each variable. It is not cumulative like other languages.
For example, this statement
Dim i, j, k As Integer
will declare only k as an Integer. i and j will be of type Variant, which is almost certainly (again) not what you wanted.
Instead, you need to write
Dim i As Integer, j As Integer, k As Integer
Of course, in this particular case, since VB 6 allows you to use variables with a lower bound that is not 0, it's probably best to code the loop like this:
Dim i As Integer
For i = LBound(arffArray) To UBound(arffArray)
' Do something with the array
Next i