频率排序 JS 中的对象数组,如果频率匹配,则根据对象属性进行排序



我有对象数组

[
{"X" : {
"price" : "5"
}
},
{"Y" : {
"price" : "3"
}
},
{"Y" : {
"price" : "3"
}
},
{"Z" : {
"price" : "4"
}
},
{"Q" : {
"price" : "2"
}
},
{"X" : {
"price" : "5"
}
},
{"Z" : {
"price" : "4"
}
},
{"X" : {
"price" : "5"
}
}
]

我想对数组进行频率排序,以便我像[object:count]

如何让数组将 arr 转换为格式:

// [{key: x, count: 3, price: 5}},{key: y:, count: 2, price: 3}
[{x:3},{y:2},{z:2},{q:1}]

但是我面临的问题是,如果频率匹配,那么排序必须检查对象的属性 i:e 在这种情况下是价格,如果价格大于应该赋予权重年龄的其他匹配元素,所以在这种情况下 z 价格大于 y,所以应该优先考虑 z。

[{x:3},{z:2},{y:2},{q:1}]

这是我到目前为止尝试过的:

var a = ["x", "v"], b = ["x", "y"], c = ["d", "y"];
var d = a.concat(b, c);

function sortByFrequency(array) {
var frequency = {};

array.forEach(function(value) { frequency[value] = 0; });

var uniques = array.filter(function(value) {
return ++frequency[value] == 1;
});

return uniques.sort(function(a, b) {
return frequency[b] - frequency[a];
});
}

var frequency = sortByFrequency(d);

console.log(frequency);
.as-console-wrapper{min-height:100%}

回答后更新

我仍然不知道如何将数组转换为这种格式

var arr = [
{"key":"X",
	"price" : "5",
	"count" :"3"
}
,
{"key":"Y",
	"price" : "3",
	"count" :"2"
}
,
{"key":"Z",
	"price" : "4",
	"count" : "2"
}
];
var r = _.sortBy(_.sortBy(arr, 'price'), 'count'); 
console.log(JSON.stringify(r));
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore.js"></script>

现在它可以工作了,但是如何从数组中将对象转换为这种格式

您可以使用以下 ES6 代码执行此操作:

function sortByFrequency(a) {
return Array.from(
a.reduce( (acc, o) => {
const key = Object.keys(o)[0];
const obj = acc.get(key) || Object.assign({ key, count: 0 }, o[key]);
obj.count++;
return acc.set(key, obj);
}, new Map),
([key, obj]) => obj
).sort( (a, b) => b.count - a.count || b.price - a.price );
}
// Sample input
const a = [{
X: {
price: "5"
}
}, {
Y: {
price: "3"
}
}, {
Y: {
price: "3"
}
}, {
Z: {
price: "4"
}
}, {
Q: {
price: "2"
}
}, {
X: {
price: "5"
}
}, {
Z: {
price: "4"
}
}, {
X: {
price: "5"
}
}];
// Perform transformation & output
const res = sortByFrequency(a);
console.log(res);
.as-console-wrapper { max-height: 100% !important; top: 0; }

解释

该代码构建一个Map以确保每个键一个条目。它是用reduce创建的,它作为起始值new Map,然后被引用为acc

reduce将遍历输入数组a,对于每个条目,它将使用Object.keys提取key。由于每个对象只有一个键(并且应该有一个键),因此它是从生成的键数组中提取的[0]

然后通过acc.get验证我们是否已经拥有该密钥的条目。如果是这样,obj设置为我们之前为该键存储的对象。如果不是 - 这是第一次迭代中的情况 - 创建一个新对象,其中包含获得正确值的keycount属性,并且该对象与输入数组(o[key])中的更深对象合并。实际上,这意味着price键和值被添加到已经具有keycount的对象中。

无论哪种情况(无论我们创建了一个新对象还是从 Map 中检索它),它的 count 属性都会递增。

然后将此对象存储在 Map 中带有acc.set(key, obj)的相应键处。这将返回到reduce内部结构(即返回更新的acc),这将是下一次迭代中acc的值,因为这就是reduce的工作方式。

