我有一个有趣的问题,我不确定如何解决。我精通回溯类型的功能,但我被以下难题困扰:
假设我们有一个对象集合,每个对象都定义了一个url部分数组,如['/root', '/nested', '/nested', '/leaf']
。给定一组url部分,找到与url部分完全匹配的对象。这很简单,但这里是踢球者:对象定义的url部分也可以是通配符,如['/root', '/:id', '/nested', ':name', '/leaf']
。现在它变得有趣了,因为完全匹配比通配符匹配更重要,并且应尽可能长时间地遵循完全匹配,即使它将以最多通配符结束。
我想用一个启发式函数来评估每个路由匹配需要一些递归。但我不确定如何开始或如何确保始终找到有效匹配。
这就是我要找的东西:
var objects = [
{ id: 1, urlParts: ['/base', '/nested'] },
{ id: 2, urlParts: ['/base', '/nested', '/nested'] },
{ id: 3, urlParts: ['/base', '/nested', '/:name'] },
{ id: 4, urlParts: ['/base', '/other', '/:name'] },
{ id: 5, urlParts: ['/base', '/:name', '/nested', '/leaf'] },
{ id: 6, urlParts: ['/base', '/:id', '/nested', '/:leaf'] },
{ id: 7, urlParts: ['/base', '/a_name', '/nested', '/leaf'] },
{ id: 8, urlParts: ['/base', '/a_name', '/nested', '/:leaf'] }
];
console.log(matchByUrlParts(['/base']) == null);
console.log(matchByUrlParts(['/base', '/nested']) == 1);
console.log(matchByUrlParts(['/base', '/nested', '/nested']) == 2);
console.log(matchByUrlParts(['/base', '/nested', '/other']) == 3);
console.log(matchByUrlParts(['/base', '/other']) == null);
console.log(matchByUrlParts(['/base', '/other', '/other']) == 4);
console.log(matchByUrlParts(['/base', '/another_name', '/nested', '/leaf']) == 5);
console.log(matchByUrlParts(['/base', '/1234', '/nested', '/variable']) == 6);
console.log(matchByUrlParts(['/base', '/a_name', '/whoops', '/leaf']) == null);
console.log(matchByUrlParts(['/base', '/a_name', '/nested', '/leaf']) == 7);
console.log(matchByUrlParts(['/base', '/a_name', '/nested', '/variable']) == 8);
function matchByUrlParts(urlParts) {
return 'not implemented';
}

