Possible multiple enumeration - jak uniknąć

Possible multiple enumeration - jak uniknąć
Mikan
  • Rejestracja:ponad 9 lat
  • Ostatnio:10 miesięcy
  • Postów:75
0

Powiedzmy że mam coś takiego:

Kopiuj
class Foo
{
    public int Id { get; set;}
    public string Boo { get; set;}
}

public IEnumerable<Foo> Do(IEnumerable <Foo> foos)
{
    var ids = foos.Select(x => x.Id);
    var cache = api.Get(ids);
    foreach(var foo in foos)
    {
        var someData = cache.TryGetValue(foo.Id);
        if(someData != null)
        {
            foo.Boo = someData;
        }
        else 
        { //something else
        }
     yield return foo;
    }
}

foos jest bardzo ciężkie i zje całą pamięć jak dam .ToList()
R# informuje mnie że: possible multiple enumeration
Api przyjmuje tylko listę id.

Czy można to zrobić jakoś inaczej?

Grzegorz Świdwa
Grzegorz Świdwa
  • Rejestracja:ponad 5 lat
  • Ostatnio:około 4 lata
  • Postów:385
0
Kopiuj
public IEnumerable<obj> Do(IEnumerable<int> IDs, int PageSize)

Można wtedy wewnątrz wczytywać dane "partiami". Nie wiem czy idę w dobrym kierunku bo nie za bardzo wiem o co Ci chodzi. gdybyś chciał pobrać kilka tysięcy rekordów to może to będzie dobre?

Kopiuj
public class obj
        {
            public obj(int Id, string Value)
            {
                this.Id = Id;
                this.Value = Value;
            }

            public int Id { get; set; }
            public string Value { get; set; }
        }
        public class api
        {
            public static IEnumerable<obj> Get(IEnumerable<int> IDs)
            {
                Random random = new Random();
                foreach (int id in IDs)
                    yield return new obj(id, random.Next(100, 10000).ToString());
            }
        }

        public IEnumerable<obj> Do(IEnumerable<int> IDs, int PageSize)
        {
            int n = 0;
            do
            {
                bool LastLoop = (IDs.Count() <= n + PageSize) ? true : false;
                IEnumerable<int> IDsInCurrentLoop = (LastLoop) ? IDs.ToList().GetRange(n, PageSize) : IDs.ToList().GetRange(n, IDs.Count() - n);
                IEnumerable<obj> importedData = api.Get(IDsInCurrentLoop);
                foreach (obj o in importedData)
                    yield return o;
                n += PageSize;
            } while (n >= IDs.Count());
        }

A najlepiej to w ogóle jak obawiasz się o pamięć to podzielić sobie te ID na partie do pobrania z API i wywoływać Do dla partii danych

ŁF
Moderator
  • Rejestracja:ponad 22 lata
  • Ostatnio:około 9 godzin
0

A tak? :>

Kopiuj
     foreach(var foo in foos)
     {
         var cache = api.Get(new List<int> { foo.Id });
         // ...
     }

jasmina tmp
  • Rejestracja:ponad 7 lat
  • Ostatnio:ponad rok
  • Postów:10
0
ŁF napisał(a):

A tak? :>

Kopiuj
     foreach(var foo in foos)
     {
         var cache = api.Get(new List<int> { foo.Id });
         // ...
     }

No tak to będzie wolał Api w pętli.. chyba nie bardzo

edytowany 1x, ostatnio: ŁF
ŁF
Zdziwiłabyś się (vide post autora wątku z 12:54). Czasem szybciej jest zrobić mnóstwo drobnych rzeczy, niż naraz jedną kobyłę. Sam się kilka lat temu na tym boleśnie sparzyłem, kiedy połączyłem wiele drobnych, a kosztownych zapytań z EF w jedno duże. To jedno niby działało dużo szybciej, koszt zapytania też spadł, nawet testowałem je na prodykcyjnych danych - a mimo to produkcja klęknęła. Okazało się, że przegapiłem, że to jedno duże potrzebuje 120MB pamięci do wykonania.
ŁF
Hipotetycznie: danych jest kilkaset MB. Przesyłasz to do api, które jest webserwisem. Ile pamięci (być może ciągły blok!) będzie potrzebował serwer, zanim dane przekonwertuje do postaci wymaganej przez odpowiednią metodę api? A przecież jeszcze potrzeba pamięci na wygenerowanie odpowiedzi... Ile takich zapytań obsłuży serwer równolegle? Ile pamięci będzie potrzebował klient i na jak długo, zanim zakończy wysyłanie? A tu ponownie - trzeba odebrać odpowiedź i gdzieś ją trzymać.
ŁF
Do tego wysyłanie danych pojedyńczo lub w małych paczkach pozwala kontrolować zatrzymywanie/wznawianie pracy bez utraty całego wyniku, w przypadku błędu nie tracisz wszystkich wyników tylko jeden, a aplikacje nie zamrażają się na długi czas pracy natywnego kodu związany z przetwarzaniem dużego bloku danych.
jasmina tmp
  • Rejestracja:ponad 7 lat
  • Ostatnio:ponad rok
  • Postów:10
1

Tutaj nie chodzi o to co masz wewnątrz Do(..) tylko gdzie i jak używasz wyników z Do. tzn kiedy te wyniki materializujesz - wtedy idzie faktyczna pętla .
Jak nie zrobisz jawnie ToList to materializacja zrobi się sama przy pierwszym odwołaniu do wyniku.
Komunikat ostrzega o tym że się może zrobić (materializacja - pętla) sama kilka razy np. costam = Do.. i costam2 = Do..
Jeśli masz pewność, że nie - bo tak nie napisałeś - to jest OK.
Jeśli nie, to za pierwszym razem się zmaterializuje i za kolejnym ponownie.

Inna kwestia, że obawiasz się wszystkich wyników z Do(.., ale jeśli to miejsce gdzie wołasz Do(.. nie sprawdza tych częściowych wyników i nie przerywa pętli to i tak dostaniesz wszystkie wyniki - wtedy nie ma sensu yield.
Jeśli natomiast sprawdzasz lub wybierasz wyniki np. Do(..).Where(costam) to jest szansa że pętla będzie krótsza wtedy bym dał ToList() i operował już na tym wyniku.

edytowany 5x, ostatnio: jasmina tmp
ŁF
Nieprawda. Jeśli iterujesz po IEnumerable albo IEnumerable<T>, to materializuje się tylko kolejny element, a poprzedni znika, tzn. zostaje kiedyś "zniknięty" przez GC. Oczywiście da się to spieprzyć robiąc źle enumerator, ale trzeba by się postarać. Pośrednio stąd właśnie ostrzeżenie "possible multiple enumeration", bo jeśli pierwsza iteracja materializowałaby całą listę naraz, a nie tylko poszczególne elementy, to druga iteracja nie byłaby problemem, bo elementy i tak były by już zmaterializowane.
Mikan
  • Rejestracja:ponad 9 lat
  • Ostatnio:10 miesięcy
  • Postów:75
0

Dużo później w kodzie jest to materializowane w paczkach.

Rozwiązałem problem pobierając wszystkie dane z api (nie jest ich tak dużo).

Zadałem to pytanie dlatego że próbowałem wcześniej rozwiązania @ŁF i działa ono szybciej niż to moje, dlatego pomyślałem że to przez dwie enumeracje, ale może coś źle sprawdzałem :)

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)