在最后一次迭代之后,reduce将返回完成的 Map。然后将其转换为带有Array.from的数组。在执行过程中,我们转换每个条目,因为默认情况下,Map 条目将转换为键/值对(数组),但我们只想保留值(因为它现在包含key属性)。所以这就是提供给Array.from的回调参数中发生的情况:

([key, obj]) => obj

现在我们有一个对象数组,其中每个对象都有三个所需的属性。唯一剩下的就是排序。

为此,我们减去正在比较的两个对象的计数(就像您已经做的那样)。然而,当它们相等时,我们需要做更多的事情。在这种情况下,差异为零,这是伪造的,因此对于布尔||,我们强制 JavaScript 评估它之后的内容。在这种情况下,我们按价格排序,再次通过相互减去价格。请注意,您的价格是字符串,但减法运算符会即时将它们转换为数字。

使用reduce然后sort尝试这个。

解释

为了达到预期的结果,您可以将任务分解为以下步骤。

1. 分组- 使用属性名称 (x,y,z)按数组中的项目分组
2。排序- 按降序对 Step1 中的结果进行排序,其中第一个条件是项目计数,第二个条件是价格。

1.分组 -javascript中没有原生group by函数。因此,我们可以利用reduce函数,它基本上在数组序列上运行函数并返回累积值。

一个。在reduce函数中,累加器将从一个空数组开始,如reduce函数末尾的代码注释中所述。

二.我们通过遍历对象来获得像"x"、"y"、"z"这样的属性名称。此外,我们使用第 0 个索引,因为只有一个属性,如"x"、"y"、"z"。

三.之后,我们检查该属性是否已在数组中。

d.如果属性不在数组中,那么我们需要将属性添加到数组中。

e.我们创建一个对象来处理稍后将使用的计数和价格信息。

f.如果属性已经存在,如步骤 c 中所述,那么我们需要将该属性的计数递增elementInArray[propName].count++;

2. 排序
a.sort函数采用比较函数。在该函数中,我们首先通过count比较 2 个项目。如果count相等,那么我们按price比较它们。

var arr = [
{"X" : {
"price" : "5"
}
},
{"Y" : {
"price" : "3"
}
},
{"Y" : {
"price" : "3"
}
},
{"Z" : {
"price" : "4"
}
},
{"Q" : {
"price" : "2"
}
},
{"X" : {
"price" : "5"
}
},
{"Z" : {
"price" : "4"
}
},
{"X" : {
"price" : "5"
}
}
];
var frequency = arr.reduce(function (accumulatorObject, currentValue) { 
var propName = Object.keys(currentValue)[0];
var elementInArray = accumulatorObject.find((element) => Object.keys(element)[0] === propName);
if (elementInArray) {
elementInArray[propName].count++;
}
else {
var newObject = {};
	newObject[propName]  = {};
	newObject[propName].count = 1;
	newObject[propName].price = +currentValue[propName].price;
	accumulatorObject.push(newObject);
}
return accumulatorObject;
}, []); //  // Accumulator starts with Empty array. 
frequency.sort(function(first,second){ 
var diff =  second[Object.keys(second)].count - first[Object.keys(first)].count;
	if( diff === 0) {
		return second[Object.keys(second)].price - first[Object.keys(first)].price;
	}
return diff;});
console.log(frequency);

我只是给你骨架,但是lodash或下划线中的_.sortBy方法将执行稳定的排序(即保留以前的排序)并允许您应用多种排序。

function sortByFrequency(arr) {
// Transform arr to the format: 
// [{key: x, count: 3, price: 5}},{key: y:, count: 2, price: 3}, ... 
arr = _.sortBy(_.sortBy(arr, 'price'), 'count'); 
// in lodash, this is arr = _.sortBy(arr, ['price', 'count'])
// transform arr into the format that you want 
return arr.map(x => /* function */)
}

只是对约翰对阿加洛答案的评论的更新

将每个对象键映射到新对象

在 Else 块中,您可以编写此内容

var newObject = {};  
newObject[propName]  = {};
newObject[propName].count = 1;
Object.assign(newObject[propName], currentValue[propName]); // this line should do the trick also you need not convert proce to number as while sorting it is done at runtime .
//newObject[propName].price = +currentValue[propName].price;
accumulatorObject.push(newObject);

最新更新