优化矩阵内的重叠

时间:2017-03-17 06:56:15

标签: python matrix mathematical-optimization

我正在尝试解决以下问题,我非常肯定有一个解决方案。我无法透露太多信息,因此我将其制定为一般术语并提出以下寓言。我在这里介绍你 沉睡的僧侣问题

  

想象一个修道院,1​​2名僧人在那里睡觉,吃饭,祈祷或在外面工作   修道院。每个和尚,每晚需要睡8或6个小时。每   和尚有X金额,他必须在花园,摊位或工作   羊群(例如,修道院外的活动)和Y小时   在这些Y小时内,在修道院的墙壁上闲逛   每个和尚要么徘徊(例如祈祷,吃饭,喝啤酒)   或者他正在睡觉S小时(Y总是大于S)。

     

这个修道院的负责人问是否有可能找到一个   最好的睡眠安排,使床的数量可以   减少了,每个僧侣都得到了必要的睡眠。

我对该问题的输入数据以下列形式给出:

time_slots_in_monastery = [
#  time of the day
#14 16 18 20 22 24 2  4  6  8 10  12
(0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0),
(0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0),
(0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0),
(0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0),
(0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0),
(0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0),
(0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0),
(0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0),
(0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0),
(0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0),
(0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0),
(0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0),
]

每行代表一名僧侣。数字1表示修道院内的时间段,0表示僧侣在外面的时间。

另外,我有一个包含每个睡眠要求的向量 僧。

required_sleeping = [4, # number of sleeping slots , each one is 2 hours.
                     3,
                     3,
                     4,
                     4,
                     4,
                     4,
                     3,
                     3,
                     3,
                     3,
                     3]

当前,并且真正不满意的睡眠解决方案是:

