【问题标题】:Removing every Nth element from Wildcard List - Java从通配符列表中删除每个第 N 个元素 - Java
【发布时间】:2020-04-30 21:04:02
【问题描述】:

我正在尝试创建一个从未知类型(通配符)列表中删除每个第 N 个元素的方法,但是我尝试这样做的每一种方式,它都不会删除指定的元素,但我不知道为什么。我已经为此苦苦挣扎了两天,所以我在这里发帖作为最后的手段。提前感谢您的帮助。

我目前的代码如下:

public static void removeEveryNthElement(List<?> list, int n) {

    //Set list equal to an ArrayList because List is immutable
    list = new ArrayList<>(list);

    //If n is negative or zero throw an exception
    if(n <= 0) {
        throw new IllegalArgumentException("Integer n needs to be a positive number.");
    }

    //If the list is null, throw an exception
    if(list == null) {
        throw new NullPointerException("The list must not be null.");
    }

    //Remove every nth element in the list
    for(int i = 0; i < list.size(); i++) {
        if(i % n == 0) {
            list.remove(i);
        }
    }

我尝试的另一种方法是将 for 循环替换为以下内容:

list.removeIf(i -> i % 3 == 0);

但是,当我这样做时,我收到错误,即参数类型的 % 运算符未定义。 我还尝试使用 for 循环将列表中的每个元素单独添加到另一个可修改列表中,但无论我做什么,我都没有运气。如果您能帮助我,将不胜感激!

【问题讨论】:

    标签: java list generics wildcard


    【解决方案1】:

    您的代码最严重的问题是删除索引 i 处的元素会更改所有后续元素的索引,因此删除第一个元素后,您删除元素 (i % n) 的条件是错误的。

    解决此问题的一种方法是以相反的顺序迭代:

    for (int i = list.size()-1; i >= 0; i--) {
        if (i % n == 0) {
            list.remove(i);
        }
    }
    

    另一种方法是增加 i 而不是增加一个,而是增加 n 并针对删除的元素进行调整:

    for (int i = 0; i < list.size(); i += n) {
        list.remove(i);
        i--;
    }
    

    并且,由于i--; 后跟i += n;i += n-1; 相同:

    for (int i = 0; i < list.size(); i += n-1) {
        list.remove(i);
    }
    

    附加说明:if (list == null) 在语句 list = new ArrayList&lt;&gt;(list); 之后检查是无用的,因为如果 list 为 null,new ArrayList&lt;&gt;(list); 已经抛出 NullPointerException

    【讨论】:

    • 这很有意义,因为我看到我没有考虑剩余元素的左移。尽管如此,我的问题仍然存在,因为我的代码实际上并没有删除任何元素(传递给方法的列表与调用方法后的列表保持一致),我不知道为什么
    • @BrandonBischoff 当然,传递给该方法的列表永远不会更改 - 因为您使用 list = new ArrayList&lt;&gt;(list); 创建了该列表的副本。仅修改了该副本,您需要返回该更改的副本。
    • 问题是方法不允许有返回类型。此外,List 是不可变的,这就是我创建副本的原因。我想我只是想知道如何将原始列表更新为修改后的列表?
    • 这最终成为了最佳答案。我唯一需要修改的是它需要删除 i-1 。此外,我没有创建要修改的副本,而是在我的测试用例中创建了一个 ArrayList 而不是 List,因此它是可变的。非常感谢你帮了我很多
    【解决方案2】:

    您需要记住,基于其他集合创建新集合正在删除对原始集合的引用 - values from collection are coppied to new one - 任何修改都不会影响超出方法范围的任何内容。您需要传递支持从自身删除对象的集合或从方法返回新集合。请记住,类型不定义对象的行为 - 它取决于与您转换为的类兼容的实现。 Here is an example of what I said about the backend implementation(两个变量都是 List 类型但实现不同)。

    当您想“就地”执行此操作时,以下是代码:

    public static void main(String[] args) {
        List<Integer> list2 = new ArrayList<>();
        list2.add(1);
        list2.add(2);
        list2.add(3);
        list2.add(4);
    
        removeEveryNthElement(list2, 3); // deleted number 3 because it is 3rd element
    }
    
    public static void removeEveryNthElement(List<?> list, int n) {
        for (int i = 2; i < list.size(); i += 3) {
            list.remove(i);
        }
    }
    

    但我建议不要做任何对程序员不透明的操作。当您知道您将值传递给方法并且“它做某事”然后因为它已更改而获取值时,最好阅读和理解更大的程序。对于这个例子,我使用泛型和流:

    public static void main(String[] args) {
        List<Integer> list1 = Arrays.asList(1, 2, 3, 4);
        list1 = removeEveryNthElement2(list1, 3); //deleted number 3
        System.out.println();
    }
    
    public static <T> List<T> removeEveryNthElement2(List<T> list, int n) {
        final Predicate<T> p = new Predicate<T>() {
            int i = 0;
    
            @Override
            public boolean test(final T t) {
                return ++i % n != 0;
            }
        };
    
        return list.stream().filter(p).collect(Collectors.toList());
    }
    

    【讨论】:

    • list.remove(i) 返回 UnsupportedOperationException,因为列表是不可变的,这就是我在方法中将副本创建为 ArrayList 的原因
    • 这就是我在提到后端实现时所指出的。如果在将其传递给方法之前创建 ArrayList / LinkedList,则可以使列表可变。 list = new ArrayList(list); //将实现更改为可变列表 removeEveryNthElement(list, int n); //运行你的方法
    • 这对我帮助最大,因为我遇到的问题源于我为测试该方法而创建的 JUnit 测试用例。每次我使用 List 而不是 ArrayList 等类型时,我都不知道该怎么做。不知道我怎么能忽略这样一个简单的解决方案。无论如何,我不能感谢你,非常感谢你的帮助
    【解决方案3】:

    Brandon,我先建议你的方法是side-effecting 调用方法中内置的列表。这是允许的,但可能会导致更复杂的代码中出现难以理解的错误。相反,请尝试在您的方法中创建一个新列表,并分配返回值:

    public class Remove {
    
        public static void main(String[] args) {
            List<String> list = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h");
            list = removeElements(list, 3);
            System.out.println(list);
        }
    
        public static <T> List<T> removeElements(List<T> list, int n) {
            List<T> newList = new ArrayList<T>();
            for (int i = 0; i < list.size(); i++) {
                if (i % n != 0) {
                    newList.add(list.get(i));
                }
            }
            return newList;
        }
    }
    

    因此,它使方法更简单,因为我们不再迭代正在修改的列表。

    查看Side effect--what's this? 了解更多关于副作用的信息。

    【讨论】:

    • 完全明白有回报;但是,根据给定的要求,我不允许这样做。我应该修改传递给方法的原始列表。据说 List 是不可变的,所以我不得不制作一个副本。
    • 啊,作业?也许他们让你体会到后续问题带来的副作用的危害......
    • 所以,如果你不能改变它,也不能归还它,那就……改变它然后扔掉?也许在此期间打印它。最好有调用方法的相关部分。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-01
    相关资源
    最近更新 更多