【问题标题】:Best search algorithm for longest nights - Javascript最长夜晚的最佳搜索算法 - Javascript
【发布时间】:2019-04-17 01:51:13
【问题描述】:

考虑到一系列酒店房间和可用期(1 月 1 日至 1 月 6 日):

[
  {
    roomId: 101,
    availability: [
      { roomId: 101, date: '2018-01-01' },
      { roomId: 101, date: '2018-01-02' },
      { roomId: 101, date: '2018-01-03' },
      { roomId: 101, date: '2018-01-05' },
      { roomId: 101, date: '2018-01-06' }
    ]
  },
  {
    roomId: 102,
    availability: [
      { roomId: 102, date: '2018-01-01' },
      { roomId: 102, date: '2018-01-03' },
      { roomId: 102, date: '2018-01-04' },
      { roomId: 102, date: '2018-01-05' }
    ]
  },
  {
    roomId: 103,
    availability: [
      { roomId: 103, date: '2018-01-02' },
      { roomId: 103, date: '2018-01-03' },
      { roomId: 103, date: '2018-01-06' }
    ]
  },
  {
    roomId: 104,
    availability: [
      { roomId: 104, date: '2018-01-04' },
      { roomId: 104, date: '2018-01-05' },
      { roomId: 104, date: '2018-01-06' }
    ]
  },
  {
    roomId: 105,
    availability: [
      { roomId: 105, date: '2018-01-01' },
      { roomId: 105, date: '2018-01-02' },
      { roomId: 105, date: '2018-01-04' },
      { roomId: 105, date: '2018-01-06' }
    ]
  }
]

上述可用性的表格说明:

|     | 1 Jan | 2 Jan | 3 Jan | 4 Jan | 5 Jan | 6 Jan |
| 101 |   O   |   O   |   O   |       |   O   |   O   |
| 102 |   O   |       |   O   |   O   |   O   |       |
| 103 |       |   O   |   O   |       |       |   O   |
| 104 |       |       |       |   O   |   O   |   O   |
| 105 |   O   |   O   |       |   O   |       |   O   |

基于上述输入的预期结果是具有分组可用性的最终房间:

{
  roomId: 101, // determined by the first object in the array
  availability: [
    { roomId: 101, date: '2018-01-01' },
    { roomId: 101, date: '2018-01-02' },
    { roomId: 101, date: '2018-01-03' },
    { roomId: 104, date: '2018-01-04' },
    { roomId: 104, date: '2018-01-05' },
    { roomId: 104, date: '2018-01-06' }
  ]
}

最终分组选择为:101 & 104

|     | 1 Jan | 2 Jan | 3 Jan | 4 Jan | 5 Jan | 6 Jan |
| 101 |  ✔️  |  ✔️   |  ✔️  |       |   O   |   O   |
| 102 |   O   |       |   O   |   O   |   O   |       |
| 103 |       |   O   |   O   |       |       |   O   |
| 104 |       |       |       |   ✔️  |  ✔️  |  ✔️  |
| 105 |   O   |   O   |       |   O   |       |   O   |

因此,最终选择是如何确定的,是基于整个入住期间最少移动的房间。

中执行此操作的最有效的搜索算法(就性能而言)是什么? (需要高效以保持快速处理,即使有很长的可用性请求或更多房间分组

我会将我的算法放在答案部分,但我认为不是 最有效的方法。如果有更好的方法,请提出建议!

【问题讨论】:

  • 您的问题/问题听起来很有趣,可能最好在此处发布codereview.stackexchange.com,因为您的问题非常先进。 :)
  • 我不是数学家,也不知道更好的算法,但我认为您缺少另一个约束。如果您有多种选择可供选择,您应该选择周围空洞最少的那个。否则,您将开始将所有房间的所有预订碎片化,从而无法选择一个房间进行较长时间的预订,导致大量短期预订占用所有房间。
  • @Oliver 我已经考虑过这一点并测试过,选择最少的空洞并不能真正获得最少的房间数量。很明显,如果我们在测试用例中加入更多的可用性。 ://

标签: javascript javascript algorithm search grouping


【解决方案1】:

我建议在几天内使用循环,并在每次迭代中,确定从当天开始连续开放时间最长的房间;然后,将天数增加该天数。

为了使解析更容易,您可以对数据进行预处理,以便availability 信息在由数字日期索引索引的对象中可用 - 例如,转

availability: [
  { roomId: 105, date: '2018-01-01' },
  { roomId: 105, date: '2018-01-02' },
  { roomId: 105, date: '2018-01-04' },
  { roomId: 105, date: '2018-01-06' }
]

进入

'105': {
  1: true,
  2: true,
  4: true,
  6: true
}

这样,要确定从第 N 天起房间 X 可以使用多长时间,只需重复测试 rooms[x][n] === true 并递增 n 直到测试失败。

如果要多次执行此操作(从同一个房间数据集开始),您可以提前一次进行所有实际计算,然后构建一个包含最佳房间的对象以供选择每天,例如:

{ // keys represent day index
  1: { roomId: 101, availableUntil: 3 },
  2: { roomId: 101, availableUntil: 3 }, // just as good as room 103
  3: { roomId: 101, availableUntil: 3 }, // just as good as 103 and 102
  // room 101 not available on Jan 4, room 104 becomes the best room to choose:
  4: { roomId: 104, availableUntil: 6 },
  5: { roomId: 104, availableUntil: 6 },
  6: { roomId: 104, availableUntil: 6 } // just as good as 105
}

然后,给定一个人想留下的天数,计算最少破坏性的房间变化是一个简单的属性查找问题,直到到达结束日期。

为了简化以下代码的可读性,我将使用辅助函数

const dateStrToDayIndex = dateStr => Number(dateStr.match(/\d\d$/)[0]);

