Obliczenia warunkowe w kolekcji

0

Witam wszystkich, to mój pierwszy post więc chciałem się przywitać. Założyłem konto ponieważ nie mogę znaleźć rozwiązania na mój problem. Posiadam spore doświadczenie w C, ale niestety nie umiem sobie z tym poradzić w elegancki sposób. Otóż:

Załóżmy, że mam strukturę w klasie, lub klasę (nieistotne) która posiada pola (ID, Wartość, Nazwa). np:

private struct dane
    {
        /*00*/public double     ID;
        /*01*/public int        Wartosc;
        /*02*/public string     Nazwa;
    }

Tworzę kolekcje:

private List<dane> Lista = new List<dane>();

Dodaje jakieś tam pola do tej listy i chciałbym np:

  • policzyć średnią z pola Wartość dla tego samego numeru ID;

  • policzyć odchylenie std również dla tego samego pola ID;

  • wykonać jakąkolwiek inną operację dla danych w określonym zbiorze.

Na razie robię to w chamski prosty sposób, w pętli for, sprawdzając obecny i poprzedni ID, dodając interesujące mnie dane do innej kolekcji i licząc z niej to co chce.
Nie podoba mi się to rozwiązanie, ponieważ jest trochę skomplikowane i jest nieeleganckie. Nie znam wszystkich własności C# i szukam jakiegoś eleganckiego rozwiązania...

0
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication10
{
    // możesz również użyć Tuple<int, string>()
    // zamiast struktury.
    public struct Rate
    {
        public int value;
        public string description;
    }

    class DTO : Dictionary<int, Rate>
    {
        //przciążamy metodę Add abyś niemusiał pisać
        //data.Add(1, new Rate etc...);
        public void Add(int key, int value, string description)
        {
            var data = new Rate();
            data.value = value;
            data.description = description;
            Add(key, data);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var data = new DTO();
            data.Add(1, 5, "piątka");
            data.Add(2, 3, "trujka");
            data.Add(3, 4, "czwórka");
            data.Add(4, 6, "szóstka");

            //linq o którym mówił kolega w komentarzu.
            var selected = data.Skip(1).Take(2);

            foreach (var d in selected)
            {
                Console.WriteLine("Id {0} Wartość: {1} Opis {2} \r\n", d.Key, d.Value.value, d.Value.description);
                //średnia ocen
                //Odchylenie średniej
            }
            Console.ReadKey();
        }
    }
}
0

LINQ

0

Tworzenie struktur mutable to poważny błąd.

@somekind dlaczego powinno być klasą?

1

Bo nie ma powodu żeby było strukturą, ani nie spełnia warunków do uczynienia z tego tworu struktury.

0

Bo nie ma powodu żeby było strukturą

Nie widziałem, żeby autor przedstawił cały kod zatem skąd wiesz, że struktura tutaj się nie sprawdzi?

ani nie spełnia warunków do uczynienia z tego tworu struktury

Którego warunku nie spełnia?

1
śmieszek napisał(a):

Nie widziałem, żeby autor przedstawił cały kod zatem skąd wiesz, że struktura tutaj się nie sprawdzi?

Np. dlatego, że implementacja własnych struktur się nigdy jeszcze nie przydała początkującym, bo nie stykają się z problemami tej skali, którą mogą rozwiązać strukturami bez strzelania sobie w stopę.

Którego warunku nie spełnia?

Pierwszych trzech.

0

tak na szybko, pewnie można to ciekawiej i ladniej zrobić:

            double avarage = data.Average(x => x.Value.value);
            foreach (var d in data)
            {
                Console.WriteLine("Id {0} Wartość: {1} Opis {2} Odchylenie oceny {3} \r\n", 
                    d.Key, d.Value.value, d.Value.description, (avarage-d.Value.value).ToString());
            }

            Console.WriteLine("Srednia ocen {0} \r\n", avarage.ToString());
0

@somekind być może wymyśliłeś sobie własne zasady, ale zgodnie z MSDN nie ma problemu z taką strukturą.

0

Dzięki za odpowiedzi. Nie spodziewałem się aż takiego odzewu, co miło mnie zaskoczyło ;)

Dyskusja na temat struktur jest bezcelowa. Struktury to moja spuścizna po C. W systemach embedded gdzie liczy się optymalizacja i nie stosuje się jeżyków obiektowych, to struktury są jedynym rozsądnym miejscem do przechowywania danych, informacji o bibliotekach (klasach) itp. Całkiem prawdopodobnie struktura w moim przypadku to zły pomysł. Pewnie powinienem zrobić osobną klasę.

