Javascript 按最短步长对数组进行排序

Javascript sorting array by shortest step

这是一个仓库 aisle/row/rack 问题。从过道往下看,我左边是第 6 排,右边是第 5 排。

我需要以尽可能少的步骤访问机架列表。这是行和机架的数组:

[   
    {"rowIndex": 5, "rowRackIndexActual": 1},
    {"rowIndex": 5, "rowRackIndexActual": 3},
    {"rowIndex": 5, "rowRackIndexActual": 4},
    {"rowIndex": 6, "rowRackIndexActual": 5},
    {"rowIndex": 6, "rowRackIndexActual": 6},
    {"rowIndex": 5, "rowRackIndexActual": 6},
    {"rowIndex": 5, "rowRackIndexActual": 7},
    {"rowIndex": 6, "rowRackIndexActual": 8},
    {"rowIndex": 5, "rowRackIndexActual": 8},
    {"rowIndex": 6, "rowRackIndexActual": 9}
]

从下往上看输出应该是这样的:

(step 9) row 6, rack 9  |  
(step 8) row 6, rack 8  + row 5, rack 8 (step 7)
                        | row 5, rack 7 (step 6)
                        / row 5, rack 6 (step 5)
(step 4) row 6, rack 5  \
                        | row 5, rack 4 (step 3)
                        | row 5, rack 3 (step 2)
                        | row 5, rack 1 (step 1)
                        

此问题与出现在两行中的机架 8 有关。我的排序是先选择左边的一排,但最短的一步实际上是向上移动一排,因为我的最后一个架子在第 5 排(右排)——在这种情况下是第 7 步;

我显然需要跟踪一行何时发生变化——何时向左或向右移动。到目前为止,这是我的排序声明:

        let prevRow = aisleRackArr[0].rowIndex; // the first item in the array
        aisleRackArr.sort(function(a, b) {
            console.log(a.rowIndex, a.rowRackIndexActual, b.rowIndex, b.rowRackIndexActual, prevRow);
            if (a.rowRackIndexActual === b.rowRackIndexActual) {
                console.log(`got the same rowRackIndex`);
                if (a.rowIndex === prevRow) {
                    console.log(`a should come first`);
                    return 0;
                } else {
                    console.log(`a came second`);
                    return 1;
                }
            }
            prevRow = b.rowIndex;
            return 0;
        });

这是排序函数的输出(a.row、a.rack、b.row、b.rack、prevRow):

5 3 5 1 5
5 4 5 3 5
6 5 5 4 5
5 6 6 5 5
5 7 5 6 6
6 8 5 7 5
5 8 6 8 5 <---------- not returning a even though a.row===prevRow
got the same rowRackIndex
a should come first
6 9 5 8 5

乍一看,我们似乎可以简单地按 rackIndex 排序,然后按 rowIndex 排序。但是,当我们需要在左行中向上移动并且接下来的两个机架是相同的索引时,这不能解释这种情况。

我该怎么做才能解决这个问题?

编辑 这是一张图片:

好吧,不管机架号如何,下面的示例都是一个不可知的解决方案——一个可重用的函数,它将获取对象数组并按主要 属性(“机架”)对对象进行排序值,如果它们恰好相等,它们将与次要的(“行”)值进行比较:

a[primary] - b[primary] || a[secondary] - b[secondary]

即:

{row: 6 rack: 8} - {row:5, rack: 8} OR {row: 6 rack: 8} - {row:5, rack: 8}
//           ⬆️ This equals out ⬆️ ⏭️      ⬆️  row: 5 first  ⬆️

详情见下方示例

// Mixed order to really test capability
const rowRack = [   
  {"row": 5, "rack": 1},  
  {"row": 6, "rack": 9},  
  {"row": 5, "rack": 3},
  {"row": 5, "rack": 4},
  {"row": 6, "rack": 5},
  {"row": 5, "rack": 7},
  {"row": 6, "rack": 8},
  {"row": 5, "rack": 8},
  {"row": 5, "rack": 6},
  {"row": 5, "rack": 2}
];
/**
* Given an array of objects (objectArray), it will sort each object by the
* first given property's (primary) value. If there is a value that equals
* another value, the second given property's (secondary) value will be 
* compared to each other.
* @param {array<object>} objectArray - An array of object literals
* @param {string} primary - The property's value to compare with.
* @param {string} secondary - The property's value to compare with if the 
*                             primary values are equal. 
* @return {array<object>}
*/  

