counting multiple instances of a number in a range - excel

I have a range of numbers and I need to identify if the first number of each cell is repeated anywhere in the corresponding row.
For example, in row 2 below, column 2 and column 3 both start with a 3. I know that if I do =LEFT(TRIM(cell)) to get just the first number but how do I find the rows that have repeated numbers in the row so row 1 isn't marked but row 2 is?
100|600|203|700| |
202|302|301|400|600|

Use a helper column with this as an array formula:
=SUMPRODUCT(--(COLUMN($A1:$E1)<>MATCH(INT($A1:$E1/100),INT($A1:$E1/100),0)))>0
Being an array formula it must be confirmed with Ctrl-Shift-Enter instead of Enter when exiting edit mode. If done correctly then Excel will put {} around the formula.

Consider the following UDF():
Public Function AnyDups(rng As Range) As Boolean
Dim valu() As String
Dim i As Long, L As Long
L = rng.Count
ReDim valu(1 To L)
AnyDups = False
If L = 1 Then Exit Function
i = 1
For Each r In rng
valu(i) = Left(r.Value, 1)
i = i + 1
Next r
For i = 1 To L - 1
v = valu(i)
For j = i + 1 To L
If v = valu(j) Then AnyDups = True
Next j
Next i
End Function
For example:
The code just loops through the possible combinations of left-most characters in the cells.
It should work with either text or numeric data.

One way to do it would be to use this formula as a basis:
=IF(ISERROR(FIND(LEFT(TRIM(A1),1),B1)),FALSE,"Row "& ROW(A1))
Depending on how you want to check your row, you can adapt it. You could either have one formula to check one cell (Lock the A1 reference and drag right) - which would allow you to know where the match is but take more space on the sheet.
Or, if you don't have too many cells to check in each row, you could concatenate all cells in the same formula:
=IF(ISERROR(FIND(LEFT(TRIM(A1),1),B1&C1&D1&E1)),FALSE,"Row "& ROW(A1))
I'm sure Gary's Student will have a more elegant answer though!

Related

Divide all cells in column A by last cell in column A and repeat for columns B-L

I have a spreadsheet of numbers from A1 to L10. I need to divide cells A2-A9 by the last number in the column (A10). I need the result of those calculations to go into column N (N1-N8). Then I need to repeat that for column B - divide B2-B9 by B10 and put the result of those calculations into column O (O1-O8). This needs to repeat for columns C-L putting the result of the calculations into columns P-Y. I have the code to divide A2-A9 by A10, but I cannot figure out how to repeat this for the rest of the columns.
Sub Divide_a_range_by_last_number()
Dim i As Long
For i = 2 to 9
Worksheets("sheet1").Cells(i,14).Value = Worksheets("sheet1").Cells(i,1).Value / (Range("A1").End(xlDown))
Next i
End Sub
Any suggestions are appreciated.
It sounds like you need one more level of iteration, to repeat the divide process for columns A through L. You could accomplish this using another for-loop - to iterate through each column, and call your "divide" function each time. You'll just need to update your divide function to accept the current column number to operate on:
' Iterate through columns A-L.
For j = 1 to 12
' Provide the current column that you want the divide function to operate on.
Call Divide_a_range_by_last_number(i)
Next
' Update the function signature to accept the current column number.
Sub Divide_a_range_by_last_number(curColumn as Long)
Dim i As Long
For i = 2 to 9
' Update this line to use curColumn variable, instead of static column numbers (i.e. 1 and 14).
Worksheets("sheet1").Cells(i,curColumn + 13).Value = Worksheets("sheet1").Cells(i,curColumn).Value / Worksheets("sheet1").Cells(10,curColumn).Value
Next i
End Sub
I'm pretty new to VBA, so the syntax may not be perfect. But, this should get you started.

Counting the repeating values in comma-separated lists

