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

32

我有两个数组

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。


1
真正的 b 对象除了 id 之外还包含其他内容吗? - georg
可能是Javascript - sort array based on another array的重复问题。 - Heretic Monkey
12个回答

41
你可以为 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)

由于每个比较都使用indexOf,而indexOf本身相对于字典查找(O(1))而言已经很慢(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]);

13

使用 ES6 的 map 和 find 方法

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));

11

Ramda 对于这些类型的问题非常有用。

当数据大小较小时,我们可以使用简单的reduce函数和indexOf助手。

// 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 } ]

这对于小型数据集效果良好,但在处理大型数据集时,每次迭代通过减少运算进行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改成纯函数吗? :) - davidchambers

2
您可以仅基于a创建c,而无需使用b:
var a = [2,3,1,4];
var c = [];

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

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

}

希望这能帮到你!

可能不完全符合问题的要求,但仍然是一个有用的角度,可以让人们跳出固有思维模式。 - Melih Yıldız'

2
很好的解决方案。只是将其保留以备将来使用 :)
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}]

1
这个解决方案使用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>');

对于更大的对象,我建议使用使用映射进行排序


1
或许更简单
b.sort(function(obj1,obj2){
   return a.indexOf(obj1.id) > a.indexOf(obj2.id)
});

1
使用Ramda,您需要通过mapObjIndexed函数将对象在b中映射到它们的索引,然后在a中搜索该值。您可以在这里尝试。
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))

0
你可以使用 Array 对象的方法:

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)));

console.log(JSON.stringify(c, 0, 2));


0

我将在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),
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous"></script>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接