使用最小/最大值

时间:2016-12-08 12:01:39

标签: grouping combinations dynamic-programming combinatorics

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

我想要做的是我有一堆对象具有最小值和最大值,如果两个对象具有某种重叠值,则可以推导出这些值,因此可以将它们放在一个组中

这个问题可能需要动态编程来解决。

示例对象:

1 ( min: 0, max: 2 )
2 ( min: 1, max: 3 )
3 ( min: 2, max: 4 )
4 ( min: 3, max: 5 )
  • 对象1可以与对象2,3
  • 分组
  • 对象2可以与对象1,3,4
  • 分组
  • 对象3可以与对象1,2,4
  • 分组
  • 对象4可以与对象2,3
  • 分组

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

[1, 2]
[3, 4]

[1]
[2, 3]
[4]

[1]
[2, 3, 4]

[1, 2, 3]
[4]

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

  • 例如最少量的组

    [1,2] [3,4]

    [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);
        }
    }
}

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

从这一点来说,我不知道如何继续,如何生成所有解决方案然后检查每个解决方案有多好,并选择最好的解决方案

或者甚至有更好的解决方案不使用任何一种?

首选PH​​P解决方案,但此问题不是特定于PHP的

1 个答案:

答案 0 :(得分:0)

当我第一次看到你的算法时,我对它的效率印象深刻:) 这里用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函数,因为它不会返回索引。但这就是你所寻找的我相信的。