感谢任何帮助让我开始。
答案 0 :(得分:1)
您可以为每个对象构建一个模式字符串,每个对象的每个URL部分都有一个字符。所以这样的模式的字符串长度将是urlPart
数组的大小。每个字符都是“0”或“1”。如果相应的URL部分是通配符,则它应为“1”。
示例:鉴于此对象:
{ id: 6, urlParts: ['/base', '/:id', '/nested', '/:leaf'] },
相应的模式是:
"0101"
...其中“1”字符表示通配符的位置。
搜索首先尝试非通配符部分的要求,可以通过按字母顺序查找模式首先出现的匹配来实现。
由于与对象关联的这种模式(成本)不依赖于输入,而只依赖于对象数组,因此可以在处理任何输入之前计算它,并使用这些模式扩展对象。然后,您可以按该模式对对象进行排序。
有了它,您只需找到输入的第一个匹配项,您就会知道它是最佳匹配项(因为您对对象进行了排序)。
这个ES6函数实现了这个想法:
function preprocess() {
objects.forEach( o =>
o.pattern = o.urlParts.map( part => +part.startsWith("/:") ).join('')
);
objects.sort( (a, b) => a.pattern.localeCompare(b.pattern) );
}
function matchByUrlParts(input) {
const found = objects.find( ({id, urlParts, pattern}) => {
return urlParts.length == input.length
&& input.every( (part, i) => urlParts[i] == part || pattern[i] == "1")
});
return found && found.id || null;
}
var objects = [
{ id: 1, urlParts: ['/base', '/nested'] },
{ id: 2, urlParts: ['/base', '/nested', '/nested'] },
{ id: 3, urlParts: ['/base', '/nested', '/:name'] },
{ id: 4, urlParts: ['/base', '/other', '/:name'] },
{ id: 5, urlParts: ['/base', '/:name', '/nested', '/leaf'] },
{ id: 6, urlParts: ['/base', '/:id', '/nested', '/:leaf'] },
{ id: 7, urlParts: ['/base', '/a_name', '/nested', '/leaf'] },
{ id: 8, urlParts: ['/base', '/a_name', '/nested', '/:leaf'] }
];
preprocess();
console.log(matchByUrlParts(['/base']) == null);
console.log(matchByUrlParts(['/base', '/nested']) == 1);
console.log(matchByUrlParts(['/base', '/nested', '/nested']) == 2);
console.log(matchByUrlParts(['/base', '/nested', '/other']) == 3);
console.log(matchByUrlParts(['/base', '/other']) == null);
console.log(matchByUrlParts(['/base', '/other', '/other']) == 4);
console.log(matchByUrlParts(['/base', '/another_name', '/nested', '/leaf']) == 5);
console.log(matchByUrlParts(['/base', '/1234', '/nested', '/variable']) == 6);
console.log(matchByUrlParts(['/base', '/a_name', '/whoops', '/leaf']) == null);
console.log(matchByUrlParts(['/base', '/a_name', '/nested', '/leaf']) == 7);
console.log(matchByUrlParts(['/base', '/a_name', '/nested', '/variable']) == 8);
.as-console-wrapper { max-height: 100% !important; top: 0; }
在预处理期间,part.startsWith("/:")
的结果将转换为带有一元+
的数字,产生0或1. map
函数返回一个0和1位的数组,然后将它们连接成模式字符串。此模式存储在每个原始对象的新pattern
属性中。然后,这些对象使用sort()
回调函数按该新属性进行排序。
第二个函数非常简单:它使用find
遍历排序的对象数组,检查每个对象的两个条件:
find
将停止迭代并返回相应的对象。然后,该函数将返回该对象的id
属性,如果没有匹配则返回null
。
零和一的模式可能会变得很长。如果对象和输入有100个URL部分,那么这个模式系统就没问题了:一个字符串很容易长100个字符并进行比较。但是,如果您使用数字实现此操作,则会遇到准确性问题(因为浮点精度有限)。
答案 1 :(得分:0)
这是我自己的算法版本,似乎可以解决问题:
function matchByUrlParts(urlParts) {
let lowest = objects.reduce((lowest, obj) => {
var cost = obj.urlParts.length == urlParts.length && appraiseRouteCost(obj.urlParts, urlParts);
var foundMatch = typeof cost == 'number' && !isNaN(cost);
return (foundMatch && (!lowest || cost < lowest.cost) && {'obj':obj,'cost':cost}) || lowest;
}, null);
return lowest && lowest.obj.id;
}
function appraiseRouteCost(a, b, depth = 0) {
var cost = a[depth] == b[depth] ? 0 : a[depth].startsWith('/:') && 10 ^ (a.length - depth) || undefined;
return cost + (depth < a.length-1 ? appraiseRouteCost(a, b, depth + 1) : 0);
}
var objects = [
{ id: 1, urlParts: ['/base', '/nested'] },
{ id: 2, urlParts: ['/base', '/nested', '/nested'] },
{ id: 3, urlParts: ['/base', '/nested', '/:name'] },
{ id: 4, urlParts: ['/base', '/other', '/:name'] },
{ id: 5, urlParts: ['/base', '/:name', '/nested', '/leaf'] },
{ id: 6, urlParts: ['/base', '/:id', '/nested', '/:leaf'] },
{ id: 7, urlParts: ['/base', '/a_name', '/nested', '/leaf'] },
{ id: 8, urlParts: ['/base', '/a_name', '/nested', '/:leaf'] }
];
console.log(matchByUrlParts(['/base']) == null);
console.log(matchByUrlParts(['/base', '/nested']) == 1);
console.log(matchByUrlParts(['/base', '/nested', '/nested']) == 2);
console.log(matchByUrlParts(['/base', '/nested', '/other']) == 3);
console.log(matchByUrlParts(['/base', '/other']) == null);
console.log(matchByUrlParts(['/base', '/other', '/other']) == 4);
console.log(matchByUrlParts(['/base', '/another_name', '/nested', '/leaf']) == 5);
console.log(matchByUrlParts(['/base', '/1234', '/nested', '/variable']) == 6);
console.log(matchByUrlParts(['/base', '/a_name', '/whoops', '/leaf']) == null);
console.log(matchByUrlParts(['/base', '/a_name', '/nested', '/leaf']) == 7);
console.log(matchByUrlParts(['/base', '/a_name', '/nested', '/variable']) == 8);
&#13;
我发现这个解决方案比其他答案更容易理解(当然这是主观的),而且这个解决方案也执行slightly better。