从JSON中选择不同的值

40

我有以下JSON数据:

{"DATA": [{"id":11,"name":"ajax","subject":"OR","mark":63},
{"id":12,"name":"javascript","subject":"OR","mark":63},
{"id":13,"name":"jquery","subject":"OR","mark":63},
{"id":14,"name":"ajax","subject":"OR","mark":63},
{"id":15,"name":"jquery","subject":"OR","mark":63},
{"id":16,"name":"ajax","subject":"OR","mark":63},
{"id":20,"name":"ajax","subject":"OR","mark":63}],"COUNT":"120"}

有没有好的方法可以从这个JSON中找出<code>中不同的名字?

结果为<code>:javascript,jquery,ajax

我可以使用以下方法来实现:

var arr=[''];
var j=0;
for (var i = 0; i < varjson.DATA.length; i++) {
  if($.inArray(varjson.DATA[i]["name"],arr)<0){
      arr[j]=varjson.DATA[i]["name"];
      j++;
  }
}

有没有更好的方法可以让我获得更好的性能?

8个回答

70

如果你想节省一些循环,我建议使用一个对象和一个数组:

var lookup = {};
var items = json.DATA;
var result = [];

for (var item, i = 0; item = items[i++];) {
  var name = item.name;

  if (!(name in lookup)) {
    lookup[name] = 1;
    result.push(name);
  }
}

这种方法基本上避免了调用indexOf/inArray,并且您将获得一个数组,可以比迭代对象的属性更快地进行迭代 - 这也是因为在第二种情况下,您需要检查hasOwnProperty

当然,如果您只需要一个对象,您可以避免检查和result.push,并在需要时使用Object.keys(lookup)获取数组,但这不会比这更快。


它花费的时间几乎与我的相同。这是比较结果http://jsfiddle.net/MsYGJ/1/。其中,sberry方法表现更好。 - Nithesh Narayanan
能否请您发布包含我的方法作为比较的jsfiddle呢?因为在我的测试中,这种方法绝对比您的原始方法更快,而且似乎也比sberry的方法更快 - 但我想要再次确认。可能是我漏掉了什么,或者您的实现方式有所不同。 - ZER0
好的,你必须删除第354行的警报。没有它在我的Firefox / Chrome / Safari上几乎是瞬间完成的,与其他方法相比; 还有我的测试结果显示。 - ZER0
1
@ZER0,你遍历数组的方式很棒,点个赞。 - sam
不想打击你的积极性,但是因为有人点赞,这篇文章在我的动态里又出现了,已经过去将近5年了。@ZERO,你的代码问题在于你只执行了一次方法,而我的方法迭代了10万次。这是一个更新版本,显示我的方法大约快了3倍:http://jsfiddle.net/MsYGJ/113/。当然,在这种时间尺度上进行优化很少是必要的。 - sberry

23

Underscore.js 对于这种事情非常适合。你可以使用 _.countBy() 来获取每个 name 的计数:

data = [{"id":11,"name":"ajax","subject":"OR","mark":63},
        {"id":12,"name":"javascript","subject":"OR","mark":63},
        {"id":13,"name":"jquery","subject":"OR","mark":63},
        {"id":14,"name":"ajax","subject":"OR","mark":63},
        {"id":15,"name":"jquery","subject":"OR","mark":63},
        {"id":16,"name":"ajax","subject":"OR","mark":63},
        {"id":20,"name":"ajax","subject":"OR","mark":63}]

_.countBy(data, function(data) { return data.name; });

提供:

{ajax: 4, javascript: 1, jquery: 2} 

要获取键的数组,只需使用_.keys()

_.keys(_.countBy(data, function(data) { return data.name; }));
提供:
["ajax", "javascript", "jquery"]

这个countBy方法能否选择一对字段使用呢?例如,我想要每个键/日期的出现次数。谢谢@iiSeymour。http://stackoverflow.com/questions/34075039/counting-number-of-elements-with-desired-conditions-in-json/34075630#34075630 - pceccon

18

使用jQuery的unique方法。

var UniqueNames= $.unique(data.DATA.map(function (d) {return d.name;}));

alert($.unique(names));

JSFiddle


这里有一个问题。如果结果中重复出现的名称超过两次,那么该名称只会出现两次。例如:如果“ajax”出现了3次,则结果中只包含“2 ajax”。 - Nithesh Narayanan
检查一下 JSFiddle,它并不像你说的那样工作...我已经把所有名称都命名为“ajax”,但结果只显示了一个 Ajax。 请查看 JSFiddle - Hardik Sondagar
nop。请在此处检查 http://jsfiddle.net/MsYGJ/3/。此外,它比其他方法需要更多的时间。 - Nithesh Narayanan

