【问题标题】:How to access an array from a certain point every loop?如何在每个循环中从某个点访问数组?
【发布时间】:2016-06-18 13:36:55
【问题描述】:

问题详情

我下面的代码将计算结果存储在数组funds() 中,并针对所选范围重复该过程。到最后会有一个包含 170 个值的一维数组。我需要在每个循环中从某个点访问数组,以用不同的值填充新行。

问题详解

我遇到的核心问题是将该数组打印到工作簿上的一个范围,该范围由 10 行 x 17 列组成。

一旦“对于选定范围内的每个单元格”循环退出,我设法让它下降一行,但现在它用相同的初始数组值填充新行!

这是当前的输出:

我尝试了什么?

  • 我已经尝试过 Redim,但对示例的长度感到不知所措。
  • 我尝试过手动复制和粘贴,但感觉是作弊...
  • 我研究了如何通过复制过程删除元素..

总的来说,我相信每个人都知道如何使用的简单方法!但它是什么?

简而言之...

每个循环都会删除最初的 17 个值,然后将接下来的 17 个数组值打印到范围内的新行。重复 10 次。

代码

Option Explicit
Public funds(0 To 170) As Variant

Sub cumulativeperformance()

    Dim selectedrange As Range
    Dim cell As Range
    Dim value1 As Double
    Dim Value2 As Long
    Dim i, x, d, c As Integer
    Dim total(0 To 170) As Double

    'Copy the table to report
    Worksheets("Data").Range("C3:T13").Copy
    Sheets("Report").Range("B39").PasteSpecial
    Worksheets("Data").Range("B3:T13").Copy
    Sheets("Report").Range("A39").PasteSpecial xlPasteValues

    'Repeat 9 times
    c = 39
    For d = 0 To 9

        c = c + 1
        Set selectedrange = Worksheets("Report").Range(Cells(c, 3), Cells(c, 19))
        For Each cell In selectedrange

            value1 = cell.Value

            'get the value of cell to the left of current cell
            Value2 = cell.Offset(0, -1).Value

            'get the difference to previous month
            value1 = value1 / Value2 - 1

            'assign result + 1 to array
            total(x) = value1 + 1

            'If initial fund slot is 0, then store first result of calculation in that slot
            If i = 0 Then
                funds(i) = total(0) - 1
            ElseIf i > 0 Then
                'Do calculation on remaining values and store in fund array
                funds(i) = (funds(i - 1) + 1) * total(i) - 1
            End If

            'MsgBox "cumulative performance: " & funds(I) & " Cell address: " & cell.Address
            i = i + 1
            x = x + 1

        Next

        'Write from one dimensional Array To The worksheet
        Dim Destination As Range
        Dim j As Integer

        Set Destination = Range(Cells(c, 3), Cells(c, 3)) 'starts at
        Set Destination = Destination.Resize(1, 17) 'UBound(funds))
        Destination.Value = funds

        'MsgBox "Hi there"

    Next d

    'one-off commands in here
    Range("C40:S49").NumberFormat = "0.00%"
    Call portfoliomay

End Sub

【问题讨论】:

  • 不是创建一维数组,而是创建一个二维数组,例如:Dim Funds(1 to 10, 1 to 17)。像填写 Range 一样填写数组,第一个维度是行,第二个维度是列。然后做Set Destination = Destination.Resize(ubound(Funds,1),ubound(Funds,2)) : Destination.Value = Funds
  • 那么..您的资金数组是否填充了正确更新的值?您只是在考虑更新新单元格吗?
  • 谢谢大家,您的回答和编辑帮助很大。问题解决了!

标签: vba excel


【解决方案1】:

目标范围和源数组应该具有相同的维度,以便能够正确分配值,正如 Ron Rosenfeld 所评论的那样。这可以通过使用一维数组来重复使用 10 次,一次仅针对一行 array(columns),或使用二维数组用于整个目标范围 (10x17) array(rows, columns)

方法#1:一维数组

