简体   繁体   中英

Executing code N times and other code N+1 times

The question is about while-loops in which I need some code to be executed N times and some other code N+1 times. NOT about concatening Strings, I just use this as bad-coded yet short example.

Let me explain my question by providing an example.

Say I want to concatenate N+1 Strings, by glueing them with "\\n", for example. I will have N+1 lines of text then, but I only need to add N times "\\n".

Is there any boilerplate solution for this type of loop in which you have to execute some code N times and other code N+1 times? I'm NOT asking for solution to concatenate Strings! That is just a (bad) example. I'm looking for the general solution.

The problem I have with this is code duplication, so to code my example I'll do this (bad pseudo code, I know I have to use StringBuilder etc.):

String[] lines = <some array of dimension N+1>;
String total = lines[0];
for (int i = 1; i < N + 1; i++){
    total += "\n" + lines[i];
}

The problem becomes worse if the code that has to be executed N+1 times, becomes larger, of course. Then I would do something like

codeA(); // adding the line of text
for (int i = 1; i < N + 1; i++){
    codeB(); // adding the "\n"
    codeA();
}

To remove the duplication, you can do this different by checking inside the loop, too, but then I find this quite stupid as I know beforehand that the check is pre-determined, as it will only be false the first iteration:

for (int i = 0; i < N + 1; i++){
    if (i > 0){
        codeB(); // adding the "\n"
    }
    codeA();
}

Is there any solution for this, a sort of while-loop that initializes once with codeA() en then keeps looping over codeB() and codeA()?

People must have run into this before, I guess. Just wondering if there are any beautiful solutions for this.

To my dissapointment, I believe that there is no such construct that satisfies the conditions as you have stated them and I will attempt to explain why (though I can't prove it in a strictly mathematical way).

The requirements of the problem are:

  1. We have two parts of code: codeA() and codeB()
  2. The two parts are executed a different number of times, N and N+1
  3. We want to avoid adding a condition inside the loop
  4. We want to execute each part only as many times as strictly necessary

2) is a direct consequence of 1). If we didn't have two parts of code we would not need a different number of executions. We would have a single loop body.

4) is again a consequence of 1). There is no redundant execution if we have a single loop body. We can control its execution through the loop's condition

So the restrictions are basically 1) and 3).

Now inside the loop we need to answer two questions on each iteration: a) do we execute codeA() ? and b) do we execute codeB() ? We simply do not have enough information to decide since we only have a single condition (the condition of the loop) and that condition will be used to decide if both of the code parts would be executed or not.

So we need to break 1) and/or 3) Either we add the extra condition inside the loop or we delegate the decision to some other code (thus not having two parts anymore).

Apparently an example of delegation could be (I am using the string concatenation example):

String [] lines = ...
for (int i = 0; i < N; i++){
   // delegate to a utility class LineBuilder (perhaps an extension of StringBuilder) to concatenate lines
   // this class would still need to check a condition e.g. for the first line to skip the "\n"
   // since we have delegated the decisions we do not have two code parts inside the loop
   lineBuilder.addLine( lines[i] );
}

Now a more interesting case of delegation would be if we could delegate the decision to the data itself (this might worth keeping in mind). Example:

List<Line> lines = Arrays.asList(
             new FirstLine("Every"),    // note this class is different
             new Line("word"), 
             new Line("on"), 
             new Line("separate"), 
             new Line("line") );

StringBuffer sb = new StringBuffer();

for (Line l : lines) {
    // Again the decision is delegated. Data knows how to print itself
    // Line would return: "\n" + s
    // FirstLine would return: s
    sb.append( l.getPrintVersion() );
}

Of course all of the above does not mean that you couldn't implement a class that tries to solve the problem. I believe though this is beyond the scope of your original question not to mention that would be an overkill for simple loops

Concatenating Strings like this is a bad idea and a much bigger issue IMHO.

However to answer your question I would do

String sep = "";
StringBuilder sb= new StringBuilder();
for(String s: lines) {
    sb.append(sep).append(s);
    sep = "\n";
}
String all = sb.toString();

Note: there is usually a good way to avoid needing to create this String at all such a processing the lines as you get them. It is hard to say without more context.

This kind of thing is fairly common, like when you build sql. This is the pattern that I follow:

String[] lines ...//init somehow;
String total = lines[0];
boolean firstTime = true;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++){
    if(firstTime) firstTime = false;
    else sb.append('\n');

    sb.append(lines[i]);
}

Note that this is not the same, as the first example and here is why:

String[] lines = <some array of dimension N+1>;
String total = lines[0];
for (int i = 1; i < N + 1; i++){
    total += "\n" + lines[i];
}

Assuming you have an array of [0] = 'line1' and [1] = 'line2' Here you end up with line1line2\\n, when the desired output is:

line1\\nline2.

The example I provided is clear, and does not perform poorly. In fact a much bigger performance gain is made by utilizing StringBuilder/Buffer. Having clear code is essential for the pro.

Personally i have most of the time the same problem, on the String example i use the StringBuilder as you said, and just delete the characters added to much:

StringBuilder sb = new StringBuilder();
for(int i=0; i<N; i++) {
    sb.append(array[i]).append("\n");
}
sb.delete(sb.length-1, sb.length);  // maybe check if sb contains something

In the common case i suppose there is no other way than adding the if you suggested. To make the code more clear i would check at the end of the for loop:

StringBuilder sb = new StringBuilder();
for(int i=0; i<N; i++) {
    sb.append(array[i]);
    if(i < N) {
         sb.append("\n");
    }
}

But i totally agree this is sad to have this double logic

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM