【问题标题】:Finding lowest value with no overlapping dates寻找没有重叠日期的最低值
【发布时间】:2020-12-23 22:10:07
【问题描述】:

我有一个包含条件、开始和结束日期以及值的电子表格。目标是找到每个唯一条件和开始日期的最低值,而没有重叠日期(不包括结束日期)。我制作了一个数据透视表以方便自己,但我知道可能有一种方法可以使用某些公式或条件格式突出显示满足上述要求的所有有效行。

我附上了一个谷歌驱动器链接,可以在其中找到电子表格here,我也有一些工作表的图像。我知道条件格式可能是可行的,但我只是不知道如何将我想要它做的所有事情组合在一个公式中。

下面的例子:

第 2 行是有效条目,因为从 2021 年 3 月 15 日开始,第 1 行的值最低,与第 9 行相同。

第 5 行有效,因为开始日期不在第 2 行的日期范围内(不包括结束日期)

第 7 行无效,因为开始日期介于第 6 行的开始日期和结束日期之间

【问题讨论】:

  • 我认为同时拥有数据透视表和数据图像是令人困惑的,因为它们似乎不同意。如果第 5 行在数据透视表中有效,那仅仅是因为第 4 行没有开始日期吗?在数据视图中,为什么第 4 行有效 - 开始日期不在第 3 行的日期范围内?
  • 我很难理解您要描述的实际算法是什么。您是否想要具有非重叠日期且每个项目的值总和最小的组?或者可能是最早的开始日期优先,然后尝试适应其余日期,并且仅在它们在同一天开始时使用该值?
  • @Martí 我认为您将非重叠日期与每个项目的最小值分组是对的。这些值不是总和,它们只是对应于每个“项目/开始日期”组合的数字。因此,第 2-4 行是一组 3 个“项目 1/03-15-2021”数据点,并选择具有最低值的数据点。根据有效条目的日期范围,第 5 行也是有效的。希望这能提供一点清晰。
  • 我想我明白了。可以肯定的是,如果在第 1 项中我们有 3 行:[01-01 到 01-03 的值为 10]、[01-02 到 01-05 的值为 1] 和 [01-03 到 01-04 的值为10];我希望选择第一个和第三个。那正确吗?如果没有,请您详细说明一下吗?
  • @Martí 是的,很抱歉,我无法清楚地表达我想要做什么。

标签: google-sheets google-sheets-formula


【解决方案1】:

您可以在项目中添加有界脚本。然后,您可以使用分配了功能(类似按钮)的图片/绘图来调用它,或者将菜单添加到 Google 表格。

根据您在问题和 cmets 中所说的,这似乎可以满足您的要求。请注意,这需要 V8 运行时(应该是默认值)。

function validate() {
  // Get the correct sheet
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
  const sheet = spreadsheet.getSheetByName('Sheet1')
  
  // Get the data
  const length = sheet.getLastRow() - 1
  const range = sheet.getRange(2, 1, length, 4)
  const rows = range.getValues()
  const data = Array.from(rows.entries(), ([index, [item, start, end, value]]) => {
    /*
     * Row               Index
     *  1   Criteria 1
     *  2   Item 1         0
     *  3   Item 1         1
     *  4   Item 1         2
     *
     * row = index + 2
     */
    return {
      row: index + 2,
      criteria: item,
      start: start.getTime(),
      end: end.getTime(),
      value: value
    }
  })
  
  
  // Sort the data by criteria (asc), start date (asc), value (asc) and end date (asc)
  data.sort((a, b) => {
    let order = a.criteria.localeCompare(b.criteria)
    if (order !== 0) return order
    
    order = a.start - b.start
    if (order !== 0) return order
    
    order = a.value - b.value
    if (order !== 0) return order
    
    order = a.end - b.end
    return order
  })
  
  
  // Iterate elements and extract the valid ones
  // Notice that because we sorted them, the first one of each criteria will always be valid
  const valid = []
  let currentCriteria
  let currentValid = []
  
  for (let row of data) {
    if (row.criteria !== currentCriteria) {
      // First of the criteria
      valid.push(...currentValid)    // Move the valids from the old criteria to the valid list
      currentValid = [row]           // The new list of valid rows is only the current one (for now)
      currentCriteria = row.criteria // Set the criteria
    } else {
      const startDateCollision = currentValid.some(valid => {
        row.start >= valid.start && row.start < valid.end
      })
      if (!startDateCollision) {
        currentValid.push(row)
      }
    }
    
  }
  valid.push(...currentValid)
  
  
  // Remove any old marks
  sheet.getRange(2, 5, length).setValue('')

  // Mark the valid rows
  for (let row of valid) {
    sheet.getRange(row.row, 5).setValue('Valid')
  }
}

算法纲要

  1. 我们得到包含数据的工作表。在这种情况下,我们按名称进行操作(如果不是默认的Sheet1,请记住更改它)
  2. 我们读取数据并将其转换为更多对象数组,在这种情况下更易于管理
  3. 我们对数据进行排序。这类似于您在代码中所做的转置。它还强制执行优先顺序并按标准对其进行分组
  4. 迭代行,只保留有效的:
    • 我们保留所有有效的列表 (valid) 和一个仅用于当前标准的列表 (currentValid),因为我们只需要检查与相同标准中的数据冲突。
    • 第一次迭代总是会进入 if 块(因为 currentCriteria 是undefined)。
    • 更改条件时,我们将currentValid 中的所有行转储到valid。我们在最后一个条件的循环之后做同样的事情
    • 更改条件时,CurrentValid 是一个以当前行作为元素的数组,因为第一行将始终有效(因为排序)
    • 对于其他行,我们检查开始日期是否在该条件的任何有效行的开始日期和结束日期之间。如果不是,请将其添加到此条件的有效行中
  5. 我们删除validity行中所有当前的“Valid”,并填写valids

算法的基石实际上是对数据进行排序。它使我们不必搜索最佳行,因为它总是下一个。它还确保条件的第一行始终有效。

学习资源

代码参考

【讨论】:

    猜你喜欢
    • 2018-07-06
    • 2017-05-08
    • 1970-01-01
    • 2017-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多