【问题标题】:A collection of closest values from two arrays两个数组中最接近的值的集合
【发布时间】:2020-05-30 00:49:15
【问题描述】:

我正在寻找一种算法优化来解决一个可能难以解释的简单问题。当您阅读代码时,我不是在寻找速度或性能,而是在寻找简单性和清晰度。也许有人有比我更聪明的解决方案。我想单行可能有点过头了。

我有两个按日期排序的单元格集合。每个单元格都可以有一个价格值。我们可以假设同一日期的两个单元格中不可能有价格。我想要一个日期集合,但是日期没有价格:

  • 哪个系列的价格与过去最接近
  • 如果没有过去的价格,那就看未来

这是我目前所拥有的(它给出了准确的结果):

const array1 = [
  { date: '2019-11-10' },
  { date: '2019-11-11' },
  { date: '2019-11-12' },
  { date: '2019-11-13' },
  { date: '2019-11-14' },
  { date: '2019-11-15', price: 10 },
  { date: '2019-11-16' },
];

const array2 = [
  { date: '2019-11-10' },
  { date: '2019-11-11' },
  { date: '2019-11-12', price: 10 },
  { date: '2019-11-13' },
  { date: '2019-11-14' },
  { date: '2019-11-15' },
  { date: '2019-11-16' },
];

const merged = Object.values(array1).map((element, index) => {
  let filled;
  if (element.price) {
    filled = 1;
  }
  if (array2[index].price) {
    filled = 2;
  }
  if (filled) {
    return {
      date: element.date,
      filled
    }
  } else {
    return {
      date: element.date
    }
  }
});

const first = merged.find(element => element.filled);

let currentFill = first && first.filled;

const emptyMap = merged.map((element, index, array) => {
  if (!element.filled) {
    return {
      date: element.date,
      empty: currentFill
    }
  }
  currentFill = element.filled;
  return element;
})

console.log(emptyMap);

【问题讨论】:

  • 在输出中包含日期和价格不是更好吗?如果您想知道某个日期的价格,您仍然必须找到它。或者,您可以添加一个属性,告诉您价格来自哪个集合和/或告诉您它是过去、未来还是确切日期的价格。也许更好:创建一个以日期为键的对象,这样您就可以在 O(1) 中查询这些日期的价格。
  • 我愿意接受任何建议,即使这意味着要改变结构。最重要的是我有两个单独的日期集合,其中一些有价格,我需要将它与这两个条件合并。

标签: javascript arrays algorithm optimization


【解决方案1】:

这是我对此的看法:

const array1 = [
  { date: '2019-11-10' },
  { date: '2019-11-11' },
  { date: '2019-11-12' },
  { date: '2019-11-13' },
  { date: '2019-11-14' },
  { date: '2019-11-15', price: 10 },
  { date: '2019-11-16' },
];

const array2 = [
  { date: '2019-11-10' },
  { date: '2019-11-11' },
  { date: '2019-11-12', price: 10 },
  { date: '2019-11-13' },
  { date: '2019-11-14' },
  { date: '2019-11-15' },
  { date: '2019-11-16' },
];

const mappedToCollection = array1.map((el, i) => ({
  date: el.date,
  //You can just as easily store the actual collection here and not just a number
  collection: (el.price && 1) ||
    (array2[i].price && 2) ||
    undefined,
}));

const firstExactMatch = mappedToCollection.find(el => el.collection);

const closestCollections = mappedToCollection.reduce((acc, el, i) => [
  ...acc,
  {
    date: el.date,
    collection: el.collection ||
      (acc[i-1] && acc[i-1].collection) ||
      (firstExactMatch && firstExactMatch.collection),
    exactMatch: !!el.collection,
  },
], []);

console.log(closestCollections);

正如问题中所述,这不是最高效或最简短的解决方案,而是试图使其具有可读性和明确性。

【讨论】:

  • 非常好的方法。我肯定比我更喜欢它。谢谢!
【解决方案2】:

据我了解,最难阅读的代码可能是最后一条语句:“如果过去没有价格,请展望未来”。

