【问题标题】:Java beginner q - iterate ArrayList, select+remove items problem [duplicate]Java初学者q - 迭代ArrayList,选择+删除项目问题[重复]
【发布时间】:2020-07-17 16:00:18
【问题描述】:

我正在尝试从 String ArrayList 中删除偶数长度的单词,它几乎可以正常工作,只是由于某种原因,一个偶数单词正在通过。

我的代码:

public ArrayList<String> removeEvenLength(ArrayList<String> a) {
    for (int i = 0; i < a.size(); i++) {
      String wordEntry = a.get(i);
      if (wordEntry.length() % 2 == 0) {
        a.remove(i);
      }
    }
    return a;

我一定错过了什么,但我无法确定到底是什么。非常感谢指针。

【问题讨论】:

    标签: java arrays list


    【解决方案1】:

    问题是您在迭代 ArrayList 时正在修改它,这会改变它的大小。每次删除一个元素时,您都需要将索引减一,因为该索引现在将引用下一个元素。

    public static ArrayList < String > removeEvenLength(ArrayList < String > a) {
     for (int i = 0; i < a.size(); i++) {
      String wordEntry = a.get(i);
      if (wordEntry.length() % 2 == 0) {
       a.remove(i);
       i--;
      }
     }
     return a;
    }
    

    向后循环也可以解决这个问题,因为元素永远不会移动到您尚未检查的位置。

    public static ArrayList < String > removeEvenLength(ArrayList < String > a) {
     for (int i = a.size() - 1; i >= 0; i--) {
      String wordEntry = a.get(i);
      if (wordEntry.length() % 2 == 0) {
       a.remove(i);
      }
     }
     return a;
    }
    

    您还可以在 Java 8 及更高版本中使用 List#removeIf 来更轻松地完成此任务。

    public static ArrayList < String > removeEvenLength(ArrayList < String > a) {
     a.removeIf(str -> str.length() % 2 == 0);//or str.length() & 1 == 0
     return a;
    }
    

    您可以使用Stream#filter 构造一个具有奇数长度Strings 的新List,而无需修改旧的。

    public static ArrayList < String > removeEvenLength(ArrayList < String > a) {
     return a.stream().filter(str -> str.length() % 2 == 1).collect(Collectors.toCollection(ArrayList::new));
    }
    

    【讨论】:

    • 关于您的第一个解决方案:在这些情况下,我个人更喜欢从最后一个元素迭代到第一个 for (int i = (a.size() - 1); i &gt;= 0; i--)。这样,当您删除一个元素时,它不会移动/更改尚未迭代的值的索引。
    • @OHGODSPIDERS 好建议。
    【解决方案2】:

    您的代码运行良好,但由于您将从特定索引中删除任何元素,因此列表索引将更改意味着删除元素之后的直接元素将更新为已删除元素。

    您可以将您的代码修改为以下代码:

      public ArrayList<String> removeEvenLength(ArrayList<String> a) {
            for (int i = 0; i < a.size(); i++) {
                String wordEntry = a.get(i);
                if (wordEntry.length() % 2 == 0) {
                    a.remove(i);
                    i-=1;
                }
            }
            return a;
        }
    

    【讨论】:

      【解决方案3】:

      在迭代时删除会导致问题。您可以为此使用removeIf

      a.removeIf(wordEntry -> (wordEntry.length() % 2 == 0));
      

      或使用ListIterator 来迭代数组列表

      ListIterator<String> iter = a.listIterator();
      while(iter.hasNext()){
          if(iter.next().length() % 2 == 0){
              iter.remove();
          }
      }
      

      【讨论】:

        【解决方案4】:

        这里的问题是,当您执行a.remove(i); 时,ArrayList 会自动更新其索引,因此您最终会跳过一个值。以下是如何发生这种情况的示例:

        • 您将获得第一个元素 (a.get(0);)
        • 你发现它是一个偶数长度的字符串(wordEntry.length() % 2 == 0
        • 你删除它 (a.remove(i);)
        • 您的 for 循环前进到下一个值(现在 i = 1)
        • 您会看到现在的第二个元素 (a.get(1);),但是因为您删除了第一个元素,所以这现在是第三个元素。

        在这种情况下,您跳过了第二个元素,这就是为什么有些字符串会被忽略的原因。这是我建议使用增强的 for 循环的地方,它显着简化了事情,因此您无需担心您所在的索引。它看起来像这样:

        public ArrayList<String> removeEvenLength(ArrayList<String> a) {
            for (String wordEntry : a) {
                if (wordEntry.length() % 2 == 0) {
                    a.remove(wordEntry);
                }
            }
            return a;
        }
        

        或者,如果您想使用更简单的单线,ArrayList 提供了一些非常方便的方法来操作 ArrayList。一种称为 removeIf() 的方法几乎完全符合您的要求,但我认为在继续使用此类内置方法之前学习正确使用 for 循环是件好事。也就是说,如果你想走那条路,我会这样做:

        public ArrayList<String> removeEvenLength(ArrayList<String> a) {
            a.removeIf((wordEntry) -> wordEntry.length() % 2 == 0);
            return a;
        }
        

        另外,我只是觉得我应该注意还有其他方法可以找到偶数。您也可以使用(wordEntry.length() &amp; 1) == 0。就性能或其他方面而言没有真正的区别,这实际上只是个人喜好,但我只是觉得我应该提一下另一种方法:)

        【讨论】:

          【解决方案5】:

          从前到后移除时,您的方法与元素不同步。所以以相反的顺序删除它们。

          ArrayList<String> words = new ArrayList<>(List.of("abc", "efgh", "o", "pq", "rs"));
          words = removeEvenLength(words);
          System.out.println(words);
                  
          public static ArrayList<String> removeEvenLength(ArrayList<String> a) {
             for (int i = a.size()-1; i >= 0; i--) {
                String wordEntry = a.get(i);
                if (wordEntry.length() % 2 == 0) {
                  a.remove(i);
                }
             }
             return a;
          }
          

          如上所述,您也可以使用removeIf()

          解释。

          当您继续删除元素时,您的索引仍会正常递增以到达下一个元素。但是列表已经通过删除以前的元素而发生了变化,因此索引可能会跳过需要检查的元素。

          假设您要删除偶数元素。

          • 考虑a = [5,20,40], index = 1
          • 删除a[index++];该列表现在是[5,40] and index = 240 将不会被检查,因为列表现在大小为 2,迭代将停止。

          通过反向删除它们,列表长度的减少不会影响索引。

          • 再次考虑a = [5,20,40], index = 2
          • 删除a[index--];该列表现在是[5,20] and index = 120 将被检查并删除。然后索引将为 0,并且将保留一个元素。

          可以通过在删除项目时调整索引来缓解这种行为。但是,如果以相反的顺序移除,则不需要进行此类调整。

          【讨论】:

            猜你喜欢
            • 2020-07-09
            • 2020-07-03
            • 1970-01-01
            • 2015-03-13
            • 2014-07-12
            • 1970-01-01
            • 2017-03-29
            • 2012-11-30
            • 1970-01-01
            相关资源
            最近更新 更多