const route = (objectArray, primary, secondary) => {
  return objectArray.sort((a, b) => 
    a[primary] - b[primary] || a[secondary] - b[secondary]);
};
  
console.log(route(rowRack, "rack", "row"));
.as-console-row::after { width: 0; font-size: 0; }
.as-console-row-code { width: 100%; word-break: break-word; }
.as-console-wrapper { min-height: 100% !important; max-width: 30%; margin-left: 70%; }
<pre>
(step 9) row 6, rack 9  |  
(step 8) row 6, rack 8  + row 5, rack 8 (step 7)
                        | row 5, rack 7 (step 6)
                        / row 5, rack 6 (step 5)
(step 4) row 6, rack 5  \
                        | row 5, rack 4 (step 3)
                        | row 5, rack 3 (step 2)
                        | row 5, rack 2 (step 1)
                        | row 5, rack 1 (step 0)
</pre>

如我所料,我需要跟踪当前行。由于我们可以向上移动一行或向下移动一行,因此每行中的每个货架都被赋予了两个索引,如果选择了过道中的每个货架,则表示通过过道的最佳路线。 “向下”指数不是“向上”指数的反面。向下移动一排时,第一个架子将在您的左侧。向上移动一排时,第一个架子将在您的右侧。然后,索引在向上移动时由 over/up/over 分配,向下移动时由 over/down/over 分配。

我们采用我们的挑选列表——一个订单中所有正在填充的项目的数组,其中还包含可以在其中找到产品的行和货架——并提取出唯一的过道。

这是一个选择列表中的示例记录:

{
    "idorder": 527261,
    "orderIndex": 3,
    "sku": "WZH76-4-W-YRS-PKP-XL",
    "upc": "195041059206",
    "quantity": 1,
    "warehouse": 10,
    "zone": 10,
    "row": "20",
    "rack": 20,
    "tier": 1,
    "shelf": 2,
    "bin": 5,
    "rowNorm": "000020",
    "aisleNum": 1,
    "rowIndex": 2,
    "rowRackIndex": 2,
    "rowRackIndexActual": 2,
    "revRowRackIndexActual": -2,
    "ecFactor": 0,
    "aisleMaxRackIndex": 10,
    "aisleRackIndex": 3,
    "reverseAisleRackIndex": 16,
    "rackCode": "10-10-20-20"
  }

从整个挑选列表中我们得到独特的过道:

uniqueAisles = [...new Set(pickListArr.map((a) => a.aisleNum))].keySort({aisleNum: `asc`});

然后,我们遍历过道,从挑选列表中选择与当前过道匹配的每个产品:

sarr = [];
aisleRackArr = pickListArr.filter((r) => r.aisleNum === aa);

然后通过获取该过道的独特货架再次优化我们的 aisleRackArray:

aisleRackArr = [...new Map(aisleRackArr.map((item) => [item.aisleRackIndex, item])).values()];

然后我们确定走道的哪个方向:向上或向下。这与本次讨论无关,因此我们将跳过它。

一旦我们知道我们前进的方向,我们就会根据它的向上或向下索引对行中的每个选定机架进行粗略排序 - 来自订单选择列表:

if (curDir === `up`) {
    aisleRackArr.keySort({rowRackIndexActual: `asc`, rowindex: `desc`});
} else {
    aisleRackArr.keySort({revRowRackIndexActual: `asc`, rowIndex: `asc`});
}

我们知道基于上述排序的第一个机架,我们知道我们要去的方向。

const dirFactor = curDir === `up`?1 : -1;
curRack = aisleRackArr[0];

让我们获取第一条记录并将其推入临时排序数组 (sarr)

