如何在Java中实现列表折叠

我有一个List,并希望将它减少到单个值(函数编程术语“折叠”,Ruby术语inject ),就像

 Arrays.asList("a", "b", "c") ... fold ... "a,b,c" 

由于我感染了函数式编程思想(Scala),我正在寻找一种更简单/更短的编码方式

 sb = new StringBuilder for ... { append ... } sb.toString 

你要找的是一个字符串join()方法,Java从8.0开始。 尝试以下方法之一。

  1. 静态方法String#join(delimiter, elements)

     Collection source = Arrays.asList("a", "b", "c"); String result = String.join(",", source); 
  2. Stream接口支持折叠操作,与Scala的foldLeft函数非常相似。 看一下下面的连接收集器 :

     Collection source = Arrays.asList("a", "b", "c"); String result = source.stream().collect(Collectors.joining(",")); 

    您可能希望静态导入Collectors.joining以使代码更清晰。

    顺便说一下,这个收集器可以应用于任何特定对象的集合:

     Collection numbers = Arrays.asList(1, 2, 3); String result = numbers.stream() .map(Object::toString) .collect(Collectors.joining(",")); 

要回答您的原始问题:

 public static  A fold(F> f, A z, Iterable xs) { A p = z; for (B x : xs) p = ff(p).f(x); return p; } 

F看起来像这样:

 public interface F { public B f(A a); } 

正如dfa建议的那样, Functional Java已经实现了这一点,以及更多。

例1:

 import fj.F; import static fj.data.List.list; import static fj.pre.Monoid.stringMonoid; import static fj.Function.flip; import static fj.Function.compose; F> sum = stringMonoid.sum(); String abc = list("a", "b", "c").foldLeft1(compose(sum, flip(sum).f(","))); 

例2:

 import static fj.data.List.list; import static fj.pre.Monoid.stringMonoid; ... String abc = stringMonoid.join(list("a", "b", "c"), ","); 

例3:

 import static fj.data.Stream.fromString; import static fj.data.Stream.asString; ... String abc = asString(fromString("abc").intersperse(',')); 

特定

 public static  Y fold(Collection list, Injector filter){ for (T item : list){ filter.accept(item); } return filter.getResult(); } public interface Injector{ public void accept(T item); public Y getResult(); } 

然后使用看起来像

 fold(myArray, new Injector(){ private StringBuilder sb = new StringBuilder(); public void Accept(String item){ sb.append(item); } public String getResult() { return sb.toString(); } } ); 

如果你想将一些function方面应用于普通的旧Java,而不需要切换语言, 尽管你可以使用 LamdaJ , fork-join(166y)和google-collections是帮助你添加语法糖的库。

在google-collections的帮助下,您可以使用Joiner类 :

 Joiner.on(",").join("a", "b", "c") 

Joiner.on(",")是一个不可变对象,因此您可以自由地共享它(例如作为常量)。

您还可以配置null处理,如Joiner.on(", ").useForNull("nil");Joiner.on(", ").skipNulls()

为了避免在生成大字符串时分配大字符串,可以使用它通过Appendable接口或StringBuilder类附加到现有的Streams,StringBuilders等:

 Joiner.on(",").appendTo(someOutputStream, "a", "b", "c"); 

在写出地图时,你需要两个不同的分隔符来表示键和值之间的分隔和分隔:

 Joiner.on(", ").withKeyValueSeparator(":") .join(ImmutableMap.of( "today", "monday" , "tomorrow", "tuesday")) 

你正在寻找的是一个字符串“join”函数,遗憾的是,Java没有。 您将不得不滚动自己的连接function,这不应该太难。

编辑: org.apache.commons.lang.StringUtils似乎有许多有用的字符串函数(包括join)。

不幸的是,在Java中,你无法逃脱那个循环,但是有几个库。 例如,你可以尝试几个库:

  • lambdaj
  • functionaljava
  • 特别是在你的情况下,你可以重用我的这个代码

首先,您需要一个Javafunction库,它提供通用仿函数和折叠等function投影。 我在这里设计并实现了一个function强大(function强大)但很简单的库: http : //www.codeproject.com/KB/java/FunctionalJava.aspx (我发现其他库提到过于复杂)。

