VBA error: A value used in the formula is the wrong data type? - vba

I'm trying to create a function which generates a secret encoded message. It takes in three things: a string, for example, "testingtestingonetwothree", as well as the desired number of characters, for example 5, and the desired number of words, for example 5. It generates the message by starting at the first character, and extracting every fifth character through the string, putting these characters into a codeword, then starting at the second character and extracting every fifth character through the string, putting these into a second codeword, and so on. It just outputs a string, with the codewords separated by a space. So for this example it would produce: "tntnt egieh stntr tegwe isooe".
I'm okay at coding but new to VBA. I've made what I think is a valid function, but when it's used in the spreadsheet I get a #VALUE! error: "A value used in the formula is the wrong data type". This is the user defined function I made:
Function encode(strng, numchars, numwords)
Dim word As Integer
Dim step As Integer
Dim temp As String
Dim output As String
For word = 1 To numchars
step = word
temp = ""
Do While step <= Len(strng)
temp = temp & Mid(strng, 1, step)
step = step + numchars
Loop
If word = 1 Then output = temp Else output = output & " " & temp
Next word
encode = output
End Function
And when it's used in the spreadsheet I just call it, as in
=encode(A16,A7,A10)
Where A16 contains testingtestingonetwothree, A7 contains 5 and A10 contains 5.
Does my function seem okay? And is there anything you guys can see which could be giving the value error? Any help would be greatly appreciated, thanks a lot for reading.
EDIT: This now outputs a value, but the wrong value. It outputs: "ttestintestingtesttestingtestingontestingtestingonetwot tetestingtestingtestitestingtestingonetestingtestingonetwoth ", when it should output: "tntnt egieh stntr tegwe isooe". Is there anything you guys can see that my function is doing wrong?
EDIT2: After fixing the Mid function, to
temp = temp & Mid(strng, step, 1)
as per vacip's answer, the function now produces the correct answer.

Ok, everyone says it works, but for me, it doesn't produce the desired output. What the...???
Anyway, I think your Mid function is in the wrong order, try it like this:
temp = temp & Mid(strng, step, 1)
Also, make sure to properly declare your variables, like this:
Function encode(strng As String, numchars As Integer, numwords As Integer) As String
I have also rewritten your IF statement, that one-line thing is strange for me...
If word = 1 Then
output = temp
Else
output = output & " " & temp
End If
This way it worked for me.

Other people have addressed the type problem. Here is a different suggestion. The cipher that you are describing is a simple transposition cipher, specifically a columnar transposition ( https://en.wikipedia.org/wiki/Transposition_cipher#Columnar_transposition )
The way people did this pre-computer was to write the characters into a grid row by row then read them off column by column. In fact -- this is probably still the easiest way to implement it even with computers. Declare a variant which can be redimensioned to be an array with e.g. 5 columns (where 5 is the skip between letters) and the number of rows is chosen to be large enough so that the grid can hold the string. After you load up the characters row by row, read them off column by column using nested for loops.
Once you get a basic example working, you can try to implement a version which uses a key to determine the order that you read off the columns for added security.
Coding classical cryptography/cryptanalysis as an excellent way to learn a programming language. Almost the first thing I do when I try to learn a new language is to implement a Vigenere cipher in it. Even though it is long out of print and can be somewhat tricky to translate to modern dialects of Basic the book "Cryptanalysis for Microcomputers" by Caxton Foster is great fun and can be purchased for just a few dollars from online used bookstores.

You need to define your Function's type. So in this case I believe you would want
Function encode(strng, numchars, numwords) As String

I tested your code exactly as it is, and it worked fine.
So, your problem may be:
A certain argument of your function is not the right type. (I bet the len method is the problem in there).
Check if A16 is really a string. If not, consider converting it to a string before if you want to pass numbers too:
Function encode(strng as variant, numchars as integer, numwords as integer) as string
strng = str(strng)
Check also if A7 and A10 are really integers.

Related

Match Words and Add Quantities vb.net

I am trying to program a way to read a text file and match all the values and their quantites. For example if the text file is like this:
Bread-10 Flour-2 Orange-2 Bread-3
I want to create a list with the total quantity of all the common words. I began my code, but I am having trouble understanding to to sum the values. I'm not asking for anyone to write the code for me but I am having trouble finding resources. I have the following code:
Dim query = From data In IO.File.ReadAllLines("C:\User\Desktop\doc.txt")
Let name As String = data.Split("-")(0)
Let quantity As Integer = CInt(data.Split("-")(1))
Let sum As Integer = 0
For i As Integer = 0 To query.Count - 1
For j As Integer = i To
Next
Thanks
Ok, lets break this down. And I not seen the LET command used for a long time (back in the GWBASIC days!).
But, that's ok.
So, first up, we going to assume your text file is like this:
Bread-10
Flour-2
Orange-2
Bread-3
As opposed to this:
Bread-10 Flour-2 Orange-2 Bread-3
Now, we could read one line, and then process the information. Or we can read all lines of text, and THEN process the data. If the file is not huge (say a few 100 lines), then performance is not much of a issue, so lets just read in the whole file in one shot (and your code also had this idea).
Your start code is good. So, lets keep it (well ok, very close).
A few things:
We don't need the LET for assignment. While older BASIC languages had this, and vb.net still supports this? We don't need it. (but you will see examples of that still floating around in vb.net - especially for what we call "class" module code, or "custom classes". But again lets just leave that for another day.
Now the next part? We could start building up a array, look for the existing value, and then add it. However, this would require a few extra arrays, and a few extra loops.
However, in .net land, we have a cool thing called a dictionary.
And that's just a fancy term of for a collection VERY much like an array, but it has some extra "fancy" features. The fancy feature is that it allows one to put into the handly list things by a "key" name, and then pull that "value" out by the key.
This saves us a good number of extra looping type of code.
And it also means we don't need a array for the results.
This key system is ALSO very fast (behind the scene it uses some cool concepts - hash coding).
So, our code to do this would look like this:
Note I could have saved a few lines here or there - but that would make this code hard to read.
Given that you look to have Fortran, or older BASIC language experience, then lets try to keep the code style somewhat similar. it is stunning that vb.net seems to consume even 40 year old GWBASIC type of syntax here.
Do note that arrays() in vb.net do have some fancy "find" options, but the dictionary structure is even nicer. It also means we can often traverse the results with out say needing a for i = 1 to end of array, and having to pull out values that way.
We can use for each.
So this would work:
Dim MyData() As String ' an array() of strings - one line per array
MyData = File.ReadAllLines("c:\test5\doc.txt") ' read each line to array()
Dim colSums As New Dictionary(Of String, Integer) ' to hold our values and sum them
Dim sKey As String
Dim sValue As Integer
For Each strLine As String In MyData
sKey = Split(strLine, "-")(0)
sValue = Split(strLine, "-")(1)
If colSums.ContainsKey(sKey) Then
colSums(sKey) = colSums(sKey) + sValue
Else
colSums.Add(sKey, sValue)
End If
Next
' display results
Dim KeyPair As KeyValuePair(Of String, Integer)
For Each KeyPair In colSums
Debug.Print(KeyPair.Key & " = " & KeyPair.Value)
Next
The above results in this output in the debug window:
Bread = 13
Flour = 2
Orange = 2
I was tempted here to write this code using just pure array() in vb.net, as that would give you a good idea of the "older" types of coding and syntax we could use here, and a approach that harks all the way back to those older PC basic systems.
While the dictionary feature is more advanced, it is worth the learning curve here, and it makes this problem a lot easier. I mean, if this was for a longer list? Then I would start to consider introduction of some kind of data base system.
However, without some data system, then the dictionary feature is a welcome approach due to that "key" value lookup ability, and not having to loop. It also a very high speed system, so the result is not much looping code, and better yet we write less code.

excel function syntax calling error

I've created this function:
Public Function getLastCellValue(ByVal row As Integer, ByVal column As String) As String
If (Cells(row, column).Value = "") Then
getLastCellValue = getLastCellValue(row - 1, column)
Else: getLastCellValue = Cells.Item(row, column).Value
End If
End Function
When I use it in a cell this:
=getLastCellValue(1,"C")
Excel tells me that the function contains an error and focuses on the second parameter: "C".
I'm going crazy because I do not understand the mistake.
Cells(row, column) expects numeric parameter values. If you mean to refer to cell addresses (like, "A1") then it might be simpler to use Range instead.
That said I strongly recommend taking this implementation over to Code Review once you get it to work as expected, ...I'd have several things to point out ;)
Your code works as is on my machine. My bet is on #Matteo's comment
Make sure your Excel language is using the comma (,) to separate functions parameters; if your name tells me something about your nationality, I guess you're using an Italian/Spanish system (which means you should separate inputs by semi-column (;)

A Perfectly good Substring Error

Im having a problem parsing a string array of Directories. The end goal is to query the path tied to the [global].MyDataDir & "\saved" to get all folders in this directory. However the actual foldernames, the last bit of text after the last indexof "\" holds the name of a plugin that I need to compare against an enumerated list of plugins for further functionality I won't get into here. The problem here is my last bit of code wont work. The Dim foldername as String = (etc...), It returns an error saying Index and length must refer to a location within the string. Parameter name: length.
Can any of you wizards, help me out here. Much appreciated.
Dim dirList As String() = System.IO.Directory.GetDirectories([global].MyDataDir & "\saved")
For dir As Integer = 0 To dirList.Length - 1
If IO.Directory.GetFiles(dirList(dir)).Length > 0 Then
For Each file As String In IO.Directory.GetFiles(dirList(dir))
Dim folderName As String = dirList(dir).ToString.Substring(dirList(dir).ToString.LastIndexOf("\"), dirList(dir).ToString.Length - 1)
Next
End If
Next
Semper Fi.
Use System.IO.Path.GetDirectoryName() instead.
Next time use the VB.NET Left() convenience function to avoid getting this wrong.
I found the reason....
The problem lies in the arguments of Substring(starting index, length of copy from starting index). I was under the impression, the length argument would take into account the entire string when calculating the length. Instead the second argument of this function acts upon the results of the first argument, not the entire string. So the length of the string is actually much longer than what exists after taking an index of it.
Thanks for the help.

adding variables numical values (newb question)

Yesterday i had a look at how to set values of variables from nummbers stored in external txt files
the variables then needed to be added up so i used trial and error first
((XVAL) + (NEWVAL))
assuming that XVAL was set to 10 and NEWVAL was set to 20 i expected to get the answer of thirty but waqs presented with the new value of 10 20
VB.net pysicaly added the two values together but i wanted the mathematical product of the two which is ((10) + (20)) = 30
yep its a newb question could anyone explain how to achieve what im affter
XVAL and NEWVAL are strings, so they are simply being concatenated together. You need to convert them to integers, so that VB.NET will treat them as such. To do this, use the Int32.Parse() method.
Dim intXVAL As Integer = Int32.Parse(XVAL)
Dim intNEWVAL as Integer = Int32.Parse(NEWVAL)
Dim result = intXVAL + intNEWVAL
You want to cast them to a number first.
Try CDbl.
See http://msdn.microsoft.com/en-us/library/Aa263426 for more.
edit: Oops, thought you were talking about VBA.
Try using Double.Parse(YOURVALUE) if you're talking about VB.NET.
Have you tried the Val() function?
Val(XVAL) + Val(NEWVAL)
The + operator in VB.NET (for backwards-compatibility reasons) means both add and concatenate depending on the types of the variables it is being used with. With two numeric types (Integer, Single, Double, etc.), it adds the values together as you would expect. However, with String types, it concatenates the two strings.
Presumably, then, your XVAL and NEWVAL variables are String types because they're being read out of a text file, which is causing VB.NET to concatenate them into a new string instead of add them together. To get the behavior you're expecting, you need to convert them to numeric types.
Some of the other answers suggest casting simply casting the string values to numeric types (CInt, CSng, CDbl, etc.), but this may not work as expected if the value contained by your string cannot be converted to number. The Int32.Parse method will throw an exception if the value held by your string cannot be represented as a number. This is especially important to keep in mind if you're reading values from a text file that are not guaranteed to adhere to any particular constraints.
Instead, you probably want to use something like Int32.TryParse, which returns a Boolean value indicating whether or not the conversion succeeded and will not throw an exception.
As you are reading from a text file I assume that you are reading your values out as strings, so when you do this:
((XVAL) + (NEWVAL))
It is effectively concatenating the two strings together. In order to get the mathematical product of the two values these need to be int/integers which is the number type.
There are a number of ways you can do this, but in essence you have to 'cast' the strings to ints and then do your calculation.
So in vb.net it would be something like this (pseudo code):
Dim xval As String = "10"
Dim newval As String = "20"
Dim x As Integer = Int32.Parse(xval)
Dim n As Integer = Int32.Parse(newval)
Dim prod As Integer = x + n
Console.WriteLine(prod)
There are a number of other methods of doing this, for example using:
int.Parse(...)
or
Integer.TryParse(...)
More information on these sorts of type conversions can be found here:
http://dotnetperls.com/integer-parse-vbnet
One thing to bear in mind with these sorts of conversions is that you have to be certain that your input data is convertable. Otherwise your code will throw exceptions. This is where TryParse is useful as you can use this to check the inputs and handle invalid inputs without the need for exceptions.

MS-Access: Replace "bracket"

In one of the ms-access table I work with we have a text field with a set size.
At the end of this field there is some extra code that varies depending on the situation.
I'm looking for a way to remove one of these code but even when the last part is truncated by the field maximum size.
Let's call the field "field" and the code I'm looking to remove "abc-longcode".
If I use the replace SQL function with the string abc-longcode the query will only work when the code is complete.
If I also want my update query (that does nothing but remove this specific code at the end of my field) to work on incomplete codes how would that translate into ms-SQL?
It would have to remove (or replace with "" to be precise) all of the following (example of course, not the real codes):
abc-longcode
abc-longcod
abc-longco
abc-longc
abc-long
abc-lon
abc-lo
abc-l
Obviously I could do that with several queries. Each one replacing one of the expected truncated codes... but it doesn't sound optimal.
Also, when the field is big enough to get all of the code, there can sometime be extra details at the end that I'll also want to keep so I cannot either just look for "abc-l" and delete everything that follows :\
This query (or queries if I can't find a better way) will be held directly into the .mdb database.
So while I can think of several ways to do this outside of a ms-sql query, it doesn't help me.
Any help?
Thanks.
You can write a custom VBA replace method that will replace any of the given cases {"abc-longcode", ... "abc-l"}. This is essentially the same tack as your "several queries" idea, except it would only be one query. My VBA is rusty, but something like:
public function ReplaceCodes(str as string) as string
dim returnString as string
returnString = str
returnString = replace(returnString,"abc-longcode","")
// ... etc...
ReplaceCodes = returnString
end function
I may have gotten the parameter order wrong on replace :)
I would use my own custom function to do this using the split function to get the first part of the string. You can then use that value in the update query.
Public Function FirstPart(thetext As String) As String
Dim ret As String
Dim arrSplitText As Variant
arrSplitText = Split(thetext, "-")
ret = arrSplitText(0)
FirstPart = ret
End Function
Can you use:
Left(FieldX,InStr(FieldX,"abc-")-1)
EDIT re Comment
If there is a space or other standard delimiter:
IIf(InStr(InStr(FieldX, "abc-"), FieldX, " ") = 0, Left(FieldX, InStr(FieldX, "abc-") - 1), Replace(FieldX, Mid(FieldX, InStr(FieldX, "abc-"), InStr(InStr(FieldX, "abc-"), FieldX, " ") - InStr(FieldX, "abc-")), ""))