如何按照正确的顺序对每个自定义排序函数的实体列表进行一次排序



给定一组(未排序(实体

const entities = [
{ id: "person-1", type: "person", fields: { age: 34 }}, 
{ id: "car-2", type: "car", fields: { manufacturer: "bar" }}, 
{ id: "house-2", type: "house", fields: { constructionYear: 2010 }}, 
{ id: "person-4", type: "person", fields: { age: 71 }},
{ id: "person-2", type: "person", fields: { age: 57 }}, 
{ id: "house-1", type: "house", fields: { constructionYear: 1968 }}, 
{ id: "car-1", type: "car", fields: { manufacturer: "foo" }},
{ id: "person-3", type: "person", fields: { age: 42 }},
];

和一堆";来源";其中一个可选的排序对象描述排序索引;isLessThan"比较函数作为字符串

const sources = [
{ type: "person", sort: { index: 1, isLessThanFunctionAsString: "(left, right) => left.fields.age < right.fields.age" }},
{ type: "car" },
{ type: "house", sort: { index: 0, isLessThanFunctionAsString: "(left, right) => left.fields.constructionYear < right.fields.constructionYear" }},
];

每个来源都描述了如何处理给定类型的实体。";人;定义了类型为";人;应该进行排序

我无法控制配置,isLessThan函数是一个字符串化函数,其签名是(leftEntity: Entity, rightEntity: Entity) => boolean,因此比较函数内部的逻辑可以是任何东西

我想根据从sources收集的信息对数组entities进行排序,并从开始

const entities = [{id:"person-1",type:"person",fields:{age:34}},{id:"car-2",type:"car",fields:{manufacturer:"bar"}},{id:"house-2",type:"house",fields:{constructionYear:2010}},{id:"person-4",type:"person",fields:{age:71}},{id:"person-2",type:"person",fields:{age:57}},{id:"house-1",type:"house",fields:{constructionYear:1968}},{id:"car-1",type:"car",fields:{manufacturer:"foo"}},{id:"person-3",type:"person",fields:{age:42}}];
const sources = [{type:"person",sort:{index:1,isLessThanFunctionAsString:"(left, right) => left.fields.age < right.fields.age"}},{type:"car"},{type:"house",sort:{index:0,isLessThanFunctionAsString:"(left, right) => left.fields.constructionYear < right.fields.constructionYear"}}];
function sortEntities(unsortedEntities, allSources) {
// if there are no entities, there is nothing to do
if (unsortedEntities.length === 0) {
return unsortedEntities;
}
// only care for the sources with a sort function
const sourcesWithSort = allSources.filter(source => !!source.sort);
// if there are no sources with sort, there is nothing to do
if (sourcesWithSort.length === 0) {
return unsortedEntities;
}
// since we can only compare two entities of the same type we must sort the entities by type first
let sortedEntities = entities.sort((leftEntity, rightEntity) => {
// no need for sorting if both have the same type
if (leftEntity.type === rightEntity.type) {
return 0;
}
if (leftEntity.type < rightEntity.type) {
return -1;
}
return 1;
});
// we must sort the sources by sort index ( at this point we now that sort must exist )
const sortSources = sourcesWithSort.sort((leftSource, rightSource) => leftSource.sort.index - rightSource.sort.index);
// NOW we can start sorting the entities
for (const source of sortSources) {
sortedEntities = sortedEntities.sort((leftEntity, rightEntity) => {
const {
type
} = source;
// we can't compare entities if the types aren't equal to the source type
if (leftEntity.type !== type || rightEntity.type !== type) {
return 0;
}
const isLessThanFunction = (new Function("return " + source.sort.isLessThanFunctionAsString))();
const isLeftEntityLessThanRightEntity = isLessThanFunction(
leftEntity,
rightEntity
);
if (isLeftEntityLessThanRightEntity) {
return -1;
}
return 1;
});
}
return sortedEntities;
}
console.log(sortEntities([...entities], [...sources]));

当处理许多实体(>100(和许多来源(>20(时,我的方法变得非常缓慢

你对如何改进代码或提出更快的替代方案有什么想法吗?

您可以更改columns数组以使用比较器函数,该函数直接返回所需的排序顺序。购买sort-comapreFn回调函数。对于数值字段,它将是减法。

const columns = [
{ type: "person", sort: { index: 1, comparator: (left, right) => left.fields.age - right.fields.age }},
{ type: "car", sort: undefined },
{ type: "house", sort: { index: 0, comparator: (left, right) => left.fields.constructionYear - right.fields.constructionYear }},
];

如果你想要一辆汽车,那就是

comparator: (a, b) => a.fields.manufacturer.localeCompare(b.fields.manufacturer)

然后创建一个映射器对象,将type映射到indexcomparator函数。如果没有提到索引,则将其设置为-Infinity(因为在您的情况下,顶部有car结果(

const columnMap = columns.reduce((acc, o) => {
const { type, sort: { index = -Infinity, comparator } = {} } = o
acc[type] = { index, comparator };
return acc
}, {})

它创建了这个对象:

{
"person": {
"index": 1,
"comparator": (a,b)=>a.fields.age-b.fields.age
},
"car": {
"index": -Infinity,
"comparator": undefined
},
"house": {
"index": 0,
"comparator": (a,b)=>a.fields.constructionYear-b.fields.constructionYear
}
}
  • 然后根据index值对实体进行排序
  • 如果索引值相同(或都是-Infinity(,则根据type名称进行排序
  • 如果两种类型相同,则根据特定类型的comparator函数进行排序

下面是一个工作片段:

const entities=[{id:"person-1",type:"person",fields:{age:34}},{id:"car-2",type:"car",fields:{manufacturer:"bar"}},{id:"house-2",type:"house",fields:{constructionYear:2010}},{id:"person-4",type:"person",fields:{age:71}},{id:"person-2",type:"person",fields:{age:57}},{id:"house-1",type:"house",fields:{constructionYear:1968}},{id:"car-1",type:"car",fields:{manufacturer:"foo"}},{id:"person-3",type:"person",fields:{age:42}}],
columns=[{type:"person",sort:{index:1,comparator:(e,s)=>e.fields.age-s.fields.age}},{type:"car",sort:void 0},{type:"house",sort:{index:0,comparator:(e,s)=>e.fields.constructionYear-s.fields.constructionYear}}];
function sortEntities(array, columns) {
const columnMap = columns.reduce((acc, o) => {
const { type, sort: { index = -Infinity, comparator } = {} } = o
acc[type] = { index, comparator };
return acc
}, {})

return array.sort((a,b) => 
columnMap[a.type].index - columnMap[b.type].index 
|| a.type.localeCompare(b.type)
|| columnMap[a.type].comparator?.(a,b)
)
}
console.log(sortEntities(entities, columns))


如果不能更改columns数组,可以使用eval创建自己的比较器函数,以使用字符串创建函数。

const entities=[{id:"person-1",type:"person",fields:{age:34}},{id:"car-2",type:"car",fields:{manufacturer:"bar"}},{id:"house-2",type:"house",fields:{constructionYear:2010}},{id:"person-4",type:"person",fields:{age:71}},{id:"person-2",type:"person",fields:{age:57}},{id:"house-1",type:"house",fields:{constructionYear:1968}},{id:"car-1",type:"car",fields:{manufacturer:"foo"}},{id:"person-3",type:"person",fields:{age:42}}],
columns =[{type:"person",sort:{index:1,isLessThanFunctionAsString:"(left, right) => left.fields.age < right.fields.age"}},{type:"car"},{type:"house",sort:{index:0,isLessThanFunctionAsString:"(left, right) => left.fields.constructionYear < right.fields.constructionYear"}}];

function sortEntities(array, columns) {
const columnMap = columns.reduce((acc, { type, sort }) => {
let index = -Infinity, comparator;
if (sort) {
eval("var isLessThanFunction =" + sort.isLessThanFunctionAsString)
index = sort.index;
comparator = (a, b) => isLessThanFunction(a, b) ? -1 : 1
}
acc[type] = { index, comparator };
return acc
}, {})
return array.sort((a, b) =>
columnMap[a.type].index - columnMap[b.type].index 
|| a.type.localeCompare(b.type) 
|| columnMap[a.type].comparator?.(a, b)
)
}
console.log(sortEntities(entities, columns))

首先,您需要确保您的数据已准备好放入表中。您将需要处理原始数据并将其转换为一组记录。之后,您可以更容易地对数据进行排序。最后,您可以将排序后的记录转换为一个表,并将其添加到文档中。

此外,您的分拣机上有一把名为isLessThan的钥匙。您应该将其更改为更通用的内容,如fncallback。您将希望所有分拣机都以类似方式键入。

const main = () => {
document.body.append(table(sort(process(entities), columns), columns));
};
const sort = (data, columns) => {
const sorters = columns
.map(({ sort }) => sort)
.filter(s => s)
.sort(({ index: a }, { index: b }) => a - b)
.map(({ isLessThan }) => isLessThan); // This is not good...
return data.sort((a, b) => {
let sorted = 0, index = 0;
while (sorted === 0 && index < sorters.length) {
sorted = sorters[index](a, b);
index++;
}
return sorted;
});
};
const process = (rawData) =>
Object.entries(rawData.reduce((acc, { id, type, fields }) => {
const [, index] = id.match(/w+-(d+)/);
if (!acc[index]) acc[index] = { id: +index };
acc[index][type] = { fields };
return acc;
}, {})).sort(([k1], [k2]) => k1 - k2).map(([, v]) => v);
const table = (records, columns) =>
group('table', {},
group('thead', {},
group('tr', {},
...columns.map(col =>
leaf('th', { innerText: col.type })
)
)
),
group('tbody', {},
...records.map(record =>
group('tr', {},
...columns.map(col =>
leaf('td', {
innerText: render(record[col.type], record, col)
})
)
)
)
)
);
const render = (data, record, column) =>
data ? JSON.stringify(data) : '';
const leaf = (tagName, options) =>
Object.assign(document.createElement(tagName), options);
const group = (tagName, options, ...children) =>
appendAll(leaf(tagName, options), children);
const appendAll = (el, children = []) => {
children.forEach(child => el.appendChild(child));
return el;
};
const entities = [
{ id: "person-1", type: "person", fields: { age: 34 }}, 
{ id: "car-2", type: "car", fields: { manufacturer: "bar" }}, 
{ id: "house-2", type: "house", fields: { constructionYear: 2010 }}, 
{ id: "person-4", type: "person", fields: { age: 71 }},
{ id: "person-2", type: "person", fields: { age: 57 }}, 
{ id: "house-1", type: "house", fields: { constructionYear: 1968 }}, 
{ id: "car-1", type: "car", fields: { manufacturer: "foo" }},
{ id: "person-3", type: "person", fields: { age: 42 }},
];
const columns = [
{ type: "id" },
{ type: "person", sort: { index: 1, isLessThan: (left, right) => left?.fields?.age < right?.fields?.age }},
{ type: "car" },
{ type: "house", sort: { index: 0, isLessThan: (left, right) => left?.fields?.constructionYear < right?.fields?.constructionYear }},
];
main();
table { border-collapse: collapse; }
table, th, td { border: thin solid grey; }
th, td { padding: 0.5rem; }
th { background: #DDD; }
tbody tr:nth-child(even) { background: #EEE; }
td { font-family: monospace; font-size: smaller; }

最新更新