Trochę bardziej zaawansowane Linq

0

W ramach ćwiczeń Linq próbuję sobie rozwiązywać prostsze zadanka z hackerranka jednolinijkowcami. Ale nie jestem w stanie znaleźć jakichś bardziej zaawansowanych przykładów w internecie. Wszędzie są tylko opisy jak działa dana funkcja. Jeśli ktoś zna jakieś źródło takich przykładów to chętnie przygarnę.

A teraz mój problem.

Mam napisać funkcję, która na wejście przyjmuje dwie listy intów tej samej długości.
Funkcja ma zwrócić również listę intów, w której pierwszy wyraz ma mówić ile razy wyraz pierwszej listy był większy niż odpowiadający wyraz drugiej listy.
Drugi wyraz odwrotnie.
Jeśli odpowiadające sobie wyrazy są równe to nie zwiększamy żadnego z wyrazów listy wynikowej.

Przykład:

a = {1, 2, 3}
b = {2, 2, 3}

1 < 2
2 = 2
3 = 3

wynik = {0, 1}

Mam takie cuś:

public static List<int> Resolve(List<int> a, List<int> b)
        {
            var ar = a.Select(x => x).Count(x => x > b[a.IndexOf(x)]);
            var br = b.Select(x => x).Count(x => x > a[b.IndexOf(x)]);
            return new List<int> {ar, br};
        }

I nie mogę dojść do tego jak można użyć Linq, żeby jedno zapytanie od razu zwracało wynikową listę.

1

Poczytaj o Zip i Aggregate. I na MSDN są przykłady.

1

Tak jak kolega wyżej napisał plus ewentualnie możesz zjoinować po indeksach.

0

No o .Zip() czytałem, do tego doszedłem jak scalić te elementy list. Problem mam z inkrementacją jednego z dwóch elementów w zależności od spełnienia warunku.
I jak to zrobić bez wcześniejszej inicjalizacji wyjściowej listy z dwoma zerami?

1

Żebyś nie miał za prosto i mógł sobie przećwiczyć, to sobie zoptymalizuj ten overengineering :D

  List<int> a = new List<int> { 1, 2, 3, 1, 5, 8, 1 };
  List<int> b = new List<int> { 2, 2, 3, 4, 0, 1, 9 };

            var xxxxxx = a.Select((item, index) => new { item, index })
                        .Join(b.Select((item, index) => new { item, index }),
                          aItem => aItem.index,
                          bItem => bItem.index,
                          (aItem, bItem) => new { greater = aItem.item > bItem.item ? "a" : aItem.item == bItem.item ? "none" : "b" })
                        .Where(x => x.greater != "none")
                        .OrderBy(x => x.greater)
                        .GroupBy(x => x.greater)
                        .Select(x => x.Count());

            var wtf = a.Select((item, index) => new { item, index }).Aggregate(new Dictionary<string, int>(),
                (result, aListItemWithIndex) =>
                {
                    string key = aListItemWithIndex.item > b[aListItemWithIndex.index] ? "a" :
                        aListItemWithIndex.item < b[aListItemWithIndex.index] ? "b" : "none";
                    int value = aListItemWithIndex.item > b[aListItemWithIndex.index] ? 1
                        : aListItemWithIndex.item == b[aListItemWithIndex.index] ? 0 : 1;

                    if (!result.TryAdd(key, value))
                    {
                        result[key] += value;
                    }
                    return result;
                }).Where(x => x.Key != "none").OrderBy(x => x.Key).Select(x => x.Value);
1

To raczej nie jest kwestia użycia LINQ lecz wymyślenia jak z tych dwóch list zrobisz jedną, zawierającą informacje o tym, w której ze źródłowych list element był większy. Może w tym pomóc odejmowanie. ;)

1

Tak jak wyżej - zip i aggregate

"oneliner" (rozbity na linie dla komentarzy):

a.Zip(b, (x, y) => new // przyjmujesz parę elementów z kolekcji a i b jako (x, y)
    {
        x = x > y ? 1 : 0, // zwróć obiekt z dwoma własnościami mówiącymi kolejno czy x>y i czy y>x
        y = y > x ? 1 : 0
    })
    .Aggregate(
        new int[2], // typ zwróconego obiektu i zarazem wartość początkowa - pusta tablica dwuelementowa
        (result, row) => { result[0] += row.x; result[1] += row.y; return result; } // dodajemy wartości do wyniku i zwracamy ten sam obiekt
    );

albo wykorzystując odejmowanie

a.Zip(b, (x, y) => x - y) // zwracasz tylko różnicę elementów - ujemny wynik oznacza że y był większy od x
    .Aggregate(
        new int[2], // typ zwróconego obiektu i zarazem wartość początkowa - pusta tablica dwuelementowa
        (result, row) =>
        {
                result[0] += row > 0 ? 1 : 0;
                result[1] += row < 0 ? 1 : 0;
                return result;  // dodajemy wartości do wyniku i zwracamy ten sam obiekt
        }
    ).Dump();

z tym że odejmowanie tutaj to trochę hack i nie działa dla skrajnych wartości (np int.MinValue i int.MaxValue daje odwrotny wynik, możesz rzutować do longa dla precyzji)
miałem nie wrzucać gotowego rozwiązania ale po odpowiedzi urke poczułem jakby ktoś mi obrażał Linq
poćwicz na trudniejszym przykładzie, albo spróbuj skrócić jeszcze ten bo można to zrobić choćby kosztem pamięci z użyciem tupli

1

miałem nie wrzucać gotowego rozwiązania ale po odpowiedzi urke poczułem jakby ktoś mi obrażał Linq

Przecież to specjalnie po to żeby przećwiczył, bo już w pierwszym poście walnął tragiczny błąd logiczny, w którym porównuje po pierwszym indeksie danej liczby, zamiast po poprawnych indeksach.

Jak mam być upierdliwy i skoro i tak podałeś już rozwiązanie, to tutaj zip nie jest potrzebny


List<int> a = new List<int> { 1, 2, 3, 1, 5, 8, 1 };

List<int> b = new List<int> { 2, 2, 3, 4, 0, 1, 9 };

var x = a.Select((item, index) => new { a = item > b[index] ? 1 : 0, b = b[index] > item ? 1 : 0 }).Aggregate(new List<int>(2) { 0, 0 }, (result, curr) => { result[0] += curr.a; result[1] += curr.b; return result; });
1
obscurity napisał(a):
a.Zip(b, (x, y) => new // przyjmujesz parę elementów z kolekcji a i b jako (x, y)
  {
      x = x > y ? 1 : 0, // zwróć obiekt z dwoma własnościami mówiącymi kolejno czy x>y i czy y>x
      y = y > x ? 1 : 0
  })
  .Aggregate(
      new int[2], // typ zwróconego obiektu i zarazem wartość początkowa - pusta tablica dwuelementowa
      (result, row) => { result[0] += row.x; result[1] += row.y; return result; } // dodajemy wartości do wyniku i zwracamy ten sam obiekt
  );

Właśnie coś takiego próbowałem zrobić tylko nie wiedziałem jaka będzie składnia, zwłaszcza nie wiedziałem co dać jako 1szy argument .Aggregate().

0

Zip + Aggregate lub coś takiego

 a.Zip(b).GroupBy(x => new { }).Select(x => new 
            { 
                first = x.Count(s => s.First > s.Second), 
                second = x.Count(s => s.First < s.Second)
            });

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