如何避免循环遍历多个数组

15

我想通过url来传递一个班级。目前,两个for循环总是被执行。有没有更简洁的写法以避免执行任何不必要的代码?

const arrCommon = ['foo1', 'foo2', 'foo3', 'foo4'];
const arrOther = ['bar1', 'bar2', 'bar3'];

const activeUrl = window.location.href;
const activePage = activeUrl.substring(activeUrl.lastIndexOf('/') + 1);

for(let i=0; i<arrCommon.length; i++) {
    if (activePage == arrCommon[i])
         //if 'foo1, foo2 foo3 or foo4' add class to element 1
}

for(let i=0; i<arrOther.length; i++) {
    if (activePage == arrOther[i]) {
         //if 'bar1' add class to element 2
         //if 'bar2' add class to element 3
         //...
    }
}

我不确定您尝试实现什么,因为可以用多种方式将循环组合在一起,但根据您的需求,这可能会破坏逻辑。大多数方法都需要通过 [...arrCommon, ...arrOther]arrCommonarrOther 放入单个列表中。 - Michael Bauer
@MisterJojo 学院活动,贡献,考试 - Fergoso
@MisterJojo,它可以在arrCommon或arrOther中。 - Fergoso
1
你的错误在于使用具有O(n)搜索复杂度的数组,而不是具有O(1)查找的集合。 - Alnitak
5个回答

12
为了避免O(n)的迭代,请使用SetMap,这些数据结构通常具有“次线性”查找时间 - 通常为O(1)。
const setCommon = new Set(['foo1', 'foo2', 'foo3', 'foo4']);
const mapOther = new Map(['bar1', el2], ['bar2', el3], ['bar3', el4]]);

const activeUrl = window.location.href;
const activePage = activeUrl.substring(activeUrl.lastIndexOf('/') + 1);

if (setCommon.has(activePage)) {
    ...
}

if (mapOther.has(activePage)) {
    let el = mapOther.get(activePage);
    ...
}

确实如此。但是,为了知道需要将类添加到哪个元素,您仍然需要在第二个“if”块内进行迭代,不是吗?我认为我已经找到了一个简单的JS对象的解决方案。这也是O(1),对吧? - Eric Duminil
@EricDuminil 啊,原来这就是 OP 在那些评论中所指的意思! - Alnitak
这仍然需要O(n)来创建Set和Map。如果原始定义从数组切换到用作字典的对象,则可以获得O(1)的好处。 - Travis J
@TravisJ 构建一个对象字面量在底层仍然是一个O(n)操作 - 只是由解析器而不是解释器完成。但如果有什么的话,我认为这个答案的优点在于它确切地表达了意图,并且非常简洁明了。 - Alnitak
构建对象文字需要为要创建的属性编写指令的数量。类似于构建数组。然而,从该构造中调用Map或Set是每个数组的额外O(n)。例如,这种方法比下面展示的重构方法慢两倍。 - Travis J

4

是的,但这真的不必要。像你所描述的O(n)操作并不是重构的理想目标,也构成了微小的优化。因此,这很可能会损害可读性,而只省下了几条指令,收益微乎其微。

以下是正确的做法:

const arrCommon = ['foo1', 'foo2', 'foo3', 'foo4'];
const arrOther = ['bar1', 'bar2', 'bar3'];

const activeUrl = window.location.href;
const activePage = activeUrl.substring(activeUrl.lastIndexOf('/') + 1);

for(let i = 0, m = Math.max(arrCommon.length,arrOther.length); i < m; i++){
 if(activePage == arrCommon[i]){
  //if 'foo1, foo2 foo3 or foo4' add class to element 1

  // search complete
  break;
 }
 if(activePage == arrOther[i]){
   //if 'bar1' add class to element 2
   //if 'bar2' add class to element 3
   //...

  // search complete
  break;
 }
}

补充说明

评论中提出这种方法导致执行更多的指令。确实,由于条件短路,在给定场景下它确实执行了更多的指令,尽管只是轻微的增加。在OP给出的示例中,它执行了一个额外的指令......但是,主要目的是达到O(n),这就是它所达到的,并将多个循环合并为一个循环。在这里使用break仍然会是O(n),但合并的循环平均来说会减少指令数。此外,可以删除未定义的检查以减少指令。在本设计中,这现在几乎适用于每种情况;如果我们正在研究存在异常大量的查询的情况,那么不同的方法将更加适用,但这是相当容易处理的。

还有使用O(1)查找的机会,如其他答案所示,只要涉及数组就仍需要O(n)设置(将比较作为对象创建将允许O(1)查找并且不需要从数组映射)。没有更改项目的定义,这不一定比O(n)优化得更好,但在合理组成时至少读起来还不错。

总的来说,这种类型的讨论正是为什么微调您的代码是一个坏主意的确切原因。这浪费了最小的收益所需的时间和精力。

重构

