In Excel, I have many products with different sizes listed in columns, such that the sizes "10x10 cm", "11x11 cm" and "15x15 cm" belongs to Product A, etc.
In some other cells, I am selecting a product (either Product A, Product B, or Product C) and a size.
I want, for each of the other products, to determine which size is closest to the selected product:
I don't know how to solve this. One solution might be to remove all non-numeric characters from the strings and add the two values on each side of the "x" and then select the size with the lowest absolute difference from the sum of the selected size.
But I guess it would be easier to do a mapping and use a VLOOKUP to choose the first found size in a given column.
However, the problem is that I do not only have 3 products with a few different sizes, but rather 15 different products with 10 different sizes, so I don't know how to do a mapping in a clever way.
1) Creating a lookup table with the values extracted for each product,
Source sheet:
Code:
Sub lookup()
Dim i As Long, j As Long, prod As Integer, str As String
prod = InputBox("Enter Number of Products")
Sheets.Add.Name = "LookupSheet"
j = 1
For i = 1 To prod
Columns(i).Copy Sheets("LookupSheet").Cells(1, j)
j = j + 2
Next i
For j = 1 To prod * 2 Step 2
For i = 2 To Sheets("LookupSheet").Cells(Rows.Count, j).End(xlUp).Row
str = Replace(Replace(Sheets("LookupSheet").Cells(i, j), " ", ""), "cm", "")
Sheets("LookupSheet").Cells(i, j + 1) = Left(str, InStr(str, "x") - 1) _
* Mid(str, InStr(str, "x") + 1, 999)
Next i
Next j
End Sub
This simple code creates a lookup sheet with the corresponding values. The code ignores any spaces present between the texts.
LookupSheet:
Since you have 15 different products, run this macro to extract the lookup data. This should be a one time activity unless you have additional products.
2) Assuming you enter the product and dimensions to F5 and F6, i would suggest you to data validation with dropdowns to select from the list,
3) Using a worksheet_change event, detect for changes in F5 and F6,
Code:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim str As String, result As Integer, i As Long
'F5 and F6 contains Product and Size repectively
If (Target.Address = "$F$5" Or Target.Address = "$F$6") _
And Range("F5") <> "" And Range("F6") <> "" Then
str = Replace(Replace(Range("F6"), " ", ""), "cm", "")
result = Left(str, InStr(str, "x") - 1) * Mid(str, InStr(str, "x") + 1, 999)
j = 8
For i = 1 To Cells(1, Columns.Count).End(xlToLeft).Column
If Cells(1, i) <> Range("F5") Then
Range("E" & j) = Cells(1, i)
j = j + 1
End If
Next i
End If
End Sub
This code automatically populated the rest of the product types in the column E,
4) The variable result would contain the product/area of the value that you provide in F6. The only task pending would be to loop through the lookup sheet to find the nearest match. The Algorithm is below,
Algorithm:
Compare cell F5 with the data in row 1 of lookup sheet (need to loop)
If they are equal, ignore and move to next value. If not, need to loop the immediate next column to find the next match, and populate the result in the corresponding cell in source sheet.
Algorithm for column wise looping is below,
Steps:
diff = cell.value - result
if diff < 0 then multiply diff by -1
loop:
nextdiff = nextcell.value - result (multiply by -1 if negative)
if nextdiff < diff then
diff = nextdiff
end if
end loop:
The cell value with the least difference would be your best match for that particular product type.
Bit lengthier explanation, hope this helps.
Related
Thanks to some wonderful people here on StackOverflow I have almost completed the code for my scheduling template generator! I just have three more things I would like to add that I am having some trouble with.
Current Breakdown: I have two sheets - "2 - Staff Listing" and "X - Template"
-The Values in in the staff listing sheet are a column of staff names beginning in Cell D9
-Then the Staff values are transferred to the template sheets starting in Cell B6
-There is a drop-down menu in the template sheet in Cell C2 where you can select which template you want to make just as an example it could read 5 Week or 5 Week with AM/PM
-Based on this Key I have it repeating each staff member's names X amount of times (5 in this case) and double that if AM/PM is selected so 10 times in this case
What I am hoping to still achieve:
-In the template sheet I would like column C to be the week number and column D to show AM/PM.
-So, for a 5 week AM/PM Template you would have Column C staring in cell C6 list Week 1, Week 1, Week 2, Week 2, up to Week 5. So, once for each week in the Key if it is AM/PM it should appear twice once for AM and once for PM.
-Then I would like it to list AM starting in cell D6 and then go PM and just keep repeating for the length of the names in column B. Additionally, if it is not an AM/PM template then I would like to hide column D.
Below is the code I currently have.
Sub populate_Template()
Worksheets("X - Template").Range("B6:K1000").ClearContents
Dim SourceData As Range
Set SourceData = Sheets("2 - Staff Listing").Range("D9")
Dim RepititionCell As Range
Set RepititionCell = Sheets("X - Template").Range("C2")
Dim Destination As Range
Set Destination = Sheets("X - Template").Range("B6")
Dim lr As Long
Dim arr As Variant
With SourceData.Parent
lr = .Cells(.Rows.count, SourceData.Column).End(xlUp).Row
arr = .Range(SourceData, .Cells(lr, SourceData.Column)).Value
End With
Dim repetition As Long
If Right(RepititionCell, 5) = "AM/PM" Then repetition = Split(RepititionCell.Value, " ")(0) * 2 Else repetition = Split(RepititionCell.Value, " ")(0)
Dim newarr() As String
ReDim newarr(1 To (lr - SourceData.Row + 1) * repetition, 1 To 1)
Dim count As Long
count = 0
Dim i As Long
For i = LBound(arr) To UBound(arr)
Dim y As Long
For y = 1 To repetition
count = count + 1
newarr(count, 1) = arr(i, 1)
Next y
Next i
Destination.Resize(UBound(newarr) - LBound(newarr) + 1).Value = newarr
End Sub
First you need to dim newarr to have three columns instead of one:
ReDim newarr(1 To (lr - SourceData.Row + 1) * repetition, 1 To 3)
Then your For y Next loop should fill in those other two columns. You can use the Mod operator to determine if you're on an odd or even row.
For y = 1 To repetition
Count = Count + 1
newarr(Count, 1) = arr(i, 1)
'if you're doubling up. You may want to put this in a Boolean variable
'to reduce code duplication
If Right(RepititionCell, 5) = "AM/PM" Then
'if it's an odd number
If y Mod 2 = 1 Then
newarr(Count, 2) = "Week " & (y + 1) / 2
newarr(Count, 3) = "AM"
Else 'even number
newarr(Count, 2) = "Week " & y / 2
newarr(Count, 3) = "PM"
End If
Else
'not doubling up, so y is the week number and nothing in column D
newarr(Count, 2) = "Week " & y
End If
Next y
Finally, you have to change your write line to account for the new columns
Destination.Resize(UBound(newarr, 1), UBound(newarr, 2)).Value = newarr
I got rid of the LBound part of the Resize because you're starting at 1 anyway. It's less robust if you happen to start your array at some other number. But between the likelihood that would happen and the improved readability, I think it's better.
I'm trying to solve a problem in VBA and after a long time of browsing the web for solutions, I really hope someone is able to help me.
It's actually not a very hard task, but with very little programming and VBA knowledge as a new learner, I hope I can find a useful tip or solution with the help of the community.
So my problem is as follows:
I have a table with 3 columns, the first is filled with a number to use as an ID. Column 2 and 3 have different values that needs to be compared:
What I'd like to do is select the range of column rows of column 2 and 3 based on the same ID. Once I have selected the relevant ranges of the columns, I want to compare if one name of column 2 matches one name of column 3.
So there is no need to have all names of the desired column ranges to match. One name match is enough. If a name matches, it should automatically fill in a new column "result" with 1 for match (0 for no match).
Do you have an idea, how I can select specific cells of a column based on an identifier?
Dim ID_counter As Long
ID_counter = 1
If Cell.Value = ID_counter IN Range("Column1")
Then Range("Column2").Select
AND Range("Column3").Select
WHERE ID_counter is the same
In Column4 (If one Cell.Value IN Range("Column2-X:Column2-Y")
IS IDENTICAL TO Range("Column3-X:Column3-Y"), return 1, else return 0
End Sub
Many thanks in advance for your help!
This works for your example so perhaps you can generalise it. The formula in D2 is
=IF(A2=A1,"",MAX(IF($A$2:$A$10=A2,COUNTIF($B$2:$B$10,$C$2:$C$10))))
and is an array formula so must be confirmed with CTRL, SHIFT and ENTER.
Array alternative via Match() function
This approach compares the string items of columns B and C by passing two arrays (named b,c) as arguments (c.f. section [1]):
chk = Application.Match(b, c, 0)
The resulting chk array reflects all findings of the first array's items via (1-based) position indices of corresponding items in the second array.
Non-findings return an Error 2042 value (c.f. section [2]b)); assumption is made that data are grouped by id.
Sub OneFindingPerId()
'[0]get data
Dim data: data = Sheet1.Range("A1:D10") ' << project's sheet Code(Name)
Dim b: b = Application.Index(data, 0, 2) ' 2nd column (B)
Dim c: c = Application.Index(data, 0, 3) ' 3rd column (C)
'[1]get position indices of identic strings via Match() function
Dim chk: chk = Application.Match(b, c, 0) ' found row nums of a items in b
'[2]loop found position indices (i.e. no error 2042)
Dim i As Long
For i = 2 To UBound(chk) ' omit header row
'a) define start index of new id and initialize result with 0
If data(i, 1) <> data(i - 1, 1) Then
Dim newId As Long: newId = i
data(newId, 4) = 0
End If
'b) check if found row index corresponds to same id
If Not IsError(chk(i, 1)) Then ' omit error 2042 values
If data(chk(i, 1), 1) = data(i, 1) Then ' same ids?
If data(newId, 4) = 0 Then data(newId, 4) = 1 ' ~> result One if first occurrence
End If
End If
Next i
'[3]write results
Sheet1.Range("A1").Resize(UBound(data), UBound(data, 2)) = data
End Sub
First enter this user defined function in a standard module:
Public Function zool(r1, r2, r3) As Integer
Dim i As Long, v1 As Long, v2 As String
Dim top As Long, bottom As Long
zool = 0
v1 = r1.Value
top = r1.Row
' determine limits to check
For i = top To 9999
If v1 <> r1.Offset(i - top, 0).Value Then
Exit For
End If
Next i
bottom = i - 1
For i = top To bottom
v2 = Cells(i, "B").Value
If v2 <> "" Then
For j = top To bottom
If v2 = Cells(j, "C").Value Then zool = 1
Next j
End If
Next i
End Function
Then in D2 enter:
=IF(OR(A2="",A2=A1),"",zool(A2,B2,C2))
and copy downwards:
(this assumes that the data has been sorted or organized by ID first)
Ive been trying to make something in Excel to find multiple combinations of sums.
I have list of numbers that needs to be added together to be either within ranges of 500-510 or 450-460.
Only two numbers from the list can be used to find the sum. the numbers can not be used more than once. and giving the combinations of multiple results would be great. and if a number is not used it is ok.
I've tried the solver add-in and some other tips I found from this site but could not find something that gives multiple answers.
Does anyone know if this will be possible?
I'd break this into 2 tasks. First would be to simply generate all of the index pairs to test in the input array. That's relatively simple with recursive procedure. This one uses a private Type to store the pairs, but it could adapted to use some other method of storing the pairs:
Private Type Tuple
ValueOne As Long
ValueTwo As Long
End Type
Private Sub FindCombinations(elements As Long, ByRef results() As Tuple, _
Optional ByVal iteration As Long = 0)
If iteration = 0 Then ReDim results(0)
Dim idx As Long
For idx = iteration To elements - 1
Dim combo As Tuple
With combo
.ValueOne = iteration
.ValueTwo = idx
End With
results(UBound(results)) = combo
If iteration <> elements And idx <> elements Then
ReDim Preserve results(UBound(results) + 1)
End If
Next
If iteration < elements Then FindCombinations elements, results, iteration + 1
End Sub
Then, you use a "entry-point" procedure to generate the index combinations, use those to index into your source array, and apply your selection criteria:
Private Sub FindMatchingSets(testSet() As Long)
Dim indices() As Tuple
FindCombinations UBound(testSet) + 1, indices
Dim idx As Long, results() As Tuple
For idx = LBound(indices) To UBound(indices)
Dim tupleSum As Long
tupleSum = testSet(indices(idx).ValueOne) + testSet(indices(idx).ValueTwo)
If indices(idx).ValueOne <> indices(idx).ValueTwo And _
((tupleSum >= 500 And tupleSum <= 510) Or _
(tupleSum >= 450 And tupleSum <= 460)) Then
Debug.Print testSet(indices(idx).ValueOne) & " + " & _
testSet(indices(idx).ValueTwo) & " = " & tupleSum
End If
Next
End Sub
It isn't clear what you intend to do with the results, so this simply outputs the calculated values to the Immediate Window. Example calling code:
Private Sub Example()
Dim test(4) As Long
test(0) = 100
test(1) = 200
test(2) = 250
test(3) = 260
test(4) = 400
FindMatchingSets test
End Sub
May modify it according to your need & try
Sub test()
Dim X, Y, TRw, GotNum, First, Second As Long
TRw = 1
With ThisWorkbook.ActiveSheet
For X = 1 To 100 ' assumed col A1 to A100 is the list
GotNum = .Cells(X, 1).Value
If (GotNum >= 450 And GotNum <= 460) Or (GotNum >= 500 And GotNum <= 510) Then
.Cells(X, 1).Font.Color = RGB(255, 0, 0)
First = GotNum
For Y = X + 1 To 100
GotNum = .Cells(Y, 1).Value
If (GotNum >= 450 And GotNum <= 460) Or (GotNum >= 500 And GotNum <= 510) Then
Second = GotNum
TRw = TRw + 1
.Cells(TRw, 3).Value = First ' write 1st Number in Col C
.Cells(TRw, 4).Value = Second ' write 2nd Number in Col D
.Cells(TRw, 5).Value = First + Second ' write Sum of 1st & 2nd in Col C
End If
Next Y
End If
Next X
End With
End Sub
I think your question needs to be a little clearer in terms of what your expected output is (do you want a list of combos, or just to see the results?), but here's my solution.
I've put a list of 20 numbers in column Y, and assigned them all a letter (a through to t) in column X
Then I've built a matrix of the combinations of a to t, and have entered the following formula (the below is for cell C3, but it can be copied and pasted into all parts of the matrix)
=IF(C$2=$B3,"x",VLOOKUP(C$2,$X:$Y,2,FALSE)+VLOOKUP($B3,$X:$Y,2,FALSE))
I've then used conditional formatting to set the colour of the cells if they meet your criteria for the sum - you can do this by highlighting all the sums (cell C3:V22) and going to
home / conditional formatting / new rule...
picking the rule type format only cells that contain
and then in the drop down menus picking Cell Value / Between / Your high range
and then selecting a format (fill background colour, usually)
Do this once for the "high" sum, and once for the "low" sum. You can make the colours the same or different, depending on what you want to see.
I've also for reference included a reference to what the number is in Row 1 and column A. The formula for row 1 is (example is for C1, but it can be copied across)
=VLOOKUP(C2,$X:$Y,2,FALSE)
And the formula for column A is (example for A3) =VLOOKUP(B3,$X:$Y,2,FALSE)
The advantage of this approach is that it's all in excel (no code required), but the disadvantage is that it's hard to get a list of results. You could use a different formula to just return the sum (e.g. return the text "205+298") when it meets one of the conditions, but then it's still a pain to get it out of the matrix format and into a single list. Much easier using VBA
I want to extract 10 digit mobile form random string in cell A1 which has text as well as mobile numbers some has one mob ile other has two or maybe three mobile numbers .All mobile number to be saved in different coloumns.Only excel sheet formula is required
First of all, you will need to create a named range. The purpose of the named range is to normalize and split the data by space so that it can be read by other formulas without having to type that out each and every time.
First, put your data in column A starting in row 1 (as shown in your sample data image). Then create a named range with a name of SplitString and define it with this formula:
=INDEX(TRIM(MID(SUBSTITUTE(TRIM(SUBSTITUTE($A1,"."," "))," ",REPT(" ",999)),999*(ROW($1:$10)-1)+1,999)),)
Note the ROW($1:$10). The 10 in that is a guess that the strings will never have more than 10 entries to evaluate in a single cell. This is consistent with your sample data where the cell with the most entries is ROHTAK (BUILDER) 7777777777 PAL 6666666666 which has 5 entries to be evaluated. If you need to increase the number, just increase the 10 to be a higher number.
Then in cell B1 and copied over and down, use this formula which utilizes the SplitString named range that has been defined:
=IFERROR(IF(AND(ISNUMBER(--$A1),LEN($A1)=10,COLUMN(A1)=1),--$A1,--INDEX(SplitString,MATCH(1,INDEX((COUNTIF($A1:A1,SplitString)=0)*(LEN(SplitString)=10)*(ISNUMBER(--SplitString)),),0))),"")
I would try using regular expressions as mentioned in this answer : https://stackoverflow.com/a/22542835/2068595
On the face of it, I would look for this regex [0-9]{10} (meaning 10 consecutives characters from 0 to 9) in your column.
With data in column A run this short macro:
Sub numbersss()
Dim N As Long, L As Long, K As Long
Dim i As Long, j As Long, t As String
N = Cells(Rows.Count, "A").End(xlUp).Row
For j = 1 To N
t = Cells(j, 1).Text
L = Len(t)
For i = 1 To L
If Mid(t, i, 1) Like "[0-9]" Then
Else
Mid(t, i, 1) = " "
End If
Next i
ary = Split(Application.WorksheetFunction.Trim(t), " ")
K = 2
For Each a In ary
If Len(a) = 10 Then
Cells(j, K) = "'" & a
K = K + 1
End If
Next a
Next j
End Sub
For example:
If someone posts a pure formula solution, please ignore this post.
I have a excel sheet with 2 columns
Contact Status Max Probability
80-Opp Closed Won
0-NC Closed Won
40-Pending 30-Connect
10-Working 20- Engagement
80-Opportunity 30-Connect
40-Pending 10- Engagement
I need to check if the no in contact status ie the numeric value before '-' is less than numeric value in max probabilty before '-', then i need the whole value of max probability update the contact status field
For eg.
10-Working 20- Engagement
10-Working should be replaced by 20- Engagement
OR
We can have 20- Engagement in a new column if the above mentioned conditioned is satisfied, if replacing the contact Status column is difficult
How can achieve this?
This little macro will do the trick. Just change Row to the starting row and ColA/B to the two columns you want to use.
Note that GetNum returns -1 if there is no number at the start which, in this implementation, means no copying. It was unclear what you wanted to do tith Closed Won so I chose the safest option. If you do want it copied, just return a huge number instead of -1.
Option Explicit
Function GetNum(s As String) As Integer
If Mid(s, 1, 1) < "0" Or Mid(s, 1, 1) > "9" Then
GetNum = -1
Else
GetNum = Val(s)
End If
End Function
Sub Macro1()
Dim Row As String
Dim ColA As String
Dim ColB As String
ColA = "A"
ColB = "B"
Row = "2"
While Range(ColA & Row).Value <> ""
If GetNum(Range(ColA & Row).Value) < GetNum(Range(ColB & Row).Value) Then
Range(ColA & Row).Value = Range(ColB & Row).Value
End If
Row = CStr(Val(Row) + 1)
Wend
End Sub
This was tested on:
to generate:
This macro will do:
Sub a()
ColumnContSt=1
ColumnMaxProb=2
i = 1
While (Cells(i, 2) <> "")
colB = Cells(i, ColumnMaxProb)
colA = Cells(i, ColumnContSt)
posB = InStr(1, colB, "-")
posA = InStr(1, colA, "-")
If (posB <> 0 And posA <> 0) Then
intColB = CInt(Mid(colB, 1, posB - 1))
intColA = CInt(Mid(colA, 1, posA - 1))
If (intColA < intColB) Then
Cells(i, 1) = Cells(i, 2)
End If
End If
i = i + 1
Wend
End Sub
Just change ColumnContSt = 1 and ColumnMaxProb = 2 for your actual column NUMBERS ie. A=1, B=2, etc.
HTH!
If you are flexible about the actual data, the easiest way to do this is to make two columns (can be hidden in some far away sheet):
0 NC
10 Engagement
30 Connect
...
And then use VLOOKUP(CELL_WITH_PROBABILITY, REGION_WITH_TABLE, 2, true). So if you put my small 3 element table in the upper corner of a sheet and probability in C1, it'd look like VLOOKUP(A1:B3, C1, 2, true)