OR-tools

Niedawno xpil wrzucił zagadkę dotyczącą rozmieszczenia liczb na wierzchołkach dwunastościanu foremnego. Nieco rozochocony zeszłorocznym Advent of Code (który w znacznym stopniu odpuściłem, za wiele srok) stwierdziłem, że „to się zaprogramuje”.

Suma liczb na każdym boku była dość spora, ale istniało ograniczenie w postaci wymogu, że muszą być liczbami pierwszymi, więc może nie będzie tak źle? No bo na ile sposobów można wybrać pięć liczb z nieco ponad trzystu tak, by suma dawała określoną wartość? Otóż niestety na wiele i po wstępnej przymiarce wiedziałem, że brnę w ślepą uliczkę.

Przypomniałem sobie o Z3 solver, które bywa wykorzystywane w CTFach do rozwiązywania zadań i wyglądało trochę na szwajcarski scyzoryk. Tyle, że nie znam tego rozwiązania – nigdy nie znalazłem czasu, by się nauczyć. Ale od czego mamy AI? Porozmawiam z chatem, na pewno pomoże.

Rozmowę zacząłem jednak od problemu ogólnego, trochę licząc, że jest jakiś wyjątkowa właściwość lub algorytm dla tego dwunastościanu. Gdy poprosiłem o kod w Pythonie, ku mojemu zdziwieniu zaproponował rozwiązanie z użyciem nie Z3, tylko OR-tools. Zerknąłem i okazuje się, że Microsoft zrobił Z3, a Google zrobiło coś może mniej uniwersalnego, ale podobno szybszego, przeznaczonego do optymalizacji.

Przyznaję, że OR-tools robi dobre wrażenie. Podobnie jak Z3 nie jest proste i intuicyjne, ale po krótkiej chwili walki z chatGPT udało się złożyć program, który znalazł rozwiązanie. W bardzo krótkim czasie, rzędu kilkunastu sekund. Co ciekawe, algorytm jest niedeterministyczny. Rozwiązania nie podaję, bo jest na stronie z rozwiązaniem zagadki – na oko bardzo podobne. Jeśli komuś zależy to znajdę to co chatGPT zaproponował.

To teraz wypadałoby nauczyć się obu narzędzi, ale raczej nie znajdę na to czasu. Za to przynajmniej będę wiedział, że istnieją i co mniej więcej potrafią.

I ciekawostka. Wiecie co to jest „LUB-przykładowe narzędzia”? Jest to odpowiednik „OR-Tools Examples” w tłumaczeniu na oficjalnej stronie Google. To tak dla ustalenia, gdzie jesteśmy z automatycznymi tłumaczeniami. Chciałem napisać, „z AI”, ale chyba nie było tam wykorzystane – Gemini tłumaczy znacznie lepiej i całkiem sensownie.

Sprawdzanie IP

Kiedyś popełniłem wpis jak sprawdzić IP. Minęło trochę czasu i sposobów na sprawdzenie adresu IP komputera znalazło się nieco więcej. Niektóre przydatne, inne zabawne. Tak naprawdę są to sposoby na sprawdzanie z jakim adresem IP wychodzimy do internetu.

Zabawny sposób na sprawdzenie IP – pomoże nam kozioł (trzeba kliknąć kozła).

Polski sposób na sprawdzenie adresu IP – pomoże nam Wirtualna Polska podająca twojeip.

Łatwy do zapamiętania sposób na sprawdzenie adresu. A nawet trzy sposoby. Pod warunkiem, że jesteśmy anglojęzyczni… whatismyip.com, whatismyipaddress.com oraz ipaddress.my.

Sposób sprawdzenia zewnętrznego IP w skrypcie? ipconfig.sh lub ipinfo.io/ip.

Do skryptów przyda się także api.ipify.org, szczególnie, że umie JSON.

I to by było na tyle. Kiedyś, wiele lat temu, gdy powstawał szkic tego wpisu, działały sposoby na sprawdzanie adresu IP przez zapytanie DNS. Albo mniej lub bardziej zabawne strony, które podają adres IP w wersji audio. Coraz mniej tego zostało. Co więcej, nawet zapytania w wyszukiwarkach zwracają średnio adekwatne wyniki. Samych serwisów jest coraz mniej i coraz częściej są obwieszone reklamami jak choinki. No cóż, chyba sprawdzanie IP przestaje być potrzebne…

Dict, set, list!

Przy okazji niedawnego code review dostałem pytanie, czemu w skrypcie napisanym w Pythonie nie korzystam z obiektu typu set, tylko z dict. Chodziło o cache na kilka tysięcy elementów, odpytywany kilkaset tysięcy razy. Z przyzerowym hit ratio. Zdziwiłem się, bo kojarzyłem, że czytelna konstrukcja wykorzystująca in dla obiektu typu list

data = [x for x in range(1000)]
y = 1001
if y in data:
    print("Hit")

jest raczej wolna. Zwłaszcza w porównaniu z nieco mniej czytelnym wariantem z użyciem dict:

data = {x: True for x in range(1000)}
y = 1001
if data.get(y):
    print("Hit")

Odpisałem o co chodziło i usłyszałem, że przecież set jest szybki. Coś mi zaświtało. Bo niby set jest bardziej podobny w użyciu do listy, ale pod spodem ma parę ciekawych właściwości. Zresztą, gdy poprosimy LLM o optymalizację pod kątem szybkości, otrzymamy coś w stylu[1]:

data = {x for x in range(1000)} # Converted 'data' to a set
y = 1001
if y in data:  # Checking membership still works the same way
    print("Hit")

Różnice w czasie wykonania możemy zgrubnie i niezbyt elegancko sprawdzić w następujący sposób:

Jak już jesteśmy przy tego typu ciekawostkach. A co jeśli mamy w cache ciągi znaków i chcemy sprawdzić, czy nasz ciąg znaków nie kończy się jednym z ciągów z cache? LLM poproszony o optymalizację znowu podpowiada, że lepszy jest set. Ale nieoczekiwanie sugeruje też wykorzystanie regexpów. I co? Ku mojemu zaskoczeniu regexp w stylu

pattern = re.compile("("+"|".join(our_set)+")"$")
return bool(pattern.search(tested_value))

okazuje się najszybszym rozwiązaniem! Nie to, że uważam regexpy za szczególnie wolne, co to, to nie. Oczywiście pattern wykonujemy raz, nie dla każdej testowanej wartości.

Dalsza lektura:
O’reily High performance Python Dictionaries and Sets

[1] Tak, różnica niezbyt rzuca się w oczy przy takim zapisie. Mało widać różnicę między dict a set. Kiedyś już o tym wspominałem. Czytelniejszy zapis – którego chyba nikt nie używa – tamże.