根据另一个 id 数组对对象数组进行排序

Posted

技术标签:

【中文标题】根据另一个 id 数组对对象数组进行排序【英文标题】:Sort an array of objects based on another array of ids 【发布时间】:2016-06-02 23:40:04 【问题描述】:

我有 2 个数组

a = [2,3,1,4]
b = [id: 1, id: 2, id: 3, id: 4]

如何根据ab 进行排序?我想要的输出是

c = [id: 2, id: 3, id: 1, id: 4]

我更喜欢使用 Ramda 或常规 JS。

【问题讨论】:

真正的b 对象是否包含除id 之外的任何其他内容? javascript - sort array based on another array的可能重复 【参考方案1】:

您可以为 JavaScript 的 Array#sort 方法提供自定义比较函数。

使用自定义比较功能保证排序顺序:

var sortOrder = [2,3,1,4],
    items     = [id: 1, id: 2, id: 3, id: 4];

items.sort(function (a, b) 
  return sortOrder.indexOf(a.id) - sortOrder.indexOf(b.id);
);

MDN:

如果 compareFunction(a, b) 返回小于 0,则将 a 排序到低于 b 的索引(即 a 排在第一位)。 如果 compareFunction(a, b) 返回 0,则 a 和 b 彼此保持不变,但对所有不同元素进行排序。注意:ECMAscript 标准不保证这种行为,因此,并非所有浏览器(例如,至少可以追溯到 2003 年的 Mozilla 版本)都尊重这一点。 如果 compareFunction(a, b) 返回大于 0,则将 b 排序到低于 a 的索引(即 b 排在第一位)。

Hitmands 对上述解决方案做出了非常中肯的评论:

这种方法是 O(n2),并且会在大型列表中导致性能问题。最好先构建字典,使其保持 O(n)

因此,上述解决方案最终可能无法满足您在大输入时所需的速度。

实施 Hitmands 的建议:

let sortOrder = [2,3,1,4],
    items     = [id: 1, id: 2, id: 3, id: 4];

const itemPositions = ;
for (const [index, id] of sortOrder.entries()) 
  itemPositions[id] = index;


items.sort((a, b) => itemPositions[a.id] - itemPositions[b.id]);

【讨论】:

【参考方案2】:

Ramda 非常适合这类问题。

在数据量小的地方,我们可以使用简单的reduce函数,以及indexOfhelper。

// match id of object to required index and insert
var sortInsert = function (acc, cur) 
  var toIdx = R.indexOf(cur.id, a);
  acc[toIdx] = cur;
  return acc;
;

// point-free sort function created
var sort = R.reduce(sortInsert, []);

// execute it now, or later as required
sort(b);
// [  id: 2 ,  id: 3 ,  id: 1 ,  id: 4  ]

这适用于小型(ish)数据集,但每次通过归约迭代的indexOf 操作对于大型数据集效率低下。

我们可以通过从另一边解决问题来解决这个问题,让我们使用groupBy 按对象的 id 对我们的对象进行分组,从而创建字典查找(好多了!)。然后我们可以简单地map 覆盖所需的索引并将它们转换为该位置对应的对象。

这是使用这种方法的解决方案:

var groupById = R.groupBy(R.prop('id'), b);

var sort = R.map(function (id) 
    return groupById[id][0];
);

sort(a);
// [  id: 2 ,  id: 3 ,  id: 1 ,  id: 4  ]

最后,这又是一个解决方案,非常简洁:

R.sortBy(R.pipe(R.prop('id'), R.indexOf(R.__, a)))(b);
// [  id: 2 ,  id: 3 ,  id: 1 ,  id: 4  ]

我喜欢使用Ramda 组合具有行为的函数并将算法与其作用的数据分开的方式。您最终会得到非常易读且易于维护的代码。

【讨论】:

你能让sortInsert成为一个纯函数吗? :) 我不认为 Ramda 在这里大放异彩,我认为这些解决方案的可读性不如纯 JS 替代方案...【参考方案3】:

使用 ES6 地图和查找

const a = [2,3,1,4];
const b = [id: 1, id: 2, id: 3, id: 4];

// map over a, find it in b and return    
const c = a.map((i) => b.find((j) => j.id === i));