solution = [
    (0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0),
    (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
    (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
    (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
    (0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0),
    (0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0),
    (0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0),
    (0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0),
    (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
    (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
    (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
    (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
    (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
    ]

# total beds:
#    0, 0, 12, 12, 12, 5, 0, 0 , 0, 0, 0, 0

所有僧侣都在下午6点睡觉。但是如果我们让僧侣11和12使用相同的床,我们可以将床的数量减少到11个。更好的是,僧侣1和2也可以共用床,然后我们将床的数量减少到10个。

better_solution = [
        (0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0),
        (0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0),
        (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
        (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
        (0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0),
        (0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0),
        (0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0),
        (0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0),
        (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
        (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
        (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
        (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
        ]

现在有12名僧侣,问题可能是暴力迫使,但实际上我有大约50名僧侣,时间分辨率是5分钟,而不是2小时。因此,我正在寻找一种方法来找到一种方法来解决f-最小搜索风格或任何其他非暴力的问题。

我在这里介绍我在Python中的暴力解决方案:

 import pprint
 time_slots_in_monastery = [
 #  time of the day
 #14 16 18 20 22 24 2  4  6  8 10  12
 (0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0),
 (0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0),
 (0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0),
 (0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0),
 (0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0),
 (0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0),
 (0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0),
 (0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0),
 (0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0),
 (0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0),
 (0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0),
 (0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0),
 ]
 required_sleeping = [4,
                      3,
                      3,
                      4,
                      4,
                      4,
                      4,
                      3,
                      3,
                      3,
                      3,
                      3]

 def make_new_bed(rl):
     bed = '0' * rl
     return bed

 def search_in_beds(beds, rl, sleep_time, first, last):
     sleep_slots = '0' * sleep_time
     sleep = '1' * sleep_time
     index = False
     for cn, bed in enumerate(beds):
         try:
             index = bed[first:last+1].index(sleep_slots)
             index += first
             bed = bed[:first] + bed[first:last+1].replace(sleep_slots, sleep, 1) + bed[last+1:]
             beds[cn] = bed
             print()
             print('monastery time from: %s to: %s' % (first, last))
             print('this monk found a place in bed(%s) from (%s) to (%s)' % (cn, index, index + sleep_time-1))
             print('bed(%s) time: %s ' % (cn, bed))
         except:
             """
             I did not found a free time in this bed
             """
             pass
     if index:
         return index, index + sleep_time - 1
     #adding a bed and searching again
     beds.append(make_new_bed(rl))
     return search_in_beds(beds, rl, sleep_time, first, last)

 def monks_beds(t, rsleep, rl=12):
     beds = []
     output = []
     for cn, i in enumerate(t):
         sleep_time = rsleep[cn]
         first = i.index(1)
         last = len(i) - 1 - i[::-1].index(1)
         first, last = search_in_beds(beds, rl, sleep_time, first, last)
         out = rl * '0'
         out = out[:first] + sleep_time * '1' + out[last:]
         output.append([int(x) for x in out])
     return beds

 print('time_slots_in_monastery:')
 pprint.pprint(time_slots_in_monastery)
 print('required_sleeping')
 pprint.pprint(required_sleeping)
 pprint.pprint(monks_beds(time_slots_in_monastery, required_sleeping))

2 个答案:

答案 0 :(得分:1)

天真的解决方案

我能想到的一个解决方案没有考虑到“在修道院的时间”总是确保两个相邻僧侣的睡眠时间永远不会重叠,模仿我们一天的长度:

monks = numpy.array([
    (1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0),
    (0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0),
    (0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0),
    (1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1),  # Pattern wraps around at midnight
    ...
])

我相信(但现在无法正式证明)此解决方案是最佳的,如:

  1. 足够长的一天,你只需要一张床。
  2. 对于一小时太短的一天,您需要第二张床,第一个时间段。在剩下的时间里,床会空着。
  3. 一天两小时,前两个时段需要第二张床。在剩下的时间里,床会空着。
  4. 每个时段的床位数将一直填满最后一个时段,然后再次需要第三个床位。
  5. 继续。
  6. 所以我们需要解决的是如何以编程方式转换两个相邻时间不重叠的和尚睡眠时间:

    import numpy
    
    monks = numpy.array([
        (0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0),
        (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
        (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
        (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
        (0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0),
        (0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0),
        (0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0),
        (0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0),
        (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
        (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
        (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
        (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
        (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0),
    ])
    
    # Hours of sleep required is sum along columns
    hours = numpy.sum(monks, axis=1)
    
    # Reset all sleeping times to first column
    monks = numpy.sort(monks, axis=1)[:, ::-1]
    
    # Generate sleeping pattern without overlap of two neighboring monks. When
    # one monk rises, the next one goes to bed.
    hours = numpy.cumsum(hours)
    
    # Insert 0 shift for first monk
    hours = numpy.insert(hours, 0, 0)
    
    for s, i in zip(hours, range(monks.shape[0])):
        monks[i, :] = numpy.roll(monks[i, :], s)
    
    beds = numpy.max(numpy.sum(monks, axis=0))
    
    print monks
    # [[1 1 1 1 0 0 0 0 0 0 0 0]
    #  [0 0 0 0 1 1 1 0 0 0 0 0]
    #  [0 0 0 0 0 0 0 1 1 1 0 0]
    #  [1 0 0 0 0 0 0 0 0 0 1 1]
    #  [0 1 1 1 1 0 0 0 0 0 0 0]
    #  [0 0 0 0 0 1 1 1 1 0 0 0]
    #  [1 0 0 0 0 0 0 0 0 1 1 1]
    #  [0 1 1 1 1 0 0 0 0 0 0 0]
    #  [0 0 0 0 0 1 1 1 0 0 0 0]
    #  [0 0 0 0 0 0 0 0 1 1 1 0]
    #  [1 1 0 0 0 0 0 0 0 0 0 1]
    #  [0 0 1 1 1 0 0 0 0 0 0 0]
    #  [0 0 0 0 0 1 1 1 0 0 0 0]]
    
    print beds
    # 4
    

    连续“修道院时间”的可能解决方案

    第二个解决方案我“可以想象工作”(但绝对不能证明是最优的)是使用相同的算法,限制在“修道院时间”时间,排序“修道院时间”矩阵。

    1. 首先,我会对矩阵进行排序,以便最早进入的僧侣出现:

      time_slots_in_monastery = numpy.array([
          (0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0),  # monk 0 enters earlier than 1
          (0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0),
          (0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0),
          (0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0),
          (0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0),
          (0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0),
          (0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0),
          (0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0),
          (0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0),
          (0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0),
          (0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0),
          (0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0),
      ])
      
    2. 然后对绑定的条目(同时进入的僧侣)进行排序,以便最早离开的僧侣出现:

      time_slots_in_monastery = numpy.array([
          (0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0),
          (0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0),
          (0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0),  # monk 2 and 3 enter at same time, but 2 leaves earlier than 3
          (0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0),
          (0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0),
          (0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0),
          (0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0),
          (0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0),
          (0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0),
          (0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0),
          (0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0),
          (0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0),
      ])
      
    3. 应用以前的算法,但仅限于time_slots_in_monastery二进制掩码:

      1. Monk 0开始在Monk 0掩码索引0
      2. 睡觉
      3. 当Monk 1停止时,Monk 0开始睡觉。从Monk 1掩码索引0
      4. 开始,覆盖掩码之外的重叠
      5. 继续为所有僧侣。
    4. 这个想法是让两个相邻的僧侣尽可能多地重叠并尽早填充第一个插槽。

      def rangemod(val, start, stop):
          """ Modulo in range between start and stop
      
          Adjusted from
          http://stackoverflow.com/questions/3057640/math-looping-between-min-and-max-using-mod
          """
          p = stop - start
          mod = (val - start) % p
          if mod < 0:
              mod += p
          return start + mod;
      
      
      timeslots = numpy.array([
          (0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0),
          (0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0),
          (0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0),
          (0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0),
          (0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0),
          (0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0),
          (0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0),
          (0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0),
          (0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0),
          (0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0),
          (0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0),
          (0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0),
      ])
      
      sleephours = numpy.array([4, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3, 3])
      
      # Sort timeslots
      startidx = numpy.array([list(row).index(1) for row in timeslots])
      endidx = numpy.array([list(row)[::-1].index(1) for row in timeslots])
      
      sortidx = numpy.array([
          x for _, _, x in sorted(
              zip(startidx, endidx, range(len(startidx))),
              key=lambda (x, y, z): (x, -y)
          )
      ])
      
      # Put all indices and sleephours in new order
      startidx = startidx[sortidx]
      endidx = endidx[sortidx]
      endidx = timeslots.shape[1] - endidx
      sleephours = sleephours[sortidx]
      timeslots = timeslots[sortidx, :]
      
      print timeslots
      # [[0 1 1 1 1 1 0 0 0 0 0 0]
      #  [0 0 1 1 1 1 1 1 0 0 0 0]
      #  [0 0 1 1 1 1 1 1 0 0 0 0]
      #  [0 0 1 1 1 1 1 1 1 0 0 0]
      #  [0 0 1 1 1 1 1 1 1 0 0 0]
      #  [0 0 1 1 1 1 1 1 1 0 0 0]
      #  [0 0 1 1 1 1 1 1 1 1 0 0]
      #  [0 0 0 1 1 1 1 1 1 0 0 0]
      #  [0 0 0 1 1 1 1 1 1 0 0 0]
      #  [0 0 0 1 1 1 1 1 1 0 0 0]
      #  [0 0 0 1 1 1 1 1 1 1 0 0]
      #  [0 0 0 0 0 1 1 1 1 1 0 0]]
      
      out = numpy.zeros_like(timeslots)
      
      # Fill sleep schedule
      prevstop = startidx[0]
      for r, s, e, t, row in zip(sleephours, startidx, endidx, timeslots, out):
          sleeprange = numpy.arange(s, e)
          sleeppattern = numpy.zeros(e - s)
          sleeppattern[:r] = 1
          sleeppattern = numpy.roll(sleeppattern, prevstop - s)
          row[sleeprange] = sleeppattern
          prevstop += r
          prevstop = rangemod2(prevstop, s, e)
      
      print out
      # [[0 1 1 1 1 0 0 0 0 0 0 0]
      #  [0 0 1 0 0 1 1 1 0 0 0 0]
      #  [0 0 0 1 1 1 1 0 0 0 0 0]
      #  [0 0 1 0 0 0 0 1 1 0 0 0]
      #  [0 0 0 1 1 1 0 0 0 0 0 0]
      #  [0 0 0 0 0 0 1 1 1 0 0 0]
      #  [0 0 1 1 1 0 0 0 0 0 0 0]
      #  [0 0 0 0 0 1 1 1 1 0 0 0]
      #  [0 0 0 1 1 1 0 0 0 0 0 0]
      #  [0 0 0 0 0 0 1 1 1 0 0 0]
      #  [0 0 0 1 1 1 1 0 0 0 0 0]
      #  [0 0 0 0 0 0 0 1 1 1 0 0]]
      
      print numpy.max(numpy.sum(sleeping_slots, axis=0))
      # 6
      

答案 1 :(得分:1)

这是与上述方案略有不同的解决方案。我已经改组了僧侣的索引,我确信重建索引不会有问题。我相信解决方案是最佳的。

以下是算法:

首先,如果4和3,我们将僧侣分组为单位。

  • 3名睡眠4个单位的僧侣,可以归为一个只需要一张床的单位。这是这组3名僧侣的最佳解决方案。
  • 同样地,4个睡眠3个单位的僧侣可以被认为是一个单独使用一张床的单位。

现在我们需要计算这些单位的数量,并为每个单位分配一张单人床。

现在关于剩下的僧侣:

我们肯定会把剩下4个时间单位的僧侣带到这个单位,并继续增加僧侣(他们需要3个单位时间),直到我们达到12个单位时间。我们为这个组分配一张床。

我们现在必须为其余不属于前一单位的僧侣分配一张床。

该计划如下所示:

if __name__ == '__main__':

    reqSleep = [4, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3, 3]
    num4     = len([r for r in reqSleep if r==4])
    num3     = len([r for r in reqSleep if r==3])

    group4, left4 = num4/3, num4%3
    group3, left3 = num3/4, num3%4

    groups = {}
    groups['group3'] = [
        (1,1,1,0,0,0,0,0,0,0,0,0),
        (0,0,0,1,1,1,0,0,0,0,0,0),
        (0,0,0,0,0,0,1,1,1,0,0,0),
        (0,0,0,0,0,0,0,0,0,1,1,1),
    ]

    groups['group4'] = [
        (1,1,1,1,0,0,0,0,0,0,0,0),
        (0,0,0,0,1,1,1,1,0,0,0,0),
        (0,0,0,0,0,0,0,0,1,1,1,1),
    ]

    print num4, '-->', (group4, left4)
    print num3, '-->', (group3, left3)

    alreadyIn = []
    for i in range(group4):
        alreadyIn += groups['group4']

    for i in range(group3):
        alreadyIn += groups['group3']

    # we add the number of monks who
    # sleep for 4 hours
    alreadyIn += groups['group4'][:left4]

    # Now we are left with `left3`. How many can 
    # we add to this ? We take until they consume 
    # 12 time units. 
    num3ToFill = 0
    currTotal  = left4*4
    for i in range(left3):
        if currTotal + 3 > 12: break
        num3ToFill += 1
        currTotal  += 3

    # Fill in the number of groups
    alreadyIn += groups['group3'][-num3ToFill:]

    # Finally add the leftover monks at the end
    if left3 > num3ToFill:
        alreadyIn += groups['group3'][: (left3-num3ToFill)]

    print 'Monk sleep specs ...'
    for x in  alreadyIn:
        print x

    print 'Beds used ...'
    numBeds = map(sum, zip(*alreadyIn))
    print numBeds

    print 'done'   

结果如下:

5 --> (1, 2)
7 --> (1, 3)
Monk sleep specs ...
(1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0)
(0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0)
(0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1)
(1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0)
(0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0)
(0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0)
(0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1)
(1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0)
(0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0)
(0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1)
(1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0)
(0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0)
Beds used ...
[4, 4, 4, 4, 4, 4, 3, 3, 2, 3, 3, 3]

通过更仔细地查看“所需睡眠”时间,可以优化添加更多僧侣和不同时间单位。所有算法都可能解决优化该阵列的问题。