25

我有一个 List 并希望将其减少为单个值(函数式编程术语“折叠”,Ruby 术语inject),例如

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

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

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

14 回答 14

13

要回答您的原始问题:

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

F 看起来像这样:

public interface F<A, B> { 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<String, F<String, String>> 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(','));
于 2009-06-04T15:38:04.033 回答
10

给定

public static <T,Y> Y fold(Collection<? extends T> list, Injector<T,Y> filter){
  for (T item : list){
    filter.accept(item);
  }
  return filter.getResult();
}

public interface Injector<T,Y>{
  public void accept(T item);
  public Y getResult();
}

然后用法看起来像

fold(myArray, new Injector<String,String>(){
  private StringBuilder sb = new StringBuilder();
  public void Accept(String item){ sb.append(item); }
  public String getResult() { return sb.toString(); }
}
);
于 2009-06-04T14:38:18.623 回答
8

如果您想将某些功能方面应用于普通的旧 Java,而不需要切换语言,尽管您可以 LamdaJfork-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"))
于 2009-06-08T22:22:52.123 回答
7

您正在寻找的是join()Java 自 8.0 以来的字符串方法。尝试以下方法之一。

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

    Collection<String> source = Arrays.asList("a", "b", "c");
    String result = String.join(",", source);
    
  2. Stream接口支持折叠操作,非常类似于 Scala 的foldLeft功能。看看下面的连接Collector

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

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

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

    Collection<Integer> numbers = Arrays.asList(1, 2, 3);
    String result = numbers.stream()
            .map(Object::toString)
            .collect(Collectors.joining(","));
    
于 2015-03-17T12:54:43.860 回答
6

您正在寻找的是一个字符串“join”函数,不幸的是,Java 没有。您将不得不推出自己的加入功能,这应该不会太难。

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

于 2009-06-04T13:58:50.297 回答
3

Eclipse CollectionsinjectInto如 Ruby 和 Smalltalk)makeStringappendString. 以下将适用于您的示例:

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);

注意:我是 Eclipse Collections 的提交者。

于 2013-01-08T17:53:36.707 回答
2

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

于 2009-06-04T13:58:33.027 回答
2

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

那么您的解决方案将如下所示:

Seq.of("","a",null,"b","",null,"c","").foldl(
    new StringBuilder(), //seed accumulator
    new Func2<StringBuilder,String,StringBuilder>(){
        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"

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

下面是 Seq.foldl 的实际实现,Seq 实现了 Iterable<E>:

public <R> R foldl(R seed, final Func2<? super R,? super E,? extends R> 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;
}
于 2010-05-21T17:48:16.200 回答
2

Java 8 风格(函数式):

// Given
List<String> 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
于 2018-02-22T07:09:42.963 回答
1

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

我相信 Apache Commons lib 有一个名为 join 的函数,它会做你想做的事。

它必须足够好才能在方法中隐藏循环。

public static String combine(List<String> 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<String> list, String separator){
    return recursiveCombine("", list, 0, separator);
}

public static String recursiveCombine(String firstPart, List<String> 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);
}
于 2009-06-04T13:59:13.273 回答
1

现在您可以使用String.join()Java 8。

    List strings = Arrays.asList("a", "b", "c");
    String joined = String.join(",", strings);
    System.out.println(joined);
于 2015-07-30T12:09:56.963 回答
1

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

static <T, R> R foldL(BiFunction<R, T, R> lambda, R zero, List<T> theList){

     if(theList.size() == 0){
      return zero;
     }

     R nextZero = lambda.apply(zero,theList.get(0));

     return foldL(lambda, nextZero, theList.subList(1, theList.size()));                  
    }
于 2015-11-17T09:46:57.157 回答
1

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

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;
        }

    }
}
于 2016-09-10T15:17:11.037 回答
-3

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

import java.util.Arrays;
import java.util.List;

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

    }
}
于 2010-09-07T18:49:08.263 回答