Do czego to jest?
Mój program wczytuje spore pliki tekstowe które zawierają dużą ilość danych rozmieszczonych w kolumnach, i wykonuje na nich operację matematyczne, porównania itp. Dla użytkownika końcowego liczy się wynik.
Stworzyłem strukturę zawierająca dane z jednej linii. Pola struktury są nazwane tak jak nazwy kolumn.
Program czyta linię (string) konwertuje na strukturę i dodaje do listy. Po przeczytaniu całego pliku mam listę struktur zawierającą dane z pliku. Może faktycznie słusznym rozwiązaniem jest stworzenie klasy reprezentującej jedną linię, a potem listę zawierającą obiekty klasy jednej linii?

Po przeczytaniu pliku muszę wykonać operację matematyczne np: obliczyć średnią 4-tej kolumny dla tych samych liczb z 2-giej kolumny np dla danych:

 12 FF 57686 234600  780 406 2425    -3866144    -16       27771    -17   26  39  124  -19   73   -9 34
 24 FF 57686 234600  780 774 2821      279237     12       27722     18   27  30   83   -2   51   -1 C7
 19 FF 57686 234600  780 517 1007     5238962     -7       27762    -14   24  59  103   -3   61   -1 F7
 25 FF 57687 001800  780 159 2528     2730479    -56       27752     12   46  22  292 -112  119  -17 4A
 17 FF 57687 001800  780 369  543     2031803   -102       27642   -109   63  65  135   17   78    7 13

Muszę policzyć średnią kolumny 10, ale tylko dla wartości 57686 w kolumnie 3. Mało tego, potem muszę policzyć średnią dla wartości 57687 itd.
Chcę żeby program robił to sam, i umieszczał wyniki np w innej liście.....

Mam nadzieje, że dobrze wytłumaczyłem. Co do LINQ - ciekawe rozwiązanie, nie za bardzo jeszcze rozumiem działanie tych operatorów....

0
śmieszek napisał(a):

@somekind być może wymyśliłeś sobie własne zasady, ale zgodnie z MSDN nie ma problemu z taką strukturą.

https://msdn.microsoft.com/en-us/library/ms229017(v=vs.110).aspx

X AVOID defining a struct unless the type has all of the following characteristics:
It logically represents a single value, similar to primitive types (int, double, etc.).
It has an instance size under 16 bytes.
It is immutable.
It will not have to be boxed frequently.

0
wojlej napisał(a):

Może faktycznie słusznym rozwiązaniem jest stworzenie klasy reprezentującej jedną linię, a potem listę zawierającą obiekty klasy jednej linii?

To wymaga jedynie zamiany struct na class w Twojej definicji. A pozwoli Ci uniknąć niezrozumiałych zachowań kodu, debugowania oraz problemów wydajnościowych w przyszłości. Rozumiem, że w C używałeś struktur, bo po prostu nie miałeś innego wyjścia na logiczne pogrupowanie wartości. W C# do tego celu używamy klas. Struktur używa się w wyjątkowych przypadkach, które @some_ONE podlinkował wyżej. Problem polega głównie na tym, że struktury są typami przekazywanymi przez wartość, więc każde ich przypisanie albo przekazanie do innej funkcji powoduje utworzenie kopii takiej struktury, co wiąże się z narzutem wydajnościowym. Ponadto, efekty przekazywania przez wartość (co tak naprawdę się zmieni, jeśli zmienimy w funkcji wartość pola struktury) potrafią być niezłą łamigłówką nawet dla doświadczonych programistów.

obliczyć średnią 4-tej kolumny dla tych samych liczb z 2-giej kolumny

Mniej więcej tak: lista.GroupBy(q => q.Kolumna2, q => q).Select(q => q.Average(x => x.Kolumna4))

Muszę policzyć średnią kolumny 10, ale tylko dla wartości 57686 w kolumnie 3.

To z kolei mniej więcej tak: lista.Where(q => q.Kolumna3 == 57686).Avg(q => q.Kolumna10);

Ogólnie LINQ jest bardzo przydatnym narzędziem do takich zadań. Ale z drugiej strony, jest też niezbyt wydajny.
Zależy Ci na wydajności? Skąd wiesz, jakie działania masz wykonać? Może należałoby wczytywać z pliku do jakiejś mądrzejszej struktury danych niż lista, albo np. liczyć średnie na bieżąco.

0

@some_ONE

  1. Struktura ma 16 bajtów,
  2. Jest osadzona w kolekcji generycznej zatem nie będzie boxingu,
  3. Nie jest readonly ale to tylko problem dla programisty a nie maszyny wirtualnej.
0

@somekind

Problem polega głównie na tym, że struktury są typami przekazywanymi przez wartość, więc każde ich przypisanie albo przekazanie do innej funkcji powoduje utworzenie kopii takiej struktury, co wiąże się z narzutem wydajnościowym.

