JavaScript(ES6):比“for循环”更高效的方法还有吗?

4

这不是一个重复内容,请查看下面我的评论!

是否有比ES6中的for循环更高效的解决方案?

我写了以下代码,但性能不够好。有什么改进的建议吗?非常感谢。

基本上我有一个关于汽车的对象和一个有关用户喜好的数组。预期行为是将所有相关车名推入一个数组中。

用户可以给出任意数量的偏好。如果在偏好中提到了所有规格,则只应该推送一个汽车名称。因此,有些偏好将成为“剩菜”。

因此,在下面的例子中,Honda会出现,但BMW不会出现,这是预期的(但速度非常缓慢)行为。

// Car objects
const cars = [{
    name: "Honda",
    category: "eco",
    specs: {
      0: "green",
      1: "fast",
      2: "automatic"
    }
  },
  {
    name: "BMW",
    category: "sport",
    specs: {
      0: "blue",
      1: "fast",
      2: "automatic"
    }
  }
]

// User preferences
const preferences = ["green", "fast", "4x4", "automatic", "panorama"]

// function to get length/amount of car specifications
function objsize(Myobj) {
  var osize = 0,
    key;
  for (key in Myobj) {
    if (Myobj.hasOwnProperty(key)) osize++;
  }
  return Object(osize);
};


//function to check if ALL specifications are included in the user preferences
function checkSpecs(spec_item) {
  return preferences.includes(spec_item)
}

// main function
function filter_func() {

  //final results
  let matched_cars = []


  for (i = 0; i < objsize(cars); i++) {

    let specs_collector = []

    for (j = 0; j < objsize(cars[i].specs); j++) {
      specs_collector.push(cars[i].specs[j])
    }

    if (specs_collector.every(checkSpecs) === true) {
      matched_cars.push(cars[i].name)
      specs_collector = []
    }

  }
  console.log(matched_cars)
}

filter_func()


2
可能是重复的问题:检查一个数组中的每个元素是否在第二个数组中 - Heretic Monkey
2
我已经写了下面这段代码,但它的性能不够好。你如何定义性能?你是如何进行衡量的?每当你需要处理一组数值时,都必须使用某种循环来处理每个数值,无论是显式还是隐式。 - Felix Kling
1
顺便说一句,objsize 是不必要且错误的。 Object(osize) 创建了一个数字对象,这意味着 5 === Object(5)false。 要遍历数组,请使用 for(var i = 0; i < array.length; i++)for (var item of arr)arr.forEach(function(item) {})。 要遍历对象,请使用 for (var prop in obj)。 但是看起来 cars[i].specs 应该是一个数组,而不是一个对象。 - Felix Kling
2
期望的解决方案不包含任何“for循环”,因为它们在设计上是缓慢的。但实际上,它们并非“设计上”缓慢。在浏览器中,“for”循环已经被大量优化。当然,循环可能不是问题的“最佳”解决方案,但这是另一个问题。 - Felix Kling
在关系代数术语中,这就是“除法运算符”。我认为没有通用的高效算法来计算它,但在许多领域中,你可以找到一个相当不错的算法。https://pdfs.semanticscholar.org/9211/3556cef6e582357c26c2458e0ab8b66267d8.pdf - SQL Hacks
显示剩余8条评论
3个回答

9
你无法避免查看每一辆车,也无法避免查看汽车的每个规格,因为你想测试每一个规格。 通过使用 Set,可以避免每次循环偏好。
所以,这可能比循环更快,但它更加简单、易于理解,因为代码几乎像英语一样:过滤那些每个规格都在偏好中的汽车。

// Car objects
const cars = [{
    name: "Honda",
    category: "eco",
    specs: ["green", "fast","automatic"]
    },
  {
    name: "BMW",
    category: "sport",
    specs: ["blue", "fast","automatic"]
    }
]

const preferences = new Set(["green", "fast", "4x4", "automatic", "panorama"])

let filtered = cars.filter(car => car.specs.every(spec => preferences.has(spec)))
console.log(filtered)


“这可能比原来快或慢”,因为使用了Set,所以它比原来快了preferences.length倍。 - slider
@slider,是的...但有时候ES6函数(如every)在实践中的表现并不像简单的for循环那样好。我提到这个可能是因为我不想做出我没有测试过的任何声明。 - Mark
@MarkMeyer,你的代码片段真漂亮,而且速度更快。太棒了。我刚刚在我的代码中尝试了一下,它像魔法一样顺利地运行,并且确实感觉更快了。我会尝试测量我的解决方案并与你的进行比较。之后我会发布结果。谢谢! - Umut885

2
使用原帖中的数据:

-- 编辑 --

根据OP中的数据:

const array_intersect = (a, b) => a.filter( i => (b.indexOf(i) >= 0) )
const a_contains_b = (a, b) => array_intersect(a, b).length == b.length

var cars = [{
    name: "Honda",
    category: "eco",
    specs: ["green", "fast", "automatic"]
  },
  {
    name: "BMW",
    category: "sport",
    specs: ["blue", "fast", "automatic"]
  }
]

const preferences = ["green", "fast", "4x4", "automatic", "panorama"]

let filtered = cars.filter(car => a_contains_b(preferences, car.specs))
console.log(filtered);


虽然你没有 for 循环,但是你的 array_intersect 函数是 *O(n^2)*,因此并不更有效率。 - slider
1
@slider 原则上,您是正确的。但唯一的方法是在类似使用条件下进行测试,因为测试结果显示存在广泛的差异。请查看 https://github.com/dg92/Performance-Analysis-JS - Yoshimitsu

2

至少一个循环是无法避免的。您总是需要通过for...或其他构造方式(如array.filter())循环遍历所有汽车。但是,还有另一种提高性能的方法。您可以使用位掩码。这将要求更改汽车对象的数据结构,以便每辆汽车已经包含与其规格相对应的位掩码,并且当用户选择所需规格时,同样应添加规格代码。 (但我怀疑这可能会带来很多麻烦,而收益却很少。)

// Let's pretend there are preset binary digits corresponding 
// to each one of the available preferences:
//
//     "blue" => 1
//     "green" => 2
//     "red" => 4
//     "fast" => 8
//     "slow" => 16
//     "automatic" => 32
//     "4x4"  => 64
//     "panorama" => 128
//
// You would encode this into the data before processing

var cars = [{
    name: "Honda",
    category: "eco",
    specs: ["green", "fast", "automatic"],
    bin_specs: 42 // 2 + 8 + 32
  },
  {
    name: "BMW",
    category: "sport",
    specs: ["blue", "fast", "automatic"],
    bin_specs: 41 // 1 + 8 + 32
  }
]

const preferences = ["green", "fast", "4x4", "automatic", "panorama"]
const bin_preferences = 234 // 2 + 8 + 64 + 32 + 128]

let filtered = cars.filter(car => (car.bin_specs & bin_preferences) === car.bin_specs)

console.log(filtered);


这是一个非常有趣的答案,但只有在我知道每个可能的用户偏好元素时才可能实现。在我的情况下,它不起作用,但对于其他用例肯定有效。谢谢。 - Umut885
是的,你必须预先为每个偏好分配这些值。如果你不能控制后端,那就算了吧。 - Yoshimitsu

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