【问题标题】:Filtering out JSON by an array通过数组过滤掉 JSON
【发布时间】:2016-07-19 02:11:25
【问题描述】:

我有一个 JSON 文件

{
    "data": [
        {
            "name": "Jake",
            "id": "123"
        },
        {
            "name": "Bob",
            "id": "234"
        }]
}

所有 id 都是唯一的,假设我有一个禁止的 id 数组 ["123","423"],我想删除数组中所有具有 id 号的条目(作为输出我想如下所示)。

{
    "data": [
        {
            "name": "Bob",
            "id": "234"
        }]
}

如果 JSON 和数组中有几千个条目,那么实现此目的的中等效率方法(在普通计算机上运行几秒钟)是什么?

【问题讨论】:

标签: javascript json


【解决方案1】:

您可以将Array.prototype.filter() 方法与.indexOf() 结合使用:

var bannedIds = ["123", "423"];
var input = {
    "data": [
        {
            "name": "Jake",
            "id": "123"
        },
        {
            "name": "Bob",
            "id": "234"
        }]
};

input.data = input.data.filter(function(v) {
  return bannedIds.indexOf(v.id) === -1;
});

console.log(input);

如果您不想覆盖原始数组,则只需将 .filter() 调用的结果分配给新变量即可。

如果在处理大量数据时上述操作太慢,您可以尝试将 .filter() 替换为传统的 for 循环,和/或将 .indexOf() 替换为从数组创建的查找对象禁止ID。

【讨论】:

  • 请注意,由于是O(nm),所以速度很慢。转换为 for 不会有太大的性能提升。
  • 我会说它是 O(nm),其中 n 是原始数据的长度,m 是禁止列表的长度。对于一个简短的禁止列表,这可能没问题。
  • @SpencerWieczorek - 好吧,它会节省一堆函数调用,这可以对大量数据产生明显的影响。我还意识到用循环替换.indexOf() 不如使用由禁止的 id 数组生成的键创建对象有用,因此我编辑了答案以提及这一点。 (我不会包含完整的代码,因为这与 Set 的其他答案基本相同。)
【解决方案2】:

如果你可以使用 ES6,你可以这样做:

const source = {
    "data": [
        {
            "name": "Jake",
            "id": "123"
        },
        {
            "name": "Bob",
            "id": "234"
        }
    ]
};
const banned = ["123", "423"];

// O(n) startup cost for constant access time later
const bannedSet = new Set(banned);

// O(n)
const result = source.data.filter(x => !bannedSet.has(x.id));

console.log(result);

如 cmets 中所述,创建Set 需要启动成本。但是,这让您可以调用Set.prototype.has,这是不变的。

然后,只需遍历每个元素并过滤掉禁止集合中的元素。

如果你不能使用 ES6,你可以用一个普通的 JS 对象替换 Set。如果您必须支持 IEArray.prototype.filter 使用 polyfill(感谢 @nnnnnn)。

更新

@SpencerWieczorek 指出ES6 spec 似乎表明Set.prototype.has 迭代。我过早地谈到查找是不变的(我从其他语言继承了我的经验)。通常,集合会比 O(n) 做得更好,例如常量或 O(log n) 取决于底层实现。您的里程可能会有所不同,因此nnnnnn's answer 在某些情况下可能会更快。

在这里尝试一些解决方案,并使用大量数据进行确认。

【讨论】:

  • 不错。请注意,.filter() 是一种 ES5 方法(尽管如果 OP 需要支持 IE
  • @nnnnnn 谢谢!我记错了浏览器兼容性。
  • 你确定Set.prototype.has 是不变的吗? It seems to iterate according to this.
  • @SpencerWieczorek 我实际上不确定。也许实现取决于浏览器。我会相应地更新我的答案。
【解决方案3】:

编辑

我避免使用filter 或类似的东西,因为这涉及创建一个新数组。对于我们正在讨论的数据大小,这实际上可能没问题,但我下面的方法更有效。


在我的笔记本电脑上,整个程序运行时间约为 0.2 秒。 (它使用 10,000 个条目和 100 个禁止的 ID。)

var o = {
    data: []
};

for (var i = 0; i < 10000; i++) {
    o.data.push({
        name: i % 2 === 0 ? 'Jake' : 'Bob', // couldn't think of more names :-)
        id: ''+i // convert to string
    });
}

var banned = {};

for (var i = 0; i < 100; i++) {
    banned[''+(i * 3)] = true; // ban 0, 3, 6, 9, 12, ...
}

for (var i = o.data.length - 1; i >= 0; i--) {
    if (banned[o.data[i].id]) {
        o.data.splice(i, 1);
    }
}

console.log(o);

// { data:
//    [ { name: 'Bob', id: '1' },
//      { name: 'Jake', id: '2' },
//      { name: 'Jake', id: '4' },
//      { name: 'Bob', id: '5' },
//      { name: 'Bob', id: '7' },
//      { name: 'Jake', id: '8' },
//      { name: 'Jake', id: '10' },
//      ...

【讨论】:

  • 也许我应该说“数组的副本?”或者只是“将创建一个新数组”。
【解决方案4】:

我假设您已经解析了 JSON 数据,并且您有一个指向要过滤的数组的变量。此外,您还有一个带有“禁止”ID 的数组。

var data = [{
        "name": "Jake",
        "id": "123"
    }, {
        "name": "Bob",
        "id": "234"
    }, {
        "name": "Joe",
        "id": "345"
    }];

var banned = ["123", "345"];

以下函数可能会在性能方面做得最好:

// Modifies the data array "in place", removing all elements
// whose IDs are found in the "banned" array
function removeBanned(data, banned) {
    // Index the "banned" IDs by writing them as the properties
    // of a JS object for really quick read access later on
    var bannedObj = {};
    banned.forEach(function(b) { bannedObj[b] = true; });

    var index = data.length - 1;

    while (index >= 0) {
        if (bannedObj[data[index].id]) {
            data.splice(index, 1);
        }
        --index;
    }
}

【讨论】:

    【解决方案5】:

    这似乎足够快,但我建议你制作一个免费的干净副本,而不是修改现有的数组,它可能会更快。

    function filterout(o,p,f) {
      var i = 0; f = f.join(); 
      while( o[i] ) {
        if( f.match( o[i][p] ) ){ o.splice(i,1) }
        i++ 
      };
    }
    
    var filter = ["123","423"];
    
    var object =
        {
        "data": [
            {
                "name": "John",
                "id": "723"
            },
            {
                "name": "Jake",
                "id": "123"
            },
            {
                "name": "Bob",
                "id": "234"
            }]
    };
    
    filterout( object.data, "id", filter );
    
    console.log(JSON.stringify( object ));

    【讨论】:

    • .pop() 不带任何参数,它会删除数组中的最后一个元素。
    • 使用“id”为“42”的数组元素测试此代码。它将被删除,因为您正在执行 "123,423".match("42"),其计算结果为 true
    猜你喜欢
    • 2022-01-22
    • 1970-01-01
    • 2020-11-25
    • 2019-12-02
    • 1970-01-01
    • 1970-01-01
    • 2020-02-24
    • 2010-10-27
    • 1970-01-01
    相关资源
    最近更新 更多