此要求意味着,您不能只通过集合继续前进,并且仅在必要时应用先前处理的元素中的值,您还必须向前看,直到发生某些事情。

为了最简单,我建议从前到后走一次,忽略“向前看”部分,然后,在结果集合中,从后到前复制最后到达(即最早)填充的元素到前面的:

const array1 = [
  { date: '2019-11-10' },
  { date: '2019-11-11' },
  { date: '2019-11-12' },
  { date: '2019-11-13' },
  { date: '2019-11-14' },
  { date: '2019-11-15', price: 10 },
  { date: '2019-11-16' },
]

const array2 = [
  { date: '2019-11-10' },
  { date: '2019-11-11' },
  { date: '2019-11-12', price: 10 },
  { date: '2019-11-13' },
  { date: '2019-11-14' },
  { date: '2019-11-15' },
  { date: '2019-11-16' },
]

const length = array1.length
const result = new Array(length)
let emptySource = undefined

// First run: from earlier to more recent
// Applies value for "empty" when price was available once
for (let i = 0; i < length; ++i) {
  let e1 = array1[i]
  let e2 = array2[i]
  const date = e1.date
  if (e1.price) {
    result[i] = {
      'date': date,
      'filled': 1
    }
    emptySource = 1
  } else if (e2.price) {
    result[i] = {
      'date': date,
      'filled': 2
    }
    emptySource = 2
  } else {
    result[i] = {
      'date': date,
      'empty': emptySource
    }
  }
}

// Second run: from more recent to earlier
// Finds the earliest element ever "filled",
// then applies its value to all still unset "empty" elements
// (which necessarily all have a lower index)
for (let i = length - 1; i >= 0; --i) {
  const e = result[i]
  if (e.filled) {
    emptySource = e.filled
  } else if (e.empty === undefined) {
    e.empty = emptySource
  }
}

console.log(result)

【讨论】:

  • 嗯,是的,它确实有效,而且非常清晰和直率。另一方面,代码现在很长,例如,如果我们确定它会在那里,则不需要通过数组两次并将date 放在所有 3 个条件中。我正在寻找原始但清晰和 codegolf/one-liner 聪明但不可读的代码之间的平衡解决方案。感谢您的意见。
  • “3 个条件”是您(隐含)要求区分“填充”和“空”属性的产物。我编写了代码,所以它产生了你所做的确切输出。如果不需要,我可以大量缩短代码。
  • 我很想看看短版:)
【解决方案3】:

const array1 = [
  { date: '2019-11-10' },
  { date: '2019-11-11' },
  { date: '2019-11-12' },
  { date: '2019-11-13' },
  { date: '2019-11-14' },
  { date: '2019-11-15', price: 10 },
  { date: '2019-11-16' },
];

const array2 = [
  { date: '2019-11-10' },
  { date: '2019-11-11' },
  { date: '2019-11-12', price: 10 },
  { date: '2019-11-13' },
  { date: '2019-11-14' },
  { date: '2019-11-15' },
  { date: '2019-11-16' },
];

function collect(collection1, collection2) {
  let firstEmptyId = null
  let currentId = null
  let indexes = [/* [1] optimization */]
  let collection = collection1.map(({ date, price }, i) => (
    ((price && (currentId = 1)) || (collection2[i].price && (currentId = 2)))
      ? ((firstEmptyId || (firstEmptyId = currentId)), { date, filled: currentId })
      : ((firstEmptyId || /*see[1]*/ indexes.push(i)), { date, empty: currentId })
  ))
  // only two iterations => index.length === 2
  indexes.forEach((i) => (collection[i].empty = firstEmptyId))
  return collection
}

console.log(collect(array1, array2))

【讨论】:

  • 这是一个很好的答案。
  • 有时,我仍然想知道如果“阅读代码时的简单性和清晰性”是问题,那么包含(({,},)=&gt;(((&amp;&amp;(=))||([].&amp;&amp;(=)))?((||(=)),{,:}):((||.()),{,:}))) 的代码如何成为答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-12-16
  • 2019-01-16
  • 2021-06-22
  • 2021-04-11
  • 2021-03-05
  • 2016-02-26
  • 1970-01-01
相关资源
最近更新 更多