My data in column A has comma-separated values (one/two digit numbers).
I need a way to count the number of repeats in two consecutive rows.
Example data:
DATA Expected Results
1,2,3,4,5 4
2,3,4,5 2
3,4 0
10,11,40,60,72,75 3
10,40,3,75
Note that each list doesn't have the same number of values. A1 has 5 values, A2 has 4, A3 has 2, etc.
I'd appreciate any help. Thanks!
Here is one option for B2:
=IFERROR(SUMPRODUCT(--(1*TRIM(MID(SUBSTITUTE(A2,",",REPT(" ",99)),(ROW(INDEX(AAA:AAA,1):INDEX(AAA:AAA,LEN(A2)-LEN(SUBSTITUTE(A2,",",""))+1))-1)*99+1,99))=(TRANSPOSE(1*TRIM(MID(SUBSTITUTE(A3,",",REPT(" ",99)),(ROW(INDEX(AAA:AAA,1):INDEX(AAA:AAA,LEN(A3)-LEN(SUBSTITUTE(A3,",",""))+1))-1)*99+1,99)))))),"")
Enter through CtrlShiftEnter
Drag down...
Picture shows dots as I couldn't use commas due to my version of Excel.
Borrowed some knowledge from here
as a VBA alternative
Public Function commonCount(aRng As Range, bRng As Range) As Integer
Dim bArr() As String, aArr() As String, a As Integer, b As Integer
aArr = Split(aRng.Value, ",")
bArr = Split(bRng.Value, ",")
For a = LBound(aArr) To UBound(aArr)
For b = LBound(bArr) To UBound(bArr)
If aArr(a) = bArr(b) Then
commonCount = commonCount + 1
GoTo nexta
End If
Next b
nexta:
Next a
End Function
In B2 commonCount(A2,A3)

Locate the last used cell in a block of cells

I need a formula to locate the last used cell in a block. By last used cell I mean:
Locate the last column (right-most) containing non-null data
Locate the lowest cell in that column that contains non-null data
Return the address of that cell
For example, in the block B2:I16:
The function should return: I15 rather than D16. I already have a VBA UDF that I am trying to replace:
Public Function FindLast(r As Range) As String
Dim nLastRow As Long, nLastColumn As Long
Dim nFirstRow As Long, nFirstColumn As Long
Dim i As Long, j As Long
nLastRow = r.Rows.Count + r.Row - 1
nLastColumn = r.Columns.Count + r.Column - 1
nFirstRow = r.Row
nFirstColumn = r.Column
For i = nLastColumn To nFirstColumn Step -1
For j = nLastRow To nFirstRow Step -1
If Len(r(j, i)) > 0 Then
FindLast = r(j, i).Address(0, 0)
Exit Function
End If
Next j
Next i
End Function
As the worksheet must work in a macro-free environment.
This {array formula} works:
=ADDRESS(MAX(ROW(L1:P5)*(LEN(L1:P5)>0)*(COLUMN(L1:P5)=
MAX(COLUMN(L1:P5)*(LEN(L1:P5)>0)))),
MAX(COLUMN(L1:P5)*(LEN(L1:P5)>0)), 4)
Press Ctrl + Shift + Enter
Obviously the second term catches the correct column (which is the easy part). The first term includes the second term in it, in order to search that column for the last populated row.
In the figure below it was applied on the range L1:P5 and yielded the correct result O4.
The only shortcoming I found so far is that it will error out if the range contains error cells, but from reading the OP's UDF it doesn't seem to be an issue. If it does, some additional IFERROR will solve it:
=ADDRESS(MAX(ROW(L1:P5)*IFERROR(LEN(L1:P5)>0, 0)*(COLUMN(L1:P5)=
MAX(COLUMN(L1:P5)*IFERROR(LEN(L1:P5)>0,0)))),
MAX(COLUMN(L1:P5)*IFERROR(LEN(L1:P5)>0, 0)), 4)
Press Ctrl + Shift + Enter
EDIT: Added parameter 4 to the ADDRESS function to remove the $ from the result. The results in my tests match the OP's UDF after I modified it replacing r(j, i) with r.Parent.Cells(j, i).
Here is a non CSE version:
=ADDRESS(AGGREGATE(14,6,(ROW(INDEX(L1:P5,0,AGGREGATE(14,6,COLUMN(L1:P5)/(L1:P5<>""),1)-MIN(COLUMN(L1:P5))+1)))/(INDEX(L1:P5,0,AGGREGATE(14,6,COLUMN(L1:P5)/(L1:P5<>""),1)-MIN(COLUMN(L1:P5))+1)<>""),1),AGGREGATE(14,6,COLUMN(L1:P5)/(L1:P5<>""),1))
My first approach was similar to #ScottCraner's.
=ADDRESS(MOD(AGGREGATE(14,6,(ROW(L1:P5)+COLUMN(L1:P5)*10^7)*(L1:P5<>""),1),10^7),AGGREGATE(14,6,COLUMN(L1:P5)*(L1:P5<>""),1),4)
Here the first AGGREGATE is used to calculate maximum of:
COL_NUM*10^7+ROW_NUM
for nonempty cells (multiplication by 10^7 ensures column precedence). So this function technically returns both coordinates (e.g. for P4 it is 160000004 - 16th column and 4th row). MOD extracts row number.
But if one AGGREGATE can return both coordinates (as one number), the next step was to try find a formula to return the address using AGGREGATE only once. This is the best I could come up with:
=BASE(AGGREGATE(14,6,(DECIMAL(ROW(L1:P5),36)+36^6*(DECIMAL(ADDRESS(1,COLUMN(L1:P5),4),36)-1)*(L1:P5<>"")),1),36)
This formula:
decodes column letters from Base36 (shifted 6 digits left) to
decimal
decodes row number(!) from Base36 to decimal
calculates maximum for nonempty cells
encodes result as Base36
Drawbacks:
BASE was introduced in Excel2013
Formula return P000004 instead of P4 - but it is still valid cell address - can be use with INDIRECT
Performs a lot of calculations - it is only an attempt to solve the problem with one AGGREGATE only.

