Java에서 향상된 for 루프의 마지막 반복


140

루프가 마지막으로 반복되는지 확인하는 방법이 있습니까? 내 코드는 다음과 같습니다

int[] array = {1, 2, 3...};
StringBuilder builder = new StringBuilder();

for(int i : array)
{
    builder.append("" + i);
    if(!lastiteration)
        builder.append(",");
}

이제 마지막 반복에서 쉼표를 추가하고 싶지 않습니다. 이제 마지막 반복인지 확인하거나 for 루프 또는 외부 카운터를 사용하여 추적을 유지하고 있는지 확인할 수있는 방법이 있습니다.


1
네! 재밌 네요. 정확히 같은 질문을하고 싶었습니다!
PhiLho

같은 종류의 질문이 반환됩니다. 이제 한 요소가 다른 처리가 필요한 경우 왜 루프를 만들고 싶습니까? stackoverflow.com/questions/156650/…
xtofl

고정 배열을 사용하는 이유는 무엇입니까? for (int i = 0; i <array.length; i ++ if (i <array.lenth) ,,
AnthonyJClink

답변:


223

또 다른 대안은 i를 추가하기 전에 첫 번째 반복이 아닌 쉼표를 추가하는 것 입니다. (단 "" + i,를 사용하지 마십시오 -실제로 연결을 원하지 않으며 StringBuilder에는 append (int) 오버로드가 완벽하게 좋습니다.)

int[] array = {1, 2, 3...};
StringBuilder builder = new StringBuilder();

for (int i : array) {
    if (builder.length() != 0) {
        builder.append(",");
    }
    builder.append(i);
}

이것에 대한 좋은 점은 어떤 것으로도 작동한다는 Iterable것입니다. 항상 색인을 생성 할 수는 없습니다. ( "쉼표를 추가하고 마지막에 제거"는 실제로 StringBuilder를 사용할 때 좋은 제안입니다. 그러나 스트림에 쓰는 것과 같은 작업에는 작동하지 않습니다. 그러나이 정확한 문제에 대한 최선의 방법 일 수 있습니다. )


2
좋은 패턴이지만 builder.length ()! = 0은 취성입니다. 루프 전에 버퍼에 무언가가 조건부로 추가된다고 상상해보십시오. 대신 isFirst부울 플래그를 사용하십시오. 보너스 : 더 빠릅니다.
Jason Cohen

2
@Jason : 필자는 첫 번째 반복에서 빌더가 비어 있지 않은 "isFirst"패턴을 사용합니다. 그러나 필요하지 않은 경우 내 경험에 구현에 상당한 양의 부풀음을 추가합니다.
Jon Skeet

3
@Liverpool (등) : 1000 불필요한 검사가 매우이며, 아주 성능에 상당한 영향을 미칠 가능성. Dinah의 솔루션이 추가 한 추가 문자로 인해 StringBuilder가 확장되어 최종 문자열의 크기가 (계속)으로 두 배가 될 수 있음을 지적 할 있습니다.
Jon Skeet

2
불필요한 버퍼 공간. 그러나 중요한 점은 가독성입니다. Dinah보다 내 버전이 더 읽기 쉽습니다. 반대의 느낌이 든다면 괜찮습니다. 병목 현상을 찾은 후 불필요한 "if"의 성능 영향 만 고려합니다.
Jon Skeet

3
또한 내 솔루션은 쓰기 만 가능하기 때문에 더 일반적으로 적용 가능한 솔루션입니다. 내 솔루션을 가져 와서 스트림 등에 쓰도록 변경하면 나중에 불필요한 데이터를 다시 가져올 수 없을 수 있습니다. 나는 일반적으로 적용 가능한 패턴을 좋아합니다.
Jon Skeet

145

이를 수행하는 다른 방법 :

String delim = "";
for (int i : ints) {
    sb.append(delim).append(i);
    delim = ",";
}

업데이트 : Java 8의 경우 이제 수집기가 있습니다.


1
비슷한 답변을 삭제해야했습니다. 다른 답변을보다 신중하게 살펴보기 전에 게시하지 말라고 가르쳐줍니다.
Michael Burr

1
또한 이것은 다른 주석 스레드에서 언급 한 Robert Paulson의 잠재적 인 문제를 처리합니다.이 기법은 배열 값이 추가 될 때 비어있는 StringBuilder에 의존하지 않습니다.
Michael Burr

1
코드가 더 매끄럽고 깔끔하고 재미 있지만 if 버전보다 덜 분명합니다. 항상 더 분명하고 읽기 쉬운 버전을 사용하십시오.
Bill K

8
@Bill : 어렵고 빠른 규칙만큼 좋지는 않습니다. '영리한'솔루션이 "보다 정확한"솔루션 인 경우가 있습니다. 요컨대이 버전을 읽기 어렵다는 것을 정말로 알고 있습니까? 관리자 가이 단계를 수행 해야하는 방식 중 하나는 차이가 중요하지 않다고 생각합니다.
그렉 사건

파일 시스템의 쓰기 호출과 같은 다른 리소스에 쓰더라도 작동합니다. 좋은 생각.
Tuxman

39

항상 추가하는 것이 더 쉬울 수 있습니다. 그런 다음 루프를 완료하면 최종 캐릭터를 제거하십시오. 그런 식으로 조건을 줄입니다.

StringBuilderdeleteCharAt(int index)색인을 사용하여를 사용할 수 있습니다length() - 1


5
이 문제에 가장 효율적인 솔루션입니다. +1.
리얼 레드.

9
어쩌면 그것은 나지만, 당신이 방금 추가 한 것을 제거한다는 사실을 정말로 좋아하지 않습니다 ...
Fortega

2
포르테가 : 매번 99 % 할 일을 점검하는 것을 좋아하지 않습니다. Dinah 또는 sblundy의 솔루션을 적용하는 것이 더 논리적이며 더 빠릅니다.
버팔로

1
내 의견으로는 가장 우아한 해결책. 감사합니다!
Charles Morin

32

작업에 잘못된 도구를 사용하고있을 수 있습니다.

이것은 당신이하는 일보다 더 수동적이지만 조금 "오래된 학교"가 아니라면 더 우아합니다.

 StringBuffer buffer = new StringBuffer();
 Iterator iter = s.iterator();
 while (iter.hasNext()) {
      buffer.append(iter.next());
      if (iter.hasNext()) {
            buffer.append(delimiter);
      }
 }

15

이것은 이 StackOverflow 질문 의 거의 반복입니다 . 원하는 것은 StringUtils 이며 join 메소드 를 호출하는 것입니다.

StringUtils.join(strArr, ',');

14

다른 솔루션 (아마도 가장 효율적인)

    int[] array = {1, 2, 3};
    StringBuilder builder = new StringBuilder();

    if (array.length != 0) {
        builder.append(array[0]);
        for (int i = 1; i < array.length; i++ )
        {
            builder.append(",");
            builder.append(array[i]);
        }
    }

이것은 배열이 비어 있지 않은 것을 제외하고는 자연스러운 해결책입니다.
orcmid

5
@orcmid : 배열이 비어 있으면 여전히 올바른 출력 (빈 문자열)을 제공합니다. 당신의 요점이 무엇인지 잘 모르겠습니다.
Eddie


6

명시 적 루프는 암시 적 루프보다 항상 더 잘 작동합니다.

builder.append( "" + array[0] );
for( int i = 1; i != array.length; i += 1 ) {
   builder.append( ", " + array[i] );
}

길이가 0 인 배열을 처리하는 경우를 대비하여 모든 것을 if 문으로 묶어야합니다.


필자는 반복적으로 테스트를 불필요하게 수행 할 필요가 없기 때문에이 접근법을 좋아합니다. 내가 추가 할 유일한 것은 빌더가 직접 정수를 추가 할 수 있으므로 ""+ int를 사용할 필요가없고 append (array [0]) 만 있으면됩니다.
Josh

배열이 하나 이상의 요소를 갖도록하려면 전체 로트 주위에 하나 이상이 필요합니다.
Tom Leys

2
왜 모두가 문자열 연결이 INSIDE 인 예제를 계속 사용합니까? 은 ","새의 StringBuilder에 + X 컴파일 ( ",")으로 .Append (x)를 (로 .toString) ...
존 가드너

@Josh and @John : n00b 예제를 따르십시오. 한 번에 너무 많은 것을 소개하고 싶지 않습니다. 그러나 당신의 요점은 매우 좋습니다.
S.Lott

@Tom : 그렇습니다. 나는 이미 그렇게 말했다. 편집이 필요하지 않습니다.
S.Lott

4

클래식 인덱스 루프로 변환하면 그렇습니다.

또는 마지막 쉼표를 삭제 한 후 삭제할 수 있습니다. 이렇게 :

int[] array = {1, 2, 3...};
StringBuilder

builder = new StringBuilder();

for(int i : array)
{
    builder.append(i + ",");
}

if(builder.charAt((builder.length() - 1) == ','))
    builder.deleteCharAt(builder.length() - 1);

나는 단지 commons-langStringUtils.join() 에서 사용 합니다.


3

Class Separator 가 필요합니다 .

Separator s = new Separator(", ");
for(int i : array)
{
     builder.append(s).append(i);
}

클래스의 구현 Separator은 간단합니다. toString()빈 문자열을 반환하는 첫 번째 호출 을 제외한 모든 호출에서 반환되는 문자열을 래핑 합니다.


3

java.util.AbstractCollection.toString ()을 기반으로 구분 기호를 피하기 위해 일찍 종료됩니다.

StringBuffer buffer = new StringBuffer();
Iterator iter = s.iterator();
for (;;) {
  buffer.append(iter.next());
  if (! iter.hasNext())
    break;
  buffer.append(delimiter);
}

효율적이고 우아하지만 다른 답변만큼 자명하지는 않습니다.


(! i.hasNext())전체 접근 방식의 견고성의 중요한 부분 인 when을 조기에 포함하는 것을 잊었습니다 . (여기의 다른 솔루션은 빈 컬렉션을 정상적으로 처리하므로 귀하도 마찬가지입니다! :)
Mike Clark

3

해결책은 다음과 같습니다.

int[] array = {1, 2, 3...};
StringBuilder builder = new StringBuilder();
bool firstiteration=true;

for(int i : array)
{
    if(!firstiteration)
        builder.append(",");

    builder.append("" + i);
    firstiteration=false;
}

첫 번째 반복을 찾으십시오 :)  