然后你的解决方案看起来像:

 Seq.of("","a",null,"b","",null,"c","").foldl( new StringBuilder(), //seed accumulator new Func2(){ public StringBuilder call(StringBuilder acc,String elmt) { if(acc.length() == 0) return acc.append(elmt); //do not prepend "," to beginning else if(elmt == null || elmt.equals("")) return acc; //skip empty elements else return acc.append(",").append(elmt); } } ).toString(); //"a,b,c" 

请注意,通过应用fold,真正需要考虑的唯一部分是Func2.call的实现,3行代码定义了一个接受累加器和元素的运算符并返回累加器(我的实现考虑了空字符串和nulls,如果删除那个case,那么它就是2行代码)。

这是Seq.foldl的实际实现,Seq实现了Iterable

 public  R foldl(R seed, final Func2 binop) { if(binop == null) throw new NullPointerException("binop is null"); if(this == EMPTY) return seed; for(E item : this) seed = binop.call(seed, item); return seed; } 

GS Collections有injectInto(比如Ruby),makeString和appendString。 以下内容适用于您的示例:

 String result1 = FastList.newListWith("a", "b", "c").makeString(","); StringBuilder sb = new StringBuilder(); FastList.newListWith("a", "b", "c").appendString(sb, ","); String result2 = sb.toString(); Assert.assertEquals("a,b,c", result1); Assert.assertEquals(result1, result2); 

注意:我是GS Collections的开发人员。

不幸的是,Java不是一种函数式编程语言,并没有一种很好的方法来做你想做的事情。

我相信Apache Commons lib有一个叫做join的函数 ,可以做你想做的事情。

它必须足以在方法中隐藏循环。

 public static String combine(List list, String separator){ StringBuilder ret = new StringBuilder(); for(int i = 0; i < list.size(); i++){ ret.append(list.get(i)); if(i != list.size() - 1) ret.append(separator); } return ret.toString(); } 

我想你可以递归地做到这一点:

 public static String combine(List list, String separator){ return recursiveCombine("", list, 0, separator); } public static String recursiveCombine(String firstPart, List list, int posInList, String separator){ if (posInList == list.size() - 1) return firstPart + list.get(posInList); return recursiveCombine(firstPart + list.get(posInList) + separator, list, posInList + 1, seperator); } 

现在,您可以将String.join()与Java 8一起使用。

  List strings = Arrays.asList("a", "b", "c"); String joined = String.join(",", strings); System.out.println(joined); 

在lambdas的支持下,我们可以使用以下代码:

 static  R foldL(BiFunction lambda, R zero, List theList){ if(theList.size() == 0){ return zero; } R nextZero = lambda.apply(zero,theList.get(0)); return foldL(lambda, nextZero, theList.subList(1, theList.size())); } 

下面是折叠列表的代码,通过在我们前进时保留后面的节点和折叠的信息。

 public class FoldList { public static void main(String[] args) { Node a = new Node(1); Node b = new Node(2); Node c = new Node(3); Node d = new Node(4); Node e = new Node(5); Node f = new Node(6); Node g = new Node(7); Node h = new Node(8); Node i = new Node(9); a.next = b; b.next = c; c.next = d; d.next = e; e.next = f; f.next = g; g.next = h; h.next = i; foldLinkedList(a); } private static void foldLinkedList(Node a) { Node middle = getMiddleNodeOfTheList(a); reverseListOnWards(middle); foldTheList(a, middle); } private static Node foldTheList(Node a, Node middle) { Node leftBackTracePtr = a; Node leftForwardptr = null; Node rightBackTrack = middle; Node rightForwardptr = null; Node leftCurrent = a; Node rightCurrent = middle.next; while (middle.next != null) { leftForwardptr = leftCurrent.next; rightForwardptr = rightCurrent.next; leftBackTracePtr.next = rightCurrent; rightCurrent.next = leftForwardptr; rightBackTrack.next = rightForwardptr; leftCurrent = leftForwardptr; leftBackTracePtr = leftCurrent; rightCurrent = middle.next; } leftForwardptr = leftForwardptr.next; leftBackTracePtr.next = middle; middle.next = leftForwardptr; return a; } private static void reverseListOnWards(Node node) { Node startNode = node.next; Node current = node.next; node.next = null; Node previous = null; Node next = node; while (current != null) { next = current.next; current.next = previous; previous = current; current = next; } node.next = previous; } static Node getMiddleNodeOfTheList(Node a) { Node slowptr = a; Node fastPtr = a; while (fastPtr != null) { slowptr = slowptr.next; fastPtr = fastPtr.next; if (fastPtr != null) { fastPtr = fastPtr.next; } } return slowptr; } static class Node { public Node next; public int value; public Node(int value) { this.value = value; } } } 

Java 8风格(function):

 // Given List arr = Arrays.asList("a", "b", "c"); String first = arr.get(0); arr = arr.subList(1, arr.size()); String folded = arr.stream() .reduce(first, (a, b) -> a + "," + b); System.out.println(folded); //a,b,c 

没有这样的function,但您可以创建类似下面的内容,并在需要时调用它。

 import java.util.Arrays; import java.util.List; public class FoldTest { public static void main( String [] args ) { List list = Arrays.asList("a","b","c"); String s = fold( list, ","); System.out.println( s ); } private static String fold( List l, String with ) { StringBuilder sb = new StringBuilder(); for( String s: l ) { sb.append( s ); sb.append( with ); } return sb.deleteCharAt(sb.length() -1 ).toString(); } }