从通配符列表中删除每第N个元素 - Java

3

我想创建一个方法,从一个未知类型的List中删除每个第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 循环,将列表中的每个元素单独添加到另一个可修改的列表中,但无论我做什么,都没有成功。如果您能帮助我解决这个问题,我将非常感激!

3个回答

2
您的代码最严重的问题是,删除索引为i的元素会更改所有后续元素的索引,因此在删除第一个元素后,删除元素的条件(i%n)是错误的。
解决该问题的一种方法是倒序迭代。
for (int i = list.size()-1; i >= 0; i--) {
    if (i % n == 0) {
        list.remove(i);
    }
}

另一种方法是将i的增量不是1,而是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);
}

另外需要注意的是:在语句list = new ArrayList<>(list);之后,检查if (list == null)是无用的,因为new ArrayList<>(list);如果list为空,则已经抛出了NullPointerException。请保留HTML标签。

这是有道理的,因为我看到我没有考虑剩余元素向左移动的问题。尽管如此,我的问题仍然存在,因为我的代码实际上没有删除任何元素(传递到方法中的列表在调用方法后保持不变),我找不出原因。 - Brandon Bischoff
1
@BrandonBischoff 当然可以,传入该方法的列表永远不会被更改 - 因为您使用 list = new ArrayList<>(list); 创建了该列表的副本。只有该副本被修改,您需要返回已更改的副本。 - Thomas Kläger
问题在于该方法不允许有返回类型。此外,List是不可变的,这就是为什么我创建了一个副本。我想我只是忘记了如何将原始列表更新为修改后的列表? - Brandon Bischoff
这最终成为了最佳答案。我需要修改的唯一一件事是需要删除i-1。此外,我没有创建一个副本进行修改,而是在我的测试用例中创建了一个ArrayList而不是List,因此它是可变的。非常感谢你的帮助,你帮了我很多。 - Brandon Bischoff

1
你需要记住,基于其他集合创建新集合会删除对原始集合的引用-从集合中复制的值到新集合-任何修改都不会影响超出方法范围的任何内容。您需要传递支持从自身删除对象的集合或从方法返回新集合。请记住,类型不定义对象的行为-它取决于与您转换的类兼容的实现。 这是我所说的关于后端实现的示例(两个变量都是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());
}

1
list.remove(i) 返回 UnsupportedOperationException,因为列表是不可变的,这就是为什么我在我的方法中创建了一个 ArrayList 的副本。 - Brandon Bischoff
这就是我在提到后端实现时指出的问题。如果您在将ArrayList / LinkedList传递给方法之前创建它,那么您可以使列表可变。 list = new ArrayList(list); //将实现更改为可变列表 removeEveryNthElement(list, int n); //运行您的方法 - siwonpawel
这对我帮助最大,因为我遇到的问题源于我创建用于测试方法的JUnit测试用例。每次我使用List类型而不是ArrayList等类型,我都无法弄清楚该怎么做。不确定我如何忽略这样一个简单的解决方案。无论如何,我无法感谢你足够,非常感谢你的帮助。 - Brandon Bischoff

0
Brandon,首先我建议你的方法正在副作用调用方法中构建的列表。虽然这是允许的,但在更复杂的代码中可能会导致难以理解的错误。相反,尝试在你的方法中创建一个新的列表,并将返回值赋值给它:
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;
    }
}

作为结果,这使得方法变得更简单,因为我们不再迭代修改的列表。
请查看副作用-这是什么? 以了解更多关于副作用的信息。

完全理解有一个返回值的必要性;然而,根据我接收到的要求,我不能这样做。我应该修改传递到方法中的原始列表。也就是说,List是不可变的,所以我不得不复制一份。 - Brandon Bischoff
啊,作业?也许他们让你通过后续问题来体会副作用的恶劣影响。 - Don Branson
那么,如果你不能改变它,也不能返回它,那就只能改变它并将其丢弃了?也许在此期间打印出来会更好。最好有调用方法的相关部分。 - Don Branson

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