sarr.push({rowIndex: curRack.rowIndex, rackIndex: curRack.rowRackIndexActual, aisleNum: curAisleNum, aisleSort: +(+(+curRack.rowRackIndex * 10)) * (+dirFactor), rackCode: curRack.rackCode});

然后将其从我们的过道货架阵列中移除:

aisleRackArr.splice(0, 1);

现在,我们将遍历过道货架阵列中的剩余记录,寻找我们应该移动到的下一个货架:

while (aisleRackArr.length > 0) {
    curRack = getNextRack(aisleRackArr, dirFactor, curRack.rowIndex);

一旦我们知道我们的下一个机架,将它推入临时数组,然后将其从我们的机架数组中删除:

    sarr.push({rowIndex: curRack.rowIndex, rackIndex: curRack.rowRackIndexActual, aisleNum: curAisleNum, aisleSort: curRack.sorter, rackCode: curRack.rackCode});
    const curIndex=aisleRackArr.indexOf(curRack);
    aisleRackArr.splice(curIndex, 1);

并继续循环;

}

一旦我们完成循环,将整个临时数组推送到我们的主路由数组中:

mapRouteArr.push(...sarr);

确定下一个机架的代码:

我们需要获取数组中接下来的三个记录,因为这些记录将包含我们接下来可以进行的每一个可能步骤:向左移动、向右移动或向上移动。一旦我们有了这三个记录,我们就会根据机架索引(我们在行的上或下多远)分配一个新的排序顺序,如果这些记录中的一个或多个在同一行(左或右)中,我们当前。我们将根据该标准对这三个记录进行排序。如果其中两个机架在同一层,则给我们当前行中的机架优先。

然后我们 return 该行,while 循环将其存储在临时排序数组中,然后将其从混合中删除。

我们继续前进,直到机架阵列为空。

function getNextRack(rackArray, dirFactor, curRow) {
    next3Arr = rackArray.slice(0, 4);

    if (dirFactor === 1) { // going up!
        next3Arr.forEach((a) => {
            a.sorter = +(+(+a.rowRackIndexActual * 10) + (+curRow === +a.rowIndex ? 0 : 1));
            a.currentRow = curRow;
        });
        return next3Arr.sort((a, b)=> a.sorter-b.sorter)[0];
    } else { // going down!
        next3Arr.forEach((a) => {
            a.sorter = +((+a.rowRackIndexActual) * -10) - (+curRow === +a.rowIndex ? 1 : 0);
            a.currentRow = curRow;
        });
        return next3Arr.sort((a, b) => a.sorter - b.sorter)[0];
    }
}

我的测试用例使用 10 个订单,导致需要访问 47 个不同的机架。排序的结果如下所示。该数组考虑了所有可能的 left/right、up/down 和不同的行长度情况,并且它应该是这样的:

[
    {
        "rowIndex": 2,
        "rackIndex": 2,
        "aisleNum": 1,
        "aisleSort": 20,
        "rackCode": "10-10-20-20"
    },
    {
        "rowIndex": 1,
        "rackIndex": 6,
        "aisleNum": 1,
        "aisleSort": 61,
        "rackCode": "10-10-10-70"
    },
    {
        "rowIndex": 1,
        "rackIndex": 8,
        "aisleNum": 1,
        "aisleSort": 80,
        "rackCode": "10-10-10-90"
    },
    {
        "rowIndex": 2,
        "rackIndex": 9,
        "aisleNum": 1,
        "aisleSort": 91,
        "rackCode": "10-10-20-90"
    },
    {
        "rowIndex": 3,
        "rackIndex": 9,
        "aisleNum": 2,
        "aisleSort": -100,
        "rackCode": "10-10-30-90"
    },
    {
        "rowIndex": 3,
        "rackIndex": 8,
        "aisleNum": 2,
        "aisleSort": -81,
        "rackCode": "10-10-30-80"
    },
    {
        "rowIndex": 4,
        "rackIndex": 8,
        "aisleNum": 2,
        "aisleSort": -80,
        "rackCode": "10-10-40-80"
    },
    {
        "rowIndex": 4,
        "rackIndex": 7,
        "aisleNum": 2,
        "aisleSort": -71,
        "rackCode": "10-10-40-70"
    },
    {
        "rowIndex": 4,
        "rackIndex": 4,
        "aisleNum": 2,
        "aisleSort": -41,
        "rackCode": "10-10-40-40"
    },
    {
        "rowIndex": 3,
        "rackIndex": 0,
        "aisleNum": 2,
        "aisleSort": 0,
        "rackCode": "10-10-e4-10"
    },
    {
        "rowIndex": 5,
        "rackIndex": 1,
        "aisleNum": 3,
        "aisleSort": 20,
        "rackCode": "10-10-50-10"
    },
    {
        "rowIndex": 5,
        "rackIndex": 3,
        "aisleNum": 3,
        "aisleSort": 30,
        "rackCode": "10-10-50-30"
    },
    {
        "rowIndex": 5,
        "rackIndex": 4,
        "aisleNum": 3,
        "aisleSort": 40,
        "rackCode": "10-10-50-40"
    },
    {
        "rowIndex": 6,
        "rackIndex": 5,
        "aisleNum": 3,
        "aisleSort": 51,
        "rackCode": "10-10-60-50"
    },
    {
        "rowIndex": 6,
        "rackIndex": 6,
        "aisleNum": 3,
        "aisleSort": 60,
        "rackCode": "10-10-60-60"
    },
    {
        "rowIndex": 5,
        "rackIndex": 6,
        "aisleNum": 3,
        "aisleSort": 61,
        "rackCode": "10-10-50-60"
    },
    {
        "rowIndex": 5,
        "rackIndex": 7,
        "aisleNum": 3,
        "aisleSort": 70,
        "rackCode": "10-10-50-70"
    },
    {
        "rowIndex": 5,
        "rackIndex": 8,
        "aisleNum": 3,
        "aisleSort": 80,
        "rackCode": "10-10-50-80"
    },
    {
        "rowIndex": 6,
        "rackIndex": 8,
        "aisleNum": 3,
        "aisleSort": 81,
        "rackCode": "10-10-60-80"
    },
    {
        "rowIndex": 6,
        "rackIndex": 9,
        "aisleNum": 3,
        "aisleSort": 90,
        "rackCode": "10-10-60-90"
    },
    {
        "rowIndex": 7,
        "rackIndex": 9,
        "aisleNum": 4,
        "aisleSort": -100,
        "rackCode": "10-10-70-90"
    },
    {
        "rowIndex": 8,
        "rackIndex": 8,
        "aisleNum": 4,
        "aisleSort": -80,
        "rackCode": "10-10-80-80"
    },
    {
        "rowIndex": 7,
        "rackIndex": 4,
        "aisleNum": 4,
        "aisleSort": -40,
        "rackCode": "10-10-70-40"
    },
    {
        "rowIndex": 9,
        "rackIndex": 2,
        "aisleNum": 5,
        "aisleSort": 30,
        "rackCode": "10-10-90-20"
    },
    {
        "rowIndex": 10,
        "rackIndex": 3,
        "aisleNum": 5,
        "aisleSort": 31,
        "rackCode": "10-10-100-30"
    },
    {
        "rowIndex": 10,
        "rackIndex": 7,
        "aisleNum": 5,
        "aisleSort": 70,
        "rackCode": "10-10-100-70"
    },
    {
        "rowIndex": 11,
        "rackIndex": 8,
        "aisleNum": 6,
        "aisleSort": -90,
        "rackCode": "10-10-110-80"
    },
    {
        "rowIndex": 11,
        "rackIndex": 7,
        "aisleNum": 6,
        "aisleSort": -71,
        "rackCode": "10-10-110-70"
    },
    {
        "rowIndex": 12,
        "rackIndex": 7,
        "aisleNum": 6,
        "aisleSort": -70,
        "rackCode": "10-10-120-70"
    },
    {
        "rowIndex": 12,
        "rackIndex": 5,
        "aisleNum": 6,
        "aisleSort": -51,
        "rackCode": "10-10-120-50"
    },
    {
        "rowIndex": 11,
        "rackIndex": 4,
        "aisleNum": 6,
        "aisleSort": -40,
        "rackCode": "10-10-110-40"
    },
    {
        "rowIndex": 12,
        "rackIndex": 3,
        "aisleNum": 6,
        "aisleSort": -30,
        "rackCode": "10-10-120-30"
    },
    {
        "rowIndex": 11,
        "rackIndex": 2,
        "aisleNum": 6,
        "aisleSort": -20,
        "rackCode": "10-10-110-20"
    },
    {
        "rowIndex": 11,
        "rackIndex": 1,
        "aisleNum": 6,
        "aisleSort": -11,
        "rackCode": "10-10-110-10"
    },
    {
        "rowIndex": 12,
        "rackIndex": 1,
        "aisleNum": 6,
        "aisleSort": -10,
        "rackCode": "10-10-120-10"
    },
    {
        "rowIndex": 13,
        "rackIndex": 2,
        "aisleNum": 7,
        "aisleSort": 30,
        "rackCode": "10-10-130-20"
    },
    {
        "rowIndex": 13,
        "rackIndex": 4,
        "aisleNum": 7,
        "aisleSort": 40,
        "rackCode": "10-10-130-40"
    },
    {
        "rowIndex": 13,
        "rackIndex": 6,
        "aisleNum": 7,
        "aisleSort": 60,
        "rackCode": "10-10-130-60"
    },
    {
        "rowIndex": 15,
        "rackIndex": 6,
        "aisleNum": 8,
        "aisleSort": -70,
        "rackCode": "10-10-150-60"
    },
    {
        "rowIndex": 16,
        "rackIndex": 6,
        "aisleNum": 8,
        "aisleSort": -60,
        "rackCode": "10-10-160-60"
    },
    {
        "rowIndex": 15,
        "rackIndex": 5,
        "aisleNum": 8,
        "aisleSort": -50,
        "rackCode": "10-10-150-50"
    },
    {
        "rowIndex": 15,
        "rackIndex": 4,
        "aisleNum": 8,
        "aisleSort": -41,
        "rackCode": "10-10-150-40"
    },
    {
        "rowIndex": 15,
        "rackIndex": 3,
        "aisleNum": 8,
        "aisleSort": -31,
        "rackCode": "10-10-150-30"
    },
    {
        "rowIndex": 16,
        "rackIndex": 2,
        "aisleNum": 8,
        "aisleSort": -20,
        "rackCode": "10-10-160-20"
    },
    {
        "rowIndex": 15,
        "rackIndex": 1,
        "aisleNum": 8,
        "aisleSort": -10,
        "rackCode": "10-10-150-10"
    },
    {
        "rowIndex": 17,
        "rackIndex": 2,
        "aisleNum": 9,
        "aisleSort": 20,
        "rackCode": "10-10-170-20"
    },
    {
        "rowIndex": 17,
        "rackIndex": 3,
        "aisleNum": 9,
        "aisleSort": 30,
        "rackCode": "10-10-170-30"
    }
]

EC 机架是端盖,它们属于奇数行 - 右侧的行朝过道看。整个排序中的第一个机架是红色的。使用该起点并遵循上述数组,我们现在有可能选择一组订单的最短路径。

请注意:当我们完成一个过道时,有时下一个过道中最近的货架会在我们身后。下面是确定下一个过道中最近的货架的计算,这些因素必须在当前过道中转身才能移动到下一个最近的货架:

if (curDir === `up`) {
    x = reverseMinRack.reverseAisleRackIndex + previousReverseAisleRackIndex + curDir === `up` ? 0 : 1;// +1 accounts for having to turn around
    y = minRack.aisleRackIndex + previousAisleRackIndex + curDir === `down` ? 0 : 1; // +1 accounts for having to turn around
}
if (x === y) {
    curDir = curDir === `up` ? `down` : `up`;
}
if (x < y) {
    curDir = `down`;
}
if (x > y) {
    curDir = `up`;
}