您将如何编写从列表中删除元素的“纯”函数?

时间:2019-08-09 15:18:49

标签: java functional-programming code-design pure-function

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

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

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

我们假设该方法接收两个参数,listelement来删除:

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

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

5 个答案:

答案 0 :(得分:4)

怎么样:

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

在Wikipedia中,它必须:

  • 对于相同的输入具有相同的收益-在这里看不到为什么这不是真的,没有随机数,没有IO等任何原因
  • 无状态-此函数仅处理参数。也可以使其static

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

如果您试图用Java实现功能样式的代码,那么它是新列表这一事实会引起问题。您可能是指称为参照透明性的属性。总而言之,如果可以在不更改程序语义的情况下将其替换为对自身的引用,则该值具有引用透明性。 Java并不真正支持此功能,这是Java不能真正起作用的原因之一。

在Haskell中,(几乎)所有表达式都具有参照透明性,即列表[1, 2, 3, 4]不仅等效于另一个列表[1, 2, 3, 4],而且是同一件事。这与数学上的相似,77是相同的东西,说“ 77是不同的Arrays.equals()我有这个方程式。”

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

希望可以清除它。

答案 1 :(得分:4)

仔细阅读您提到的要点:

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

关于实现,我认为流是最简单的,并且返回新列表,因此在纯度方面没问题

return list.stream()
 .filter(e -> !e.equals(element))
 .collect(Collectors.toList());

答案 2 :(得分:2)

根据Wikipedia,对现有列表进行的任何突变均不会使“属性1”失效。

  

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

您唯一真正的希望是返回一个 new List,其中所有元素都被复制,除了您要删除的元素。< / p>

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

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

请记住,这实际上是非常昂贵和/或浪费的,并且对于收藏而言并不是真正有价值的练习。

答案 3 :(得分:1)

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

答案 4 :(得分:0)

例如,使用ListIterator

ListIterator<Book> iter = books.listIterator();
while(iter.hasNext()){
    if(iter.next().getIsbn().equals(isbn)){
        iter.remove();
    }
}