4

Learning VBA for Excel, I am trying to do as much of my coding without the use of loops. As an exercise, multiplying the numbers of two adjacent ranges, I came up with this:

Sub multiply_range()

Dim a, b, c As Range
Set a = Range("a1:a5")
Set b = Range("b1:b5")
Set c = Range("c1:c5")

a.Value = Evaluate("row(" & a.Address & ")")
b.Value = Evaluate("row(" & b.Address & ")")
c.Value = Evaluate(a.Address & "*" & b.Address)

End Sub

Which works quite nicely. Now I want to do something similar but using arrays instead. Starting with this code:

Sub multiply_array()

Dim aArr(), bArr(), cArr()
ReDim aArr(5), bArr(5), cArr(5)

For i = 0 To 4
    aArr(i) = i + 1
    bArr(i) = i + 1
Next

For i = 0 To 4
    cArr(i) = aArr(i) * bArr(i)
Next

For i = 0 To 4
    Range("D" & i + 1).Value = cArr(i)
Next

End Sub

How would you replace any one of these FOR loops with code that doesn't use loops?

Community
  • 1
  • 1
Alex Wilding
  • 167
  • 2
  • 10
  • there's no way (that I know of) to do this, unless you want to "cheat" and write your array out to a range and then use the sumproduct function, or the method you have at the start. What's wrong with looping? I can't think of a language where you could do what you want w/o some kind of syntax sugar to make it look like you're not using a loop but the compiler turns it in to a loop 'under the hood', but I'm often wrong too...so take it for what it's worth. – sous2817 Aug 28 '15 at 14:15
  • @sous2817 ExcelHero just showed that it is possible – rohrl77 Aug 28 '15 at 14:19
  • @rohrl77 for an overly simple example. what if array1 was 12 to 17 and array2 was 45 to 50? Can you do that without looping? – sous2817 Aug 28 '15 at 14:20
  • @sous2817 In your example you obviously need to loop... but that doesn't mean ExcelHero's example is overly simple. The need to multiply two arrays that have the same size is something I come across often in my work. Given the OP had the same thing in mind, I'm obviously not the only one. – rohrl77 Aug 28 '15 at 14:26
  • @sous2817 As for what happens under the hood... you may be right. I've heard the same arguments mentioned elsewhere. – rohrl77 Aug 28 '15 at 14:27
  • Just curious - why don't you want a loop? Is it something practical for you or is it just for curiosity's sake? – BruceWayne Aug 28 '15 at 14:32
  • @rohrl77 both of my arrays are the same size (the elements being the numbers I have in the range)...still you could probably do some math to figure out and concoct a formula to solve that. But if one array was [1,14,9,15,23] and the other was [23,6,19,46,7]. You can't evaluate that without a loop (or writing to a range and using a formula). My point being, what the OP is asking for in his title doesn't match what he'd like to do in his example. The solution *isn't* multiplying two arrays together (and absolutely no disrespect to Excel Hero's solution, it's solving the question asked). – sous2817 Aug 28 '15 at 14:32
  • @sous2817 Ok. Title doesn't match the answer given. But the example the OP gave is in fact multiplying two arrays... hence the answer by ExcelHero. I am also interested in the question of doing it totally "off sheet" though, so I'll wait to see if someone has an answer for that. – rohrl77 Aug 28 '15 at 14:39
  • @rohrl77 completely agree with you, and Excel Hero gave a great example on how to solve his problem w/o using arrays, which is what the OP should be doing (not using an array). If you're waiting for someone to show you how to multiply two arrays together w/o looping, you're going to be waiting a long time (unless you can simplify the arrays down and use a formula, which is what Excel Hero did). – sous2817 Aug 28 '15 at 14:46
  • @sous2817 *what if array1 was 12 to 17 and array2 was 45 to 50?* That's not any trouble: `[d1].Resize(n) = Evaluate("(11+row(1:" & n & "))*(44+row(1:" & n & "))")` – Excel Hero Aug 28 '15 at 15:03
  • @ExcelHero yep, that is why I said "still you could probably do some math to figure out and concoct a formula to solve that." what about [1,14,9,15,23] and [23,6,19,46,7], can you solve that with Evaluate? I guess I should change my original comment to say something along the lines of "For a lot of situations, there is no way to multiply two arrays together without looping. In situations where you can, you could just as easily use a function to solve the problem and can skip the array part all together." – sous2817 Aug 28 '15 at 15:10
  • 1
    @ExcelHero foot meet mouth :) Thank you for showing me a way to to it! – sous2817 Aug 28 '15 at 15:23
  • @sous2817 My pleasure. – Excel Hero Aug 28 '15 at 15:27

3 Answers3

7

Here you go:

Sub Squares()
    Dim n&
    n = 5
    [d1].Resize(n) = Evaluate("row(1:" & n & ")^2")
End Sub

UPDATE

Here is a variant that uses no loops and no ranges:

