Ostatnia iteracja ulepszonej pętli for w Javie

140

Czy istnieje sposób, aby określić, czy pętla jest iterowana po raz ostatni. Mój kod wygląda mniej więcej tak:

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

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

Chodzi o to, że nie chcę dodawać przecinka w ostatniej iteracji. Czy jest teraz sposób, aby określić, czy jest to ostatnia iteracja, czy też utknąłem w pętli for lub używając zewnętrznego licznika do śledzenia.

Rtęciowy
źródło
1
Tak! To zabawne, po prostu chciałem zadać dokładnie to samo pytanie!
PhiLho
Powraca ten sam rodzaj pytania (i tak będzie). Dlaczego miałbyś chcieć utworzyć pętlę, jeśli jeden element wymaga innego traktowania? stackoverflow.com/questions/156650/ ...
xtofl
Skoro masz stałą tablicę, po co używać rozszerzonej dla? for (int i = 0; i <array.length; i ++ if (i <array.lenth) ,,,
AnthonyJClink

Odpowiedzi:

223

Inną alternatywą jest dołączenie przecinka przed dodaniem i, ale nie w pierwszej iteracji. (Nawiasem "" + imówiąc, proszę nie używać - tak naprawdę nie chcesz tutaj konkatenacji, a StringBuilder ma doskonale dobre przeciążenie append (int).)

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

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

Fajną rzeczą w tym jest to, że będzie działać z każdym Iterable- nie zawsze możesz indeksować rzeczy. („Dodaj przecinek, a następnie usuń go na końcu” to dobra sugestia, gdy naprawdę używasz StringBuilder - ale nie działa w przypadku takich rzeczy, jak pisanie do strumieni. Jest to jednak prawdopodobnie najlepsze podejście do tego konkretnego problemu. )

Jon Skeet
źródło
2
Dobry wzorzec, ale builder.length ()! = 0 jest kruchy - wyobraź sobie, że coś jest dodawane (warunkowo!) Do bufora przed twoją pętlą. Zamiast tego użyj isFirstflagi logicznej. Bonus: także szybciej.
Jason Cohen
2
@Jason: Z pewnością używam wzorca „isFirst”, w którym kreator nie będzie pusty w pierwszej iteracji. Jednak, gdy nie jest to potrzebne, z mojego doświadczenia wynika, że ​​implementacja jest dość wzdęta.
Jon Skeet
3
@Liverpool (itp.): 1000 niepotrzebnych kontroli jest bardzo, bardzo mało prawdopodobne, aby miało jakikolwiek znaczący wpływ na wydajność. Mógłbym równie podkreślić, że dodatkowy charakter roztworem Dinah dodanej może spowodować StringBuilder mają się rozwijać, podwojenie rozmiaru końcowego łańcucha z (cd)
Jon Skeet
2
niepotrzebna przestrzeń buforowa. Jednak ważną kwestią jest czytelność. Wydaje mi się, że moja wersja jest bardziej czytelna niż wersja Dinah. Jeśli uważasz, że jest inaczej, nie ma sprawy. Brałbym pod uwagę tylko wpływ na wydajność niepotrzebnych „jeśli” po znalezieniu wąskiego gardła.
Jon Skeet,
3
Poza tym moje rozwiązanie ma bardziej ogólne zastosowanie, ponieważ polega tylko na umiejętności pisania. Możesz wziąć moje rozwiązanie i zmienić je na pisanie w strumieniu itp. - gdzie później możesz nie być w stanie odzyskać niepotrzebnych danych. Lubię wzory, które mają ogólne zastosowanie.
Jon Skeet,
145

Inny sposób na zrobienie tego:

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

Aktualizacja: dla Java 8 masz teraz Kolektory

zestaw narzędzi
źródło
1
Musiałem usunąć moją podobną odpowiedź - to nauczy mnie, żebym nie publikowała przed dokładniejszym przyjrzeniem się innym odpowiedziom.
Michael Burr,
1
Należy również zauważyć, że rozwiązuje to potencjalny problem, o którym Robert Paulson wspomniał w innym wątku komentarzy - ta technika nie zależy od tego, że StringBuilder jest pusty, gdy wartości tablic są dołączane.
Michael Burr,
1
Chociaż kod jest bardziej zgrabny / schludny / zabawny, jest mniej oczywisty niż wersja if. Zawsze używaj bardziej oczywistej / czytelnej wersji.
Bill K
8
@Bill: To nie jest świetne, jak twarda i szybka zasada; są chwile, kiedy „sprytne” rozwiązanie jest „bardziej poprawnym” rozwiązaniem. A konkretnie, czy naprawdę uważasz, że ta wersja jest trudna do odczytania? Tak czy inaczej opiekun musiałby przez to przejść - nie sądzę, aby różnica była znacząca.
Greg Case,
Działa to, chociaż piszesz do innych zasobów, takich jak wywołania zapisu systemu plików. Dobry pomysł.
Tuxman,
39

Łatwiej będzie zawsze dołączać. A potem, kiedy skończysz z pętlą, po prostu usuń ostatni znak. W ten sposób też ton mniej warunkowych.

Można użyć StringBuilder„s deleteCharAt(int index)z indeksu samopoczucialength() - 1

Dinah
źródło
5
najbardziej efektywne rozwiązanie tego problemu. +1.
Real Red.
9
Może to ja, ale naprawdę nie podoba mi się fakt, że usuwasz coś, co właśnie dodałeś ...
Fortega
2
Fortega: Nie lubię sprawdzać za każdym razem czegoś, co robię w 99% przypadków. Wydaje się bardziej logiczne (i jest szybsze) zastosowanie rozwiązania Dinah lub sblundy'ego.
Buffalo
1
Moim zdaniem najbardziej eleganckie rozwiązanie. Dziękuję Ci!
Charles Morin
32

Może używasz złego narzędzia do pracy.

To jest bardziej ręczne niż to, co robisz, ale jest w pewnym sensie bardziej eleganckie, jeśli nie trochę „oldschoolowe”

 StringBuffer buffer = new StringBuffer();
 Iterator iter = s.iterator();
 while (iter.hasNext()) {
      buffer.append(iter.next());
      if (iter.hasNext()) {
            buffer.append(delimiter);
      }
 }
Omar Kooheji
źródło
14

Inne rozwiązanie (być może najbardziej wydajne)

    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]);
        }
    }