3

툴킷에서 언급했듯이 Java 8에는 이제 Collectors가 있습니다. 코드는 다음과 같습니다.

String joined = array.stream().map(Object::toString).collect(Collectors.joining(", "));

나는 그것이 당신이 찾고있는 것과 정확히 일치한다고 생각하며, 다른 많은 것들에 사용할 수있는 패턴입니다.


1

또 다른 옵션.

StringBuilder builder = new StringBuilder();
for(int i : array)
    builder.append(',').append(i);
String text = builder.toString();
if (text.startsWith(",")) text=text.substring(1);

나는 다른 질문에 변형을
가졌습니다

1

여기에 설명 된 많은 솔루션은 IMHO, 특히 외부 라이브러리에 의존하는 솔루션보다 약간 위에 있습니다. 내가 항상 사용했던 쉼표로 구분 된 목록을 얻기위한 깨끗하고 명확한 관용구가 있습니다. 조건부 (?) 연산자를 사용합니다.

편집 : 의견에 따르면 원래 솔루션은 정확하지만 최적화되지 않았습니다. 두 번째 시도 :

    int[] array = {1, 2, 3};
    StringBuilder builder = new StringBuilder();
    for (int i = 0 ;  i < array.length; i++)
           builder.append(i == 0 ? "" : ",").append(array[i]); 

