使用对象的 min/max 值制作对象组(组合)

Making groups (combinations) of objects using their min/max values

首先,这是我的第一个问题,你可以告诉我如何改进它,使用什么标签。

我想做的是我有一堆对象,它们具有最小值和最大值,您可以通过这些值推断出两个对象是否具有某种重叠值,因此可以将它们放在一个组中

这道题可能需要动态规划来解答。

示例对象:

1 ( min: 0, max: 2 )
2 ( min: 1, max: 3 )
3 ( min: 2, max: 4 )
4 ( min: 3, max: 5 )

如您所见,有多种方法可以对这些元素进行分组

[1, 2]
[3, 4]

[1]
[2, 3]
[4]

[1]
[2, 3, 4]

[1, 2, 3]
[4]

现在应该有某种规则来推断哪个解决方案是最佳解决方案

我现在拥有的:

$objects = [...objects...];

$numberOfObjects = count($objects);
$groups    = [];
for ($i = 0; $i < $numberOfObjects; $i++) {
    $MinA           = $objects[$i]['min'];
    $MaxA           = $objects[$i]['max'];
    $groups[$i]    = [$i];
    for ($j = $i + 1; $j < $numberOfObjects; $j++) {
        $MinB = $objects[$j]['min'];
        $MaxB = $objects[$j]['max'];
        if (($MinA >= $MinB && $MinA <= $MaxB) || ($MaxA >= $MinB && $MaxA <= $MaxB) || ($MinB >= $MinA && $MinB <= $MaxA)) {
            array_push($groups[$i], $j);
        }
    }
}

这基本上创建了一个数组,其中包含可以组合在一起的对象的索引

从这一点开始,我不知道如何进行,如何生成所有解决方案,然后检查每个解决方案的好坏,然后选择最好的一个

或者也许有更好的解决方案不使用这些?

PHP 解决方案是首选,尽管这个问题不是 PHP-specific

当我第一次看到你的算法时,它的效率给我留下了深刻的印象:) 这里重写为 javascript,因为我很久以前就离开了 perl:

function setsOf(objects){
  numberOfObjects = objects.length
  groups    = []
  let i
  for (i = 0; i < numberOfObjects; i++) {
      MinA           = objects[i]['min']
      MaxA           = objects[i]['max']
      groups[i]    = [i]
      for (j = i + 1; j < numberOfObjects; j++) {
          MinB = objects[j]['min']
          MaxB = objects[j]['max']
          if ((MinA >= MinB && MinA <= MaxB) || (MaxA >= MinB && MaxA <= MaxB) || 
            (MinB >= MinA && MinB <= MaxA)) {
              groups[i].push(j)
          }
      }
  }
  return groups
}

如果您碰巧在 javascript 中也想得很好,您可能会发现这种形式更直接(但是它是相同的):

function setsOf(objects){
  let groups = []
  objects.forEach((left,i) => {
    groups[i]=[i]
    Array.from(objects).splice(i+1).forEach((right, j) => {
      if ((left.min >= right.min && left.min <= right.max) || 
          (left.max >=right.max && left.max <= right.max) || 
          (right.min >= left.min && right.min <= left.max))
        groups[i].push(j+i+1)
    })
  })
  return groups
}

所以如果我们 运行 它,我们得到:

a = setsOf([{min:0, max:2}, {min:1, max:3}, {min:2, max:4}, {min:3, max: 5}])
[Array(3), Array(3), Array(2), Array(1)]0: Array(3)1: Array(3)2: Array(2)3: Array(1)length: 4__proto__: Array(0)
JSON.stringify(a)
"[[0,1,2],[1,2,3],[2,3],[3]]"

并且它确实令人印象深刻地捕获了复合组 :) 一个弱点是它正在捕获包含比必要更多对象的组,而不是捕获所有可用对象。您似乎有一个非常自定义的选择标准。对我来说,这些组似乎应该是每个最后一个相交的子集,或者只是组中每个元素提供独特覆盖的子集:[0,1], [0,2], [1,2], [1,3], [2,3], [0,1,3]