15

这是一个很好的地方进行reduce操作

var uniqueArray = o.DATA.reduce(function (a, d) {
       if (a.indexOf(d.name) === -1) {
         a.push(d.name);
       }
       return a;
    }, []);

这需要比sberry的方法更多的时间。 - Nithesh Narayanan
是的,那里没有地图的必要。一个单独的reduce就可以做到。您应该知道@sberry的解决方案没有检查hasOwnProperty。原型中的杂散属性可能会进入您的列表。 - Evan Borden

10

首先,我们可以直接运行map()函数,以获得应用于varjson.DATA中每个元素的提供函数的结果而生成的新数组。

varjson.DATA.map(({name})=>name))

varjson.DATA 获取 name 数组后,我们可以将其转换为一个集合,该集合将丢弃数组中的所有重复条目,并使用展开运算符获得唯一名称的数组:

[...new Set(varjson.DATA.map(({name})=>name))]

const varjson = {
  "DATA": [{
      "id": 11,
      "name": "ajax",
      "subject": "OR",
      "mark": 63
    },
    {
      "id": 12,
      "name": "javascript",
      "subject": "OR",
      "mark": 63
    },
    {
      "id": 13,
      "name": "jquery",
      "subject": "OR",
      "mark": 63
    },
    {
      "id": 14,
      "name": "ajax",
      "subject": "OR",
      "mark": 63
    },
    {
      "id": 15,
      "name": "jquery",
      "subject": "OR",
      "mark": 63
    },
    {
      "id": 16,
      "name": "ajax",
      "subject": "OR",
      "mark": 63
    },
    {
      "id": 20,
      "name": "ajax",
      "subject": "OR",
      "mark": 63
    }
  ],
  "COUNT": "120"
}

console.log( [...new Set(varjson.DATA.map(({name})=>name))]);


1
请描述您更改了什么以及为什么更改,以帮助其他人识别问题并理解此答案。 - FZs
谢谢,这是最新的解决方案。 - stoneshishang

6

正如您在这里看到的,当您有更多的值时,有一种更好的方法。

http://jsfiddle.net/MsYGJ/

temp = {}
// Store each of the elements in an object keyed of of the name field.  If there is a collision (the name already exists) then it is just replaced with the most recent one.
for (var i = 0; i < varjson.DATA.length; i++) {
    temp[varjson.DATA[i].name] = varjson.DATA[i];
}
// Reset the array in varjson
varjson.DATA = [];
// Push each of the values back into the array.
for (var o in temp) {
    varjson.DATA.push(temp[o]);
}

在这里,我们创建了一个对象,使用name作为键,值就是来自数组的原始对象。这样做,每个替换都是O(1),无需检查它是否已经存在。然后你将每个值取出并重新填充数组。 注意:
对于较小的数组,您的方法会略微更快。 注意2:
这种方法不会保留原始顺序。

+1. 我最多有100行,通常是10行。那么我应该采用哪种方法?还是需要实现两种方法并比较差异? - Nithesh Narayanan
非常好,你的方法会给我更好的性能。谢谢 sberry。 - Nithesh Narayanan

2

尝试使用这个,MYJSON将是您的JSON数据。

var mytky=[];
mytky=DistinctRecords(MYJSON,"mykeyname");

function DistinctRecords(MYJSON,prop) {
  return MYJSON.filter((obj, pos, arr) => {
    return arr.map(mapObj => mapObj[prop]).indexOf(obj[prop]) === pos;
 })
}

我尝试过这个,但是我没有得到一个由不同键值(即“mykeyname”属性的值)组成的数组;我得到的是一个具有正确元素数量的数组,并且每个不同“mykeyname”的值都有一个元素,但是 mytky 数组保存整个对象,而不仅仅是关键值(例如来自 OP 的 {"id":11,"name":"ajax","subject":"OR","mark":63} 这样的对象,而不仅仅是像 11 这样的元素)。还有,如果 MYJSON 数组中有 N 个对象,那么我对它进行迭代的次数会是 N 的平方吗? 我认为是这样的:.filter() 对所有元素进行迭代,对于每次迭代,.map() 同样如此,对吗? - GISmatters

0

试试这个:

var distinct_list 

  = data.DATA.map(function (d) {return d[x];}).filter((v, i, a) => a.indexOf(v) === i)

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