使用包含 17 个值的一维数组进行逐行操作,一次一行。最初将数组声明为动态数组Dim funds() ...,这样您就可以轻松地重置它。然后在每次For d = 0 To 9 迭代开始时设置其基于零的长度ReDim funds(16) ...。您的其余代码将保持不变。使用这种方法,您的原始目标分配应该按预期工作Destination.Value = funds(或使用等效的较短语句Cells(c, 3).Resize(1, 17) = funds)。

方法#2:二维数组

您可以将资金声明为基于零的二维数组Dim funds(9, 16) ...。但是没有直接的方法可以将数据逐行放置。 目标分配将立即分配给整个范围 Cells(40, 3).Resize(10, 17) = funds 在您的计算循环结束后。您还需要调整funds 指令以指示funds(d, i) = ... 行。这可能是将数据放入工作表的最有效方式(性能方面),因为将数据放入单元格相对耗时。

*要使用二维数组逐行执行此操作,您必须使用此处描述的解决方法return an entire row of a multidimensional array in VBA to a one dimensional array

其他调整

您需要调整 total 数组以具有与 funds 相同的维度和指令,或调整 ix 计算。要调整ix 并保持total 不变,请在For d 迭代的开头添加i = 0,并仅使用total(x)

【讨论】:

  • 谢谢 dePatinkin,刚起床,现在就试试看吧 :)
  • 我尝试了您在方法 #1 中描述的小改动。但是我得到“下标超出范围”,当我单击调试时,它指向这一行“funds(i) = (funds(i - 1) + 1) * total(i) - 1”。我提到的更改是 1. 添加了 Dim fund() 2. 在 for D 循环开始时添加了 ReDim 资金(16)。
  • 正如我所说,它需要是funds(i) = (funds(i - 1) + 1) * total(x) - 1,但我不是确定这是你的问题。您应该针对 x 和 i 的值以及数组的边界对其进行调试。
  • 谢谢!这样可行。现在需要调试为什么第一列 C 保持为 0.95%.... 在每一行。为了说明,在我之前附在上面的图片中,行仅从 28/2/15 列更新。需要从 31/1/15 开始
  • 已修复,如果语句总数也需要为 total(x).... 我可以捐赠给你 dePatinkin 吗?足以让你喝杯咖啡:)
【解决方案2】:

编辑在 OP 确认他的目标是优化代码后(见最后)

我正在添加数组/范围使用的不同“风格”,并展示了一些可能的代码增强功能

  1. Variant 变量作为数组

    不需要DimRedim 任何数组,只需将 ita 声明为纯 Variant 变量并用将承载最终结果的范围值填充它

    类似的东西

    funds = repRng.Value
    

    其中repRng 是您要使用funds 数组本身填充的“报告”表的Range

  2. 减少变量

    根本不需要总数组。只需使用一个简单的Double 变量

  3. Dim适当

    Dim i, x, d, c As Integer
    

    将导致声明 ixd 变量为 Variant 类型,并且只有 cInteger 类型

    要将所有这些变量声明为整数,您必须键入:

    Dim i As Integer, x As Integer, d As Integer, c As Integer
    

    但我们会少用它们

  4. 减少代码

    因为你正在分配

    value1 = value1 / Value2 - 1
    

    然后

    total(x) = value1 + 1
    

    你可以把这两个语句合并成一个

    total(x) = value1 / Value2
    

    对于上面所说的,我们将改为:

    total = value1 / Value2
    
  5. 复制/粘贴

    这些陈述:

    Worksheets("Data").Range("C3:T13").Copy
    Sheets("Report").Range("B39").PasteSpecial
    Worksheets("Data").Range("B3:T13").Copy
    Sheets("Report").Range("A39").PasteSpecial xlPasteValues
    

    其实做的一样:

    Worksheets("Data").Range("B3:T13").Copy
    Sheets("Report").Range("A39").PasteSpecial xlPasteValues
    

    也可以写成:

    With Worksheets("Data").Range("B3:T13")
        Sheets("Report").Range("A39").Resize(.Rows.Count, .Columns.Count).Value = .Value
    End With
    

    这种方法既减少了时间(对于这么小的范围来说不是问题),又不使用剪贴板(至少你会小心使用 Application.CutCopyMode = False 发布)

    对于上面所说的,这个语句也将用于初始化repRngRange变量

    With Worksheets("Data").Range("B3:T13")
        Set repRng = Sheets("Report").Range("A39").Resize(.Rows.Count, .Columns.Count) '<--| define the range where to paste data
        repRng.Value = .Value '<--|  paste data
    End With
    
  6. 减少变量(第二部分)

    您的 d 变量仅用于遍历您之前复制和粘贴的行,但您使用硬编码值作为其跨度,然后使其相对于另一个硬编码参考行索引 (c = 39)

    您最好利用对您实际处理的范围的一致引用,例如(伪代码)

    Dim oneRow As Range
    
    For Each oneRow In repRng.Rows '<--| loop through rows of your relevant data range
        For Each cell In oneRow.Cells '<--| loop through cells of the current data range row
    
            'code
    
        Next cell
    Next row
    

    其中repRng 是一个Range 对象,它引用了您要循环遍历的工作表“报告”的相关单元格