Sub Squares()
    Dim a, b, n&
    n = 5
    a = Array(1, 2, 3, 4, 5)
    b = Array(1, 2, 3, 4, 5)
    [d1].Resize(n) = Evaluate("{" & Join(a, ";") & "}*{" & Join(b, ";") & "}")
End Sub
Excel Hero
  • 14,253
  • 4
  • 33
  • 40
  • @Daniel Can you explain what the `&` does in `n&`? – rohrl77 Aug 28 '15 at 14:16
  • 1
    @rohrl77 It's a short-cut. `n&` = `n as Long` http://stackoverflow.com/a/8341552/1316573 – Daniel Aug 28 '15 at 14:19
  • 1
    Doesn't quite answer what I was asking, (your code is still manipulating ranges rather than array variables) but I like the way you did it. Quite elegant. – Alex Wilding Aug 28 '15 at 14:28
  • 1
    Actually, the code does manipulate two arrays. It's an array formula. True, the work is done by the worksheet formula calculation engine, but they are indeed arrays. Perhaps you should specify that you want the work done with SAFEARRAYs. – Excel Hero Aug 28 '15 at 14:43
  • @DanielCook While I understand your meaning, Type Declaration Characters are far older (in the BASIC family of languages) than the verbose way to specify variable types. From a historical perspective, Dim n As Long is actually a long-cut. – Excel Hero Aug 28 '15 at 15:07
  • @rohrl77 Updated answer with a way this can be done without ranges. – Excel Hero Aug 28 '15 at 15:27
  • 1
    This is neat but note that this method works only for small arrays as the `Evaluate` method is limited to 255 characters. To extend to larger arrays you can add two UDF's for GetX() and GetY() as outlined in other answer – lori_m Aug 28 '15 at 16:16
  • @ExcelHero thans so much. I've been searching for this for a very long time! Didn't know that I could implement spreadsheet arrays in this way. Really useful! – rohrl77 Aug 30 '15 at 07:20
  • @rohrl77 You are welcome Lukas. How's ExcelNova going? – Excel Hero Aug 30 '15 at 07:25
3

For multiplying arbitrary arrays you can try pasting this code in a new module:

Dim X, Y

Sub MultiplyArrays()
Dim Z
X = Array(1, 2, 3, 4, 5)
Y = Array(1, 2, 3, 4, 5)
Z = [GetX()*GetY()]
Range("D1").Resize(UBound(Z)) = Application.Transpose(Z)   
End Sub

Function GetX()
GetX = X
End Function

Function GetY()
GetY = Y
End Function

Another slightly trickier way is to use worksheetfunction methods:

Sub MultiplyArrays2()
Dim X, Y, Z
X = Array(1, 2, 3, 4, 5)
Y = Array(1, 2, 3, 4, 5)
With Application
    Z = .PV(, 1, .PV(, X, Y))
    Range("D1").Resize(UBound(Z)) = .Transpose(Z)
End With
End Sub

Also see: Adding or multiplying variants in VBA

Community
  • 1
  • 1
lori_m
  • 5,487
  • 1
  • 18
  • 29
1

You can create a couple of helper-functions inspired by functional programming. You can store these in a module (together with other functions for manipulating arrays) and import them as needed.

Function Map(f As String, A As Variant) As Variant
    'assumes that A is a 1-dimensional variant array
    'and f is the name of a function that can be applied to it

    Dim i As Long
    Dim M As Variant

    ReDim M(LBound(A) To UBound(A))
    For i = LBound(A) To UBound(A)
        M(i) = Application.Run(f, A(i))
    Next i
    Map = M
End Function

Function ZipWith(f As String, A As Variant, B As Variant)
    'assumes that A,B are 1-dimensional variant arrays with the same bounds
    'and f is the name of a function with two variables
    Dim i As Long
    Dim M As Variant

    ReDim M(LBound(A) To UBound(A))
    For i = LBound(A) To UBound(A)
        Select Case f:
            Case "+":
                M(i) = A(i) + B(i)
            Case "-":
                M(i) = A(i) - B(i)
            Case "*":
                M(i) = A(i) * B(i)
            Case "/":
                M(i) = A(i) / B(i)
            Case "&":
                M(i) = A(i) & B(i)
            Case "^":
                M(i) = A(i) ^ B(i)
            Case Else:
                M(i) = Application.Run(f, A(i), B(i))
        End Select
    Next i
    ZipWith = M
End Function

Tested like thus:

Function square(x As Variant) As Variant
    square = x * x
End Function

Sub test()
    Dim A As Variant, B As Variant, squares As Variant, products As Variant

    A = Array(2, 3, 4)
    B = Array(5, 6, 7)

    squares = Map("square", A)
    products = ZipWith("*", A, B)

    Debug.Print Join(squares, " ")
    Debug.Print Join(products, " ")
End Sub

Note the absence of loops in the test sub. Output is as expected:

4 9 16
10 18 28
John Coleman
  • 51,337
  • 7
  • 54
  • 119