Trochę bardziej zaawansowane Linq

Trochę bardziej zaawansowane Linq
Qbelek
  • Rejestracja:prawie 6 lat
  • Ostatnio:około godziny
  • Postów:105
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ś:

Kopiuj
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ę.

Tasmanian Devil
Hej! Twój post prawdopodobnie zawiera niesformatowany kod. Użyj znaczników ``` aby oznaczyć, co jest kodem, będzie łatwiej czytać. (jestem botem, ta akcja została wykonana automatycznie, prawdopodobieństwo 0.99958706)
SA
  • Rejestracja:ponad 12 lat
  • Ostatnio:około 6 godzin
  • Postów:1435
1

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

edytowany 1x, ostatnio: Saalin
UR
  • Rejestracja:około 5 lat
  • Ostatnio:prawie 3 lata
  • Postów:360
1

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

Qbelek
  • Rejestracja:prawie 6 lat
  • Ostatnio:około godziny
  • Postów:105
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?

UR
  • Rejestracja:około 5 lat
  • Ostatnio:prawie 3 lata
  • Postów:360
1

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

Kopiuj
  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);

somekind
No nie powiem, na Oscara za horror roku szanse są duże.
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:5 dni
  • Lokalizacja:Wrocław
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. ;)

obscurity
  • Rejestracja:około 6 lat
  • Ostatnio:około 2 godziny
1

Tak jak wyżej - zip i aggregate

"oneliner" (rozbity na linie dla komentarzy):

Kopiuj
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

Kopiuj
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


"A car won't take your job, another horse driving a car will." - Horse influencer, 1910
edytowany 4x, ostatnio: obscurity
UR
  • Rejestracja:około 5 lat
  • Ostatnio:prawie 3 lata
  • Postów:360
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

Kopiuj
           
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; });
edytowany 1x, ostatnio: urke
Zobacz pozostały 1 komentarz
UR
Kwestia zapisu. Można zapisac i tak a.Select((aIt, aInd) => aIt - b[aInd]).Aggregate(new int[2], (result, curr) => { result[0] += curr > 0 ? 1 : 0; result[1] += curr < 0 ? 1 : 0; return result; }); I jest wtedy takiej samej długości jak solucja z zipem. Co nie zmienia faktu, że ani jednej, ani drugiej jednolinijkowcem bym nie nazwał, bo linie na 160 + znaków.
Qbelek
Faktycznie! Po nazwie założyłem, że ta funkcja zwraca poprawny indeks i nie doczytałem dokumentacji. Ale po namyśle nie wiem jak miałoby to niby działać skoro przyjmuje inta jako argument. Mój błąd :c
somekind
O, wreszcie ktoś odkrył odejmowanie, o którym pisałem wczoraj. :) No i mimo wszystko z Zipem to jakoś ładniej wygląda niż z Selectem, w sensie Zip ładniej pokazuje intencje - że operujemy na dwóch równolicznych kolekcjach.
obscurity
@somekind: no pisałem o odejmowaniu w poprzednim poście, wygląda niby fajnie, ale tak naprawdę to zbugowany kod, który nie działa dla edge case'ów z przekroczeniem zakresu i nie nadaje się do stosowania w produkcyjnym kodzie. @urke: a.Zip(b,(x,y)=>new[]{x>y?1:0,y>x?1:0}).Aggregate((x,y)=>new[]{x[0]+y[0],x[1]+y[1]}) 84 znaki bez białych znaków i z olaniem pamięci, przyjmujesz jako jednolinijkowiec? byłoby 10 znaków krócej gdyby nie wymóg arraya jako outputu
somekind
No fakt, podejście mocno zależy od tego jakie mamy ograniczenia na dane wejściowe (a w takich zadankach zazwyczaj jest to sprecyzowane).
Qbelek
  • Rejestracja:prawie 6 lat
  • Ostatnio:około godziny
  • Postów:105
1
obscurity napisał(a):
Kopiuj
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().

PI
  • Rejestracja:prawie 5 lat
  • Ostatnio:ponad 4 lata
  • Postów:2
0

Zip + Aggregate lub coś takiego

Kopiuj
 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)
            });
edytowany 2x, ostatnio: Pioterr

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.