3

在 Java(或任何类似语言)中,您将如何编写从列表中删除元素的纯函数(或方法)。

如果元素在列表中,我们只需返回一个新的(理想情况下是不可变的)列表,其中包含输入列表的所有元素,减去我们删除的元素。

但是您将如何处理列表中未找到该元素的情况?

让我们假设该方法接收 2 个参数,list以及element要删除的参数:

public SOMETHING remove(final List<String> list, final String element){
    // Copy the input list and remove the first occurrence of 'element', if I find it.
    // 
    // if I don't find it ... do something clever here ...
}

如果我调用这个方法,并且element不包含在里面list

  • 抛出异常可能会使方法“不纯”(?)
  • 修改输入列表并返回布尔值(类似于List#remove())可能会使方法“不纯”(修改输入将是副作用)
  • 如果我调用此方法,将输入作为输出返回对我来说似乎不直观。
  • 返回一个Optional.of(listCopy)(编辑:发布后添加到我的问题中)
  • 还有其他想法吗?

编辑

我应该提到我只想删除第一次出现的element,所以如果输入list中有多次出现element,我不想通过一次调用我的 remove() 方法(例如使用stream().filter())来删除它们。我刚刚在我的代码示例中编辑了注释以反映这一点。但是,这与我的问题并不完全相关,因为我的主要问题围绕着如何使该方法直观地使用并保持“纯粹”。

编辑 2

我在上述建议中添加了一个额外的想法。ReturningOptional.of(listCopy)似乎是迄今为止提出的解决方案中最优雅的解决方案。它强制调用者检查请求的操作是否成功,并且(如果成功),它返回一个新列表,从而不修改原始输入列表。如果操作不成功(element未在里面找到list),则方法返回Optional.empty()。在我看来,这也将满足下面提到的引用完整性

4

5 回答 5

5

通过你提到的几点:

  1. 抛出异常可能会使方法“不纯”(?)
    • 如果您想返回相同的列表或抛出异常,这是一个实现决策。至于“纯函数”——只要你保持一致并且相同的输入产生相同的结果(或异常),你就很好
  2. 修改输入列表并返回一个布尔值(类似于 List#remove())可能会使方法“不纯”(修改输入将是一个副作用)
    • 这将使其非纯功能性,因为您将产生副作用
  3. 如果我调用此方法,将输入作为输出返回对我来说似乎不直观。
    • 实际上没有看到任何问题。再次,您的电话是否有效。就“纯功能”而言,您可以

至于实现,我认为流是最简单的,并返回新列表,所以你在纯度方面没问题

return list.stream()
 .filter(e -> !e.equals(element))
 .collect(Collectors.toList());
于 2019-08-09T15:30:38.260 回答
4

怎么样:

public List<String> remove(final List<String> list, final String element) {
    List<String> result = new ArrayList<String>(list);
    result.remove(element);
    return result;
}

从维基百科,它必须:

  • 对相同的输入有相同的回报 - 看不出有什么理由在这里不正确,没有随机数,没有 IO 等
  • 是无状态的——这个函数只处理参数。也可以做到static

编辑(刚刚看到关于每次返回新列表的评论)

如果您尝试在 Java 中实现函数式代码,那么它是一个新列表这一事实会导致问题。您可能指的是一个称为引用透明度的属性。总而言之,如果一个值可以替换为对自身的引用,则它具有引用透明性,反之亦然,而不改变程序的语义。Java 并不真正支持这一点,这也是 Java 不能真正起作用的原因之一。

在 Haskell 中,(几乎)所有表达式都具有引用透明性,即一个列表[1, 2, 3, 4]不仅等同于另一个列表[1, 2, 3, 4],而且是同一件事。这类似于数学中7的方式,与 相同7,说“这与我在这个等式中的不同”7是没有意义的。7

回到 Java,您需要后续调用返回相同的输出。因此,由您来定义“相同”的含义。在函数世界中,这通常由值确定(即使用Arrays.equals())。但是,如果您想使用==(即当且仅当它们在内存中占据相同位置时两个列表相等),那么函数式样式可能不是您想要的。

希望清除它。

于 2019-08-09T15:23:16.153 回答
2

根据 Wikipedia,对现有列表的任何更改都会导致“属性 1”失败。

它的评估没有副作用(没有局部静态变量、非局部变量、可变引用参数或 I/O 流的突变)。

您在这里唯一真正的希望是返回一个复制了所有元素的新元素, 除了您要删除的元素。List

public List<String> remove(final List<String> list, final String element) {
    return list.stream()
                   .filter(e -> !Objects.equals(e, element))
                   .collect(Collectors.toList());
}

这也处理了“元素不在列表中”的情况,因为您返回了一个与旧列表具有相同元素的新列表实例。实际上,如果您的数据状态没有改变,那么返回相同的状态并没有真正的危害。

但请记住 - 这实际上是相当昂贵和/或浪费的,并且不会真正成为一个有价值的收藏练习。

于 2019-08-09T15:24:58.997 回答
1

您可以构建一个新的List并遍历输入数组,添加每个不是的元素element,然后返回新列表

于 2019-08-09T15:22:45.110 回答
0

例如,使用ListIterator

ListIterator<Book> iter = books.listIterator();
while(iter.hasNext()){
    if(iter.next().getIsbn().equals(isbn)){
        iter.remove();
    }
}
于 2019-08-09T15:24:59.893 回答