Tidal

Całkiem niedawno pisałem o Spotify, a tu nagle wpis o Tidal? Ano bywa i tak. Wszystko za sprawą opisywanej na Pepper okazji. Nadal aktywnej, zresztą. W skrócie: na każdym numerze w Plus (także karta!) można aktywować Tidal HiFi[1] na… rok. Za darmo, no strings attached[2].

Za darmo to uczciwa cena[3], więc stwierdziłem, że wypróbuję. Co prawda zarówno Tidal, jak i Spotify oferują pełną wersję za darmo na próbę, ale znam się na tyle, że wiem, że dłużej zajmie mi składanie playlisty. O ewentualnych zabawach z API nie wspominając. Kupiłem kartę SIM, przedłużyłem ważność, aktywowałem. Zadziałało od razu.

Po zalogowaniu zostałem odpytany o lubianą muzykę. Niestety, nie poświęciłem temu należytej ilości czasu, a szkoda. Nie znalazłem bowiem żadnego sposobu, by wrócić do uczenia, i trochę mnie zbąbelkowało. Powoli rozszerzam bąbel, ale… właśnie powoli. Zauważyłem, że serwis wygląda praktycznie identycznie jak Spotify. Sterowanie podobne, ceny podobne, funkcje podobne. Nawet podobne rozmieszczenie elementów, przynajmniej w web playerze.

Tidal wydał mi się jednak bardziej logiczny i intuicyjny. Np. funkcja trwałego pomijania utworu dostępna jest w web playerze na widoku, w postaci ikony przypominającej znak zakazu. Można wybrać blokadę utworu lub artysty. Jest to funkcja, której brakowało mi w Spotify, więc jak tylko zobaczyłem, że jest w Tidal, to poszukałem dokładniej. Okazało się, że i w Spotify jest! Nawet w podobnym miejscu, tyle, że ukryta dodatkowo warstwę głębiej w menu za „trzykropkiem”[4].

Jeśli chodzi o API to nie bawiłem się jeszcze w żadnym przypadku, ale z pobieżnego rozpoznania wyszło mi, że Tidal ma szczątkowe, Spotify niezłe, z całą oficjalną dokumentacją dla developerów. W najbliższym czasie mam zamiar przyjrzeć się możliwości synchronizacji playlist między serwisami, wtedy pewnie napiszę więcej. Zwłaszcza, jeśli nie znajdę gotowca.

Wersja family w obu przypadkach kosztuje tyle samo – 30 zł. Spotify miewa wsparcie w sprzęcie (np. głośniki WiFi) i daje możliwość sterowania różnymi urządzeniami z aplikacji. Nie wiem, czy Tidal także to oferuje i nie sprawdziłem, czy sprzęt korzysta z jakiegoś wspólnego standardu. Jeśli tylko ficzer obecny tylko w Spotify, bo byłby kolejny argument za wyborem tego właśnie serwisu.

Spotify to także większa ilość playlist. Znalazłem i coś w stylu wczoraj w Radio Nowy Świat, i podobną wersję dla Radio 357. Próżno tego szukać na Tidal.

Czyli teoretycznie wszystko przemawia za Spotify. Jednak prawda jest taka, że odkąd włączyłem Tidal, to zupełnie nie ciągnie mnie do powrotu do Spotify.

Tak czy inaczej, w kościach czuję, że nie da się zrobić automatycznej synchronizacji między nimi, więc serwis agnostyczne słuchanie raczej nie nastąpi i coś będę musiał wybrać. Prawdopodobnie wybiorę właśnie Spotify. A Tidal? Może zostanie tymczasowo muzą do pracy, w stylu osobnej playlisty?

[1] Czyli zwykły płatny pakiet, normalnie kosztujący 19,99 zł/m-c. Jest jeszcze dwukrotnie droższy HiFi Plus z niby jeszcze lepszą jakością i dodatkowymi bajerami.
[2] Jakby się kto pytał o zdanie, to czuję, że któraś z firm chce sobie szybko nabić ilość klientów. Np. przed sprzedażą.
[3] Niezupełnie za darmo, bo musiałem najpierw kupić starter za 5 zł, następnie przedłużyć mu ważność za kolejne 5 zł. No ale to pomijalne.
[4] Jakby te kropki były kreseczkami, to byłby, w gwarze frontendowców, hamburger. Jak się ten twór nazywa – nie wiem. Hamburger dietetyczny? Lód (gałkowy)? Kapanie?