该算法可能涉及更多。这是我的方法,它远没有你的方法简洁和优雅,但它有效:

function intersectingGroups (mmvs) {
  const min = []
  const max = []
  const muxo = [...mmvs]

  mmvs.forEach(byMin => {
    mmvs.forEach(byMax => { 
      if (byMin.min === byMax.min && byMin.max === byMax.max) {
        console.log('rejecting identity', byMin, byMax)
        return // identity
      }
      if (byMax.min > byMin.max) {
        console.log('rejecting non-overlapping objects', byMin, byMax)
        return // non-overlapping objects
      }
      if ((byMax.max <= byMin.max) || (byMin.min >= byMax.min)) {
        console.log('rejecting non-expansive coverage or inversed order', 
          byMin, byMax)
        return // non-expansive coverage or inversed order
      }
      const entity = {min: byMin.min, max: byMax.max, 
        compositeOf: [byMin, byMax]}
      if(muxo.some(mv => mv.min === entity.min && mv.max === entity.max))
        return // enforcing Set
      muxo.push(entity)
      console.log('adding', byMin, byMax, muxo)
    })
  })

  if(muxo.length === mmvs.length) {
    return muxo.filter(m => 'compositeOf' in m)
    // solution
  } else {
    return intersectingGroups(muxo)
  } 
}

现在应该有某种规则来推断哪个解决方案是最佳解决方案

是的,所以,通常对于谜题或您要满足的规范,这将作为问题的一部分给出。实际上,您需要一种适应性强的通用方法。最好做一个可以配置结果和接受规则的对象,然后加载你感兴趣的规则,和搜索的结果,看看哪些规则匹配到哪里。例如,使用您的算法和示例标准:

最少的组数

从以下代码开始:

let reviewerFactory = {
  getReviewer (specification) { // generate a reviewer
    return { 
      matches: [], // place to load sets to
      criteria: specification,
      review (objects) { // review the sets already loaded
        let group
        let results = {}
        this.matches.forEach(mset => {
          group = [] // gather each object from the initial set for each match in the result set
          mset.forEach(m => {
            group.push(objects[m])
          })
          results[mset] = this.criteria.scoring(group) // score the match relative to the specification
        })
        return this.criteria.evaluation(results) // pick the best score
       }
    }
  },
  specifications: {}
}

现在您可以为最少数量的组添加这样的规范:

reviewerFactory.specifications['LEAST GROUPS'] =  {
  scoring: function (set) { return set.length },
  evaluation: function (res) { return Object.keys(res).sort((a,b) => res[a] - res[b])[0] }
}

然后你可以在集合的评估中使用它:

mySet = [{min:0, max:2}, {min:1, max:3}, {min:2, max:4}, {min:3, max: 5}]
rf = reviewerFactory.getReviewer(reviewerFactory.specifications['LEAST GROUPS'])
Object {matches: Array(0), criteria: Object, review: function}
rf.matches = setsOf(mySet)
[Array(3), Array(3), Array(2), Array(1)]
rf.review(mySet)
"3"

或者,大多数对象:

reviewerFactory.specifications['MOST GROUPS'] =  {
  scoring: function (set) { return set.length },
  evaluation: function (res) { return Object.keys(res).sort((a,b) => res[a] - res[b]).reverse()[0] }
}

mySet = [{min:0, max:2}, {min:1, max:3}, {min:2, max:4}, {min:3, max: 5}]
reviewer = reviewerFactory.getReviewer(reviewerFactory.specifications['MOST GROUPS'])
reviewer.matches = setsOf(mySet)
reviewer.review(mySet)
"1,2,3"

当然这是任意的,但根据 OP 中的定义,标准也是如此。同样,您必须更改此处的算法才能使用我的 intersectingGroups 函数,因为它没有 return 索引。但我相信这就是您要找的东西。