大型数据集的嵌套循环

时间:2013-08-29 14:55:35

标签: nested-loops intersystems-cache

作为我正在研究的项目的一部分,我必须建立各种报告。为了构建这些报告,我从非常大的数据集中收集数据。因此,我试图优化我的循环结构,以最大限度地减少内部循环内的检查。

我经常使用的一个全局变量(键值类型的数据集)大致具有以下结构:

dataStatus->inputDate->state->region->street->data

我必须遍历前面的每个部分才能获得我所追求的数据。问题是,虽然我总是有状态(它是输入屏幕中的必填字段),但区域 street 可能会被省略。如果 street 留空,我必须遍历该地区的所有街道。同样,如果区域留空,我必须遍历州内的所有区域,并再次遍历每个区域的所有街道。

简单的解决方案是这样的:

loop through dataStatuses {
    loop through dates {
        if street is set {
            get data
        } else if region is set {
            loop through streets {
                get data
            }
        } else {
            loop through regions {
                loop through streets {
                    get data
                }
            }
        }
    }
}

但是,我真的想要一种跳过内部检查的方法。到目前为止,我能想出的最佳解决方案是这样的:

if street is set {
    loop through dataStatuses {
        loop through dates {
            get data
        }
    }
} else if region is set {
    loop through dataStatuses {
        loop through dates {
            loop through streets {
                get data
            }
        }
    }
} else {
    loop through dataStatuses {
        loop through dates {
            loop through regions {
                loop through streets {
                    get data
                }
            }
        }
    }
}

有没有比这更优雅的解决方案,可能会在达到数据之前满足n个级别?

非常感谢任何帮助。

3 个答案:

答案 0 :(得分:1)

嗯。嗯,首先,这个代码可能是如此重要的I / O限制,优化除磁盘访问之外的任何东西可能是无关紧要的。你可以尝试在每次if-test之前将无意义的循环抛到1000,只是为了看看它有多大差异。

索引似乎不会对您有所帮助,因为您必须在未指定值时访问所有数据,但如果您正在计算聚合,则缓存这些可能有所帮助。 Intersystem's DeepSee产品专为此而设计。

但听起来好像你只是在运行一个大报告打印所有数据,并希望优化你能做到的。在这种情况下,我认为你的解决方案就是这样。就优雅而言,它通常不会与重度优化相结合。推广解决方案的问题在于它通常比定制解决方案慢。

我认为可能使用标签和goto语句,虽然更难阅读,但运行速度可能比解决方案快一些 - 如果您认为值得的话。 Intersystems在编译的SQL例程中执行标记和getos这一事实让我觉得它可能是最快的选择。我没有测量出差异,但我认为Intersystems可能有。如果你真的需要速度,那么你应该尝试不同的方法并测量速度。一定要使用冷热常规和全局缓存进行测试。

就一般解决方案而言,如果您真的想要,可以编写生成代码的内容,类似于您手工编码的代码,但可以为一般的n级解决方案生成代码。你告诉它关于水平,它会产生解决方案。这将是一般的和快速的 - 但我非常怀疑,对于这个例子,你可以很好地利用你的时间编写这个通用的解决方案,因为手工编码非常简单,而且很可能并不常见。

答案 1 :(得分:0)

我想出了一个适合我的解决方案。这可能不是我最初寻找的完美,但由于以下原因,它适用于我:

  1. 当我完成这项工作后,其他程序员会定期处理报告,我希望在这方面尽可能简化生活。
  2. 报告生成器会很好,但目前不是最有成效的事情。
  3. 我相信我的解决方案非常容易阅读和维护而不会牺牲太多的性能:

    GatherData
        set command="do LoopThroughRegions(status,date,state,.dataCollection)"
        if ($length(street)>0){
            set command="do LoopThroughData(status,date,state,region,street,.dataCollection)"
        } elseif ($length(region)>0){
            set command="do LoopThroughStreets(status,date,state,region,.dataCollection)"
        }
    
        set date=fromDate-1,status=""
        for{
            set status=$order(^GLOBAL(status)) quit:status=""
            for{
                set date=$order(^GLOBAL(status,date)) quit:((date>toDate)||(date=""))
                xecute (command)
            }
        }
     quit
    
    LoopThroughRegions(status,date,state,dataCollection)
        set currentRegion="" 
        for{
            set currentRegion=$order(^GLOBAL(status,date,region,currentRegion)) quit:currentRegion=""
            do LoopThroughStreets(status,date,state,currentRegion,.dataCollection)
        }
     quit
    
    LoopThroughStreets(status,date,state,region,dataCollection)
        set currentStreet=""
        for{
            set currentStreet=$order(^GLOBAL(status,date,state,region,currentStreet)) quit:currentStreet=""
            do LoopThroughData(status,date,state,region,currentStreet,.dataCollection)
        }
     quit
    
    LoopThroughData(status,date,state,region,street,dataCollection)
        set dataItem="" 
        for{
            set dataItem=$order(^GLOBAL(status,date,state,region,street,dataItem)) quit:dataItem=""
            // Do stuff
        }
     quit
    

    除非提供更好的解决方案,否则我会选择自己的答案以供将来参考。希望它甚至可以帮助其他人。

答案 2 :(得分:0)

你正在按照你提出的答案向正确的方向发展,但我会略微改变它以避免使用xecute(xecute和性能很少发挥得很好)并使整体架构更好一些。

不是决定在主例程中做什么,只传入所需的参数,而是传入所有参数并决定在子例程中需要做什么。

所以你的代码将成为:

GatherData(...)
    set date=fromDate-1,status=""
    for {
        set status=$order(^GLOBAL(status)) quit:status=""
        for {
            set date=$order(^GLOBAL(status,date)) quit:((date>toDate)||(date=""))
            do LoopThroughRegions(status,date,state,region,street,.dataCollection)
        }
    }
 quit

LoopThroughRegions(status,date,state,region,street,dataCollection)
    if region'="" {
        do LoopThroughStreets(status,date,state,region,street,.dataCollection) 
        quit
    }
    set currentRegion="" 
    for {
        set currentRegion=$order(^GLOBAL(status,date,region,currentRegion)) quit:currentRegion=""
        do LoopThroughStreets(status,date,state,currentRegion,street,.dataCollection)
    }
 quit

LoopThroughStreets(status,date,state,region,street,dataCollection)
    if street'="" {
        do LoopThroughData(status,date,state,region,street,dataCollection) 
        quit
    }
    set currentStreet=""
    for{
        set currentStreet=$order(^GLOBAL(status,date,state,region,currentStreet)) quit:currentStreet=""
        do LoopThroughData(status,date,state,region,currentStreet,.dataCollection)
    }
 quit

LoopThroughData(status,date,state,region,street,dataCollection)
    set dataItem="" 
    for{
        set dataItem=$order(^GLOBAL(status,date,state,region,street,dataItem)) quit:dataItem=""
        // Do stuff
    }
 quit

我还建议将这些小子程序更改为私有程序。