Concatenate permutations between two columns

I need help with an excel assignment.
Name City
---------------
John London
Maxx NY
Ashley DC
Paris
Solution for this must be:
John-london
John-NY
John-DC
John-Paris
Maxx-london
Maxx-NY
.
.
.
.so on.
Simply I have to add text of all elements in one column to text of all elements in other column. I will appreciate if a solution without macros or VB is provided.
You can use this formula (start in Row 1 and fill down until you run out of combinations):
=IFERROR(INDEX(L_1, CEILING(ROW()/COUNTA(L_2),1) ,1) & "-" &
INDEX(L_2, 1+MOD(ROW()-1, COUNTA(L_2)) ,1), "That's it!")
I'm using named ranges "L_1" and "L_2" to refer to the first and second lists respectively
Here's an Array Formula you can use, though you will need to modify the size of the matrix depending on how many entries you have
=CONCATENATE(INDEX(A:A,MMULT(ROW(A1:A3),TRANSPOSE(ROW(B1:B4))/TRANSPOSE(ROW(B1:B4)))),"-",INDEX(B:B,MMULT(ROW(A1:A3)/ROW(A1:A3),TRANSPOSE(ROW(B1:B4)))))
Assuming Column A is Names and Column B is Cities, you would select 12 cells (3 rows high, 4 columns wide), paste the above formula in the first cell and press Ctrl + Shift + Enter to create the array formula.
If you want to see a little simpler version to see what it does before the INDEX references, check with the same area:
=CONCATENATE(MMULT(ROW(A1:A3),TRANSPOSE(ROW(B1:B4))/TRANSPOSE(ROW(B1:B4))),"-",MMULT(ROW(A1:A3)/ROW(A1:A3),TRANSPOSE(ROW(B1:B4))))
Here's a screenshot (with the formula split in 2 lines) of the single formula displaying the output over multiple cells:
This is a simple example in VBA. It is intended to show the concept, not the best practices. Please use it to get you started, and get back here if you need more help, if you want to improve the performances, etc.
The example assumes that the two lists are in A1:An and B1:Bm, and the resulting list goes in column C.
Sub Test()
Dim R1 As Integer, R2 As Integer, R As Integer, NR As Integer
NR = ActiveSheet.UsedRange.Rows.Count
Columns(3).Clear
For R1 = 1 To NR
If Not IsEmpty(Cells(R1, 1)) Then
For R2 = 1 To NR
If Not IsEmpty(Cells(R2, 2)) Then
R = R + 1
Cells(R, 3) = Cells(R1, 1) & " - " & Cells(R2, 2)
End If
Next R2
End If
Next R1
End Sub
resulting column formula should be
=column1&"-"&column2

Sort values in an Excel spreadsheet

