リストがあり、それを単一の値に減らしたい(関数型プログラミング用語 "fold"、Ruby term inject
)、
Arrays.asList("a", "b", "c") ... fold ... "a,b,c"
関数型プログラミングのアイデア(Scala)に感染しているので、それをコーディングするよりも簡単で短い方法を探しています
sb = new StringBuilder
for ... {
append ...
}
sb.toString
探しているのは、8.0以降にJavaが使用されている文字列join()
メソッドです。以下のいずれかのメソッドを試してください。
静的メソッド String#join(delimiter, elements)
:
Collection<String> source = Arrays.asList("a", "b", "c");
String result = String.join(",", source);
Stream インターフェースは、ScalaのfoldLeft
関数と非常によく似た折り畳み操作をサポートします。次の連結を見てください コレクター :
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(","));
元の質問に答えるには:
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(','));
与えられた
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(); }
}
);
言語を切り替えずに、いくつかの機能的側面をプレーンオールドJavaに適用したい場合 可能ですがLamdaJ 、 fork-join(166y) および google-collections は、その構文糖衣を追加するのに役立つライブラリです。
google-collections の助けを借りて Joinerクラス を使用できます:
_Joiner.on(",").join("a", "b", "c")
_
Joiner.on(",")
は不変オブジェクトであるため、自由に共有できます(たとえば定数として)。
Joiner.on(", ").useForNull("nil");
やJoiner.on(", ").skipNulls()
のようにnull処理を構成することもできます。
大きな文字列を生成しているときに大きな文字列が割り当てられないようにするには、それを使用して、Appendable
インターフェイスまたはStringBuilder
クラスを介して既存のStreamsやStringBuildersなどに追加できます。
_Joiner.on(",").appendTo(someOutputStream, "a", "b", "c");
_
マップを書き出すときは、エントリとキーと値の区切りに2つの異なる区切り記号が必要です。
_Joiner.on(", ").withKeyValueSeparator(":")
.join(ImmutableMap.of(
"today", "monday"
, "tomorrow", "tuesday"))
_
探しているのは文字列の「結合」関数ですが、残念ながらJavaにはありません。それほど難しくないはずの独自の結合関数をロールする必要があります。
編集:org.Apache.commons.lang.StringUtils 多くの便利な文字列関数(結合を含む)があるようです。
Eclipseコレクション にはinjectInto(RubyやSmalltalkなど)、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);
注:私はEclipseコレクションのコミッターです。
まず、Javaの関数型ライブラリが必要です。これは、一般的なファンクタとfoldのような関数型プロジェクションを提供します。強力でありながらシンプルなライブラリをここで設計および実装しました: 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"
Foldを適用することにより、実際に考慮する必要があるのは、Func2.callの実装だけであることに注意してください。これは、アキュムレータと要素を受け入れてアキュムレータを返す演算子を定義する3行のコードです(私の実装では空の文字列とnullの場合、そのケースを削除すると、コードは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;
}
残念ながら、Javaでは、そのループを回避することはできませんが、いくつかのライブラリがあります。たとえば、いくつかのライブラリを試すことができます。
ラムダのサポートにより、次のコードで実行できます。
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()));
}
以下は、リストを折りたたむためのコードです。これは、取り残されたノードの情報を保持し、前進するときに折りたたむことによって行われます。
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;
}
}
}
これで、String.join()
をJava 8で使用できます。
List strings = Arrays.asList("a", "b", "c");
String joined = String.join(",", strings);
System.out.println(joined);
残念ながら、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);
}
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