Ciekawa teoria, trochę za dużo naczytałeś się pierdołowatych blogów. Referencja też jest przekazywany przez wartość zatem jeżeli można osadzić dane w 16 bajtowej strukturze to z punktu widzenia wydajności nie ma to znaczenia, czego użyjesz. Do tego alokacja na stercie jest wolna, szczególnie jak tworzysz wiele obiektów.

Ponadto, efekty przekazywania przez wartość (co tak naprawdę się zmieni, jeśli zmienimy w funkcji wartość pola struktury) potrafią być niezłą łamigłówką nawet dla doświadczonych programistów.

Myślę, że doświadczony programista nie dopuścił by do stosowania struktur mutable.

0
śmieszek napisał(a):

Ciekawa teoria, trochę za dużo naczytałeś się pierdołowatych blogów. Referencja też jest przekazywany przez wartość zatem jeżeli można osadzić dane w 16 bajtowej strukturze to z punktu widzenia wydajności nie ma to znaczenia, czego użyjesz. Do tego alokacja na stercie jest wolna, szczególnie jak tworzysz wiele obiektów.

Rozumiem panie praktyku, że tyle danych: 12 FF 57686 234600 780 406 2425 -3866144 -16 27771 -17 26 39 124 -19 73 -9 34 zmieścisz w 16 bajtach?

Myślę, że doświadczony programista nie dopuścił by do stosowania struktur mutable.

Ale autor nie jest doświadczonym programistą i pisze struktury immutable.
I kto tu teoretyzuje?

0

Ogólnie LINQ jest bardzo przydatnym narzędziem do takich zadań. Ale z drugiej strony, jest też niezbyt wydajny.
Zależy Ci na wydajności? Skąd wiesz, jakie działania masz wykonać? Może należałoby wczytywać z pliku do jakiejś mądrzejszej struktury danych niż lista, albo np. liczyć średnie na bieżąco.

Czy jest sens skupiać się na wydajności LINQ ?? Skoro w tym wypadku głównym problemem wydajnościowym będzie wczytanie danych ? Jeśli chodzi mu o wydajność w obliczeniach to nie lepszym wyborem będzię język C?

0

OP napisał wprost, że jego priorytetem jest elegancja. Elegancja a wydajność to są często kategorie rozłączne. Z jego wpisu wynika, że zrobić to wydajnie (iteracyjnie, czyli - jak mówi - "po chamsku") już potrafi.

0
WebJarek napisał(a):

Czy jest sens skupiać się na wydajności LINQ ??

Być może nie, ja tylko ostrzegam. :)

Skoro w tym wypadku głównym problemem wydajnościowym będzie wczytanie danych ?

Dlatego zasugerowałem mądrzejsze wczytywanie danych, może da się np. wczytać tylko potrzebną część danych, albo wczytać je do mądrzejszych struktur niż lista.

0

Zależy mi na elegancji, nie potrzebuje wydajności. W obecnej chwili zrobiłem to iteracyjnie, i nie podoba mi się to.

To wygląda ciekawie:

To z kolei mniej więcej tak: lista.Where(q => q.Kolumna3 == 57686).Avg(q => q.Kolumna10);

Problem polega na tym, że muszę zrobić tyle takich wywołań ile różnych wartości w kolumnie 3 np:

lista.Where(q => q.Kolumna3 == 57686).Avg(q => q.Kolumna10);
lista.Where(q => q.Kolumna3 == 57687).Avg(q => q.Kolumna10);
lista.Where(q => q.Kolumna3 == 57688).Avg(q => q.Kolumna10);
itd.

Czyli musiałbym jeszcze wcześniej sprawdzać ile jest takich wartości w pliku

0

Zrób sobie GroupBy na podstawie Kolumna3.

0

No, najpierw napisałeś Muszę policzyć średnią kolumny 10, ale tylko dla wartości 57686 w kolumnie 3.. No to tylko czy nie tylko takiej dokładnie wartości, zdecyduj się. :P

1

Już precyzuje.
Bawię się z tym LINQ i mam coś takiego:

Klasa kontener:

namespace Test
{
    class Kontener
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public double Liczba { get; set; }
        public int Wiek { get; set; }

        public Kontener(int ID, string Name, double Liczba, int Wiek)
        {
            this.ID = ID;
            this.Name = Name;
            this.Liczba = Liczba;
            this.Wiek = Wiek;
        }
    }
}

