2009-10-24 12 views
7

Co to jest prosty i szybki sposób na uzyskanie iteratora, który zwraca co najwyżej N elementów od początku List?Ogranicza ListIterator do pierwszych elementów N (zoptymalizowanych)

Najprostsze wersje mogłem wymyślić to:

# 1:

import com.google.common.collect.Iterators; 

// ... 

public static <E> Iterator<E> lengthLimitedIterator(Iterable<E> source, int maxLen) { 
    return Iterators.partition(source.iterator(), maxLen).next().iterator(); 
} 

# 2:

public static <E> Iterator<E> lengthLimitedIterator(List<E> source, int maxLen) { 
    return source.subList(0, Math.min(source.size(), maxLen)).iterator(); 
} 

Niestety obie wersje utworzyć tymczasowy List co znacząco wpływa na wydajność, jak Nazywam tę metodę miliony razy w ciasnej pętli.

Czy są jakieś inne funkcje biblioteczne, które mogę użyć do tego?


Uwaga: Nie mogę uniknąć iteracji po liście jak ja przekazaniem go do metody, która bierze iterator jako argument i nie mogę modyfikować tej klasy.

Odpowiedz

8

Wydaje się, że feature zostaną dodane do guawa Aktualny (R06), w beta:

public static <T> Iterator<T> limit(Iterator<T> iterator, int limitSize) 
+1

Oprócz "Iterators", należy pamiętać, że ['Iterables' również ma' limit() 'metodę] (http: //docs.guava- libraries.googlecode.com/git/javadoc/com/google/common/collect/Iterables.html#limit(java.lang.Iterable,%20int)). Więc jeśli masz 'List', to najprościej zrobić' Iterables.limit (aList, 3) '. – Jonik

5

To jest miejsce, gdzie Decorator działa bardzo dobrze: Twój dekorator zachowuje liczbę, która jest zwiększana o next() i jest używana przez kontrolkę hasNext().

Przykład (celowo niekompletna):

public class LengthLimitedIterator<T> 
implements Iterator<T> 
{ 
    private Iterator<T> _wrapped; 
    private int _length; 
    private int _count; 

    public LengthLimitedIterator(Iterator<T> wrapped, int length) 
    { 
     _wrapped = wrapped; 
     _length = length; 
    } 


    public boolean hasNext() 
    { 
     if (_count < _length) 
      return _wrapped.hasNext(); 
     return false; 
    } 

    public T next() 
    { 
     // FIXME - add exception if count >= length 
     _count++; 
     return _wrapped.next(); 
    } 
5

Dlaczego nie po prostu

list.subList(0, 42).iterator(); 

Nie jestem pewien, dlaczego myśl o stworzeniu tego wykazu tymczasowego. Nie robi nic, co uważam za drogie. W rzeczywistości tworzenie tej listy jest zdecydowanie tańsze niż jej powtarzanie, co, jak zakładam, robisz.

+0

Sposób przyjmującego wymaga iterację i niestety nie można zmienić. Twój kod jest taki sam, jak mój drugi przykład, z tym wyjątkiem, że nie sprawdza, czy lista jest krótsza niż maksymalna długość (w takim przypadku subList() rzuci wyjątek.) – finnw

14

Już wiesz, że to lista, więc możesz po prostu zadzwonić pod metodę List.subList(int fromIndex, int toIndex). Zgodnie ze specyfikacją, lista podrzędna jest wspierana przez oryginalną listę, więc tak naprawdę nie tworzy całkowicie rozwiniętego List, tylko jakiś obiekt proxy.

+0

Problem polega na tym, że musisz się upewnić istnieje wystarczająco dużo dostępnych elementów na liście lub otrzymasz "IndexOutOfBoundsException". Nie wiem, czy to ograniczenie występuje również w innych proponowanych rozwiązaniach, ale byłoby miło mieć wbudowaną opcję iteracji ponad _więcej elementów. – Itai

0

Ta wersja okazuje się szybciej niż którykolwiek z pozostałych przykładów:

public static <E> Iterator<E> lengthLimitedIterator(List<E> source, int maxLen) { 
    maxLen = Math.min(maxLen, source.size()); 
    ArrayList<E> tempList = new ArrayList<E>(maxLen); 
    for (int i = 0; i < maxLen; ++ i) { 
     tempList.add(source.get(i)); 
    } 
    return tempList.iterator(); 
} 

Jeśli lista tymczasowy musi być tworzone tak, ArrayList jest szybszy niż zdobione list zwracanych przez innych metod bibliotecznych.

Domyślam się, że ArrayList otrzymuje specjalne traktowanie w VM.

Może byłoby to nieefektywne przez bardzo długi list, ale moje listy są krótkie (prawie zawsze mniej niż 50 elementów).

+0

Nawiasem mówiąc, jestem ostrożny wobec twoich "to jest szybsze niż to", ponieważ mikrobieczkowa w Javie jest bardzo, BARDZO podatna na błędy. Istnieje sto sposobów na uzyskanie mylącego wyniku. Naprawdę myślę, że powinieneś spróbować trzymać się czystego rozwiązania podlist(). Iterator(). –

+0

@Kevin, zmierzyłem to w prawdziwej aplikacji, w której go używam. Nie twierdzę, że w ogólnym przypadku jest szybciej. – finnw

1

Jeśli chodzi o wydajność, nie należy używać iterator użyć indeksu na tablica. Zapewni to znacznie lepszą wydajność. Uzyskanie pierwszych N elementów tablicy jest banalne.

2

Metoda ArrayList.sublist(int,int) nie tworzy kopii oryginalnej listy. Zamiast tego zwraca instancję SubList, która otacza oryginalną ArrayList. Iterator zwrócony przez podlistę uzyskaną z tablicy Array również nie tworzy kopii.

Moja rada to wypróbować użycie ArrayList jako typu listy podstawowej i metody sublist. Jeśli nie jest to wystarczająco szybkie, zaimplementuj własny wariant ArrayList, który implementuje metodę limitedLengthIterator. Na przykład powinieneś być w stanie pozbyć się kodu, który sprawdza równoczesne modyfikacje.

+0

Ale opakowanie jest w rzeczywistości wolniejsze niż oryginalna ArrayList – finnw

+0

@finnw - ale powinno być szybsze niż kopiowanie listy. –

+0

Zależy, ile razy jest iterowane. – finnw