bruno conde
źródło
Jest to naturalne rozwiązanie, z tym wyjątkiem, że zależy od tego, czy tablica nie jest pusta.
orcmid
5
@orcmid: Jeśli tablica jest pusta, nadal daje to prawidłowe wyjście - pusty ciąg. Nie jestem pewien, o co ci chodzi.
Eddie
7

zachowaj prostotę i użyj standardu pętli:

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

lub po prostu użyj apache commons-lang StringUtils.join ()

Gareth Davis
źródło
6

Jawne pętle zawsze działają lepiej niż niejawne.

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

Powinieneś owinąć całość w instrukcję if, na wypadek gdybyś miał do czynienia z tablicą o zerowej długości.

S.Lott
źródło
Podoba mi się to podejście, ponieważ nie wymaga niepotrzebnego wykonywania testu w każdej iteracji. Jedyne, co chciałbym dodać, to to, że builder może bezpośrednio dołączać liczby całkowite, więc nie ma potrzeby używania "" + int, po prostu dodaj (tablica [0]).
Josh
Potrzebujesz jeszcze jednego, jeśli wokół całej parceli, aby mieć pewność, że tablica ma co najmniej jeden element.
Tom Leys,
2
dlaczego wszyscy wciąż używają przykładów z konkatenacją ciągów INSIDE of append? "," + x kompiluje się do nowego StringBuilder (",") .append (x) .toString () ...
John Gardner,
@Josh i @John: Postępuj zgodnie z przykładem n00b. Nie chcę wprowadzać zbyt wielu rzeczy na raz. Twój punkt widzenia jest jednak bardzo dobry.
S.Lott,
@Tom: Racja. Myślę, że już to powiedziałem. Edycja nie jest wymagana.
S.Lott,
4

Jeśli przekonwertujesz go na klasyczną pętlę indeksu, tak.

Lub możesz po prostu usunąć ostatni przecinek po zakończeniu. Tak jak to:

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

Ja, po prostu używam StringUtils.join()ze wspólnego języka .

sblundy
źródło
3

Potrzebujesz separatora klas .

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

Implementacja klasy Separatorjest prosta. Zawija ciąg, który jest zwracany przy każdym wywołaniu z toString()wyjątkiem pierwszego wywołania, które zwraca pusty łańcuch.

akuhn
źródło
3

Oparty na java.util.AbstractCollection.toString (), kończy działanie wcześnie, aby uniknąć separatora.

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

Jest skuteczny i elegancki, ale nie jest tak oczywisty, jak niektóre inne odpowiedzi.

13ren
źródło
Zapomniałeś uwzględnić wczesny zwrot, kiedy (! i.hasNext()), co jest ważną częścią solidności całego podejścia. (Inne rozwiązania tutaj z wdziękiem radzą sobie z pustymi kolekcjami, więc twoje też powinny! :)
Mike Clark
3

