コレクションをループして単純なテキスト処理や印刷ステートメントを実行するときにこのケースに何度も遭遇し、最後の要素を特別なケースにしたいと思います(たとえば、すべての通常の要素は最後のケースを除いてカンマで区切られます)。
コードを複製したり、ifに押し込んだり、ループに入れたりする必要のない、いくつかのベストプラクティスのイディオムまたはエレガントなフォームはありますか?.
たとえば、コンマ区切りのリストで出力したい文字列のリストがあります。 (do whileソリューションは、リストに2つ以上の要素があることをすでに想定しています。それ以外の場合は、条件付きのforループの方が正しいのと同じくらい良くありません)。
例えばリスト=( "dog"、 "cat"、 "bat")
「[犬、猫、コウモリ]」を印刷したい
私は2つの方法を提示します
条件付きのforループ
public static String forLoopConditional(String[] items) {
String itemOutput = "[";
for (int i = 0; i < items.length; i++) {
// Check if we're not at the last element
if (i < (items.length - 1)) {
itemOutput += items[i] + ", ";
} else {
// last element
itemOutput += items[i];
}
}
itemOutput += "]";
return itemOutput;
}
ループのプライミング中にループを行う
public static String doWhileLoopPrime(String[] items) {
String itemOutput = "[";
int i = 0;
itemOutput += items[i++];
if (i < (items.length)) {
do {
itemOutput += ", " + items[i++];
} while (i < items.length);
}
itemOutput += "]";
return itemOutput;
}
テスタークラス:
public static void main(String[] args) {
String[] items = { "dog", "cat", "bat" };
System.out.println(forLoopConditional(items));
System.out.println(doWhileLoopPrime(items));
}
Java AbstractCollectionクラスでは、次の実装があります(すべてのEdgeケースエラーチェックが含まれているため少し冗長ですが、悪くはありません)。
public String toString() {
Iterator<E> i = iterator();
if (! i.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = i.next();
sb.append(e == this ? "(this Collection)" : e);
if (! i.hasNext())
return sb.append(']').toString();
sb.append(", ");
}
}
これらの回答には多くのforループがありますが、Iteratorとwhileループの方がはるかに簡単に読み取れることがわかりました。例えば。:
Iterator<String> itemIterator = Arrays.asList(items).iterator();
if (itemIterator.hasNext()) {
// special-case first item. in this case, no comma
while (itemIterator.hasNext()) {
// process the rest
}
}
これは、Googleコレクションで Joiner が採用しているアプローチであり、非常に読みやすいと思います。
私は通常、次のように書きます。
static String commaSeparated(String[] items) {
StringBuilder sb = new StringBuilder();
String sep = "";
for (String item: items) {
sb.append(sep);
sb.append(item);
sep = ",";
}
return sb.toString();
}
string value = "[" + StringUtils.join( items, ',' ) + "]";
私の通常の方法は、インデックス変数がゼロかどうかをテストすることです。例:
var result = "[ ";
for (var i = 0; i < list.length; ++i) {
if (i != 0) result += ", ";
result += list[i];
}
result += " ]";
しかし、もちろん、これは、Array.join( "、")メソッドを持たない言語について話す場合にのみです。 ;-)
最初の要素を特別なケースと考える方が簡単だと思います。反復が最後ではなく最初かどうかを知るのははるかに簡単だからです。何かが初めて行われたかどうかを知るのに複雑で高価なロジックは必要ありません。
public static String prettyPrint(String[] items) {
String itemOutput = "[";
boolean first = true;
for (int i = 0; i < items.length; i++) {
if (!first) {
itemOutput += ", ";
}
itemOutput += items[i];
first = false;
}
itemOutput += "]";
return itemOutput;
}
あなたのケースは単にテキストを処理しているだけなので、ループ内に条件文は必要ありません。 Cの例:
char* items[] = {"dog", "cat", "bat"};
char* output[STRING_LENGTH] = {0};
char* pStr = &output[1];
int i;
output[0] = '[';
for (i=0; i < (sizeof(items) / sizeof(char*)); ++i) {
sprintf(pStr,"%s,",items[i]);
pStr = &output[0] + strlen(output);
}
output[strlen(output)-1] = ']';
末尾のコンマの生成を回避するために条件を追加する代わりに、先に進んでそれを生成し(ループを単純かつ条件なしに保つため)、最後に単純に上書きします。多くの場合、他のループの繰り返しと同じように特別なケースを生成し、最後に手動で置き換える方が明確です(ただし、「replace it」コードが数行を超える場合、この方法は実際には困難になる可能性があります読んだ)。
私はあなたの2番目の例、つまりループの外で特殊なケースを処理するには、少し簡単に書いてください。
String itemOutput = "[";
if (items.length > 0) {
itemOutput += items[0];
for (int i = 1; i < items.length; i++) {
itemOutput += ", " + items[i];
}
}
itemOutput += "]";
誰かがそれを探している場合のJava 8ソリューション:
String res = Arrays.stream(items).reduce((t, u) -> t + "," + u).get();
最初のアイテムにフラグを使用するのが好きです。
ArrayList<String> list = new ArrayList()<String>{{
add("dog");
add("cat");
add("bat");
}};
String output = "[";
boolean first = true;
for(String Word: list){
if(!first) output += ", ";
output+= Word;
first = false;
}
output += "]";
一般的に、私のお気に入りはマルチレベルの出口です。変化する
_for ( s1; exit-condition; s2 ) {
doForAll();
if ( !modified-exit-condition )
doForAllButLast();
}
_
に
_for ( s1;; s2 ) {
doForAll();
if ( modified-exit-condition ) break;
doForAllButLast();
}
_
重複するコードや冗長なチェックを排除します。
あなたの例:
_for (int i = 0;; i++) {
itemOutput.append(items[i]);
if ( i == items.length - 1) break;
itemOutput.append(", ");
}
_
それは他のものよりも良いもののために働きます。この特定の例では、私はこれの大ファンではありません。
もちろん、終了条件が_s2
_だけでなくdoForAll()
で何が発生するかに依存するシナリオでは、非常にトリッキーになります。 Iterator
を使用するのはそのような場合です。
これは論文です 恥ずかしげなくそれを彼の学生に宣伝した教授から:-)。正確に何を話しているかについては、セクション5を読んでください。
...
String[] items = { "dog", "cat", "bat" };
String res = "[";
for (String s : items) {
res += (res.length == 1 ? "" : ", ") + s;
}
res += "]";
とても読みやすいです。もちろん、条件を別のif
句に入れることができます。これが慣用的になっているのは(少なくとも、そうだと思います)、foreachループを使用し、複雑なループヘッダーを使用しないことです。
また、ロジックは複製されません(つまり、items
のアイテムが実際に出力文字列に追加される場所は1つだけです-実際のアプリケーションでは、これはmightより複雑で、長いフォーマット操作なので、コードを繰り返したくありません)。
このように動的に文字列を作成する場合は、+ =演算子を使用しないでください。 StringBuilder
クラスは、動的な文字列連結を繰り返す場合に非常によく機能します。
public String commaSeparate(String[] items, String delim){
StringBuilder bob = new StringBuilder();
for(int i=0;i<items.length;i++){
bob.append(items[i]);
if(i+1<items.length){
bob.append(delim);
}
}
return bob.toString();
}
次に、このような呼び出しです
String[] items = {"one","two","three"};
StringBuilder bob = new StringBuilder();
bob.append("[");
bob.append(commaSeperate(items,","));
bob.append("]");
System.out.print(bob.toString());
この質問には2つの答えがあると思います。どの言語でもこの問題に最適なイディオムと、Javaでこの問題に最適なイディオムです。また、この問題の意図は文字列を結合するタスクではなかったと思いますが、一般的なパターンなので、それを実行できるライブラリー関数を示すことは実際には役に立ちません。
まず、文字列を[]で囲み、コンマで区切られた文字列を作成するアクションは2つの別個のアクションですが、理想的には2つの別個の関数になります。
どの言語でも、再帰とパターンマッチングの組み合わせが最も効果的だと思います。たとえば、haskellでは次のようにします。
_join [] = ""
join [x] = x
join (x:xs) = concat [x, ",", join xs]
surround before after str = concat [before, str, after]
yourFunc = surround "[" "]" . join
-- example usage: yourFunc ["dog", "cat"] will output "[dog,cat]"
_
このように記述することの利点は、関数が直面するさまざまな状況と、関数の処理方法を明確に列挙することです。
これを行う別の非常に良い方法は、アキュムレータタイプの関数を使用することです。例えば:
_join [] = ""
join strings = foldr1 (\a b -> concat [a, ",", b]) strings
_
これは、c#などの他の言語でも実行できます。
_public static string Join(List<string> strings)
{
if (!strings.Any()) return string.Empty;
return strings.Aggregate((acc, val) => acc + "," + val);
}
_
この状況ではあまり効率的ではありませんが、他の場合には役立ちます(または効率は問題ではない場合があります)。
残念ながら、Javaはこれらのメソッドのいずれも使用できません。この場合、最良の方法は、例外ケース(0または1要素)の関数の上部でチェックを行うことです。 、次にforループを使用して、複数の要素を持つケースを処理します。
_public static String join(String[] items) {
if (items.length == 0) return "";
if (items.length == 1) return items[0];
StringBuilder result = new StringBuilder();
for(int i = 0; i < items.length - 1; i++) {
result.append(items[i]);
result.append(",");
}
result.append(items[items.length - 1]);
return result.toString();
}
_
この関数は、2つのEdgeケース(0または1要素)で何が起こるかを明確に示します。次に、最後の要素を除くすべての要素にループを使用し、最後に最後の要素をコンマなしで追加します。最初に非コンマ要素を処理する逆の方法も簡単です。
if (items.length == 1) return items[0];
行は実際には必要ないことに注意してください。ただし、これにより、関数が何を行っているかが一目でわかりやすくなります。
(もし誰かがhaskell/c#関数についてより多くの説明を望んでいるなら、私はそれを追加しますので注意してください)
この場合、基本的には、いくつかの区切り文字列を使用して文字列のリストを連結しています。これを行う何かを自分で書くことができます。それからあなたは次のようなものを得るでしょう:
_String[] items = { "dog", "cat", "bat" };
String result = "[" + joinListOfStrings(items, ", ") + "]"
_
と
_public static String joinListOfStrings(String[] items, String sep) {
StringBuffer result;
for (int i=0; i<items.length; i++) {
result.append(items[i]);
if (i < items.length-1) buffer.append(sep);
}
return result.toString();
}
_
_String[]
_ではなくCollection
がある場合は、イテレータとhasNext()
メソッドを使用して、これが最後かどうかを確認することもできます。
Java 8 lambdaとCollectors.joining()を使用して-
List<String> items = Arrays.asList("dog", "cat", "bat");
String result = items.stream().collect(Collectors.joining(", ", "[", "]"));
System.out.println(result);
3番目の選択肢は次のとおりです
StringBuilder output = new StringBuilder();
for (int i = 0; i < items.length - 1; i++) {
output.append(items[i]);
output.append(",");
}
if (items.length > 0) output.append(items[items.length - 1]);
しかし、最善の方法は、join()のようなメソッドを使用することです。 Javaの場合 String.join がサードパーティのライブラリにあり、コードは次のようになります。
StringUtils.join(items,',');
FWIW、Apache Commonsの join()メソッド (3232行目以降)では、ループ内でifを使用しています。
public static String join(Object[] array, char separator, int startIndex, int endIndex) {
if (array == null) {
return null;
}
int bufSize = (endIndex - startIndex);
if (bufSize <= 0) {
return EMPTY;
}
bufSize *= ((array[startIndex] == null ? 16 : array[startIndex].toString().length()) + 1);
StringBuilder buf = new StringBuilder(bufSize);
for (int i = startIndex; i < endIndex; i++) {
if (i > startIndex) {
buf.append(separator);
}
if (array[i] != null) {
buf.append(array[i]);
}
}
return buf.toString();
}
私は通常、次のようなforループを記述します。
public static String forLoopConditional(String[] items) {
StringBuilder builder = new StringBuilder();
builder.append("[");
for (int i = 0; i < items.length - 1; i++) {
builder.append(items[i] + ", ");
}
if (items.length > 0) {
builder.append(items[items.length - 1]);
}
builder.append("]");
return builder.toString();
}
「[The、Cat、in、the、Hat]」のようなカンマ区切りのリストを探しているだけなら、独自のメソッドを書く時間を無駄にしないでください。 List.toStringを使用するだけです。
List<String> strings = Arrays.asList("The", "Cat", "in", "the", "Hat);
System.out.println(strings.toString());
Listのジェネリック型に、表示する値を含むtoStringがある場合、List.toStringを呼び出します。
public class Dog {
private String name;
public Dog(String name){
this.name = name;
}
public String toString(){
return name;
}
}
次に、次のことができます。
List<Dog> dogs = Arrays.asList(new Dog("Frank"), new Dog("Hal"));
System.out.println(dogs);
そして、あなたは得るでしょう:[フランク、ハル]