代码之家  ›  专栏  ›  技术社区  ›  symbolrush

for循环和跨数据集子集的替代方法。(使用高阶函数或替代数据结构)

r
  •  3
  • symbolrush  · 技术社区  · 8 年前

    我在R中运行离散事件模拟。我算法的“核心”执行以下操作(伪代码):

    1)迭代 events

    a)变化 event[i] 取决于 resources

    b)变化 资源 取决于步骤a)的结果

    以下可重复的示例捕获主要方面:

    生成一些数据:

    set.seed(4)
    n <- 3
    nr_resources <- 2
    
    events <- data.frame(
      t = as.integer(trunc(cumsum(rexp(n)))),
      resource = NA,
      worktime = as.integer(trunc(runif(n)*10))
    )
    
    resources <- data.frame(
      id = 1:nr_resources,
      t_free = 0L
    )
    events
    resources
    
    # > events
    # t resource worktime
    # 0       NA        2
    # 4       NA        8
    # 5       NA        2
    # > resources
    # id t_free
    #  1      0
    #  2      0
    

    现在我们可以模拟资源调度:

    for (i in 1:n) {
      events$resource[i] <- resources$id[resources$t_free <= events$t[i]][1]
      resources$t_free[events$resource[i]] <- events$t[i] + events$worktime[i]
    }
    
    events
    resources
    
    # > events
    # t resource worktime
    # 0        1        2
    # 4        1        8
    # 5        2        2
    # > resources
    # id t_free
    #  1     12
    #  2      7
    

    这种方法工作得很好,但我想消除一些缺点。自从 事件 资源 被分成两个数据集,两个数据集之间有相当多的子集(搜索和替换)。这不是真正的可读性。在实际应用中,它甚至成为性能瓶颈。(当然,真正的例子要复杂得多。)

    因此,我问自己是否有更好的方法来完成这项任务在R。

    我考虑用一个普通的高阶函数替换for循环,但没有得到任何结果。

    • 典型的R lapply -方法不起作用,因为 勒普 不是为输入数据中的此迭代更改而生成的。(据我所见)
    • 我的任务有点像 Reduce 模式。自从 Reduce(sum, 1:3, accumulate = TRUE) 使用中间结果并保存它们,我想我可以使用 减少 但没有取得任何效果。

    我也考虑过重组我的数据,但直到现在都没有成功。

    我详细尝试过的

    算法的 侧面:

    失败的方法 勒普 :

    l <- list(events = events, resources = resources)
    l <- lapply(l, function(x) {
      l$events$resource <- l$resources$id[l$resources$t_free <= l$events$t][1]
      l$resources$t_free[l$events$resource] <- l$events$t + l$events$worktime
      return(l)
    })
    
    l$events
    l$resources
    

    结果变成:

    # $events
    # t resource worktime
    # 1 0        1        2
    # 2 4        1        8
    # 3 5        1        2
    # 
    # $resources
    # id t_free
    # 1  1      7
    # 2  2      0
    

    对资源的中间更改将丢失,因此总是会预订资源1。


    失败的方法 减少 :

    l <- list(events = events, resources = resources)
    l <- Reduce(function(l) {
      l$events$resource <- l$resources$id[l$resources$t_free <= l$events$t][1]
      l$resources$t_free[l$events$resource] <- l$events$t + l$events$worktime
      return(l)}, l, accumulate = TRUE)
    

    失败的原因是

    f中的错误(init,x[[i]]):未使用的参数(x[[i]])


    数据 侧面:

    我能想到的另一种方法是 更改数据 在一个数据集中表示。例如,将事件乘以资源数。我尝试了以下方法:

    data <- merge(events, resources)
    data <- data[order(data$t), ]
    data
    
    # t resource worktime id t_free
    # 0       NA        2  1      0
    # 0       NA        2  2      0
    # 4       NA        8  1      0
    # 4       NA        8  2      0
    # 5       NA        2  1      0
    # 5       NA        2  2      0
    
    for (i in seq_along(data)) {
      if ( is.na(data$resource[i])) {
        data$resource[data$t == data$t[i]] <- data$id[data$t_free <= data$t[i]][1]
        data$t_free[data$id == data$resource[i]] <- data$t[i] + data$worktime[i]
      }
    }
    
    data
    # t resource worktime id t_free
    # 0        1        2  1     12
    # 0        1        2  2      7
    # 4        1        8  1     12
    # 4        1        8  2      7
    # 5        2        2  1     12
    # 5        2        2  2      7
    
    events <- unique(data[,1:3])
    events
    # t resource worktime
    # 0        1        2
    # 4        1        8
    # 5        2        2
    
    resources <- unique(data[,4:5])
    resources
    # id t_free
    #  1     12
    #  2      7
    

    这也很有效,但我不确定这是否会带来更好的性能、可读性和可伸缩性。


    所以我的问题是:

    有其他的选择吗 算法的 侧面或侧面 数据 改进我实际解决方案的那一面?

    1 回复  |  直到 8 年前
        1
  •  2
  •   Alexis    8 年前

    我真的更喜欢你的第一个循环, 你应该考虑使用 Rcpp::sourceCpp 并将逻辑迁移到C++。 我认为这应该是可读性和速度。 如果你必须在R里做, 有一种可能性:

    t_free <- Reduce(x = 1L:n,
                     init = rep(0L, nr_resources),
                     accumulate = TRUE,
                     f = function(t_free, i) {
                       # which.max will return the location of the first TRUE
                       id <- which.max(t_free <= events$t[i])
                       # R makes a local copy of t_free here
                       t_free[id] <- events$t[i] + events$worktime[i]
                       # return the chosen resource for this "iteration"
                       attr(t_free, "resource") <- id
                       # return the modified copy
                       t_free
                     })
    
    # events$resource column by extracting the resource attribute, igonring init
    events$resource <- sapply(t_free[-1L], attr, "resource")
    # your resources$t_free column in the last element
    resources <- data.frame(id = 1L:nr_resources,
                            t_free = t_free[[n + 1L]])