Oto rozwiązanie:

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

Poszukaj pierwszej iteracji :)  

FallenAvatar
źródło
3

Jak wspomniano w zestawie narzędzi, w Javie 8 mamy teraz Kolektory . Oto jak wyglądałby kod:

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

Myślę, że robi dokładnie to, czego szukasz, i jest to wzór, którego możesz użyć do wielu innych rzeczy.

b1tw153
źródło
1

Jeszcze inna opcja.

StringBuilder builder = new StringBuilder();
for(int i : array)
    builder.append(',').append(i);
String text = builder.toString();
if (text.startsWith(",")) text=text.substring(1);
Peter Lawrey
źródło
Zrobiłem różnicę w drugim pytaniu
13ren
1

Wiele z opisanych tutaj rozwiązań jest nieco przesadzonych, IMHO, zwłaszcza te, które opierają się na bibliotekach zewnętrznych. Istnieje ładny, czysty i przejrzysty idiom do tworzenia listy oddzielonej przecinkami, którego zawsze używałem. Opiera się na operatorze warunkowym (?):

Edycja : Oryginalne rozwiązanie poprawne, ale według komentarzy nieoptymalne. Próbuję po raz drugi:

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

Proszę bardzo, w 4 wierszach kodu, w tym deklaracja tablicy i StringBuilder.

Julien Chastang
źródło
Ale tworzysz nowy StringBuilder w każdej iteracji (dzięki operatorowi +).
Michael Myers
Tak, jeśli spojrzysz na kod bajtowy, masz rację. Nie mogę uwierzyć, że takie proste pytanie może być tak skomplikowane. Zastanawiam się, czy kompilator może tutaj zoptymalizować.
Julien Chastang
Dostarczono drugie, miejmy nadzieję, lepsze rozwiązanie.
Julien Chastang
1

Oto test porównawczy SSCCE, który przeprowadziłem (związany z tym, co musiałem zaimplementować) z następującymi wynikami:

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

Przynajmniej na moim przykładzie pomijanie sprawdzania przy każdej iteracji nie jest zauważalnie szybsze, szczególnie w przypadku rozsądnych ilości danych, ale tak jest szybsze.

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)");
  }
}
Bawół
źródło
1
Proszę nie przeprowadzać własnych testów porównawczych i używać własnej mikro-wiązki testowej OpenJDK (jmh) . Rozgrzeje Twój kod i pozwoli uniknąć najbardziej problematycznych pułapek.
René
0

Innym podejściem jest przechowywanie długości tablicy (jeśli jest dostępna) w oddzielnej zmiennej (bardziej wydajne niż ponowne sprawdzanie długości za każdym razem). Następnie możesz porównać swój indeks z tą długością, aby określić, czy dodać ostatni przecinek.

EDYCJA: Inną kwestią jest rozważenie kosztu wydajności usunięcia ostatniego znaku (który może spowodować kopię ciągu) z koniecznością sprawdzania warunku w każdej iteracji.

Brian
źródło
Usunięcie ostatniego znaku nie powinno powodować tworzenia kopii ciągu. Metody delete / deleteCharAt StringBuffera ostatecznie wywołują następującą metodę wewnątrz identycznych tablic źródłowych i docelowych klasy System: System -> public static native void arraycopy (Object src, int srcPos, Object dest, int destPos, int length);
Buffalo
0

Jeśli zamieniasz tablicę tylko w tablicę rozdzielaną przecinkami, wiele języków ma właśnie do tego funkcję łączenia. Zamienia tablicę w ciąg z ogranicznikiem między każdym elementem.

Dinah
źródło
0

W tym przypadku naprawdę nie ma potrzeby wiedzieć, czy jest to ostatnie powtórzenie. Jest wiele sposobów rozwiązania tego problemu. Jednym ze sposobów byłoby:

String del = null;
for(int i : array)
{
    if (del != null)
       builder.append(del);
    else
       del = ",";
    builder.append(i);
}
fastcodejava
źródło
0

Tutaj dwie alternatywne ścieżki:

1: Narzędzia ciągów znaków Apache Commons

2: Zachowaj wywołanie logiczne first, ustaw na true. W każdej iteracji, jeśli firstjest fałszywe, dołącz przecinek; po tym ustaw firstna false.

Paul Brinkley
źródło
1) Link nie działa 2) Przeczytanie opisu zajęło mi więcej czasu niż kodu :)
Buffalo
0

Ponieważ jest to stała tablica, łatwiej byłoby po prostu uniknąć ulepszonego for ... Jeśli obiekt jest zbiorem, iterator byłby łatwiejszy.

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(",");
   }
}
AnthonyJClink
źródło