【讨论】:

【参考方案4】:

您可以根据a 创建c,而无需使用b

var a = [2,3,1,4];
var c = [];

for(var i = 0; i < a.length; i++)

    c.append(id:a[i]);


希望这会有所帮助!

【讨论】:

可能不完全是问题所要求的,但仍然是跳出框框思考的有用视角【参考方案5】:

或者可能更简单

b.sort(function(obj1,obj2)
   return a.indexOf(obj1.id) > a.indexOf(obj2.id)
);

【讨论】:

【参考方案6】:

此解决方案使用 Array#sort 和辅助对象 c 作为索引。


    "1": 2,
    "2": 0,
    "3": 1,
    "4": 3

var a = [2, 3, 1, 4],
    b = [ id: 1 ,  id: 2 ,  id: 3 ,  id: 4 ],
    c = a.reduce(function (r, a, i) 
        r[a] = i;
        return r;
    , );

b.sort(function (x, y) 
    return c[x.id] - c[y.id];
);

document.write('<pre>' + JSON.stringify(b, 0, 4) + '</pre>');

对于更大的对象,我建议使用Sorting with map。

【讨论】:

【参考方案7】:

使用 Ramda,您必须通过 mapObjIndexed 函数将 b 中的对象映射到它们的索引,然后搜索 a 中的值。你可以试试here。

var a = [2,3,1,4];
var b =  [id: 1, id: 2, id: 3, id: 4]
R.find( R.propEq('id', a[0]), b )
R.values(R.mapObjIndexed(  (num, key, obj) => R.find( R.propEq('id', a[key]), b ) , b))

【讨论】:

【参考方案8】:

很好的解决方案。只是留在这里以备将来使用:)

const sortBy = (array, values, key = 'id') => ((map) => values.reduce((a,i) => 
a.push(map[i]) && a,[]))(array.reduce((a,i) => (a[i[key]] = i) && a, ));

用法

a = [2,3,1,4]
b = [id: 1, id: 2, id: 3, id: 4]

sortBy(b, a) // [id: 2, id: 3, id: 1, id:` 4]

【讨论】:

【参考方案9】:

纯javascript,使用ArrayES2015标准)的一些方法

var a = [2,3,1,4];
var b = [id: 1, id: 2, id: 3, id: 4];
var c = [];

a.forEach(el => c.push(b.find(e => e.id == el)));

document.write(JSON.stringify(c, 0, 2));

【讨论】:

【参考方案10】:

这是一个使用 Ramda 的替代结构,它可能更简洁一些,并且这些功能很容易重新用于其他用途。

const indexBy, prop, map, flip = R
const a = [2,3,1,4]
const b = [id: 1, id: 2, id: 3, id: 4]

const toIndexById = indexBy(prop('id'))
const findIndexIn = flip(prop)

const c = map(findIndexIn(toIndexById(b)), a)
console.log(c)
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"&gt;&lt;/script&gt;

【讨论】:

【参考方案11】:

我将迭代 arcseldon 的解决方案。

这里的正确方法是翻转问题,而不是 "根据b给出的顺序对a进行排序", 我宁愿“用来自a 的数据丰富b

这使您可以将此函数的时间复杂度保持在O(n), 而不是把它带到O(n2)

const pickById = R.pipe(
  R.indexBy(R.prop('id')),
  R.flip(R.prop),
);

const enrich = R.useWith(R.map, [pickById, R.identity]);


// =====

const data = [2, 3, 1, 4];
const source = [ id: 1 ,  id: 2 ,  id: 3 ,  id: 4 ];

console.log(
  enrich(source, data),
);
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous"&gt;&lt;/script&gt;

【讨论】:

以上是关于根据另一个 id 数组对对象数组进行排序的主要内容,如果未能解决你的问题,请参考以下文章

js对象数组按照另一个数组排序

我们如何根据节点js中的另一个对象数组值对对象数组进行排序

编写一个函数对对象数组进行排序(通过使用另一个对象来指定排序路径和顺序)

根据存在于另一个模型数组中的日期对模型数组进行排序。斯威夫特 3

数组对象根据 某个字段进行排序

Java按整数字段对对象数组进行排序,如果它们相同,则按另一个整数值排序