最佳循环成语,用于特殊外壳的最后一个元素

时间:2010-06-23 21:16:06

标签: java loops while-loop idioms

我在进行简单的文本处理和打印语句时经常遇到这种情况,我循环遍历一个集合,我想要特殊情况下的最后一个元素(例如,除了最后一个元素之外,每个普通元素都将以逗号分隔情况)。

是否有一些最佳实践习惯或优雅形式,不需要复制代码或推迟if,否则在循环中。

例如,我有一个字符串列表,我想在逗号分隔列表中打印。 (虽然解决方案已经假设列表中有2个或更多元素,否则它就像使用条件的更正确的循环一样糟糕。)

e.g。 List =(“dog”,“cat”,“bat”)

我想打印“[狗,猫,蝙蝠]”

我提出了两种方法

  1. 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;
     }
    
  2. while循环启动循环

    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));
    
    }
    
  3. 在Java AbstractCollection类中,它具有以下实现(有点冗长,因为它包含所有边缘大小写错误检查,但不错)。

    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(", ");
    }
    }
    

18 个答案:

答案 0 :(得分:43)

我通常这样写:

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

答案 1 :(得分:24)

这些答案中有很多for循环,但我发现Iterator和while循环读取起来要容易得多。 E.g:

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

这是Joiner在Google集合中采用的方法,我发现它非常易读。

答案 2 :(得分:14)

string value = "[" + StringUtils.join( items, ',' ) + "]";

答案 3 :(得分:7)

我通常的做法是测试索引变量是否为零,例如:

var result = "[ ";
for (var i = 0; i < list.length; ++i) {
    if (i != 0) result += ", ";
    result += list[i];
}
result += " ]";

但是,当然,只有当我们谈论没有一些Array.join(“,”)方法的语言时才会这样。 ; - )

答案 4 :(得分:6)

我认为将第一个元素视为特殊情况更容易,因为更容易知道迭代是第一个而不是最后一个。它不需要任何复杂或昂贵的逻辑来知道第一次是否正在做某事。

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

答案 5 :(得分:2)

我喜欢为第一项使用标志。

 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 += "]";

答案 6 :(得分:2)

Java 8解决方案,以防有人正在寻找它:

String res = Arrays.stream(items).reduce((t, u) -> t + "," + u).get();

答案 7 :(得分:2)

我会选择你的第二个例子,即。处理循环外的特殊情况,只需写得更简单一点:

String itemOutput = "[";

if (items.length > 0) {
    itemOutput += items[0];

    for (int i = 1; i < items.length; i++) {
        itemOutput += ", " + items[i];
    }
}

itemOutput += "]";

答案 8 :(得分:2)

由于您的案例只是处理文本,因此您不需要循环内的条件。一个例子:

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] = ']';

不是添加条件以避免生成尾随逗号,而是继续生成它(以保持循环简单且无条件)并在最后简单地覆盖它。很多时候,我发现生成特殊情况就像任何其他循环迭代更清楚,然后在最后手动替换它(尽管如果“替换它”代码多于几行,这种方法实际上可能变得更难读取)。

答案 9 :(得分:1)

在这种情况下,您实际上是使用某个分隔符字符串连接字符串列表。你也许可以自己写一些这样的东西。然后你会得到类似的东西:

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

如果你有Collection而不是String[],你也可以使用迭代器和hasNext()方法检查这是否是最后一个。

答案 10 :(得分:1)

如果要像这样动态构建字符串,则不应使用+ =运算符。 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());

答案 11 :(得分:1)

一般来说,我最喜欢的是多级退出。变化

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(", ");
}

它比其他东西更适合某些事情。对于这个具体的例子,我不是这个的忠实粉丝。

当然,对于退出条件取决于doForAll()而不仅仅是s2的情况,情况会变得非常棘手。使用Iterator就是这种情况。

来自教授的Here's a paper无耻地将其提升为他的学生:-)。请阅读第5节,了解您正在谈论的内容。

答案 12 :(得分:1)

我认为这个问题有两个答案:任何语言中这个问题的最佳成语,以及java中这个问题的最佳习语。我也认为这个问题的目的不是将字符串连接在一起的任务,而是一般的模式,所以它显示的库函数并没有真正帮助。

首先,用[]包围字符串并创建用逗号分隔的字符串的动作是两个独立的动作,理想情况下是两个独立的动作。

对于任何语言,我认为递归和模式匹配的组合效果最好。例如,在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();
}

此功能清楚地显示了两个边缘情况(0或1个元素)中发生的情况。然后它为除了最后一个元素之外的所有元素使用循环,最后在没有逗号的情况下添加最后一个元素。在开始时处理非逗号元素的相反方式也很容易。

请注意,if (items.length == 1) return items[0];行实际上并不是必需的,但我认为它使得函数更容易一目了然。

(请注意,如果有人想要更多关于haskell / c#函数的解释请问我将其添加进去)

答案 13 :(得分:1)

...

String[] items = { "dog", "cat", "bat" };
String res = "[";

for (String s : items) {
   res += (res.length == 1 ? "" : ", ") + s;
}
res += "]";

左右是可读的。当然,您可以将条件放在单独的if子句中。它的惯用性(至少我认为是这样)是它使用foreach循环并且不使用复杂的循环头。

此外,没有逻辑重复(即只有一个地方items的项目实际上附加到输出字符串 - 在现实世界的应用程序中,可能更多复杂而冗长的格式化操作,所以我不想重复代码。)

答案 14 :(得分:1)

可以使用Java 8 lambda和Collectors.joining()作为 -

来实现
List<String> items = Arrays.asList("dog", "cat", "bat");
String result = items.stream().collect(Collectors.joining(", ", "[", "]"));
System.out.println(result);

答案 15 :(得分:0)

我通常会像这样写一个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();                           
}       

答案 16 :(得分:0)

如果你只是在寻找一个像这样的逗号分隔列表:“[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);

你会得到: [弗兰克,哈尔]

答案 17 :(得分:0)

第三种选择是以下

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() method(第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();
    }