Próbuję podzielić listę na serię mniejszych list.
Mój problem: Moja funkcja dzielenia list nie dzieli ich na listy o odpowiednim rozmiarze. Powinien podzielić je na listy o rozmiarze 30, ale zamiast tego podzieli je na listy o rozmiarze 114?
Jak sprawić, by moja funkcja podzieliła listę na liczbę X list o rozmiarze 30 lub mniejszym ?
public static List<List<float[]>> splitList(List <float[]> locations, int nSize=30)
{
List<List<float[]>> list = new List<List<float[]>>();
for (int i=(int)(Math.Ceiling((decimal)(locations.Count/nSize))); i>=0; i--) {
List <float[]> subLocat = new List <float[]>(locations);
if (subLocat.Count >= ((i*nSize)+nSize))
subLocat.RemoveRange(i*nSize, nSize);
else subLocat.RemoveRange(i*nSize, subLocat.Count-(i*nSize));
Debug.Log ("Index: "+i.ToString()+", Size: "+subLocat.Count.ToString());
list.Add (subLocat);
}
return list;
}
Jeśli użyję funkcji z listy o rozmiarze 144, wówczas wynik będzie:
Indeks: 4, Rozmiar: 120
Indeks: 3, Rozmiar: 114
Indeks: 2, Rozmiar: 114
Indeks: 1, Rozmiar: 114
Indeks: 0, Rozmiar: 114
Odpowiedzi:
Wersja ogólna:
źródło
GetRange(3, 3)
Sugerowałbym użycie tej metody rozszerzenia do podzielenia listy źródłowej na podlisty według określonego rozmiaru fragmentu:
Na przykład, jeśli podzielisz listę 18 pozycji na 5 pozycji na porcję, otrzymasz listę 4 sub-list z następującymi elementami: 5-5-5-3.
źródło
ToList()
liter i pozwolić leniwej ocenie zrobić magię.Co powiesz na:
źródło
ToList
ale nie zawracałbym sobie głowy optymalizacją - jest to tak banalne i mało prawdopodobne, aby było wąskim gardłem. Główną korzyścią z tej implementacji jest jej łatwość zrozumienia. Jeśli chcesz, możesz użyć zaakceptowanej odpowiedzi, która nie tworzy tych list, ale jest nieco bardziej złożona..Skip(n)
iterujen
elementy za każdym razem, gdy jest wywoływane, chociaż może to być w porządku, ważne jest, aby wziąć pod uwagę kod krytyczny dla wydajności. stackoverflow.com/questions/20002975/….Skip()
s w bazie kodu mojej firmy i chociaż mogą nie być „optymalne”, działają dobrze. Rzeczy takie jak operacje DB i tak trwają znacznie dłużej. Myślę jednak, że należy zauważyć, że.Skip()
„dotyka” każdego elementu <n na swojej drodze, zamiast skakać bezpośrednio do n-tego elementu (jak można się spodziewać). Jeśli iterator ma skutki uboczne dotykania elementu,.Skip()
może być przyczyną trudnych do znalezienia błędów.Rozwiązanie Serj-Tm jest w porządku, jest to również wersja ogólna jako metoda rozszerzenia list (umieść ją w klasie statycznej):
źródło
Uważam, że zaakceptowana odpowiedź (Serj-Tm) jest najsolidniejsza, ale chciałbym zasugerować ogólną wersję.
źródło
Biblioteka MoreLinq ma wywołaną metodę
Batch
Wynik jest
ids
są podzielone na 5 części z 2 elementami.źródło
Mam ogólną metodę, która obejmowałaby dowolne typy, w tym zmiennoprzecinkowe, i została przetestowana jednostkowo, mam nadzieję, że pomaga:
źródło
values.Count()
spowoduje pełne wyliczenie, a następnievalues.ToList()
kolejne. Jest to bezpieczniejsze,values = values.ToList()
bo już się zmaterializowało.Chociaż wiele powyższych odpowiedzi spełnia swoje zadanie, wszystkie strasznie zawodzą w niekończącej się sekwencji (lub naprawdę długiej sekwencji). Poniżej znajduje się całkowicie internetowa implementacja, która gwarantuje najlepszy możliwy czas i złożoność pamięci. My tylko iterujemy źródło wyliczalne dokładnie raz i używamy zwrotu z zysku do leniwej oceny. Konsument może wyrzucić listę przy każdej iteracji, dzięki czemu ślad pamięci będzie równy śladowi na liście z
batchSize
liczbą elementów.EDYCJA: Właśnie teraz zdając sobie sprawę z tego, że OP wymaga podzielenia
List<T>
na mniejszeList<T>
, więc moje komentarze dotyczące nieskończonych liczb nie mają zastosowania do PO, ale mogą pomóc innym, którzy tu trafią. Te komentarze były odpowiedzią na inne opublikowane rozwiązania, które wykorzystująIEnumerable<T>
dane wejściowe do ich funkcji, ale wielokrotnie wyliczają źródło, które można wyliczyć.źródło
IEnumerable<IEnumerable<T>>
wersja jest lepsza, ponieważ nie wymaga tak dużejList
konstrukcji.IEnumerable<IEnumerable<T>>
jest to, że implementacja prawdopodobnie będzie polegać na tym, że konsument będzie w pełni wyliczał każdy uzyskany wewnętrzny wyliczalny koszt . Jestem pewien, że rozwiązanie można sformułować w sposób pozwalający uniknąć tego problemu, ale myślę, że wynikowy kod może szybko się skomplikować. Ponadto, ponieważ jest on leniwy, generujemy tylko jedną listę na raz, a przydzielanie pamięci odbywa się dokładnie raz na listę, ponieważ znamy rozmiar z góry.Dodanie po bardzo przydatnym komentarzu mhand na końcu
Oryginalna odpowiedź
Chociaż większość rozwiązań może działać, myślę, że nie są one zbyt wydajne. Załóżmy, że chcesz tylko kilka pierwszych elementów z kilku pierwszych części. Wtedy nie chcesz iterować wszystkich (zillionowych) przedmiotów w sekwencji.
Następujące wartości będą co najwyżej dwukrotnie wyliczone: raz dla Take i raz dla Skip. Nie będzie wyliczać więcej elementów niż użyjesz:
Ile razy to wyliczy sekwencję?
Załóżmy, że dzielisz źródło na części
chunkSize
. Zliczasz tylko pierwsze N fragmentów. Z każdego wyliczonego fragmentu wyliczysz tylko pierwsze M elementów.dowolna otrzyma moduł wyliczający, wykona 1 operację MoveNext () i zwróci zwróconą wartość po usunięciu modułu wyliczającego. Zostanie to zrobione N razy
Według źródła referencyjnego zrobi to coś takiego:
Nie robi to wiele, dopóki nie zaczniesz wyliczać ponad pobranego kawałka. Jeśli pobierzesz kilka fragmentów, ale zdecydujesz, aby nie wyliczać więcej niż pierwszego fragmentu, foreach nie zostanie wykonany, ponieważ wyświetli się twój debugger.
Jeśli zdecydujesz się wziąć pierwsze M elementów pierwszego fragmentu, wówczas zwrot wydajności jest wykonywany dokładnie M razy. To znaczy:
Po zwróceniu pierwszego fragmentu pomijamy ten pierwszy fragment:
Jeszcze raz: przyjrzymy się źródłu odniesienia, aby znaleźć
skipiterator
Jak widać,
SkipIterator
wywołaniaMoveNext()
raz dla każdego elementu w części. Nie dzwoniCurrent
.Tak więc na porcję widzimy, że następujące czynności są wykonywane:
Brać():
Jeśli treść jest wyliczona: GetEnumerator (), jeden MoveNext i jeden bieżący na wyliczony element, Dispose enumerator;
Skip (): dla każdego wyliczonego fragmentu (NIE jego zawartości): GetEnumerator (), MoveNext () porcja Wielkość porcji, brak bieżącej! Usuń moduł wyliczający
Jeśli spojrzysz na to, co dzieje się z modułem wyliczającym, zobaczysz, że istnieje wiele wywołań MoveNext () i tylko wywołania
Current
elementów TSource, do których faktycznie chcesz się dostać.Jeśli weźmiesz N Chunks o rozmiarze chunkSize, wówczas wywołania MoveNext ()
Jeśli zdecydujesz się wyliczyć tylko pierwsze M elementów każdego pobranego fragmentu, musisz wywołać MoveNext M razy na wyliczony fragment.
Łącznie
Więc jeśli zdecydujesz się wyliczyć wszystkie elementy wszystkich porcji:
To, czy MoveNext wymaga dużo pracy, zależy od rodzaju sekwencji źródłowej. W przypadku list i tablic jest to prosty przyrost indeksu, z możliwością sprawdzenia poza zakresem.
Ale jeśli Twój IEnumerable jest wynikiem zapytania do bazy danych, upewnij się, że dane są naprawdę zmaterializowane na twoim komputerze, w przeciwnym razie dane zostaną pobrane kilka razy. DbContext i Dapper poprawnie prześlą dane do procesu lokalnego, zanim będzie można uzyskać do nich dostęp. Jeśli wyliczysz tę samą sekwencję kilka razy, nie zostanie ona pobrana kilka razy. Dapper zwraca obiekt będący Listą, DbContext pamięta, że dane zostały już pobrane.
Zależy od Twojego Repozytorium, czy rozsądnie jest wywołać AsEnumerable () lub ToLists () zanim zaczniesz dzielić elementy w Chunks
źródło
2*chunkSize
czasy źródłowe ? Jest to śmiertelnie niebezpieczne w zależności od źródła elementu wymiennego (być może kopii zapasowej DB lub innego niezapisanego źródła). Wyobraź sobie toEnumerable.Range(0, 10000).Select(i => DateTime.UtcNow)
wyliczenie jako dane wejściowe - otrzymasz różne czasy za każdym razem, gdy wyliczasz wyliczenie, ponieważ nie jest ono zapamiętywaneEnumerable.Range(0, 10).Select(i => DateTime.UtcNow)
. Przez wywołanieAny
będziesz za każdym razem obliczał aktualny czas. Nie jest tak źleDateTime.UtcNow
, ale weź pod uwagę wyliczenie wspierane przez połączenie z bazą danych / kursor SQL lub podobny. Widziałem przypadki, gdzie tysiące połączeń DB zostały wydane, ponieważ deweloper nie rozumieli potencjalne reperkusje „wielu wyliczeń w przeliczalna” - ReSharper stanowi wskazówkę do tego, jak dobrzeźródło
źródło
Co powiesz na ten? Chodziło o użycie tylko jednej pętli. A kto wie, może używasz tylko implementacji IList do dokładnego kodowania i nie chcesz rzutować na List.
źródło
Jeszcze jeden
źródło
źródło
źródło
Spotkałem tę samą potrzebę i użyłem kombinacji metod Skip () i Take () Linqa. Mnożę liczbę, którą biorę do tej pory przez liczbę iteracji, co daje mi liczbę elementów do pominięcia, a następnie biorę następną grupę.
źródło
Na podstawie Dimitry Pawłowa answere Chciałbym usunąć
.ToList()
. A także unikaj anonimowej klasy. Zamiast tego lubię używać struktury, która nie wymaga alokacji pamięci sterty. (AValueTuple
również wykona pracę.)Można tego użyć w następujący sposób, który iteruje kolekcję tylko raz, a także nie przydziela żadnej znaczącej pamięci.
Jeśli rzeczywiście potrzebna jest konkretna lista, zrobiłbym to w ten sposób:
źródło