การทำซ้ำครั้งล่าสุดของ Enhanced for loop ใน java


140

มีวิธีการตรวจสอบว่าการวนซ้ำเป็นครั้งสุดท้ายหรือไม่ รหัสของฉันมีลักษณะดังนี้:

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

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

ตอนนี้สิ่งที่ฉันไม่ต้องการที่จะผนวกเครื่องหมายจุลภาคในการทำซ้ำครั้งล่าสุด ขณะนี้มีวิธีการตรวจสอบว่าเป็นซ้ำล่าสุดหรือฉันติดกับห่วงหรือใช้ตัวนับภายนอกเพื่อติดตาม


1
ใช่ มันตลกฉันแค่อยากจะถามคำถามเดียวกันนี้!
PhiLho

คำถามประเภทเดียวกันส่งคืน (และจะเป็นเช่นนั้น) ตอนนี้ทำไมคุณต้องการสร้างลูปถ้าองค์ประกอบหนึ่งต้องการการรักษาที่แตกต่างกัน? stackoverflow.com/questions/156650/…
xtofl

เนื่องจากคุณมีอาร์เรย์ที่ตายตัวทำไมจึงใช้การปรับปรุงสำหรับ สำหรับ (int i = 0; i <array.length; i ++ if (i <array.lenth) ,,,
AnthonyJClink

คำตอบ:


223

อีกทางเลือกหนึ่งคือการใส่เครื่องหมายจุลภาคต่อท้ายก่อนที่คุณจะต่อท้าย i ไม่ใช่แค่การวนซ้ำครั้งแรก (โปรดอย่าใช้"" + iโดยวิธี - คุณไม่ต้องการการต่อข้อมูลที่นี่จริงๆและ StringBuilder มีการผนวกที่ดีอย่างสมบูรณ์ (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
รูปแบบที่ดี แต่ตัวสร้างความยาว ()! = 0 นั้นเปราะ - ลองนึกภาพบางสิ่งที่ได้รับการเพิ่ม (ตามเงื่อนไข!) ลงในบัฟเฟอร์ก่อนที่ลูปของคุณ ให้ใช้isFirstแฟล็กบูลีนแทน โบนัส: เร็วขึ้นด้วย
Jason Cohen

2
@ Jason: แน่นอนฉันใช้รูปแบบ "isFirst" ซึ่งผู้สร้างจะไม่ว่างเปล่าในการทำซ้ำครั้งแรก อย่างไรก็ตามเมื่อไม่จำเป็นต้องเพิ่มจำนวนมากพอที่จะนำไปใช้ในประสบการณ์ของฉัน
Jon Skeet

3
@Liverpool (ฯลฯ ): การตรวจสอบที่ไม่จำเป็น 1,000 ครั้งนั้นไม่น่ามีผลกระทบอย่างมีนัยสำคัญต่อประสิทธิภาพการทำงาน ฉันอย่างเท่าเทียมกันอาจจะชี้ให้เห็นว่าตัวละครพิเศษโดยวิธีการแก้ปัญหาของดินาห์เพิ่มอาจทำให้เกิด StringBuilder ที่จะมีการขยายตัวเพิ่มขนาดของสตริงสุดท้ายด้วย (ต่อ)
จอนสกีต

2
พื้นที่บัฟเฟอร์ที่ไม่จำเป็น อย่างไรก็ตามจุดสำคัญคือความสามารถในการอ่าน ฉันพบว่ารุ่นของฉันอ่านได้ง่ายกว่าไดน่า ถ้าคุณรู้สึกว่ามันตรงกันข้าม ฉันจะพิจารณาถึงผลกระทบด้านประสิทธิภาพของ "ifs" ที่ไม่จำเป็นหลังจากค้นหาคอขวด
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
@ บิล: นั่นไม่ดีเท่ากฎที่ยากและรวดเร็ว มีหลายครั้งที่โซลูชัน 'ฉลาด' คือโซลูชันที่ "ถูกต้องมากกว่า" คุณคิดว่าเวอร์ชั่นนี้อ่านยากหรือไม่? ไม่ว่าจะด้วยวิธีใดผู้ดูแลจะต้องผ่านมันไป - ฉันไม่คิดว่าความแตกต่างนั้นสำคัญ
Greg Case

สิ่งนี้ใช้ได้แม้ว่าคุณจะเขียนไปยังทรัพยากรอื่น ๆ เช่นการเรียกการเขียนของระบบไฟล์ ความคิดที่ดี.
Tuxman

39

มันอาจจะง่ายกว่าที่จะต่อท้าย จากนั้นเมื่อคุณทำลูปเสร็จแล้วให้ลบตัวละครตัวสุดท้ายออก มีเงื่อนไขน้อยลงเช่นกัน

คุณสามารถใช้StringBuilderเป็นdeleteCharAt(int index)ดัชนีความเป็นอยู่length() - 1


5
ทางออกที่มีประสิทธิภาพที่สุดสำหรับปัญหานี้ +1
แดงจริง

9
บางทีมันอาจจะเป็นผม แต่ผมไม่ชอบความจริงที่ว่าคุณจะเอาสิ่งที่คุณเพิ่งเพิ่ม ...
Fortega

2
Fortega: ฉันไม่ชอบการตรวจสอบทุกครั้งสำหรับบางสิ่งที่ฉันจะทำ 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);
      }
 }


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

คุณควรห่อสิ่งทั้งหมดไว้ในคำสั่ง if ในกรณีที่คุณจัดการกับอาเรย์ที่มีความยาวเป็นศูนย์