Podobieństwo i porównywanie zbiorów

Przy okazji projekciku[1] trafiłem na interesujący problem. Chodzi o porównywanie zbiorów, a dokładnie liczenie ich podobieństwa. Marzyło mi się, by wyrazić je w zakresie 0 do 1, gdzie 0 to zbiory rozłączne, a 1 zbiory identyczne[2].

Popełniłem następujący kawałek kodu, liczący, na ile set2 jest podobny do set1:

def count_similarity_percent(set1, set2):
    base_count = len(set1)
    if base_count == 0:
        base_count = 1
    matching = 0
    for element in set1:
        if element in set2:
            matching += 1
    perc = 100 * matching / base_count
    return perc

Niby działa, ale… Spójrzmy na przykłady.
Przykład 1:

A = [1, 2, 3, 4]
B = [3, 4, 5, 6]

Podobieństwo A do B wynosi 50% (2 z 4 elementów ze zbioru A występują w B).
Podobieństwo B do A także wynosi 50% (2 z 4 elementów ze zbioru B występują w A).
Wszystko fajnie. Podobieństwa są równe, niezależnie z której strony patrzeć.

Niestety, jeśli zbiory będą miały różną ilość elementów, to sprawa się komplikuje i podobieństwo A do B przestaje być równe podobieństwu B do A. Możemy to zobaczyć w kolejnym przykładzie.
Przykład 2:

A = [1, 2, 3, 4]
B = [3, 4]

Podobieństwo B do A to nadal 50%.
Podobieństwo A do B wynosi 100% – 2 z 2 elementów A występują w B.

Nie spodobało mi się to i zastanawiałem się, co można z tym zrobić. Z jednej strony chciałem, by wartości były przechodnie, tj. równe, niezależnie z które strony porównuję.

Przez głowę przeleciało mi szybko liczenie średniej z podobieństw i zapamiętywanie w obu przypadkach tego wyniku. Podobnie mógłbym zapamiętywać w obu wynikach większą – lub mniejszą – z wartości podobieństwa. Z drugiej strony kołatało mi się, że fajnie nie byłoby liczyć dwa razy.

Poszukałem, popytałem i okazało się, że są do tego miary. W tym przypadku miarą podobieństwa zbiorów jest indeks Jaccarda[3] . Jest prosta i „przechodnia”, tj. nie ma znaczenia, który zbiór z którym się porównuje.

Znalazłem taki wpis o indeksie Jaccarda dla Pythona, ale przyznaję, że nie spodobała mi się implementacja. Po pierwsze, bez sensu importowane jest numpy, które w tym przypadku niczego nie robi. Po drugie, implementacja jest poprawna, ale nieco zakręcona. Lepiej implementować na setach, nie listach. I jak najbardziej można skorzystać z wbudowanego union, a następnie liczyć wprost, wg definicji.

def jaccard_index_percent(set1, set2):
    intersection_count = len(set1.intersection(set2))
    union_count = len(set1.union(set2))
    if union_count == 0:
        union_count = 1
    return 100 * intersection_count / union_count

Zapewne użyję ww. wersji do porównywania zbiorów. Chyba, że pokuszę się jeszcze kiedyś o optymalizację pod kątem prędkości działania. O ile zauważę taką potrzebę.

[1] Jeśli coś się z tego wykluje sensownego, to niebawem opiszę dokładniej.
[2] Od 0 do 1, od 0% do 100%, bez znaczenia, wartość jest taka sama. Choć IMO ludzie lepiej rozumieją procenty, a łatwiej kodować na float. Ale to wszystko nieistotny detal.
[3] Linkuję we wpisie polską wersję, która jest lakoniczna, ale prostsza. Jeśli ktoś jest bardziej zainteresowany tematem, to polecam wersję angielską, wraz z see also.