将其重构为允许从原始设计中执行O(1)的形式将导致最佳性能,尽管在此案例中如上述标记进行微调后并不需要。这仅仅作为一种附带说明。

const activePage = 'foo3';

const commonStyles = {};
commonStyles.foo1 = commonStyles.foo2 = commonStyles.foo3 = commonStyles.foo4 = function(){
 // add class to element 1
 console.log('common');
};

const otherStyles = {};
otherStyles.bar1 = otherStyles.bar2 = otherStyles.bar3 = function(){
 //if 'bar1' add class to element 2
 //if 'bar2' add class to element 3
 //...
 console.log('other');
};

(commonStyles[activePage]&&commonStyles[activePage](),otherStyles[activePage]&&otherStyles[activePage]());


哇!简短而直接!!谢谢 :) +10 - Fergoso
你的循环为什么不使用 break 语句? - Mister Jojo
你的回答很好。但我认为Jojo先生的解决方案是对我的问题最相关的答案,所以将其标记为正确答案。再次感谢! :) - Fergoso
1
就复杂度而言,这似乎与原始版本相等或在某些情况下更糟...如果m和n是列表的长度,并且(不失一般性)我们将m设为较大的值... 这个的顺序是2m+2m+2*n+(m-n)=5m+n。 原始版本的顺序为3m+3n。由于m>=n,两者至少都是6n级别,但在m支配n的情况下,这个的顺序是4m,而原始版本仅为3m。 - user3067860
3
我认为这个答案可以通过指出实际情况来改进,即编写这样的代码是一个不好的主意。如果你想关闭所有的窗户和门,你应该写“关闭所有的窗户和关闭所有的门”;而不应该写“让i从1循环到窗户数或门数(以较大者为准),并对每个i,关闭第i个窗户(如果有的话)和关闭第i个门(如果有的话)。”后者很难阅读,没有任何好处。 - Tanner Swett
显示剩余3条评论

2

除非我漏掉了什么,你可以使用JavaScript对象作为字典(或Map),以便直接获取元素ID:

const idByPage = {
  foo1: 1, foo2: 1, foo3: 1, foo4: 1,
  bar1: 2,
  bar2: 3,
  bar3: 4
}

var page = 'bar2';

if (idByPage.hasOwnProperty(page)) {
  console.log('element' + idByPage[page]);
  // Add class to ('element' + idByPage[page])
  // ...
}


1
使用普通对象作为字典容易出错,因为普通对象具有内置键,例如 toStringvalueOfconstructor(仅仅是一个单词,更容易引起错误)。您可以使用 Object.create(null)new Map() 代替。 - Gustavo Rodrigues
@GustavoRodrigues:感谢您的评论。hasOwnProperty能解决这个问题吗?如果不能,我会重构并使用Map。 - Eric Duminil

0
你可以在JavaScript中使用数组的includes()方法。

这与最近的@Alnitak答案类似,它使用new Set().has(),但使用数组。我认为这个更简单,因为它只需要用方法调用替换循环,尽管Set.prototype.has具有更好的性能 - Gustavo Rodrigues

-1

你可以这样做。

const
    arrCommon  = ['foo1', 'foo2', 'foo3', 'foo4']
  , arrOther   = ['bar1', 'bar2', 'bar3']
  , activeUrl  = window.location.href
  , activePage = activeUrl.substring(activeUrl.lastIndexOf('/') + 1)
  ;

if (arrCommon.includes(activePage))  
  {
  // add class to element 1
  }
else
  {
  let idx2 = arrOther.indexOf(activePage)
  if (idx2 > -1) 
    {
    // add class to element idx2 + 2 ?
    }
  }

其他可能的方式
const
    elm_1 = document.querySelector('#element_1') 
  , elm_2 = document.querySelector('#element_2') 
  , elm_3 = document.querySelector('#element_3') 
  , elm_4 = document.querySelector('#element_4') 
  , elmRef = 
      { foo1: elm_1, foo2: elm_1, foo3: elm_1, foo4: elm_1
      , bar1: elm_2, bar2: elm_3, bar3: elm_4
      }
  , activeUrl  = window.location.href
  , activePage = activeUrl.substring(activeUrl.lastIndexOf('/') + 1)
  ;
if (elmRef[activePage])
  {
  elmRef[activePage].classList.add('classXY')
  }

谢谢。如果你看到我的代码逻辑,我不认为这会起作用。 - Fergoso
看起来不错。谢谢 :) +10 但是从某种程度上来说,这仍然会循环遍历不需要/无关的项目? - Fergoso
@Fergoso,.indexOf方法不是循环,可以说它是最优化的方式。(如果你关心汇编代码,可能不是最优的,但在这种情况下,无论你采用什么方式,你总会找到一个循环) - Mister Jojo
2
.indexOf 绝对是一个循环 - 它只是隐藏在一个函数调用中的循环。 - Alnitak
1
@MisterJojo 在计算机科学中,这是一个O(n)操作。这才是真正重要的。 - Alnitak
显示剩余4条评论

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