ฉันชอบวิธีนี้เพราะไม่จำเป็นต้องทำการทดสอบซ้ำทุกครั้ง สิ่งเดียวที่ฉันจะเพิ่มคือผู้สร้างสามารถผนวกจำนวนเต็มโดยตรงดังนั้นไม่จำเป็นต้องใช้ "" + int เพียงแค่ผนวก (อาร์เรย์ [0])
Josh

คุณต้องเพิ่มอีกหนึ่งรอบหากทั่วทั้งล็อตเพื่อให้แน่ใจว่ามีองค์ประกอบอย่างน้อยหนึ่งรายการ
Tom Leys

2
ทำไมทุกคนยังคงใช้ตัวอย่างพร้อมกับการต่อสตริง INSIDE of append "," + x รวบรวมเป็น StringBuilder ใหม่ (","). ผนวก (x) .toString () ...
John Gardner

@Josh และ @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);

ฉันฉันใช้StringUtils.join()จากคอมมอนส์ - langเท่านั้น


3

คุณจำเป็นต้องมีชั้นแยก

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())ซึ่งเป็นส่วนสำคัญของความแข็งแกร่งของวิธีการโดยรวม (โซลูชันอื่น ๆ ที่นี่จัดการคอลเลกชันที่ว่างเปล่าอย่างสง่างามดังนั้นคุณควรจะได้เช่นกัน! :)
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

ในฐานะที่เป็นเครื่องมือที่กล่าวถึงในชวา 8 ตอนนี้เรามีนักสะสม นี่คือสิ่งที่รหัสจะมีลักษณะ:

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

ฉันทำรูปแบบที่คำถามอื่น ๆ
13ren

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]); 

ที่นั่นคุณไปในรหัส 4 บรรทัดรวมถึงการประกาศของอาร์เรย์และ StringBuilder


แต่คุณกำลังสร้าง StringBuilder ใหม่ในทุกการวนซ้ำ (ขอบคุณผู้ประกอบการ +)
Michael Myers

ใช่ถ้าคุณดูในไบต์คุณมีสิทธิ์ ฉันไม่อยากจะเชื่อเลยว่าคำถามง่าย ๆ อาจซับซ้อนได้ ฉันสงสัยว่าคอมไพเลอร์สามารถเพิ่มประสิทธิภาพที่นี่
Julien Chastang

จัดหาโซลูชั่นที่ 2 หวังว่าจะดีกว่า
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
กรุณาอย่าม้วนมาตรฐานของตัวเองและการใช้OpenJDKs เจ้าของเทียมเปรียบเทียบไมโคร (JMH) มันจะอุ่นเครื่องรหัสของคุณและหลีกเลี่ยงข้อผิดพลาดที่เป็นปัญหามากที่สุด
René

0

อีกวิธีคือการจัดเก็บความยาวของอาร์เรย์ (ถ้ามี) เก็บไว้ในตัวแปรแยกต่างหาก (มีประสิทธิภาพมากกว่าการตรวจสอบความยาวอีกครั้งในแต่ละครั้ง) จากนั้นคุณสามารถเปรียบเทียบดัชนีของคุณกับความยาวนั้นเพื่อกำหนดว่าจะเพิ่มจุลภาคสุดท้ายหรือไม่

แก้ไข: สิ่งที่ต้องพิจารณาอีกประการหนึ่งคือการชั่งน้ำหนักค่าใช้จ่ายด้านประสิทธิภาพของการลบอักขระตัวสุดท้าย (ซึ่งอาจทำให้สำเนาสตริง) ไม่ให้ทำการตรวจสอบเงื่อนไขในการทำซ้ำแต่ละครั้ง


การลบอักขระสุดท้ายไม่ควรทำให้สำเนาสตริง StringBuffer ของวิธีการลบ / deleteCharAt ในที่สุดเรียกวิธีการดังต่อไปนี้ภายในชั้นระบบที่มาเหมือนกันและอาร์เรย์ปลายทาง: ระบบ -> ประชาชนคงที่โมฆะ arraycopy พื้นเมืองคงที่ (Object src, srcPos int, dest ปลายทาง int int, ความยาว int);
บัฟฟาโล่

0

หากคุณเพียงแค่เปลี่ยนอาเรย์เป็นอาเรย์ที่คั่นด้วยจุลภาคหลายภาษามีฟังก์ชั่นการเข้าร่วมสำหรับสิ่งนี้ มันเปลี่ยนอาร์เรย์เป็นสตริงที่มีตัวคั่นระหว่างแต่ละองค์ประกอบ


0

ในกรณีนี้คุณไม่จำเป็นต้องรู้เลยว่านี่เป็นการทำซ้ำครั้งสุดท้ายหรือไม่ มีหลายวิธีที่เราสามารถแก้ปัญหานี้ได้ วิธีหนึ่งจะเป็น:

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

0

สองเส้นทางสำรองที่นี่:

1: Apache Utons String Utils

2: เก็บบูลีนที่เรียกว่าfirstตั้งค่าเป็นจริง ในแต่ละการวนซ้ำหากfirstเป็นเท็จให้เพิ่มจุลภาคของคุณ หลังจากนั้นตั้งค่าfirstเป็นเท็จ


1) การเชื่อมโยงตาย 2) ผมใช้เวลานานในการอ่านคำอธิบายมากกว่ารหัส :)
บัฟฟาโล

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.