I have a MS Excel list with only 5 values in column A. There will always be one random duplicated value within this list. For example:
A1:A5
Heart
Finger
Hair
Heart
Nose
Here Heart is listed twice. I need a formula that detects the value that occurs twice in the list, and type this formula into colum B1:B3 to create a new list. The outcome of the new list should be the remaining three values - hence should not include Heart since it occurs twice.
Another example with preferred outcome...
A1:A5 Heart, Nose, Hair, Finger, Finger
B1:B3 Heart, Nose, Hair
In this example Finger is listed twice (in colum A) and therefore excluded in colum B (the new list).
Would really appreciate all hjelp and adwice with respect to how this could be solved with a formula in Excel.
If you are OK with helper columns, which you should be, you can use this solution:
Helper column 1 (enter in cell B1 and drag down):
= COUNTIF(A:A,A1)
Helper column 2 (enter in cell C1 and drag down):
= IF(B1=1,RANK(B1,B:B,1)+COUNTIF(B$1:B1,B1)-1,0)
Output column (enter in cell D1 and drag down):
= INDEX(A:A,MATCH(ROW(),C:C,0))
See below working example:
If necessary, you can use IFERROR to handle the errors in the D column.
Two formulas:
You can use:
=IFERROR(INDEX($A$1:$A$5,AGGREGATE(15,6,1/(COUNTIF($A$1:$A$5,$A$1:$A$5)=1)*ROW($A$1:$A$5),ROWS($1:1))),"")
If you have Excel 2016 with the TEXTJOIN function, you can use:
=IFERROR(INDEX(FILTERXML("<t><s>" & TEXTJOIN("</s><s>",TRUE,$A$1:$A$5) & "</s></t>","//s[not(.=preceding::*) and not(.=following::*)]"),ROWS($1:1)),"")
and fill down.
create an XML where each row is a separate node.
use FILTERXML with an xPath that filters out any node that is followed by or preceded by it's identical twin.
EDIT: If your version of Excel is prior to 2010, and lacks the AGGREGATE function, you can use the following array formula:
=IFERROR(INDEX($A$1:$A$5,SMALL(IFERROR(1/(COUNTIF($A$1:$A$5,$A$1:$A$5)=1)*ROW($A$1:$A$5),10^6),ROWS($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
Couple options:
If you don't care about position of the outputted elements, then this formula works:
=IF(COUNTIF($A1:$A$5,A1)>1,"",A1)
A VBA approach:
Public Function GetUniqueEntries(InputRange As Range) As Variant
Dim Cell As Range
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
For Each Cell In InputRange
If Not dict.exists(Cell.Value2) Then dict.Add Cell.Value2, Cell.Value2
Next
GetUniqueEntries = Application.Transpose(dict.Items())
End Function
Enter this as =GetUniqueEntries(A1:A5). This is an array based formula, so remember to select a wide enough output selection and press Ctrl+Shift+Enter.
Related
Let's say I have in B2 the formula =ROW(A1:A3). This formula gives an array of {1;2;3}. Because a cell only holds one value, cell B2 displays a value of 1. Any place in the worksheet, the formula =B2 gives 1, not the array. Yet, Excel still remembers the array because the formula is still in B2.
Is there any way to get the array back so it, the whole array, not its individual elements, can be used for further manipulation? I'm looking for something like OPERATION(B2) = {1;2;3}. For example, SUMPRODUCT(OPERATION(B2)) = SUMPRODUCT(ROW(A1:A3)) = 6.
As a workaround, you can store your formula in Name Manager, e.g.:
Then you can use it as a reference in Excel formulas, like =INDEX(Rows,2,1):
I realize that this is not the answer to the OP's question as they do not have the latest Office 365. I put this here for those who come later and do have it.
With the latest Office 365 with Dynamic array formulas this problem is now gone.
Putting =ROW(1:3) or the equivalent dynamic array formula =SEQUENCE(3) Excel will spill the array down automatically without the need of Ctrl-Shift-Enter:
And now one can refer to the array by only refering to A1 by putting # after the reference:
=SUM(A1#)
Then no matter how the array in A1 changes the other formula does not need to be changed.
The caveat is that the formula in A1 needs the room to spill down. If there is a cell that has anything in it that resides in the spill down array it will return an error.
Just to try and clarify this, arrays in Excel before the September 2018 update for Office 365 don't self-expand like they do in Google sheets, so if you put an array into a single cell you just get the first element of the array (see column A for example).
If you want to enter the array directly into the sheet, you have to select the cells you want it to occupy (say B2:B4) and enter it as an array formula (see column B) using CtrlShiftEnter
You can still use the array in another formula as long as it's expecting an array - e.g. with sumproduct you get the correct total, 6 (see column C).
=SUMPRODUCT(ROW(A1:A3))
Unfortunately this doesn't always work without entering the whole formula as an array formula (see column D).
=SUM(ROW(A1:A3))
EDIT
#JvDV is correct, you don't always get the first element of an array expression entered into a single cell - see this reference for explanation of the implicit intersection mechanism.
I have two methods, which might qualify as solutions or workarounds, depending on what technology you're willing to accept.
Both rely on retrieving the formula that produces the array from B2 and then evaluating that formula each time you invoke your OPERATION(B2).
Method 1: VBA
Create a UDF:
Function Arr(R as Range)
Arr = Application.Evaluate(R.Formula)
End Function
which you then invoke in a cell, e.g. =SUM(Arr(B2)).
Method 2: Name Manager + EVALUATE()
Note that this is different from Justyna's answer. The idea is to allow you to specify the address of the cell holding the array-generating formula.
Create a name that holds =EVALUATE(FORMULATEXT(INDIRECT(INDEX($1:$1048576;ROW();COLUMN()+1)))). This way you can specify that you want the content of the cell B2 by writing the string B2 to the nearest cell to the right of the cell in which you're using your newly created name.
Unfortunately, EVALUATE() cannot be used within a cell.
I am trying to identify inventory shortages for each store and the type of bread I am supplying.
Example table showing the demand for each type of bread by store.
Row 8 is what I am trying to accomplish with a formula.
Based on the quantity on hand, I can conditionally format to highlight cells red or green based on shortages.
I can't get a formula to count the number of red cells.
I am thinking I need to use sumproduct.
=SUMPRODUCT(--($B9:$B11<C9:C11))
The above works for identifying shortages for Store A (Column C). However, when dragged to column D and E it doesn't remember what the other stores needed.
I can't get the rows to sum without adding ALL rows in the range. I need each row to be individually compared to the qty on hand. I assume an array needs to be used.
This is the formula I mentioned. It uses a standard method with mmult to get the row totals of the matrix, then compares them with the amounts available:
=SUM(--($B9:$B11<MMULT($C9:C11,TRANSPOSE(COLUMN($C9:C11)^0))))
entered in C8 and pulled across. Must be entered as an array formula using CtrlShiftEnter
EDIT
OP has commented that it shouldn't be listed as a shortage if a store doesn't need a particular item, even if the stock of that item has been exhausted.
So there should be an extra condition for it to be registered as a shortage only if the current column has a number >0 in a particular row as well as the row sum being greater than the amount available:
=SUM((C9:C11>0)*($B9:$B11<MMULT($C9:C11,TRANSPOSE(COLUMN($C9:C11)^0))))
If you wanted to select only some of the rows as well it would look like
=SUM(ISNUMBER(MATCH($A9:$A11,{"Wheat","Rye"},0))*(C9:C11>0)*($B9:$B11<MMULT($C9:C11,TRANSPOSE(COLUMN($C9:C11)^0))))
EDIT: FIXED TO WORK FOR CONDITIONAL FORMATTING:
Paste this into the module:
Public Function CellColour(addr)
CellColour = Range(addr).DisplayFormat.Interior.Color
End Function
Function FINDRED(rng As Range)
FINDRED = 0
Dim cell As Range
For Each cell In rng
If (cell.Parent.Evaluate("CellColour(""" & cell.Address & """)") = RGB(255, 0, 0)) Then
FINDRED = FINDRED + 1
End If
Next cell
End Function
You can then use this like a normal excel formula:
I have a small tool that generates all the combinations of a set of items using binary patterns. The tool works, but it now requires a "helper" column for each item. (the tool requires no VBA)
I need some help to remove the "helper" columns.
I put the items in the first row starting with B1. In A2, I enter:
=DEC2BIN(ROW()-1,COUNTA($1:$1))
and copy downwards. (this makes the binary patterns)
I fill the "helper" columns (B through E) by putting:
=--MID($A2,COLUMNS($A:A),1)
in B2 and copying both across and downward.
Finally in F2 I enter the array formula:
=TEXTJOIN(",",TRUE,IF($B2:$E2=1,$B$1:$E$1,""))
and copy downwards:
As you can see, the formula is really easy. It looks for the 1's in columns B through E and joins the appropriate words from the first row.
I am trying to replace the $B2:$E2=1 with some kind of MID(A2) function. (this will eliminate the need for columns B, C, D, ...)What I have tried is the array formula:
=TEXTJOIN(",",TRUE,IF(MID(A2,ROW(INDIRECT("1:" & LEN(A2))),1)=1,$B$1:$E$1,""))
But this just yields blanks. Any help will be greatly appreciated!
EDIT#1:
If I use:
=TEXTJOIN(",",TRUE,IF(MID(A2,ROW(INDIRECT("1:" & LEN(A2))),1)="1",$B$1:$E$1,""))
I get a large set of the items, unrelated to the binary pattern.
This array formula (CSE) will eliminate B:E, and is relatively easily expandable to handle more names. It assumes you have the TEXTJOIN function (Office 365)
=TEXTJOIN(",",TRUE,IFERROR(INDEX({"Larry";"Moe";"Curly";"Shep"},N(IF(1,N(AGGREGATE(15,6,1/MID(A2,{1,2,3,4},1)*{1,2,3,4},{1,2,3,4}))))),""))
If permissible, you can replace the array constant containing the names with a vertical list of names.
EDIT: To make the formula more dynamic, enter the list of Names in some column, and NAME it Names, then you can use this formula:
=TEXTJOIN(",",TRUE,IFERROR(INDEX(Names,
N(IF(1,N(AGGREGATE(15,6,1/MID(A2,ROW(INDIRECT(
"1:"&COUNTA(Names))),1)*ROW(INDIRECT("1:"&COUNTA(
Names))),ROW(INDIRECT("1:"&COUNTA(Names)))))))),""))
Of course, you'd then change the A2 formula to:
=IFERROR(DEC2BIN(ROW()-1,COUNTA(Names)),"")
and fill down until you get blanks.
EDIT2: Eliminating Column A, and with a named range named Names:
=TEXTJOIN(",",TRUE,IFERROR(INDEX(Names,N(IF(1,AGGREGATE(
15,6,1/MID(IFERROR(DEC2BIN(ROW()-1,COUNTA(Names)),""),
ROW(INDIRECT("1:"&COUNTA(Names))),1)*ROW(INDIRECT(
"1:"&COUNTA(Names))),ROW(INDIRECT("1:"&COUNTA(Names))))))),""))
This will manually pick up up and return, have to change the frozen values, but its limited to combine only 4 words, the formulas in column A produce a text based result.
=TEXTJOIN(",",TRUE,IF(LEFT(A2,1)="1",$B$1,""),IF(MID(A2,2,1)="1",$C$1,""),IF(MID(A2,3,1)="1",$D$1,""),IF(RIGHT(A2,1)="1",$E$1,""))
I need a formula that will firstly determine which column of a table to search in, based on the contents of a specified cell matched to a column header on the table. So let's say the cell with the required word is on Tab 1, in cell A1 and contains the word Task. The formula needs to search the column headers which are in Row 1 on Tab 2 to locate the column which has the header Task.
It then needs to search down that column to locate the words mandatory, which will appear multiple times, and return the contents of the adjacent cells in column B.
I have reviewed the array formulas on the following website:
http://www.get-digital-help.com/2012/03/28/search-for-a-text-string-and-return-multiple-adjacent-values/
The below formula gets me part of the way there but it does not fulfil my initial requirement as it fixed and is searching column A for my search term.
{INDEX($B$1:$B$5,SMALL(IF(ISNUMBER(SEARCH($E$1,$A$1:$A$5)),MATCH(ROW($A$1:$A$5),ROW($A$1:$A$5))),ROW(A1)))}
I've also reviewed this page Get column by finding value in the row. Barry's INDEX Formula seems to be what I'm after but I'm having trouble integrating the two formulas together.
Any help would be much appreciated.
In B1 of Sheet1:
=COUNTIF(INDEX(Sheet2!$1:$1048576,,MATCH(A$1,Sheet2!$1:$1,0)),"mandatory")
In C1 of Sheet1, array formula**:
=IF(ROWS($1:1)>B$1,"",INDEX(Sheet2!B:B,SMALL(IF(INDEX(Sheet2!$1:$100,,MATCH(A$1,Sheet2!$1:$1,0))="mandatory",ROW(Sheet2!A$2:A$100)-MIN(ROW(Sheet2!A$2:A$100))+1),ROWS($1:1))))
Copy the formula in C1 down (though not that in B1, which is used to simply count the expected number of returns) until you start to get blanks for the results.
See here for an explanation as to why it is preferable to reference an additional counting cell (B1) rather than resort to IFERROR in such constructions
Edit: the key construction here is the part:
INDEX(Sheet2!$1:$100,,MATCH(A$1,Sheet2!$1:$1,0))
which takes advantage of the fact that if either (or both) of the row or column parameters passed to INDEX is zero (or, equivalently, omitted) and the INDEX is properly coerced (e.g. forms part of a larger formula), a reference to the entire specified column or row is generated.
Hence, and assuming that the value in A1 occurs in column D of Sheet2, the above will resolve to:
INDEX(Sheet2!$1:$100,,4)
which is
Sheet2!$D1:$D100
See here for more on this property of INDEX.
Regards
**Array formulas are not entered in the same way as 'standard' formulas. Instead of pressing just ENTER, you first hold down CTRL and SHIFT, and only then press ENTER. If you've done it correctly, you'll notice Excel puts curly brackets {} around the formula (though do not attempt to manually insert these yourself).
I'm not 100% certain that you'll be able to do what you want with merely an array formula. Although, there are folks haunting SO that are better at Array formulas than me.
If you don't mind going the UDF route, you can use this UDF by sticking it in a new module in your workbook:
Function getList(headerValue As String, rowValue As String, lookupRange As Range, returnOffset As Integer) As String
Dim lookupCol As Range, lookupCell As Range
Dim getListOut As String
'find the column to lookup into
For Each lookupCol In lookupRange.Columns
If lookupCol.Cells(1, 1).Value = headerValue Then Exit For
Next lookupCol
'search each cell in the column (for the lookupRange) for matchValue
For Each lookupCell In Intersect(lookupCol, lookupRange).Cells
'See if we have a match
If lookupCell.Value = rowValue Then
'Concatenate if necessary to the output
If getListOut = "" Then
getListOut = lookupCell.Offset(, returnOffset).Value
Else
getListOut = getListOut & "," & lookupCell.Offset(, returnOffset).Value
End If
End If
Next lookupCell
'return
getList = getListOut
End Function
And using it in your cell (like B1 on your first tab "sheet1"):
=getList(A1,"mandatory", Sheet2!A1:G6, -3)
Here A1 is the header value to search for, "mandatory" is the cell value to find in that header's column, Sheet2!A1:G6 is the table to search in, and -4 is the column offset to get the value from whatever row we found with `"mandatory" in it.
I put the word "task" in A1 and in the header (column F) on Sheet2 for that table. I put the three rows with the word "mandatory" and in Column B I put "d", "e", and "f" for each mandatory. This returned d,e,f as expected.
It's kind of like a hlookup in a vlookup that returns multiple hits in a comma delimited list, that also allows a negative offset.
I have this code below in the form of an array and I was wondering if there was a cleaner and more dynamic way to code it. (I have removed the $ just to make it easier to read)
SUMPRODUCT(IF((A1:A10 <> D1)*(A1:A10 <> D2),B1:B10))
Column A has a list of names
Column B has a list of values
Column D has a list of names not to include in the calculation
The issue with this formula is that for each new item in column D I have to append another * which would start to make a massive formula.
I tried SUMPRODUCT(IF((A1:A10 <> D1:D2),B1:B10)) but it did not work. Does anyone have any ideas?
P.S. You have to hit CTRL + SHIFT + ENTER to make the cell an array, the formula wont work otherwise.
EDIT: and I can't have D1:DX be the same size as the other ranges as I need the X to be dynamic for my specific scenario
Let's use a helper column to find the "good" rows. In C1 enter:
=IF(ISERROR(MATCH(A1,D:D,0)),1,0)
and copy down. Each "good" name is marked with a 1. In another cell:
=SUMPRODUCT(--(C1:C100)*B1:B100)
For example:
The "helper " column avoids the need for an array formula. You can have as many "bad" names in column D as you like without having to change the formula.
Enter this formula in 'E1':
=SUMPRODUCT($B:$B,ISERROR(MATCH($A:$A,$D:$D,0))*1)