배열과 StringBuilder 선언을 포함하여 4 줄의 코드로 이동합니다.


그러나 모든 반복마다 (+ 연산자 덕분에) 새 StringBuilder를 만들고 있습니다.
Michael Myers

예, 바이트 코드를 보면 맞습니다. 나는 그런 간단한 질문이 너무 복잡해질 수 있다고 믿을 수 없다. 컴파일러가 여기서 최적화 할 수 있는지 궁금합니다.
Julien Chastang

두 번째, 희망적으로 더 나은 솔루션을 제공했습니다.
Julien Chastang

1

다음은 이러한 결과와 함께 실행 한 SSCCE 벤치 마크입니다 (구현해야하는 것과 관련됨).

elapsed time with checks at every iteration: 12055(ms)
elapsed time with deletion at the end: 11977(ms)

적어도 내 예에서, 모든 반복에 체크를 건너 뛰는 특히 데이터의 제정신 볼륨에 띄게 빨라 아니지만, 그것은 이다 빨리.

import java.util.ArrayList;
import java.util.List;


public class TestCommas {

  public static String GetUrlsIn(int aProjectID, List<String> aUrls, boolean aPreferChecks)
  {

    if (aPreferChecks) {

      StringBuffer sql = new StringBuffer("select * from mytable_" + aProjectID + " WHERE hash IN ");

      StringBuffer inHashes = new StringBuffer("(");
      StringBuffer inURLs = new StringBuffer("(");

      if (aUrls.size() > 0)
      {

      for (String url : aUrls)
      {

        if (inHashes.length() > 0) {
        inHashes.append(",");
        inURLs.append(",");
        }

        inHashes.append(url.hashCode());

        inURLs.append("\"").append(url.replace("\"", "\\\"")).append("\"");//.append(",");

      }

      }

      inHashes.append(")");
      inURLs.append(")");

      return sql.append(inHashes).append(" AND url IN ").append(inURLs).toString();
    }

    else {

      StringBuffer sql = new StringBuffer("select * from mytable" + aProjectID + " WHERE hash IN ");

      StringBuffer inHashes = new StringBuffer("(");
      StringBuffer inURLs = new StringBuffer("(");

      if (aUrls.size() > 0)
      {

      for (String url : aUrls)
      {
        inHashes.append(url.hashCode()).append(","); 

        inURLs.append("\"").append(url.replace("\"", "\\\"")).append("\"").append(",");
      }

      }

      inHashes.deleteCharAt(inHashes.length()-1);
      inURLs.deleteCharAt(inURLs.length()-1);

      inHashes.append(")");
      inURLs.append(")");

      return sql.append(inHashes).append(" AND url IN ").append(inURLs).toString();
    }

  }

