Having trouble shortening strings in a column but making it dynamic for a directory. Ex: 3 strings in a column, 1 string may continue for 10 rows, another string 20 rows, and another for 15 rows. Each needing to be shorted a different amount
Specifically: I had several words that are either sepearated by a -, _ , +. So example one sheet has pv01_52352, pv+50, pv-100, irvega_242422, so the irvega word works. but for the pv's i am struggling with. The desired results are the works before the special characters, so it would be pv01, pv and irvega as a desired result .
How can I use left or anyway to shorten these all? It is quite hard for me since for one string ill need the first 4 characters, then another 5 characters to be shortened and will need to be done throughout a workbook.
Any input would be appreciated, Thanks.
I've found that determining the ASCII character number with the CODE function (using UPPER function to reduce the possibilities) produces satisfactory results.
The array formula in B2 for strictly alphabetic characters is,
=LEFT(A2, MIN(ROW(INDIRECT("1:"&LEN(A2)))+((CODE(MID(UPPER(A2), ROW(INDIRECT("1:"&LEN(A2))), 1))>64)*(CODE(MID(UPPER(A2), ROW(INDIRECT("1:"&LEN(A2))), 1))<91))*1E+99)-1)
An array formula in B2 for alphanumeric characters is,
=LEFT(A2, MIN(ROW(INDIRECT("1:"&LEN(A2)))+(((CODE(MID(UPPER(A2), ROW(INDIRECT("1:"&LEN(A2))), 1))>64)*(CODE(MID(UPPER(A2), ROW(INDIRECT("1:"&LEN(A2))), 1))<91))+((CODE(MID(A2, ROW(INDIRECT("1:"&LEN(A2))), 1))>47)*(CODE(MID(A2, ROW(INDIRECT("1:"&LEN(A2))), 1))<58)))*1E+99)-1)
Array formulas need to be finalized with Ctrl+Shift+Enter↵.
A VBA User Defined Function (aka UDF) for strictly alphabetic characters would be,
Function just_the_alpha(str As String)
Dim tmp As String, c As Long
For c = 1 To Len(str)
If Asc(Mid(UCase(str), c)) > 64 And _
Asc(Mid(UCase(str), c)) < 91 Then
tmp = tmp & Mid(str, c, 1)
Else
Exit For
End If
Next c
just_the_alpha = tmp
End Function
A VBA UDF for alphanumeric characters would be,
Function just_the_alphanum(str As String)
Dim tmp As String, c As Long
For c = 1 To Len(str)
If (Asc(Mid(UCase(str), c)) > 64 And _
Asc(Mid(UCase(str), c)) < 91) Or _
(Asc(Mid(str, c)) > 47 And _
Asc(Mid(str, c)) < 58) Then
tmp = tmp & Mid(str, c, 1)
Else
Exit For
End If
Next c
just_the_alphanum = tmp
End Function
Related
My research shows that I need to use Visual Basic. I am a programmer/developer, but have never used VB so if anyone could dumb it down it would be appreciated.
Here's my working excel function:
=IF(MATCH(1,E1:DP1,0),D1,FALSE)
I want to loop a few of those numbers such that:
=IF(MATCH(141,E1:DP378,0),D378,FALSE)
THEN take my answers (which will be strings, because column D are all strings, the rest of the excel file are numbers)
=CONCAT
end goal: have 141 String arrays populated based on the data in my table.
I went ahead and made my first attempt at VBA like this:
Sub myFunc()
'Initialize Variables
Dim strings As Range, nums As Integer, answer() As Variant, listAnswers() As Variant
'set variables
strings = ("C1:C378")
nums = 141
i = 0
j = 0
ReDim Preserve answer(i)
ReDim Preserve listAnswers(j)
'answer() = {""}
'for each in nums
For counter = 0 To nums
ReDim Preserve listAnswers(0 To j)
'set each list of answers
listAnswers(i) = Join(answer(), "insertJSONcode")
j = j + 1
'for each in Stings
For Each cell In strings
If cell <> "" Then
ReDim Preserve answer(0 To i)
answer(i) = 'essentially this: (MATCH(2,E1:DP1,0),D1,FALSE)
i = i + 1
end If
next cell 'end embedded forEach
Next LCounter 'end for loop
'is this possible? or wrong syntax?
Range("A:A").Value = listAnswers() ' should print 141 arrays from A1 to A141
End Sub
EDIT:
Important note I do NOT need to call the sheet by Name. I've successfully written integer values to by excel sheet in column A without doing so.
Also, the VBA I wrote I was never intended to work, I know it's broken at least where answer(i) is supposed to write to something. I'm only putting that code there to show I was able to at least able to get into spitting distance of the proper logic and prove I've put some serious effort into solving the problem and give a rough starting point.
Here's an image of the excel format. Column C goes down to 378 and the numbers listed from E through DP are populated by a database. It consists of blank cells and numbers between 1 and 141.
Looking back at my if statement:
=IF(MATCH(2,E2:DP2,0),D2,FALSE)
If I were to type that exactly into cell B2 it would output the correct answer "text2". which is neat and all, but I need every instance of text 2 written out, then CONCAT those results. Easy so far, I could drag that down all the way through column B and have all of my "text" strings in one column, CONCAT that column and there's the answer. However I don't just need #2, I need each number between 1 and 141. Plus I want to avoid writing 141 columns with a CONCAT on top of each one.
I use an =IF function
=IF(RIGHT(A1;1)="-";"-"&LEFT(A1;LEN(A1)-1);A1)*1
to shift a minus sign from the end of the cell to the beginning but I'd like to use it in a macro so it is performed on the same column (or the same selection)...
1) Use the the For Each.... loop construct to loop through each cell in a range.
2) If you are wanting to convert "numbers" of the 123- to -123 to a proper number and not text, use the Val command to convert a string to a number.
Note however that if you if you have cell with something like "ABC-", this will become -ABC which VBA then attempts to convert to a number ...and produces zero as a result
Sub MoveMinus()
Dim c As Range
For Each c In Intersect(Selection, Selection.Worksheet.UsedRange)
If (Right(c, 1) = "-") Then
c = Val("-" & Left(c, Len(c) - 1)) 'Val to make the result numeric
End If
Next
End Sub
I need help with extracting 5-digit numbers only from one column to another in Excel 2010. These numbers can be in any position of the string (beginning of the string, anywhere in the middle, or at the end). They can be within brackets or quotes like:
(15478) or "15478" or '15478' or [15478]
I need to ignore any numbers that are less than 5 digits and include numbers that start with 1 or more leading zeros (like 00052, 00278, etc.) and ensure that leading zeros are copied over to the next column. Could someone help me with either creating a formula or UDF?
Here is a formula-based alternative that will extract the first 5 digit number found in cell A1. I tend to prefer reasonably simple formula solutions over VBA in most situations as formulas are more portable. This formula is an array formula and thus must be entered with Ctrl+Shift+Enter. The idea is to split the string up into every possible 5 character chunk and test each one and return the first match.
=MID(A1,MIN(IF(NOT(ISERROR(("1"&MID(A1,ROW(INDIRECT("R1C[1]:R"&(LEN(A1)-4)&"C[1]",FALSE)),5)&".1")*1))*ISERROR(MID(A1,ROW(INDIRECT("R1C[1]:R"&(LEN(A1)-4)&"C[1]",FALSE))+5,1)*1)*ISERROR(MID(A1,ROW(INDIRECT("R1C[1]:R"&(LEN(A1)-4)&"C[1]",FALSE))-1,1)*1),ROW(INDIRECT("R1C[1]:R"&(LEN(A1)-4)&"C[1]",FALSE)),9999999999)),5)
Let's break this down. First we have an expression I used twice to return an array of numbers from 1 up to 4 less than the length of your initial text. So if you have a string of length 10 the following will return {1,2,3,4,5,6}. Hereafter the below formula will be referred to as rowlist. I used R1C1 notation to avoid potential circular references.
ROW(INDIRECT("R1C[1]:R"&(LEN(A1)-4)&"C[1]",FALSE))
Next we will use that array to split the text into an array of 5 letter chunks and test each chunk. The test being performed is to prepend a "1" and append ".1" then verify the chunk is numeric. The prepend and append eliminate the possibility of white space or decimals. We can then check the character before and the character after to make sure they are not numbers. Hereafter the below formula will be referred to as isnumarray.
NOT(ISERROR(("1"&MID(A1,rowlist,5)&".1")*1))
*ISERROR(MID(A1,rowlist+5,1)*1)
*ISERROR(MID(A1,rowlist-1,1)*1)
Next we need to find the first valid 5 digit number in the string by returning the current index from a duplicate of the rowlist formula and returning a large number for non-matches. Then we can use the MIN function to grab that first match. Hereafter the below will be referred to as minindex.
MIN(IF(isnumarray,rowlist,9999999999))
Finally we need to grab the numeric string that started at the index returned by the MIN function.
MID(A1,minindex,5)
The following UDF will return the first five digit number in the string, including any leading zero's. If you need to detect if there is more than one five digit number, the modifications are trivial. It will return a #VALUE! error if there are no five-digit numbers.
Option Explicit
Function FiveDigit(S As String, Optional index As Long = 0) As String
Dim RE As Object
Set RE = CreateObject("vbscript.regexp")
With RE
.Pattern = "(?:\b|\D)(\d{5})(?:\b|\D)"
.Global = True
FiveDigit = .Execute(S)(index).submatches(0)
End With
End Function
As you may see from the discussion between Mark and myself, some of your specifications are unclear. But if you would want to exclude decimal numbers, when the decimal portion has five digits, then the regex pattern in my code above should be changed:
.Pattern = "(?:\d+\.\d+)|(?:\b|\D)(\d{5})(?:\b|\D)"
I just wrote this UDF for you , basic but will do it...
It will find the first 5 consecutive numbers in a string, very crude error checking so it just says Error if anything isn't right
Public Function GET5DIGITS(value As String) As String
Dim sResult As String
Dim iLen As Integer
sResult = ""
iLen = 0
For i = 1 To Len(value)
If IsNumeric(Mid(value, i, 1)) Then
sResult = sResult & Mid(value, i, 1)
iLen = iLen + 1
Else
sResult = ""
iLen = 0
End If
If iLen = 5 Then Exit For
Next
If iLen = 5 Then
GET5DIGITS = Format(sResult, "00000")
Else
GET5DIGITS = "Error"
End If
End Function
I am trying to write a macro to find if a cell has 5 numeric values and if it does, I need to add a 0 at the end.
My macro already has some steps in it.
For example
Cell BZ2 = 9.48E+00
My macro finds the decimal point and replaces it with 94811E-5
I need to add a Zero in this case, because there are 5 numeric values, AND only when the last three characters are E-5.
Expected result is 948110E-5.
I am using a number stored as text.
Can anyone help me out?
Sub TextFormat()
Dim c As Range
Dim d As Range
For Each c In Sheets("order_export").Range("F2:F10000").Cells
If StrComp(Right(c.Value, 1), "R", vbTextCompare) = 0 Then
c.Offset(0, -1).Value = c.Offset(0, -1).Value & "R"
c.Value = Left(c.Value, Len(c.Value) - 1)
End If
Next c
For Each d In Sheets("order_export").Range("BZ2:BZ10000").Cells
If InStr(1, d.Value, ".", vbTextCompare) > 0 Then
d.NumberFormat = "#"
d.Value = Replace(d.Value, ".", "")
d.Value = d.Value & "E-5"
End If
Next d
End Sub
using this conditional
if isNumeric(left(text,5)) AND right(text,3) = "E-5" then
'add zero
text = left(text,5) & "0" & right(text, len(text) - 5)
end if
will add the 0 after the first 5 if the first 5 characters are numeric and the last 3 are e-5. the left function takes the first 5 characters. the isNumeric checks if they are numeric. and then the rest, takes the first 5 characters, puts a 0, and then the right takes all characters starting from the right going up till length - 5 (we already have the first 5 characters)
edit
as pointed out, if there is already a 0, like 123450E-5 then an extra would be added.
add ANd len(text) = 8 so that it only adds the 0 if there are 8 characters.
Excel doesn't short circuit so for coding efficiency it is better to break an AND into IF's with the most likely errors first, then the breaches
Also never using the variant functions Left and Right use string functions Left$ and Right$ instead
This link is an excellent resource re coding optimisation.
Re-cutting the earlier answers would be something like this:
c = "94811E-5"
If Len(c) = 8 Then
If IsNumeric(Left$(c, 5)) Then
If Right$(c, 3) = "E-5" Then c = Left$(c, Len(c) - 3) & "0" & Right$(c, 3)
End If
End If
MsgBox c
I am using VLookup function which looks up multiple values which are present in the column. This works very well but just takes a lot of time as I have 100,000 rows in the Excel sheet.
Is there any way to quicken this code?
The code basically looks up a particular value in a column and gets the offset. The difference between simple VLookup and this is that in case there are multiple rows with the same lookup value then it gets all the elements.
Function VLookupAll(ByVal lookup_value As String, _
ByVal lookup_column As Range, _
ByVal return_value_column As Long, _
Optional seperator As String = ", ") As String
Dim i As Long
Dim result As String
For i = 1 To lookup_column.Rows.Count
If Len(lookup_column(i, 1).Text) <> 0 Then
If lookup_column(i, 1).Text = lookup_value Then
result = result & (lookup_column(i).Offset(0, return_value_column).Text & seperator)
End If
End If
Next
If Len(result) <> 0 Then
result = Left(result, Len(result) - Len(seperator))
End If
VLookupAll = result
End Function
This is about 20-30x faster than a simple loop (tested over a column of 20k values, with 3 matches to the value being searched).
'rng: a single-column range to search for matches
'val: the value to match on
'col: offset from match in rng to concatenate values from (similar
' to the matching VLOOKUP argument)
Function MultiLookup(rng As Range, val As String, col As Long)
Dim i As Long, v, s
Dim r As Long
r = rng.Cells.Count
v = Application.Match(val, rng, 0)
s = ""
Do While Not IsError(v)
s = s & IIf(s <> "", ",", "") & rng.Cells(v).Offset(0, col - 1).Value
r = r - v
Set rng = rng.Offset(v, 0).Resize(r, 1)
v = Application.Match(val, rng, 0)
Loop
MultiLookup = s
End Function
http://www.excelhero.com/blog/2011/03/the-imposing-index.html says "Excel INDEX MATCH is significantly quicker than VLOOKUP"
You could try doing a Range.Find to see if the value exists at all in lookup column before proceeding. You are looping through every item in lookup column only to find it isn't there. If it were me, I would do a Range.find to see if lookup value is in lookup_column. If it is then you could do a countif to see how many occurrences there are...if there is only one occurrence, use plain old VLookup...and only fall back into your process if there is more than one occurrence.....may work....of course if Find fails, bail out of the function.
Another option is to load the lookup_column into any array...and process the array rather than the range.mnthat can sometimes help.
Summary:
Concate the values and do a vlookup on that new value.
For me I needed to have a formula and not a function to look up by 2 values. VLOOKUP could only work by a single value from what I've seen, so my solution was to concatenate the 2 values for a single primary key.
In my raw data tab I added a column called Lookup that simply concatenated the ID column with the Timestamp columns I had.
Then in my comparison tab I had
=VLOOKUP(CONCATENATE(A4, $F$1),'Historical Data'!$A:$G,3,FALSE)
Which took the ID column, concatenated with my lookup date at $F$1, and vlookup'ed into my data tab (Historical Data).