I have to copy some data from one table to another. There are several groups of columns that are the same except for a name at the start of the column header. I want to put those names into an array, and then loop through the groups doing the same operations on each group. I'd like to do this using an enumeration of the headers, but I can't find a way to reference the members of the enumeration by concatenating the name and the remainder of the header. Boiling it down to the essentials, the code would look like this:
Enum Headers
a1 = 1
b1
c1
End Enum
Sub test()
Dim Pick() As Variant
Pick = Array("a", "b", "c")
For i = 1 To 3
n = Headers.Concat(Pick(i), "1")
MyTable.DataBodyRange.Cells(Row, n) = OtherTable.DataBodyRange.Cells(Row, n)
Next i
End Sub
I know how to work around this, using a slightly different approach, and that's what I think I'll end up doing.
MyTable.ListColumns(Pick(i) & "1").DataBodyRange.Cells(Row,0)
That works because the column reference is to a string, so I can piece the string together in that fashion.
But I wanted to know if anyone has a way to use something akin to what I outlined above using an enumeration.
n is the value of the enumeration member, so it could be 1, 2, or 3.
This would be the column where I want to copy or paste data. The row would be "Row", so the reference is to Cells(Row, n).
Instead of specifying the member as a1, b1 or c1, I want to use an expression Concat(Pick(i), "1"), where Pick(i) will be "a", "b", or "c".
Both tables would have headers a1, b1, c1.
The problem is that
n = Headers.Concat(Pick(i), "1")
but
n = Headers.a1
would be valid. I just can't substitute Concat(Pick(i) for a1, since a "valid VB identifier" is required following "Headers.", not a string.
is not a valid statement.
My question is whether there is some way to accomplish what the invalid code is trying to do. Sort of like an indirect reference in Excel, where you can turn a string into a range or file reference.
Related
I am using the new dynamic array functions introduced in excel in 2018 (e. g. SEQUENCE, UNIQUE etc. functions).
I have a list of cell references that are that are generated dynamically, and would like to apply the INDIRECT function to these list items. A simplified example:
cell A1: =SEQUENCE(5) (results in rows column A values 1,2,3,4,5 as expected)
cell B1: ="A"&A1# (results in rows column B values A1, A2, A3, A4, A5 as expected)
cell C1: =INDIRECT(B1#) this should give me rows in column C values 1,2,3,4,5, but in fact gives me #VALUE ,#VALUE ,#VALUE ,#VALUE ,#VALUE
So the formula properly recognizes the number of rows of the original dynamic array, but for some reason does not dereference the cells properly. The strings seem to be of the proper format - a simple string function such as LEN also works: setting C1 to =LEN(B1#) results in 5 rows of the value 2.
The syntax per se seems to be OK.. for the special case of =SEQUENCE(1) in cell A1 everything works as intended. I tried the R1C1 reference format also, same result
EDIT
Overall I am trying to achieve the following
import a list form a non-Excel data source list is not a dynamic array, it's just a TSV import. I don't now beforehand how many items are in this list, and it can vary a lot
do several different calculations on values of this list.
so far my approach was to use the COUNT function to determine the number of items in the imported list, and then use that to create the second list using SEQUENCE and INDEX to retrieve values.
the problem arises for some calculations where the data contains references to other rows so I have to use indirect addressing to get at that data
The INDIRECT function cannot accept an array for an argument.
In other words:
=INDIRECT({"a1","a2"}) --> #VALUE! | #VALUE!
So you could, for example, refer to each cell in column B as a single cell:
eg:
C1: =INDIRECT(B1)
and fill down.
Depending on how you are using this, you could also use the INDEX function to return an individual element
To return the third element in the array generated by B1#:
=INDIRECT(INDEX(B1#,3))
EDIT:
After reading your comment, and depending on details you have not shared, you may be able to use a variation of the INDEX function.
For example, to return the contents of A1:A5, based on your SEQUENCE function, you can use:
=INDEX($A:$A, SEQUENCE(5))
but exactly how to apply this to your actual situation depends on the details.
As Rosenfeld points out, INDIRECT() does not accept an array as an input. If you need a function that:
"acts" like INDIRECT()
can accept an array as an input
can return an array as an output
Then we can make our own:
Public Function Indirect_a(rng As Range)
Dim arr, i As Long, j As Long
Dim rngc As Long, rngr As Long
rngc = rng.Columns.Count
rngr = rng.Rows.Count
ReDim arr(1 To rngr, 1 To rngc)
For i = 1 To rngc
For j = 1 To rngr
arr(j, i) = Range(rng(j, i).Value)
Next j
Next i
Indirect_a = arr
End Function
and use it like:
Since it creates a "column-compatible" array, it will spill-down dynamically in Excel 365.It can be used in versions of Excel prior to 365, but it must be array-entered into the block it occupies.
You can use the following formula
=BYROW(B1#,LAMBDA(a,INDIRECT(a)))
I'm trying to build a toll to make data analysis. I want to code a text box that will show the values that compound the result of each cell I select. For instance: the cell A1 contains the value "2", the cell B1 contains the value "3" and the cell C1 is =A1*B1 , so "6". I want to add a text box that when I select the cell C1 will bring the text "2*3" instead of "A1*B1" as it shows in the formula bar. Does anyone knows how to do it?
The challenge you're going to face is this: there is no such thing as a "formula" object in the Excel object model, because a formula is a string of essentially arbitrary length and construction, arbitrary levels of nesting and cell references/precedents, each of which may contain a formula (which would require some recursion) or a constant, etc. There is no "general" way to parse these things, although as the linked answer indicates, there are some tools out there which may do something similar.
If you have specific cases, you can probably hack something that will work for those cases, but will not be generally applicable to other formulae. Using a range object's .DirectPrecendents, you can trace cells which are referred to in the formula, and using some string functions you may be able to reverse-engineer the formula to constant expressions, and recombine with the operators. HOWEVER, my own testing shows this to be fraught with problems because each of these formula will return the same list of DirectPrecedents:
=$A1*$B1
=$A$1*$B$1
=$A$1*B1
=A1*B1
In those cases (and others), the precedents will be the ranges A1 and B1, respectively, but each of those variants would require that you can identify whether the row and/or column in each precedent is "absolute", at runtime, otherwise, any attempts to use string functions like Mid, Left and Instr will likely fail or return false positives.
If your formula are exceedingly simple, and you know you are always dealing with ONLY references, each of which contain constant expressions (not other formula refernces), e.g.:
=$A$1*B$1 'Where A1 contains a constant and so does B1
You could do something like:
Function parseFormula(cl as Range, operator as String)
Dim arr, i As Long
Dim ret As String
ret = "="
arr = Split(Mid(cl.Formula, 2), operator)
' yields an array like {$A$1, $B1}
For i = 0 To UBound(arr)
If (i = 0) Then
ret = ret & Range(arr(i)).Value
Else
ret = ret & operator & Range(arr(i)).Value
End If
Next
parseFormula = ret
End Function
Of course this will not work for for formula with mixed operators like:
=A1+B1/C1
Nor will it work for formula which mix constants and references like:
=65+B2/C1
I've tried the concatenate function but it wasn't really useful.
I'm trying to accomplish, so here's a screenshot/picture
Basically I need to be able to input data for the score, then have the cell autofill the data based on the users and their score with the corresponding tile, my confusion is how to do this if/with multiple users if they're on the same tile.
OK... i don't know if you have a mechanism for Tile 1 knowing that it is Tile 1 etc., but what I did was to put the numbers onto another sheet in the same cells. I.e. on sheet Tiles cell B18 has 1. In this way we can just reference that cell rather than any hardcoded values in the formula...
I also decided that if there were no names with that score, then we would just want the tile name (e.g. Tile 1)
So these give the first part of the formula that can be written into B18 and copied to the range B18:F31:
=IF(Tiles!B18="","",IF(ISERROR(MATCH(Tiles!B18,$B$7:$H$7,0)),"Tile "&Tiles!B18,...))
What this is doing is:
checking the Tiles sheet - if the corresponding cell is empty, then this isn't a tile and we just put ""
using MATCH to see if that score is held by any user - if it isn't found then it gives an error so the IF(ISERROR(...)) will return just the tile name
the ... is where the two options come in...
OPTION 1 - Loooong Formula
Replace the ... by:
MID(IF($B$7=Tiles!B18,", "&$B$2,"")&IF($C$7=Tiles!B18,", "&$C$2,"")&IF($D$7=Tiles!B18,", "&$D$2,"")&IF($E$7=Tiles!B18,", "&$E$2,""),3,9999)
...and copy the &IF($E$7=Tiles!B18,", "&$E$2,"") section with each needed column for your 22 players.
So the unit is "if the player's score is the same as the tile number, give me ", name" otherwise blank" and we join these with the & operator.
This is wrapped in a MID(...,3,9999) so we get rid of the ", " at the start of the first name we find (the joined IF statements would give e.g. ", Aaron, infinite user").
This option is a bit painful to write (and read!) but it only has to be done once and the formula can be copied and pasted...
OPTION 2 - VBA Function and Array Formulas
Public Function JOIN(rng As Variant, Optional separator = ", ") As String
JOIN = ""
Dim v As String
For Each c In rng
If IsObject(c) Then
v = c.Text
Else
v = c
End If
If v <> "" Then
If JOIN = "" Then
JOIN = v
Else
JOIN = JOIN & separator & v
End If
End If
Next c
End Function
This function will join either a range of cells' values, or an array of strings. Using it with an array formula (entered in Excel using Ctrl+Shift+Enter) means that we can do a conditional join...
So the ... becomes:
JOIN(IF($B$7:$H$7=Tiles!B18,$B$2:$H$2,""))
For this, each value in the scores range $B$7:$H$7 will be checked against the tile number. If it doesn't match, we return "", otherwise we return the corresponding name (in range $B$2:$H$2). This will give us an array of blanks and names which JOIN will join...
This option means a macro enabled workbook and using "more complex" array formulas. But array formulas are great, and I advise everyone to play with them ;-). cpearson has a good intro: http://www.cpearson.com/excel/arrayformulas.aspx
Have fun!
In Excel, the SUMIFS function is great for creating a summation occurrences where multiple conditions on multiple ranges are met. However, I am looking for a similar function, that returns the first value where these conditions are met. For instance, a table contains a column with first names, a columns with surnames, and a column with User IDs. Given the first name and surname, I need to look up the person's User ID.
Is there a formula for this? I have tried searching all kinds of combinations with "if" and "ifs", but to no avail. I do not want to write a macro for something so simple.
Though you would prefer not to write a macro for something simple like this, in this case I would say that this is the simplest solution. A short function like this would be useful, if you decide to employ this type of technique down the road at some point:
Function FindRowIndex(firstName As String, lastName As String) As Integer
With ThisWorkbook.Sheet1
For j = 1 To NumLines
If .Cells(j, 1) = firstName And .Cells(j, 2) = lastName Then
FindRowIndex = j
Exit Function
End If
Next j
End With
End Function
this works...
=INDEX(C1:C9,SUMPRODUCT((A1:A9=A10)*(B1:B9=B10)*ROW(1:9)))
the following picture shows it in position... http://i.stack.imgur.com/VFaC9.png
this picture shows the result you require... http://i.stack.imgur.com/Id0BZ.png
obviously if you need more than 9 rows just adjust the 4 references within the formula.
I have some cells with, for an example, this value:
5*A-2*B-4*C
In other cells I would put the values for A, B and C. I would like to make additional cell that would count the value. So, for an example, if in some cells it is written that the value of A is 2, value of B is 3 and value of C is 1, I would like an additional cell that would calculate and put the value 0 (that's the result of 5*2-2*3-4*1). Possible variables are A, B and C, but they don't have to be contained in every cell (e.g., some cell may be just 5*A-3*C).
Is that possible? Does anyone know how to write that function?
P.S. I can't split manualy values in different cells because there are hundreds of them.
Thanks.
The comment from Saladin Akara points the way to your simplest solution. Define some named formulas (see the Excel help topic "Working with names") for A, B, and C. Then any other cells can contain formulas that use those named values.
If that's not enough, for example if you really want to see and edit the formula in a cell, and then calculate the value of the formula in a different cell, you can use Excel's built-in evaluator without having to parse the formula yourself. The easiest way to do so is via the Evaluate method of the Application object. (Again, see the help.) Charles Williams has example code on his website that evaluates an Excel expression here: http://www.decisionmodels.com/calcsecretsh.htm
Going beyond that, you can use Application.Evaluate to evaluate expressions with (scalar) parameters without defining any names, and still without actually parsing the formula, by doing some rudimentary string replacement. There are several examples on the web, but a very good one from Doug Jenkins is here: http://newtonexcelbach.wordpress.com/2008/04/22/evaluate-function/
This can be done with the VBA function EVALUTE
simple example:
Function ev(f As Variant, A As Range, B As Range, C As Range) As Variant
Dim s As String
s = f
s = Replace(s, "A", "~A~")
s = Replace(s, "B", "~B~")
s = Replace(s, "C", "~C~")
s = Replace(s, "~A~", A.Address)
s = Replace(s, "~B~", B.Address)
s = Replace(s, "~C~", B.Address)
ev = Evaluate(s)
End Function
eg if your expression is in A2, and the values of A, B, C are in C2:E2
=ev(A2,C2,D2,E2)
returns the calculated value
You would want to use a ParamArray instead of A, B, C the variable values to allow for an arbitrary number of variables.
A slightly more complete version:
Function ev(expr As Variant, VarNames As Range, varValues As Range) As Variant
Dim s As String
Dim i As Long
s = expr
For i = 1 To VarNames.Columns.Count
s = Replace(s, VarNames.Cells(1, i), "~" & VarNames.Cells(1, i) & "~")
Next
For i = 1 To VarNames.Columns.Count
s = Replace(s, "~" & VarNames.Cells(1, i) & "~", varValues.Cells(1, i).Address)
Next
ev = Evaluate(s)
End Function
Usage:
Same data as above plus variable names in C1:E1
=ev(A2,C1:E1,C2:E2)