I've got a spreadsheet full of names and peoples' roles, like the one below:
Role Name Change
1 A Yes
2 A No
5 A N/Ap
1 B Yes
3 B No
2 C Yes
4 C No
I have to come up with a spreadsheet like the one below:
1 2 3 4 5 6
A Yes
B
C
Basically, it should retrieve the information from the first spreadsheet and be layed out clearly on the second one.
There are way too many names and roles to do it manually. VLMOVE won't work and I've tried MATCH and INDEX.
Alternative to #RocketDonkey (but thanks for more complete desired result!) could be to string together Role and Name (say in a column inserted between B & C in Sheet1 [because I think OP wants a separate sheet for the results]):
C2=A1&B2 copied down as required
then use a lookup in Sheet2!B2:
=IFERROR(VLOOKUP(B$1&$A2,Sheet1!$C$2:$D$8,2,FALSE),"")
copied across and down as required.
This assumes the grid for the results (as in question) has been constructed (and that there are 7 rows with data - adjust $8 as necessary otherwise.)
Agree with #Melanie that if you can force your data into a structure that can be interpreted as numbers (1 being yes, 0 being false, for example), Pivot tables are far and away the easiest way (since they will display numbers as the values - not the text). *(see below)
If you want to display arbitrary text, you could try this:
=IF(
SUMPRODUCT(--($A$2:$A$8=F$1),--($B$2:$B$8=$E2),ROW($A$2:$A$8))=0,"",
INDEX(
$A$1:$C$8,
SUMPRODUCT(--($A$2:$A$8=F$1),--($B$2:$B$8=$E2),ROW($A$2:$A$8)),
3))
This checks to see if the SUMPRODUCT of the three columns totals 0 (which will happen when no combo of x/y is matched (like Name: C, Role: 5, for instance), and if so, it returns "". Otherwise, it returns the value in column Value.
*A ‘pivot table option’ would be to represent the Change as a number (eg as formula in D2 copied down). Then create a pivot table from (in the example) A1:D8, with fields as shown. Copy the pivot table to a different sheet with Paste Special/Values (though shown in F11:K15 of same sheet in example). Then in that other sheet select row starting with Name A and as far down as required, Replace -1 with No, 1 with Yes and 0 with N/Ap.
AMENDED
You can use array formulas to reorganize your table, without having to change the its structure. Assuming the data is in the range A2:C8 on Sheet1 and the result table is to be in range A1:G4 on Sheet2, the following formula would be the first entry (role 1 and name A) in the result table.
=IFERROR(INDEX(Sheet1!$A$2:$C$8,MATCH(B$1&$A2,Sheet1!$A$2:$A$8&Sheet1!$B$2:$B$8,0),3),"-")
The MATCH formula returns the row number in which the role/name combination 1A occurs. The INDEX function returns the contents of the cell at the row number found by the MATCH formula and the column number 3, i.e., the Change column of your data table. The IFERROR returns "-" if the role/name combination is not in the data table.
Be sure to enter the formula using the Control-Shift-Enter key combination. Then copy the formula to the remaining cells of the result table.
The data table on Sheet1:
The result table on Sheet2:
Well since there's Excel-VBA tag, thought it would complete the solutions types by adding one in VBA :) The following code is not elegant, in any case you need to use code base, give it a try :)
Code:
Option Explicit
Public Sub sortAndPivot()
Dim d As Object
Dim ws As Worksheet
Dim sourceArray As Variant, pvtArray As Variant, v As Variant
Dim maxRole As Long
Dim i, j, k, m As Integer
Set d = CreateObject("Scripting.Dictionary")
Set ws = Worksheets("Sheet3") '-- set according to your sheet
'-- you could enhance by using an input box to select the range
sourceArray = Application.WorksheetFunction.Transpose(ws.Range("B3:D9").Value)
'-- max role number
maxRole = Application.WorksheetFunction.Max(ws.Range("B3:B9"))
'-- find unique name list
For i = LBound(sourceArray, 2) To UBound(sourceArray, 2)
If Not d.exists(sourceArray(2, i)) Then
d.Add sourceArray(2, i), i
End If
Next i
ReDim pvtArray(d.Count, maxRole)
pvtArray(0, 0) = "Name"
'-- add unique names from dictionary
j = 1
For Each v In d.keys
pvtArray(j, 0) = v
j = j + 1
Next
'-- add unique Role number list
For i = UBound(pvtArray, 2) To LBound(pvtArray) + 1 Step -1
pvtArray(0, i) = i
Next i
'-- sort into the correct positions
For k = LBound(pvtArray, 1) + 1 To UBound(pvtArray, 1)
For m = LBound(pvtArray, 2) + 1 To UBound(pvtArray, 2)
For i = LBound(sourceArray, 2) To UBound(sourceArray, 2)
If pvtArray(k, 0) = sourceArray(2, i) Then
If pvtArray(0, m) = sourceArray(1, i) Then
pvtArray(k, m) = sourceArray(3, i)
End If
End If
Next i
Next m
Next k
'Output the processed array into the Sheet in pivot view.
Range("F2").Resize(UBound(pvtArray) + 1, _
UBound(Application.Transpose(pvtArray))) = pvtArray
Set d = Nothing
End Sub
Results:
There is another way to go about it without VBA. If you create another column that concatenates the first two in the first spreadsheet, like so:
Role Name Change CheckColumn
1 A Yes 1A
2 A No 2A
5 A N/Ap 5A
1 B Yes 1B
3 B No 3B
2 C Yes 2C
4 C No 4C
Then you can use the Offset and Match functions together to find the change in the 2nd sheet. So assuming your data is laid from cell A1, the formula in cell B2 would be:
=iferror(offset(Sheet1!$A$1,match(B$1&$A2,sheet1!$D:$D,0),2),"")
Alternatively, if you put the concatenated column in sheet1 before the role column, you can use vlookup in sheet2, with the formula being:
=iferror(vlookup(B$1&$A2,sheet1!$A:$D,4,false),"")

Resources