最终结果将是以下代码:

Option Explicit

Public funds As Variant '<--| declare the simple Variant variable that will be "turned" into an array as long as we'll initialize it to a "Range" values

Sub cumulativeperformance()

    Dim cell As Range, repRng As Range, oneRow As Range
    Dim value1 As Double, total As Double
    Dim value2 As Long
    Dim iRow As Long, jCol As Long '<--| better use "Long" instead of "Integer" when dealing with numbers that cope with Excel rows indexs

    'Copy table values to report
    With Worksheets("Data").Range("B3:T13")
        Set repRng = Sheets("Report").Range("A39").Resize(.Rows.Count, .Columns.Count) '<--| define the range where to paste data
        repRng.Value = .Value '<--|  paste data
    End With

    With repRng
        Set repRng = .Offset(1, 2).Resize(.Rows.Count - 1, .Columns.Count - 2) '<--| redefine the relevant data range to loop through
    End With

    With repRng '<--| assume your relevant data range as reference
        funds = .Value '<--| have funds array properly dimensioned by filling it with relevant data pasted values: they'll be rewritten in following loops

        For Each oneRow In .Rows '<--| loop through rows of your relevant data range

            iRow = iRow + 1 '<--| update array row counter
            jCol = 1 '<--|for each new row restart array column counter
            For Each cell In oneRow.Cells '<--| loop through cells of the current data range row

                value1 = cell.Value  '<--|get the value of current cell
                value2 = cell.Offset(0, -1).Value  '<--|get the value of cell to the left of current cell
                total = value1 / value2 '<--|evaluate the ratio

                If jCol = 1 Then
                    funds(iRow, jCol) = total - 1 '<--| If initial fund slot is 1, then store first result of calculation in that slot
                Else
                    funds(iRow, jCol) = (funds(iRow, jCol - 1) + 1) * total - 1 '<--| Do calculation on remaining values and store in fundS array
                End If

                jCol = jCol + 1 'update array column counter
            Next cell

        Next oneRow

        .Value = funds '<--| fill your relevant data range with funds values
        .NumberFormat = "0.00%"
    End With

'    Call portfoliomay

End Sub

进一步的优化将避免If jCol = 1 Then 对每一行的检查,因为它不符合某些未知条件:我们确定每一个新行都将从列索引 1 开始

