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

3

我有一个对象数组

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

我希望对数组进行频率排序,使得结果格式为[对象:数量]

如何将数组转换为以下格式:

// [{key: x, count: 3, price: 5}},{key: y:, count: 2, price: 3}

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

但我遇到的问题是,如果频率匹配,那么排序就必须检查对象的属性,即在这种情况下是价格,如果价格高于其他匹配元素,那么应该给予权重,因此在这种情况下,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>

现在它可以工作,但如何从数组中获取对象并使其符合此格式?


你的代码输出是一个字符串数组,但是你的文本输出是一个对象数组。 - c2huc2hu
是的,我无法对对象进行频率排序,我先尝试了使用数组。 - John Snow
你想要哪一个?另外,你能使用外部库(如lodash)吗? - c2huc2hu
我想要的是对象和计数,而不是像 [object : count] 这样的字符串,如果匹配计数,则按属性排序。最好不使用 Loadash,我只能使用 Underscore。 - John Snow
4个回答

2
你可以使用以下ES6代码来实现:

你可以使用以下的 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中检索它),其计数属性都会递增。

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

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

([key, obj]) => obj

现在我们有一个对象数组,每个对象都有三个所需属性。唯一剩下的就是排序。
为此,我们减去正在比较的两个对象的计数(就像您已经做过的那样)。然而,当它们相等时,我们需要做更多的工作。在这种情况下,差异为零,这是错误的,因此我们使用布尔值 || 强制 JavaScript 评估其后面的内容。在这种情况下,我们再次通过减去价格来按价格排序。请注意,您的价格是字符串,但减法运算符会即时将它们转换为数字。

请问您能不能稍微解释一下,这有点超出我的理解范围。谢谢。 - John Snow
我在我的答案中添加了一个解释。请注意,排序是在函数的最后一行完成的。如果您需要更多关于某个方面的澄清,请告诉我。另外,请注意您只能接受一个答案;-) - trincot
抱歉,我不能接受你的内容,也无法为其点赞。 - John Snow
是的,我尝试过了,但声誉是个问题,我接受了他的答案,因为他保持了对象结构不变,只是添加了另一个键。这是唯一的原因。只是出于好奇,两者在性能方面会相同吗? - John Snow
1
@trincot 再次感谢,现在我有一个仓库可以给你们两个点赞了。 - John Snow
显示剩余3条评论

1

使用reduce,然后使用sort尝试此方法。

解释

为了实现所需的结果,您可以按以下步骤分解任务。

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

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

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

b.通过遍历对象,我们可以获取属性名称,例如“x”,“y”,“z”。由于只有一个属性,因此我们使用零索引。

c.之后,我们检查属性是否已经存在于数组中。

d. 如果属性不在数组中,则需要将该属性添加到数组中。

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

f. 如果属性已经存在于步骤c中提到的数组中,则需要增加该属性的计数 elementInArray[propName].count++;

2. 排序
a. sort函数采用比较器函数。在该函数中,我们首先通过其count比较两个项。如果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);


你能否简单地解释一下累加器对象的生成过程? - John Snow
我希望我能给你们两个的答案点赞,因为它们都是正确的,但我的投票数少于15,所以没有反映出来。如果可以的话,请解释一下你们做了什么。 - John Snow
在创建对象时,我们将每个对象键映射到新对象中,例如:newObject[propName] = {}; newObject[propName].count = 1; newObject[propName].price = +currentValue[propName].price;但是如果假设对象键很多,例如X有价格、数量等20或30个属性,那么我们需要像这样映射所有内容吗? - John Snow

0

我只是给你提供框架,但 lodash 或 underscore 中的 _.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 */)

}

我该如何将数组转换为这种格式,而且只有在数量相同时才按价格排序,否则我就不排序。 - John Snow
你需要哪种转换的帮助?是的,当数量相同时,这个函数只按价格排序。外部排序会“覆盖”内部排序。 - c2huc2hu
将arr转换为以下格式: // [{key: x, count: 3, price: 5}},{key: y:, count: 2, price: 3},这是你在答案中提到的。 - John Snow
你在原问题中已经编写了获取 count 的代码,只是你将其存储在 frequency 中。你可以从原始数组中获取价格(也许使用 _.find)。 - c2huc2hu
首先,我们需要生成按频率排序的数组,这将帮助我得到sortBy。那么如何生成该数组? - John Snow
显示剩余2条评论

0

关于John对Agalo答案的评论的更新

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

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

@Agalo 希望这个能够正常工作,请更新 JavaScript 中的新内容。 - Rahul Singh

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