以便索引从 1 开始到 6,以测试您的输入,但在您的实际代码中,您当然会使用一些可靠的东西来计算 dateStr 和某个日期之间的天数,例如1970 年 1 月 1 日,或类似的时间。 (或者,如果适合您,请随意使用您当前正在使用的 moment(toDate).diff(moment(fromDate), 'days')

现在,代码:首先将数据集转换为如下所示的对象:

/*
{
  101: {
    1: true,
    2: true,
    3: true,
    5: true,
    6: true,
  },
  102:
  // ...
}
*/
const dateStrToDayIndex = dateStr => Number(dateStr.match(/\d\d$/)[0]);

const datasetByRoom = dataset.reduce((datasetA, { roomId, availability }) => {
  datasetA[roomId] = availability.reduce((a, { date }) => {
    a[dateStrToDayIndex(date)] = true;
    return a;
  }, {});
  return datasetA;
}, {});

然后,主要的getBestRoomFromDay 函数,它采用一天索引并搜索datasetByRoom 中的每个房间对象,寻找从当天开始连续开放最多的房间对象:

function getBestRoomFromDay(dayIndex) {
  let bestRoomSoFar;
  let bestCumulativeDaysSoFar = 0;
  Object.entries(datasetByRoom).forEach(([room, availObj]) => {
    let thisRoomDays = 0;
    let dayIndexCheck = dayIndex;
    while (availObj[dayIndexCheck]) {
      dayIndexCheck++;
      thisRoomDays++;
    }
    if (thisRoomDays > bestCumulativeDaysSoFar) {
      bestRoomSoFar = room;
      bestCumulativeDaysSoFar = thisRoomDays - 1;
    }
  });
  return {
    room: bestRoomSoFar,
    until: dayIndex + bestCumulativeDaysSoFar
  };
}

实际上,为输入 (1-6) 中的每个 dayIndex 调用 getBestRoomFromDay 的示例:

const dataset=[{roomId:101,availability:[{roomId:101,date:'2018-01-01'},{roomId:101,date:'2018-01-02'},{roomId:101,date:'2018-01-03'},{roomId:101,date:'2018-01-05'},{roomId:101,date:'2018-01-06'}]},{roomId:102,availability:[{roomId:102,date:'2018-01-01'},{roomId:102,date:'2018-01-03'},{roomId:102,date:'2018-01-04'},{roomId:102,date:'2018-01-05'}]},{roomId:103,availability:[{roomId:103,date:'2018-01-02'},{roomId:103,date:'2018-01-03'},{roomId:103,date:'2018-01-06'}]},{roomId:104,availability:[{roomId:104,date:'2018-01-04'},{roomId:104,date:'2018-01-05'},{roomId:104,date:'2018-01-06'}]},{roomId:105,availability:[{roomId:105,date:'2018-01-01'},{roomId:105,date:'2018-01-02'},{roomId:105,date:'2018-01-04'},{roomId:105,date:'2018-01-06'}]}];const dateStrToDayIndex=dateStr=>Number(dateStr.match(/\d\d$/)[0]);const datasetByRoom=dataset.reduce((datasetA,{roomId,availability})=>{datasetA[roomId]=availability.reduce((a,{date})=>{a[dateStrToDayIndex(date)]=!0;return a},{});return datasetA},{});function getBestRoomFromDay(dayIndex){let bestRoomSoFar;let bestCumulativeDaysSoFar=0;Object.entries(datasetByRoom).forEach(([room,availObj])=>{let thisRoomDays=0;let dayIndexCheck=dayIndex;while(availObj[dayIndexCheck]){dayIndexCheck++;thisRoomDays++}
if(thisRoomDays>bestCumulativeDaysSoFar){bestRoomSoFar=room;bestCumulativeDaysSoFar=thisRoomDays-1}});return{room:bestRoomSoFar,until:dayIndex+bestCumulativeDaysSoFar}}

console.log('Example of testing getBestRoomFromDay function on all days:');
for (let i = 1; i < 7; i++) {
  console.log('Day ' + i + ': ' + JSON.stringify(getBestRoomFromDay(i)));
}

然后,要从fromto 日期字符串构造一个时间表,例如'2018-01-01''2018-01-06',只需使用适当的日期重复调用getBestRoomFromDay,将日期索引增加所需的数量每次迭代:

const dataset=[{roomId:101,availability:[{roomId:101,date:'2018-01-01'},{roomId:101,date:'2018-01-02'},{roomId:101,date:'2018-01-03'},{roomId:101,date:'2018-01-05'},{roomId:101,date:'2018-01-06'}]},{roomId:102,availability:[{roomId:102,date:'2018-01-01'},{roomId:102,date:'2018-01-03'},{roomId:102,date:'2018-01-04'},{roomId:102,date:'2018-01-05'}]},{roomId:103,availability:[{roomId:103,date:'2018-01-02'},{roomId:103,date:'2018-01-03'},{roomId:103,date:'2018-01-06'}]},{roomId:104,availability:[{roomId:104,date:'2018-01-04'},{roomId:104,date:'2018-01-05'},{roomId:104,date:'2018-01-06'}]},{roomId:105,availability:[{roomId:105,date:'2018-01-01'},{roomId:105,date:'2018-01-02'},{roomId:105,date:'2018-01-04'},{roomId:105,date:'2018-01-06'}]}];const dateStrToDayIndex=dateStr=>Number(dateStr.match(/\d\d$/)[0]);const datasetByRoom=dataset.reduce((datasetA,{roomId,availability})=>{datasetA[roomId]=availability.reduce((a,{date})=>{a[dateStrToDayIndex(date)]=!0;return a},{});return datasetA},{});function getBestRoomFromDay(dayIndex){let bestRoomSoFar;let bestCumulativeDaysSoFar=0;Object.entries(datasetByRoom).forEach(([room,availObj])=>{let thisRoomDays=0;let dayIndexCheck=dayIndex;while(availObj[dayIndexCheck]){dayIndexCheck++;thisRoomDays++}
if(thisRoomDays>bestCumulativeDaysSoFar){bestRoomSoFar=room;bestCumulativeDaysSoFar=thisRoomDays-1}});return{room:bestRoomSoFar,until:dayIndex+bestCumulativeDaysSoFar}};

function getSchedule(dateStrFrom, dateStrTo) {
  const [from, to] = [dateStrFrom, dateStrTo].map(dateStrToDayIndex);
  let day = from;
  const schedule = [];
  while (day < to) {
    const schedObj = getBestRoomFromDay(day);
    schedule.push({ from: day, ...schedObj });
    // increment day, so as to find the next longest consecutive room:
    day = schedObj.until + 1;
  }
  schedule[schedule.length - 1].until = to;
  return schedule;
}
console.log(getSchedule('2018-01-01', '2018-01-06'));

完整的,未缩小的:

const dataset = [
  {
    roomId: 101,
    availability: [
      { roomId: 101, date: '2018-01-01' },
      { roomId: 101, date: '2018-01-02' },
      { roomId: 101, date: '2018-01-03' },
      { roomId: 101, date: '2018-01-05' },
      { roomId: 101, date: '2018-01-06' }
    ]
  },
  {
    roomId: 102,
    availability: [
      { roomId: 102, date: '2018-01-01' },
      { roomId: 102, date: '2018-01-03' },
      { roomId: 102, date: '2018-01-04' },
      { roomId: 102, date: '2018-01-05' }
    ]
  },
  {
    roomId: 103,
    availability: [
      { roomId: 103, date: '2018-01-02' },
      { roomId: 103, date: '2018-01-03' },
      { roomId: 103, date: '2018-01-06' }
    ]
  },
  {
    roomId: 104,
    availability: [
      { roomId: 104, date: '2018-01-04' },
      { roomId: 104, date: '2018-01-05' },
      { roomId: 104, date: '2018-01-06' }
    ]
  },
  {
    roomId: 105,
    availability: [
      { roomId: 105, date: '2018-01-01' },
      { roomId: 105, date: '2018-01-02' },
      { roomId: 105, date: '2018-01-04' },
      { roomId: 105, date: '2018-01-06' }
    ]
  }
];
const dateStrToDayIndex = dateStr => Number(dateStr.match(/\d\d$/)[0]);

const datasetByRoom = dataset.reduce((datasetA, { roomId, availability }) => {
  datasetA[roomId] = availability.reduce((a, { date }) => {
    a[dateStrToDayIndex(date)] = true;
    return a;
  }, {});
  return datasetA;
}, {});

function getBestRoomFromDay(dayIndex) {
  let bestRoomSoFar;
  let bestCumulativeDaysSoFar = 0;
  Object.entries(datasetByRoom).forEach(([room, availObj]) => {
    let thisRoomDays = 0;
    let dayIndexCheck = dayIndex;
    while (availObj[dayIndexCheck]) {
      dayIndexCheck++;
      thisRoomDays++;
    }
    if (thisRoomDays > bestCumulativeDaysSoFar) {
      bestRoomSoFar = room;
      bestCumulativeDaysSoFar = thisRoomDays - 1;
    }
  });
  return {
    room: bestRoomSoFar,
    until: dayIndex + bestCumulativeDaysSoFar
  };
}

function getSchedule(dateStrFrom, dateStrTo) {
  const [from, to] = [dateStrFrom, dateStrTo].map(dateStrToDayIndex);
  let day = from;
  const schedule = [];
  while (day < to) {
    const schedObj = getBestRoomFromDay(day);
    schedule.push({ from: day, ...schedObj });
    // increment day, so as to find the next longest consecutive room:
    day = schedObj.until + 1;
  }
  schedule[schedule.length - 1].until = to;
  return schedule;
}
console.log(getSchedule('2018-01-01', '2018-01-06'));

如前所述,如果您必须从相同数据集计算多个getBestRoomFromDays(即不插入新预留),您可以提前构造一个包含getBestRoomFromDay 用于调用它的每个可能值,从而确保每天只进行一次计算一次

【讨论】:

  • 太棒了!我已经测试了该算法,它也适用于大数据!对while (day &lt;= to) 上的示例代码进行了小修复,并删除了schedule[schedule.length - 1].until = to;。感谢您的意见!
【解决方案2】:

是的,有一种更简单的方法:

  1. 将您的可用性信息折叠到连续可用性间隔列表中,例如[{start:1, end:3, room: "101"}, {start ...etc...
  2. 按开始日期对时间间隔进行排序,并反转列表以便您可以按顺序弹出它们(比移动更快)
  3. 将您的“need_room”初始化为第 1 天
  4. 当您的“need_room”天少于您入住的最后一天时:
    1. 弹出所有开始日
    2. 将最后一天结束的房间添加到您的输出中。如果它一直持续到您的住宿结束,那么您就完成了。
    3. 否则,您的新“need_room”日将是房间结束日之后的那一天。

【讨论】:

  • 从数组中弹出并计算的想法真的很棒!但是,我无法优化第一步(折叠可用性)。如果您显示第一步的示例代码会很棒
【解决方案3】:

我的搜索想法是使用不可用的夜间索引:

如果我们通过可用性映射并获取可用的夜间索引,我们将得到如下结果:

const availableIndexes = [
  [0, 1, 2, 4, 5],
  [0, 2, 3, 4],
  [1, 2, 5],
  [3, 4, 5],
  [0, 1, 3, 5]
]

然后我们可以有这样的不可用的夜晚索引:

const notAvailableIndexes = [
  [3],
  [1, 5],
  [0, 3, 4],
  [1, 2, 3],
  [2, 4]
]

这就是算法:

找到最大的数字(表示最长的夜晚)

step1 = [
  [3], // select this as this has the biggest number
  [1, 5],
  [0, 3, 4],
  [1, 2, 3],
  [2, 4]
]

之后删除索引

// after each step, remove the indexes

重复相同直到结束

step2 = [
  [], // not using this, as it was selected from the previous step
  [5],
  [4],
  [], // select this as this has all the availability until the end of the stay period
  [4]
]

以下是基于上述思想在 Javascript 中的粗略实现(之前未测试/运行):

const rooms = [
  {
    roomId: 101,
    availability: [
      { roomId: 101, date: '2018-01-01' },
      { roomId: 101, date: '2018-01-02' },
      { roomId: 101, date: '2018-01-03' },
      { roomId: 101, date: '2018-01-05' },
      { roomId: 101, date: '2018-01-06' }
    ]
  },
  {
    roomId: 102,
    availability: [
      { roomId: 102, date: '2018-01-01' },
      { roomId: 102, date: '2018-01-03' },
      { roomId: 102, date: '2018-01-04' },
      { roomId: 102, date: '2018-01-05' }
    ]
  },
  {
    roomId: 103,
    availability: [
      { roomId: 103, date: '2018-01-02' },
      { roomId: 103, date: '2018-01-03' },
      { roomId: 103, date: '2018-01-06' }
    ]
  },
  {
    roomId: 104,
    availability: [
      { roomId: 104, date: '2018-01-04' },
      { roomId: 104, date: '2018-01-05' },
      { roomId: 104, date: '2018-01-06' }
    ]
  },
  {
    roomId: 105,
    availability: [
      { roomId: 105, date: '2018-01-01' },
      { roomId: 105, date: '2018-01-02' },
      { roomId: 105, date: '2018-01-04' },
      { roomId: 105, date: '2018-01-06' }
    ]
  }
];

const fromDate = '2018-01-01';
const toDate = '2018-01-06';
const totalNights = moment(toDate).diff(moment(fromDate), 'days');

// Get all the not available nights index for each room
const notAvail = rooms.map(room => {
  let array = [];
  Array.from(Array(totalNights)).forEach((null, index) => {
    const date = moment(fromDate).clone().add(index, 'days').format('YYYY-MM-DD');
    // if available
    if (room.availability.find(av => av.date === date)) {
      array.push(null);
    }
    // if that night is not available, push the index
    else {
      array.push(index);
    }
  });
  // return clean array without null values
  return array.filter(Boolean);
})

/**
  we should get this array:
    const notAvail = [
      [3],
      [1, 5],
      [0, 3, 4],
      [1, 2, 3],
      [2, 4]
    ]
**/

const stack = [];
let currentNight = 0;
let tempUnavailIndex = null;

// keep searching until it reaches the end of the stay period
do {
  let roomId = null;
  let longestCount = 0;

  // Get the longest night
  notAvail.forEach((arr, index) => {
    if (index !== tempUnavailIndex) {
      const firstIndex = arr[0];
      // If the night is available
      if (currentNight !== firstIndex) {
        // if it is available until the end of the stay (empty array) 
        if (!arr[0]) {
          roomId = rooms[index].roomId;
          longestCount = totalNights - currentNight;
        }
        // else if it the longest nights
        else if (arr[0] > longestCount) {
          roomId = rooms[index].roomId;
          longestCount = arr[0];
        }
      }
    }
  });

  // If there is a day where no rooms are available, throw an error
  if (roomId === null) {
    throw new Error('No room available for the period of stay!');
  }

  // Set the room to be unavailable for next search
  tempUnavailIndex = roomId;

  // Push each night into the stack
  Array.from(Array(longestCount)).forEach(() => {
    // Push the object into the stack
    stack.push({
      roomId,
      date: moment(fromDate).clone().add(currentNight, 'day')
    });
    // Increment the night
    currentNight += 1;
  });

  // Remove the current search indexes from the notAvail array
  notAvail.map(arr => arr.filter(index => index > currentNight));
}
while (currentNight !== totalNights)

我认为应该有更好的方法来做到这一点

【讨论】:

    猜你喜欢
    • 2020-09-03
    • 1970-01-01
    • 2012-01-12
    • 1970-01-01
    • 2010-12-07
    • 2011-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多