  public static void main(String[] args) { 
        List<String> urls = new ArrayList<String>();

    for (int i = 0; i < 10000; i++) {
      urls.add("http://www.google.com/" + System.currentTimeMillis());
      urls.add("http://www.yahoo.com/" + System.currentTimeMillis());
      urls.add("http://www.bing.com/" + System.currentTimeMillis());
    }


    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 300; i++) {
      GetUrlsIn(5, urls, true);
    }
    long endTime = System.currentTimeMillis();
    System.out.println("elapsed time with checks at every iteration: " + (endTime-startTime) + "(ms)");

    startTime = System.currentTimeMillis();
    for (int i = 0; i < 300; i++) {
      GetUrlsIn(5, urls, false);
    }
    endTime = System.currentTimeMillis();
    System.out.println("elapsed time with deletion at the end: " + (endTime-startTime) + "(ms)");
  }
}

1
자체 벤치 마크를 실행하지 말고 OpenJDK 자체 마이크로 벤치마킹 하네스 (jmh)를 사용하십시오 . 코드를 따뜻하게하고 가장 문제가되는 함정을 피할 수 있습니다.
르네

0

또 다른 방법은 배열의 길이 (사용 가능한 경우)를 별도의 변수에 저장하는 것입니다 (매번 길이를 다시 확인하는 것보다 효율적). 그런 다음 색인을 해당 길이와 비교하여 최종 쉼표를 추가할지 여부를 결정할 수 있습니다.

편집 : 또 다른 고려 사항은 각 반복에서 조건부 검사를 수행하지 않고 최종 문자를 제거하는 성능 비용 (문자열 복사를 유발할 수 있음)을 측정하는 것입니다.


마지막 문자를 제거해도 문자열 복사본이 만들어지지 않아야합니다. StringBuffer의 delete / deleteCharAt 메소드는 결국 System 클래스의 동일한 소스 및 대상 배열 내에서 다음 메소드를 호출합니다. System-> public static native void arraycopy (Object src, int srcPos, Object dest, int destPos, int length);
버팔로

0

배열을 쉼표로 구분 된 배열로만 전환하는 경우 많은 언어에 정확히이를위한 결합 기능이 있습니다. 배열을 각 요소 사이에 구분 기호가있는 문자열로 바꿉니다.


0

이 경우 마지막 반복인지 알 필요가 없습니다. 이 문제를 해결할 수있는 방법은 여러 가지가 있습니다. 한 가지 방법은 다음과 같습니다.

String del = null;
for(int i : array)
{
    if (del != null)
       builder.append(del);
    else
       del = ",";
    builder.append(i);
}

0

여기에 두 가지 대체 경로가 있습니다.

1 : Apache Commons 문자열 유틸리티

2 :이라는 부울을 유지하고 firsttrue로 설정 하십시오 . 반복 할 때마다 first거짓 인 경우 쉼표를 추가하십시오. 그런 다음 firstfalse로 설정하십시오 .


1) 연결이 끊어졌습니다. 2) 코드보다 설명을 읽는 데 시간이 더 걸렸습니다. :)
Buffalo

0

고정 배열이기 때문에 간단하게 향상되는 것을 피하는 것이 더 쉬울 것입니다 ... Object가 컬렉션 인 경우 반복자가 더 쉬울 것입니다.

int nums[] = getNumbersArray();
StringBuilder builder = new StringBuilder();

// non enhanced version
for(int i = 0; i < nums.length; i++){
   builder.append(nums[i]);
   if(i < nums.length - 1){
       builder.append(",");
   }   
}

//using iterator
Iterator<int> numIter = Arrays.asList(nums).iterator();

while(numIter.hasNext()){
   int num = numIter.next();
   builder.append(num);
   if(numIter.hasNext()){
      builder.append(",");
   }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.