所以,对于每一行,我们可以

  1. 作用于它的初始列:

    funds(iRow, 1) = GetTotal(oneRow.Cells(1, 1)) - 1 'evaluate funds current row first slot (column)
    

    依赖于特定的GetTotal()函数

    Function GetTotal(cell As Range) As Double
        Dim value1 As Double
        Dim value2 As Long
    
        value1 = cell.Value  '<--|get the value of current cell
        value2 = cell.Offset(0, -1).Value  '<--|get the value of cell to the left of current cell
        GetTotal = value1 / value2 '<--|evaluate the ratio
    End Function
    

    我们收集代码以计算“附加”到单个 celltotal

  2. 计算后续列

        jCol = 2 '<--|for each new row restart array column counter
        For Each cell In Range(oneRow.Cells(1, 2), oneRow.Cells(1, oneRow.Cells.Count)) '<--| evaluate funds current row remaining slots
            funds(iRow, jCol) = (funds(iRow, jCol - 1) + 1) * GetTotal(cell) - 1
            jCol = jCol + 1 'update array column counter
        Next cell
    

    利用相同的GetTotal() 函数

最后更新的代码是:

Option Explicit

Public funds As Variant '<--| declare the simple Variant variable that will be "turned" into an array as long as we'll initialize it to a "Range" values

Sub cumulativeperformance()

    Dim cell As Range, repRng As Range, oneRow As Range
    Dim iRow As Long, jCol As Long '<--| better use "Long" instead of "Integer" when dealing with numbers that cope with Excel rows indexs

    'Copy table values to report
    With Worksheets("Data").Range("B3:T13")
        Set repRng = Sheets("Report").Range("A39").Resize(.Rows.Count, .Columns.Count) '<--| define the range where to paste data
        repRng.Value = .Value '<--|  paste data
    End With

    With repRng
        Set repRng = .Offset(1, 2).Resize(.Rows.Count - 1, .Columns.Count - 2) '<--| redefine the relevant data range to loop through
    End With

    With repRng '<--| assume your relevant data range as reference
        funds = .Value '<--| have funds array properly dimensioned by filling it with relevant data pasted values: they'll be rewritten in following loops

        For Each oneRow In .Rows '<--| loop through rows of your relevant data range

            iRow = iRow + 1 '<--| update array row counter

            funds(iRow, 1) = GetTotal(oneRow.Cells(1, 1)) - 1 'evaluate funds current row first slot (column)
            jCol = 2 '<--|for each new row restart array column counter
            For Each cell In Range(oneRow.Cells(1, 2), oneRow.Cells(1, oneRow.Cells.Count)) '<--| evaluate funds current row remaining slots
                funds(iRow, jCol) = (funds(iRow, jCol - 1) + 1) * GetTotal(cell) - 1
                jCol = jCol + 1 'update array column counter
            Next cell

        Next oneRow

        .Value = funds '<--| fill your relevant data range with funds values
        .NumberFormat = "0.00%"
    End With

'    Call portfoliomay

End Sub

Function GetTotal(cell As Range) As Double
    Dim value1 As Double
    Dim value2 As Long

    value1 = cell.Value  '<--|get the value of current cell
    value2 = cell.Offset(0, -1).Value  '<--|get the value of cell to the left of current cell
    GetTotal = value1 / value2 '<--|evaluate the ratio
End Function

一些最后的(?)注释:

A. Public变量

这些用于在不同模块的不同子/函数之间共享变量

但使用它们通常是一种不好的做法,最好将这些变量放在子/函数参数中以将它们带到需要的地方

使用问题中的代码,没有其他使用funds 的子/函数,因此最好将其声明移至累积性能():

Option Explicit

Sub cumulativeperformance()

    Dim funds As Variant '<--| declare the simple Variant variable that will be "turned" into an array as long as we'll initialize it to a "Range" values
    Dim cell As Range, repRng As Range, oneRow As Range

B.简化GetTotal()

it can be simplified to

    Function GetTotal(cell As Range) As Double
        With cell
            GetTotal = .Value / .Offset(0, -1).Value '<--|evaluate the ratio
        End With
    End Function

taking advantage of the `With cell` statement and referring to it

【讨论】:

  • 这太棒了,感谢您抽出宝贵时间完成上述工作。我正计划优化代码,但不会对给出的提示做任何事情。谢谢!
  • 我也使用了以下代码使我的代码变得更好Styling
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-11-20
  • 1970-01-01
  • 1970-01-01
  • 2016-09-12
  • 2013-04-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多