Program główny:

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            Random random = new Random();

            List<Kontener> lista = new List<Kontener>();
 
            lista.Add(new Kontener(1, "Wojtek", random.NextDouble() * 10, random.Next(1, 99)));
            lista.Add(new Kontener(2, "Zbigniew", random.NextDouble() * 10, random.Next(1, 99)));
            lista.Add(new Kontener(1, "Ola", random.NextDouble() * 10, random.Next(1, 99)));
            lista.Add(new Kontener(1, "Martynka", random.NextDouble() * 10, random.Next(1, 99)));
            lista.Add(new Kontener(1, "Wiesiek", random.NextDouble() * 10, random.Next(1, 99)));
            lista.Add(new Kontener(2, "Jola", random.NextDouble() * 10, random.Next(1, 99)));
            lista.Add(new Kontener(3, "Mirek", random.NextDouble() * 10, random.Next(1, 99)));
            lista.Add(new Kontener(2, "Zbyszek", random.NextDouble() * 10, random.Next(1, 99)));
            lista.Add(new Kontener(2, "Zbyszek", random.NextDouble() * 10, random.Next(1, 99)));
            lista.Add(new Kontener(2, "Zenek", random.NextDouble() * 10, random.Next(1, 99)));
            lista.Add(new Kontener(3, "Andrzej", random.NextDouble() * 10, random.Next(1, 99)));
            lista.Add(new Kontener(3, "Gosia", random.NextDouble() * 10, random.Next(1, 99)));
            lista.Add(new Kontener(3, "Ela", random.NextDouble() * 10, random.Next(1, 99)));
            lista.Add(new Kontener(1, "Maciek", random.NextDouble() * 10, random.Next(1, 99)));
            lista.Add(new Kontener(3, "Krysia", random.NextDouble() * 10, random.Next(1, 99)));

            foreach (Kontener k in lista)
            {
                Console.WriteLine(k.ID.ToString() + " " + k.Name + ", wiek: " + k.Wiek.ToString() + ", liczba: " + k.Liczba.ToString());
            }

            var srednia1 = lista.Where(p => p.ID == 1).Average(p => p.Liczba);
            var srednia2 = lista.Where(p => p.ID == 2).Average(p => p.Liczba);
            var srednia3 = lista.Where(p => p.ID == 3).Average(p => p.Liczba);

            Console.WriteLine(String.Empty);
            Console.WriteLine("Średnia dla 1: " + srednia1.ToString());
            Console.WriteLine("Średnia dla 2: " + srednia2.ToString());
            Console.WriteLine("Średnia dla 3: " + srednia3.ToString());

            var test =  lista.GroupBy(p => p.ID);
           
            Console.ReadKey();
        }
    }
}

Wartości: srednia1, srednia2, srednia3 - fajnie. O to mi chodziło, to znaczy policzone średnie z pola Liczba dla każdego numeru ID
W debugerze podejrzałem, że GroupBy zwraca mi 3 obiekty pogrupowane po ID - super.

Teraz jak połączyć GroupBy z liczeniem średniej? Różnych pól ID może być mnóstwo, więc chciałbym, żeby średnie dla każdego pola ID zostały wrzucone do innej kolekcji automatycznie.

P.S. Jak dodajecie formatowanie kody w C#? Znacznik "```c#" nie działa

0

var test = lista.GroupBy(p => p.ID).Select(x => x.Average(y => y.Liczba));

0

Działa...

Otrzymałem 3 wartości średnich. A teraz chciałbym zamiast średniej wykonać działanie:

k.Wiek - k.Liczba

Można zamiast Average zaimplementować własną funkcję?

P.S. Nasuneło mi się, że w tej tablicy średnich nie mam informacji, która średnia jest dla którego pola ID. Domyślam się, że rosnąco, ale jak można tę informację wyciągnąć?

0

No to wykonaj Select(k => k.Wiek - k.Liczba). I ogólnie poczytaj w jakimś tutorialu o różnych metodach, bo raczej nie będziemy Ci tu wszystkiego pisać. ;)

0

Już co raz lepiej mi idzie. Próbuje pogrupować w pewien sposób dane. Robię to w poniższy sposób ale może da się jakoś lepiej?
Muszę pogrupować dane po polu Index, ale tak żeby w jednej grupie były pola Index nie większe niż o 100 od pierwszego.
Brzmi to trochę skomplikowanie ale poniższy przykład działa.

UInt64 Index_first = data[0].Index;
List<List<common_line>> Temp = new List<List<common_line>>();
        do
        {
            Temp.Add(data.TakeWhile(p => p.Index - Index_first < 100).ToList());
            data = data.SkipWhile(p => p.Index - Index_first < 100).ToList();
            if (data.Count > 0)
                Index_first = data[0].Index;
            else
                break;
        } while(data != null);

1 użytkowników online, w tym zalogowanych: 0, gości: 1