Ten blog dalej żyje, ale w innym miejscu

Na wypadek, gdyby ktoś był zainteresowany czemu nie ma tutaj nowych wpisów, chciałbym poinformować, że są, tylko w innym miejscu:

blog.marcinpolkowski.com

Zapraszam!

| Komentarze

Kilka zdjęć z wyjazdu do USA

Zanim podzielę się zdjęciami parę zdań wstępu skąd się wziąłem na zachodnim wybrzeżu USA. Przed świętami wielkanocnymi brałem udział w ogromnej (>8k uczestników) konferencji nVidia GTC, która jak co roku odbywała się w centrum konferencyjnym w San Jose w Kalifornii. Sama konferencja to 4 intensywne dni, mnóstwo prezentacji, posterów i wydarzeń: od poniedziałku do czwartku. Konferencję miałem na oku od grudnia, ale finalnie zdecydowałem się jechać jakieś 2-3 tygodnie przed rozpoczęciem – trochę późno na szukanie okazyjnych przelotów i hoteli. Już na wstępnie poszukiwań optymalnego połączenia do Kalifornii okazało się, że najtaniej będzie polecieć w piątek i wrócić w następny piątek. Polecieć można było na 3 sposoby: bezpośrednio LOT-em do Los Angeles, do San Francisco przez Frankfurt albo do San Jose przez Londyn (San Jose to serce doliny krzemowej i leży 80 km od San Francisco). San Jose jak na dolinę krzemową przystało było dramatycznie drogie jeśli chodzi o hotele, więc musiałem “zamieszkać” gdzieś dalej. Szukając różnych opcji finalnie zdecydowałem się na hotel w centrum San Francisco i 4 dni dojazdów so San Jose (bezpośredni pociąg za $10.50). Skoro hotel w San Francisco to i lot do San Francisco. Jak do SF to tylko Lufthansa i przez Frankfurt (na prawdę rewelacyjne połączenie, testowane już 3 raz).

Pozostało pytanie jak zaplanować weekend przed konferencją. Początkowo myślałem o zwiedzeniu Los Angeles przejazd wypożyczonym autem do SF, ale skoro od razu miałem trafić do SF porzuciłem ten pomysł. Potem do głowy wpadł mi inny. Skoro mój samolot ląduje po południu w SF, a konferencja zaczyna się w poniedziałek rano, mam do dyspozycji dwa pełne dni. W SF byłem wcześniej dwa razy, więc chciałem zobaczyć coś innego. Zacząłem się zastanawiać, gdzie można się dostać samolotem z lotniska w SF. Możliwości było dużo, ale najbardziej atrakcyjny i szalony wydał mi się wypad na weekend na Hawaje. Skoro Hawaje i na krótko, to do największego miasta: Honolulu.

Sam wypad na weekend na Hawaje zdecydowanie zasługuje na oddzielny opis, który obiecuję napisać. Dziś chciałem się z Wami podzielić kilkoma ujęciami z Honolulu i SF. Do tej pory zdjęcia wrzucałem na instagram, ale ostatnio doszedłem do wniosku, że forma kwadratu jest okropnym ograniczeniem, więc kilka najlepszych ujęć wrzucam tutaj.

Moje ulubione zdjęcie z Honolulu zrobiłem drugiego (ostatniego) dnia rano ze szczytu krateru Diamond Head:

 
 
W San Francisco zrobiłem trzy wieczorne wypady na zdjęcia. Dwa z nich mogę uznać za bardzo udane: do punktu widokowego Battery Spencer przy moście Golden Gate i na wzgórze Twin Peaks. Kika najlepszych ujęć:

Widok na wejście do SF Bay, most Golden Gate i San Francisco:

 
 
Most Golden Gate po zachodzie słońca:

 
 
Widok na San Francisco i zatokę ze wzgórza Twin Peaks:

 
 
Widok na San Francisco ze wzgórza Twin Peaks po zachodzie słońca:

| Komentarze (3)

Czujniki smogu: moje vs. Airly – pierwsze porównanie

Jak już wcześniej wspominałem w Legionowie pod Warszawą działa 7 zbudowanych przeze mnie urządzeń do pomiaru ilości pyłu zawieszonego w powietrzu. Wszystkie pomiary zbiera i zgrabnie prezentuje strona internetowa zatruci.pl. Od samego początku działania strony słyszałem oficjalne głosy lokalnych władz o niskiej jakości pomiarów, rażących błędach, zawyżaniu wyników itp. Zresztą to trzeba zobaczyć:


W skrócie: mój tani czujnik jest nic nie warty, bo pokazał co innego niż jeszcze tańszy czujnik.

Z czasem sprawa trochę ucichła, ale tylko na chwilę. W lutym okazało się, że miasto zakupiło dwa czujniki od Airly. Airly jest liderem polskiego rynku (tak mi się przynajmniej wydaje) “tanich” czujników jakości powietrza. Z tym “tanich” to ostrożnie, bo za czujnik liczą 1500 zł netto + abonamet 50 zł / miesiąc. A co w zamian? Urządzenie mierzy stężenie pyłu zawieszonego, temperaturę, ciśnienie i wilgotność i przesyła dane na serwer. Do transmisji wykorzystuje łączność komórkową – stąd miesięczny abonament. Może więc za tą cenę dostajemy jakiś bardziej wypasiony laserowy sensor pyłu zawieszonego? Nie znalazłem nigdzie informacji jakiego sensora używa Airly, ale wydaje mi się, że widać to na ich filmie promocyjnym (szczególnie od 28 sekundy filmu widać moduł sensora na dwóch ujęciach):

Moim zdaniem jest to czujnik PMS5003. Jest bardzo podobny do tego, którego wykorzystuję w swoich urządzeniach (SEN0177). Na czym polega przewaga Airly? Na tym, że ich zdaniem ich czujniki są skalibrowane aby zapewniać zgodność z profesjonalnymi urządzeniami wykorzystywanymi przez GIOŚ. Wielokrotnie słyszałem zarzut, że moje czujniki nie biorą pod uwagę wilgotności powietrza i zliczają kropelki skondensowanej wody jako pył. Moim zdaniem, jeśli wilgotność miałaby mieć wpływ na pomiar to przez pęcznienie wilgotnych cząstek, a nie jakieś mityczne kropelki. Więc jak to jest z Airly? Sami piszą na swojej stronie trzy ciekawe rzeczy:

  1. Jaki jest błąd pomiaru? Zakres pomiaru dla pyłu PM2.5 podany przez producenta wynosi 0-500 μg/m³. Dokładność pomiaru podana przez producenta wynosi ±10%@100-500 μg/m³ oraz ±10 μg/m³@0-100 μg/m³.
  2. Jak urządzenie reaguje na wilgoć? Czy kondensacja pary wodnej nie powoduje, że wyniki są przez to mniej dokładne? Podczas analizy porównawczej za stacją WIOŚ nie wykazano wpływu temperatury, wilgotności, ciśnienia oraz kondensacji pary wodnej na wyniki pomiarów.
  3. Czemu wasze czujniki mają wyższe/niższe odczyty niż znajdujące się obok stacje pomiarowe Wojewódzkiego Inspektoratu Ochrony Środowiska? (…) Nasze urządzenia były kalibrowane przez okres 12 miesięcy ze stacją WIOŚ.

W skrócie: wilgotność nie jest brana pod uwagę, a dane są “normowane” na podstawie porównania z czujnikami WIOŚ. Jak rozumiem porównanie zostało wykonane dla jakieś grupy sensorów, a dla reszty jest wykorzysytne dzięki założeniu, że wszystkie sensory tego producenta działają tak samo. Nic o tej normalizacji nie wiemy. Nie wiemy również jak liczone są wyniki prezentowane na ich stronie – z jakiego czasu są uśredniane, w jaki sposób itp.

Miasto wykorzystało jeden czujnik Airly do weryfikacji moich czujników – ustawiali czujnik na kilkanaście godzin obok moich czujników w różnych częściach miasta. Pomysł fajny, niestety Airly udostępniło dane z tych pomiarów z wartościami co 60 minut – bardzo rzadko! Mimo tej niedogodności krótkie spojrzenie na dane pokazuje, że moje czujniki pokazują wyższe stężenia.

Równolegle do tych testów jedna z osób, która kupiła ode mnie czujnik kupuje prywatny czujnik Airly i instaluje go bezpośrednio przy tym ode mnie. Ze swoich czujników mam każdy indywidualny pomiar co kilkaście sekund. Z Airly mam ich przeliczony / poprawiony / normalizowany wynik zapisany co 15 minut. Porównajmy zapisy z kilku ostatnich dni, kiedy czujniki stoją koło siebie.

Porównanie 1: Pomiar Airly vs. indywidualne pomiary z mojego czujnika:

Porównanie 2: Pomiar Airly vs. uśrednione pomiary z mojego czujnika (średnia z poprzedzających 45 minut):

Ponieważ kształt obydwu krzywych jest bardzo podobny zacząłem się zastanawiać jaki najprostszym sposobem uda mi się uzgodnić obie krzywe. Po kilku iteracjach doszedłem do zadowalającego wyniku. Pomnożyłem moje pomiary (już po uśrednieniu) przez współczynnik, którego wartość zależy od stężenia pyłu: dla stężeń od 0 do 20 µg/m3 współczynnik jest równy 1. Dla stężeń od 20 do 90 µg/m3 maleje liniowo od 1 do 0.6. Dla stężeń od 90 do 1000 µg/m3 maleje liniowo od 0.6 do 0.5. Oto wynik porównania po przeliczeniu:

Wychodzi pięknie, ale jaki z tego mamy wniosek? Tu mam problem. Skoro Airly do uzgodnienia pomiarów ze stacjami WIOŚ nie używa temperatury, wilgotności i ciśnienia, to zostaje im współczynnik zależny od samego stężenia. Analogiczny jak opisany powyżej. Pytanie czy takie działanie jest legalne. Dlaczego małe laserowe czujniki miałby tak systematycznie zawyżać pomiary? Który pomiar jest prawdziwy i wiarygodny? Pytanie zostawiam na chwile otwarte, ale obiecuję do tego wrócić!

| Komentarze (2)

Budujemy własny czujnik smogu #1

Obiecałem podzielić się z Wami dokładną instrukcją budowy własnego czujnika smogu. Chciałbym skierować tą instrukcję do osób, które nie zajmują się zawodowo elektroniką i IT, ale jakieś śladowe pojęcie o powyższych posiadają. Są trzy etapy budowy czujnika: pozyskanie podzespołów, zbudowanie czujnika, zaprogramowanie. Takie też będą trzy części tego artykułu.

Cały projekt może być zmodyfikowany na wiele sposobów. Tutaj będę chciał Wam pokazać rozwiązanie moim zdaniem optymalne pod względem kosztów, jakości i wytrzymałości konstrukcji.

Potrzebne elementy czujnika

  1. Sensor pyłu zawieszonego: na rynku dostępnych jest wiele czujników laserowych mierzących stężenie pyłu zawieszonego w powietrzu. Testowałem kilka z nich i zdecydowałem się na wykorzystanie sensora SEN0177 formy DFROBOT. Sensor można zamówić w Polsce (Botland, TME) lub bezpośrednio u producenta (DFROBOT).
  2. Moduł komunikacyjny: serce naszego urządzenia. Tutaj mamy dwa wyjścia:
    1. komputer z rodziny Raspberry Pi (najlepiej Raspberry Pi Zero – ze względu na rozmiar). Moduł można kupić w wielu miejscach, np. tutaj. Raspberry PI działa pod kontrolą linuxa, więc jest bardzo uniwersalne. Łączy się ze światem przez WiFi, ale dzięki obecności złącza USB można podłączyć np. modem 3G. Raspberry będzie proste do uruchomiania i programowania, ale potrzeba nabyć sporo umiejętności, by zrobić z niego urządzanie, które będzie mogło bezawaryjnie działać długi czas (niedługo napiszę o tym więcej).
    2. moduł komunikacyjny oparty o układ ESP32. Na rynku różnych modułów komunikacyjnych jest kilkadziesiąt rodzajów, ale ja wybrałem jedną rodzinę, którą i Wam polecam. Chodzi mi o produkty firmy PyCom, w szczególności och najprostszy moduł: WiPy 2.0 IoT ESP32. Jest to małe urządzenie wyposażone w łączność WiFi, które programujemy w Pythonie. Moduł można kupić w botlandzie. Ja do swoich czujników wybieram właśnie ten moduł – i Wam też polecam.
  3. Obudowa: budując czujnik smogu stajemy przed sporym problemem – musimy zbudować urządzenie odporne na wilgoć, ale przewiewne. Na pierwszy rzut oka trudno pogodzić te dwie właściwości. Najlepszym rozwiązaniem wydaje się obudowa radiacyjna – jest to rodzaj obudowy meteorologicznej, do osłaniania czujników przed czynnikami takim jak światło słoneczne i woda bez zaburzania swobodnego przepływu powietrza. Po długich poszukiwaniach udało mi się znaleźć bardzo dobrą i tanią obudowę pod hasłem osłona czujnika stacji meteorologicznej.
  4. Dodatkowe sensory: w wersji podstawowej czujnika dodatkowe sensory nie są potrzebne, ale jak jest możliwość to warto rozszerzyć pomiar o temperaturę, ciśnienie i wilgotność. Tutaj wybrałbym coś zintegrowanego i koniecznie cyfrowego z dobrą komunikacją: np. coś takiego.
  5. Zasilanie: urządzenie będzie potrzebowało zasilania 5V, więc potrzebujemy zasilacz: ja wybieram taki zasilacz z TME. Do zasilacza warto kupić przedłużacz, który pozwoli na pozostawieniu zasilacza w budynku i przepuszczenie cienkiego kabla np. poprzez przytrzaśniecie w oknie.
  6. Drobne elementy: potrzebujmy jeszcze złącze zasilania (ja używam takiego – ważne, żeby pasowało do zasilacza), paczkę przewodów z końcówkami (można bez nich, ale ułatwiają życie) i listwę goldpin, żeńską, precyzyjną. Przydadzą się również koszulki termokurczliwe.

Potrzebne narzędzia itp.

  1. Coś do cięcia przewodów i zdejmowania izolacji
  2. Lutownica + cyna
  3. Pistolet i klej na gorąco
  4. Do prawidłowego zaprogramowania modułu WiPy potrzebna jest podstawka za 99 zł.

Podsumowanie kosztów

  1. Czujnik pyłu: 269.00 zł
  2. Moduł komunikacyjny: 135.00 zł
  3. Obudowa: 48.60 zł
  4. Dodatkowe sensory: 113.90 zł
  5. Zasilacz: 29.91 zł
  6. Przedłużacz: 20.17 zł
  7. Złącze zasilania: 6.17 zł

Razem wychodzi 622.74 zł brutto (z dodatkowych czujników można zrezygnować – wtedy wyjdzie ok. 500 zł).

W momencie pisania tego wpisu mam oczekujące zamówienia na 3 czujniki, więc zamawiam podzespoły i przy montażu zrobię dla Was instrukcję – zapraszam za ok. tydzień.

| Komentarze (13)

Powrót po nieco krótszej przerwie #2

Moi mili czytelnicy,

IMG_0452Półtora roku temu napisałem “(…) najnowsze postanowienie o powrocie do pisania na tym blogu (…)”. Przez pół roku udało mi się dotrzymywać postanowienia i publikować fajny content. Niestety potem brak czasu spowodował, że pominąłem kilka tygodnia, aż doprowadziłem do ponad rocznej przerwy. Mea culpa, mea maxima culpa!. Może była to kwestia wyczerpania ciekawych tematów – dalej trzymam się tego, że nie chcę Was tutaj zanudzać głupotami. Ma być ciekawie i na temat. Po co ta spowiedź? Wracamy do pomysłu minimum jednego wpisu na tydzień.

Zapraszam za kilka dni. Przez najbliższy czas będziemy tematycznie oscylować wokół smogu – temat z powodu sezonu jest bardzo gorący.

| Komentarze

Smogowy armageddon

Przez ostatnie dni media zdominował temat tragicznej jakości powietrza w polskich miastach. Nie jest to problem wirtualny, bo wystarczy wyjść na dwór by zobaczyć i poczuć marną jakość powietrza. Do wytworzenia smogu potrzebujemy dwóch czynników. Pierwszy to produkcja szkodliwych pyłów i zanieczyszczeń, która zimą jest mocno nasilona ze względu na sezon grzewczy, szczególnie w indywidualnych instalacjach C.O. z piecem na paliwo stało (drewno, węgiel), często opalanym odpadami. Elektrociepłownie, mimo, że opalane węglem wydalają proporcjonalnie mniej zanieczyszczeń na kW energii ze względu na zastosowane w instalacji filtry. Drugim, niezbędnym do powstania smogu warunkiem jest wystąpienie warstwy inwersyjnej w atmosferze, która blokuje wymianę powierza przy ziemi, z tym powyżej granicy inwersji. Jeżeli wysokość warstwy inwersyjnej wynosi 100-200 metrów, to znajdujemy się w zamkniętym “słoiku”, do którego wydalamy ogromne ilości zanieczyszczeń. Tyle skróconej teorii, przejdźmy do praktyki.

Wykorzystując bardzo duże stężenie pyłów w Warszawie postanowiłem sprawdzić jak problem wygląda z góry. Udało mi się wykonać dwa bardzo wymowne ujęcia:

Zdjęcie po lewej przedstawia kominy elektrociepłowni Siekierki i warszawskie drapacze chmur w tle. Zdjęcie po prawej to widok na centrum Warszawy z okolicy mostu Gdańskiego. Niestety zdjęcia nie pokazują chmur a właśnie zanieczyszczenia, którymi byliśmy zmuszeni oddychać.

Równolegle uruchomiłem pomiar stężenia pyłów w powietrzu na zewnątrz domu w Legionowie. Zakupiłem laserowy czujnik i połączyłem go z RaspberryPi. Czujnik wisi sobie bezpośrednio za oknem i komunikuje się z jeżyną przez interfejs szeregowy:

Każdy pomiar daje trzy wyniki odpowiadające pyłom o różnych frakcjach, w tym “najpopularniejszy” PM2.5. Program odbierający pomiary napisany w Pythonie:

import serial
from time import gmtime, strftime

port = serial.Serial("/dev/serial0", baudrate=9600, timeout=1.5)
data = port.read(32);

f = open("/home/pi/" + strftime("%Y-%m-%d", gmtime()) + ".txt", "a")
if ord(data[0]) == 66 and ord(data[1])==77:
	suma = 0
	for a in range(30):
		suma += ord(data[a])

	if suma == ord(data[30])*256+ord(data[31]):
		
		PM01 = str(ord(data[4])*256+ord(data[5]))
		PM25 = str(ord(data[6])*256+ord(data[7]))
		PM10 = str(ord(data[8])*256+ord(data[9]))
		str = strftime("%Y-%m-%d %H:%M:%S", gmtime()) + "\t" + PM01 + "\t" + PM25 + "\t" + PM10;
		f.write(str + "\n")
		print(str)
	
	
port.close()
f.close()

Powyższy kod uruchamiany jest co minutę. Dane zapisywane są w zwykłym pliku tekstowym:

2017-01-09 20:43:02	185	470	729
2017-01-09 20:44:02	181	440	682
2017-01-09 20:45:02	175	460	701
2017-01-09 20:46:02	172	445	725
2017-01-09 20:47:02	175	448	680
2017-01-09 20:48:02	177	449	725
2017-01-09 20:49:02	170	450	754
2017-01-09 20:50:02	176	458	746
2017-01-09 20:51:02	165	441	733
2017-01-09 20:52:02	174	434	707
2017-01-09 20:53:02	170	451	715
2017-01-09 20:54:02	170	436	702

Następnie raz na 15 minut uruchamiany jest kod rysujący wykres. Dla poprawy czytelności rysowane są średnie kroczące z pomiarów, a nie same pomiary:

import numpy
import ftplib
from ftplib import FTP
import sys
from matplotlib.dates import strpdate2num
from datetime import datetime, timedelta
import matplotlib

matplotlib.use('Agg')
import matplotlib.pyplot as plt
date = sys.argv[1];
X = numpy.loadtxt('/root/AIR/OLSZANKOWA50A/'+date+'.txt', delimiter='\t', dtype=object, converters={0: lambda x: datetime.strptime(x.decode("utf-8"), "%Y-%m-%d %H:%M:%S"), 1: numpy.float, 2: numpy.float, 3: numpy.float})

window = 900;
step = 60;
ts = min(X[:,0])
tss = datetime(ts.year, ts.month, ts.day)
t = tss;
te = t + timedelta(days=1)

T = []
PM01 = []
PM25 = []
PM10 = []
while t < te:
	t = t + timedelta(seconds=step)
	idx = numpy.logical_and(X[:,0] > (t - timedelta(seconds=window)), X[:,0] < (t + timedelta(seconds=window)))
	#print(t, sum(idx))
	T.append(t)
	if sum(idx) > 0:
		PM01.append(numpy.mean(X[idx,1]))
		PM25.append(numpy.mean(X[idx,2]))
		PM10.append(numpy.mean(X[idx,3]))
	else:
		PM01.append(numpy.nan)
		PM25.append(numpy.nan)
		PM10.append(numpy.nan)


x = []
label = []
for a in range(0,24,1):
	x.append(tss+timedelta(hours=a))
	if a%2 ==0:
		label.append((tss+timedelta(hours=a)).strftime('%H:%M:%S'))
	else:
		label.append('')

plt.figure(figsize=(20, 10))
plt.plot(T,PM01, 'b', lw=2, label='PM 1.00')
plt.plot(T,PM25, 'r', lw=4, label='PM 2.50')
plt.plot(T,PM10, 'g', lw=2, label='PM 10.0')
plt.xlabel('Time [UTC]')
plt.ylabel('ug/m3')
plt.legend(loc='upper left', shadow=True)
plt.xlim([ts, te])
plt.ylim([0, (numpy.ceil(max(PM10)/50)+1)*50])
plt.grid()
plt.title(date)
plt.xticks(x,label, rotation=0)
plt.savefig('/root/AIR/OLSZANKOWA50A/'+date+'.png',dpi=300)

File2Send = '/root/AIR/OLSZANKOWA50A/'+date+'.png'
Output_Directory = "/public_html/parking-legionowo.pl/AIR/" 


ftp = FTP('***************')
ftp.login('***************', '***************') 
file = open(File2Send, "rb") 
ftp.cwd(Output_Directory)
ftp.storbinary('STOR ' +date+'.png', file) 
print("STORing File now...")
ftp.quit() 
file.close() 
print("File transfered")

Po przygotowaniu wykresu jest on zapisywany do pliku png i wysyłany na serwer, gdzie można na bieżąco podglądać pomiary.

Tak wygląda normalny dzień “bez smogu” (te chwilowe wzrosty to prawdopodobnie momenty, gdy sąsiedzi dokładają węgla do starego pieca):

A tak wygląda dzień “smogowy” (akurat wtedy robiłem zdjęcia pokazane na początku):

Aktualne pomiary można zobaczyć na stronie sponsora: http://parking-legionowo.pl/AIR/

| Komentarze (6)

Nowy rok z lotu ptaka

Żeby zrobić ładnie zdjęcie dronem trzeba trafić na dobrą pogodę (nie może padać, zbyt mocno wiać, itp.) i dobrą porę dnia (najlepiej okolice wschodu i zachodu słońca, albo ładnie chmury). Są takie wydarzenia, które zdarzają się jeszcze rzadziej, a warte są uwiecznienia za pomocą drona: np. sylwester. Tylko raz w roku jest szansa sfotografowania i nagrania zmasowanego ataku fajerwerkowego. W tym roku w godzinę 0 panowała idealna do lotów pogoda: nie padało, nie wiało, a widoczność była rewelacyjna. Udało mi się wykonać kilka ładnych zdjęć i nagrać film:

Często pytacie mnie o fotografowanie nocą i długie ekspozycje za pomocą drona, więc postaram się niedługo odpowiedzieć Wam przez film na YouTube. Na razie zapraszam na film z nowego roku:

Jeżeli podobają się Wam moje zdjęcia i filmy to obserwujcie mnie na Instagramie (https://www.instagram.com/m.polkowski/) i subskrybujcie na YouTube (https://www.youtube.com/channel/UC0rcoI-FVLZjhIgTgfQpl9Q). Zapraszam!

| Komentarze

Świątecznie zadanie dla studentów

Co roku z okazji zbliżających się świąt proponuję studentom wstępu do programowania w pythonie na 1 roku w ramach kartkówki zadanie odmóżdżające polegające na przygotowaniu wykresu o tematyce świątecznej. Święta zbiegają się mniej więcej z wprowadzeniem do biblioteki matplotlib, więc i zadanie nie jest mocno naciągane od strony merytorycznej. Gdy dawałem to zadnie pierwszy raz byłem przekonany, że jest to z mojej strony prezent w postaci darmowego punktu do zaliczenia. Niestety co roku zdarzają się osoby, dla których wymyślenie współrzędnych kolejnych wierzchołków łamanej w kształcie choinki jest zbyt trudne.

Zadanie to ma jeszcze jeden ukryty cel – bardzo ładnie pokazuje czy dany student jest dobrym materiałem na fizyka-programistę:

  • Są osoby, które każdy element “rysunku” tworzą oddzielnie – nawet elementy choinki bywają narysowane jako pojedyncze proste. Gwiazdki / bombki również są w takich przypadkach rysowane przez wielokrotne wywołanie plot(). Autorzy takich prac z reguły mają duży problem z zaliczeniem tego prostego i wprowadzającego kursu.
  • Standardem są rozwiązania odrobinę bardziej przemyślane, gdzie wierzchołki łamanych i dodatkowe elementy są definiowane jako listy. O ile sam sposób rysowania może być przemyślany, o tyle współrzędnej dalej są wpisane ręcznie w kod programu. Większość autorów takich rozwiązań nie ma problemu z zaliczeniem przedmiotu na czwórkę.
  • Najrzadsze i najlepsze rozwiązania uwzględniają różnego rodzaju reguły matematyczne: np. funkcja generująca współrzędne łamanej tworzącej choinkę przyjmująca ilość gałęzi i wysokość jako parametry. Taka rozwiązanie spotkałem nie częściej niż raz na rok. Autorzy zaliczają zawsze na piątkę i nie mają problemów z pozostałymi (trudniejszymi) przedmiotami.

Najważniejsze w tym zadaniu jest nie sugerowania ani efektu końcowego, ani sposobu rozwiązania. Dzięki temu uruchamiana jest kreatywność (lub jej całkowity brak) i podświadome umiejętności programistyczne: czy dana osoba traktuje komputer jak ślepego wykonawce poleceń, czy jako maszynę która potrafi postępować na podstawie algorytmu.

Myślę, że mogłoby to być bardzo sprytne zadanie rekrutacyjne. Moi studenci dostawali na rozwiązanie 15-20 minut. Oto kilka przykładów:

| Komentarze

Na jakiej wysokości latają samoloty?

Analizując przepisy odnośnie ograniczeń lotów bezzałogowców w obrębie lotnisk zacząłem się zastanawiać na jakich faktycznie wysokościach latają samoloty rejsowe podczas startów i lądowań.

Wybrałem sobie jeden dzień (zeszły poniedziałek) i zebrałem informacje o wszystkich lotach nad Polską z tego dnia. Plik z danymi z całego dnia zajmuje jakieś 370 mb i wygląda mniej więcej tak:

bbc8f8f,3C5432,55.0600,11.8611,356,34000,354,5412,F-EKEB2,A306,D-AEAR,1480291202,LEJ,OSL,QY3314,0,0,BCS3314,0
bbc663a,394A0B,54.5979,11.8800,52,32000,401,0652,F-EKKL1,B77W,F-GSQL,1480291202,CDG,HND,AF274,0,0,AFR274,0
bbc879c,738076,49.0065,11.9041,120,37000,512,4753,F-ETHF1,B772,4X-ECF,1480291203,LHR,TLV,LY318,0,0,ELY318,0
bbc95e8,40066A,53.9157,12.4301,5,24000,349,6450,F-EDDT2,B752,G-BMRH,1480291202,LEJ,CPH,QY178,0,0,BCS178,0
bbbe339,,50.2147,12.6663,292,40025,385,3253,F-LKPD1,CL60,,1480291203,,,,0,0,CL60,0
bbc715e,406F01,49.8304,12.6811,16,35975,413,1000,F-LKVO1,A320,G-EZOZ,1480291203,BCN,SXF,U24536,0,64,EZY81TC,0
bbc86f4,424293,48.7195,13.1555,114,38975,516,2223,F-LKVO1,B788,VP-BBR,1480291203,LHR,GYD,J28,0,-64,AHY8,0
bbc858c,894070,50.5559,13.2681,112,35000,491,2246,F-LKVO1,A320,A9C-AO,1480291203,LHR,BAH,GF6,0,-64,GFA6,0

Mamy kod lotu, współrzędne geograficzne, wysokość, czas, lotnisko z którego wystartował, lotnisko docelowe, model samolotu itp. Komplet danych załadowałem do Matlaba. W pierwszej kolejności wyrysowałem sobie wszystkie dane:

mapa0

Sam ogrom informacji na mapie robi wrażenie, wyraźnie widać duży ruch w okolicach Okęcia. Mieliśmy rozmawiać o wysokościach, więc zaznaczam na mapie pozycje kolorami w zależności od wysokości lotu. Zielone to wysokości od 3 do 2 km, żółte od 2 do 1 km, pomarańczowe od 1 km do 500 m i czerwone poniżej 500 m:

mapa1

Widać, że obszar wokół lotnisk gdzie samoloty latają nisko jest stosunkowo duży. Dla każdego lotniska należałoby przeprowadzić oddzielną analizę. Ja zainteresowałem się Warszawskim Okęciem. Na wykresie naniosłem wysokość lotu (nad poziom terenu) w zależności od odległości w km od lotniska (dla Okęcia liczyłem odległość od przecięcia pasów startowych). Zbiór danych jest bardzo duży, a ich dokładność różna, ale wyraźnie widać różnicą pomiędzy profilem starów (czerwone) i lądowań (niebieskie):

wykres1

Wyraźnie widać, że w zależności od długości lotu samoloty osiągają różne wysokości przelotowe.

I to co najbardziej interesujące – zbliżenie na samo lotnisko:

wykres2

Wyraźnie widać, że podczas lądowań samoloty znajdują się dłużej i dalej na małej wysokości. Wydaje się to być zgodne ze zdrowym rozsądkiem. Jakby ktoś był zainteresowany mogę zrobić wykres dla innego lotniska. Bardzo ciekawe jest to zagięcie podczas lądowań równo 15 km od lotniska – może jakiś znawca tematu wyjaśni, czemu to tak wygląda. Wydawało by się, że powinno być łagodne rozpoczęcie zniżania a tu wydaje się gwałtowne.

Dla lubiących kod dziś mam fragment Matlaba rysujący wykres pokazany powyżej:

clear;clc;
T = readtable('all2.txt');

%%
airport1 = table2array(T(:,13));
airport2 = table2array(T(:,14));

flight_lon = table2array(T(:,4));
flight_lat = table2array(T(:,3));
flight_elev = table2array(T(:,6));
flight_code = table2array(T(:,1));
flight_elev = flight_elev * 0.3048;

%%
D = distance(52.165802, 20.967240, flight_lat(idx), flight_lon(idx));
D = deg2km(D);

idxs = strcmp(airport1,'WAW');
idxl = strcmp(airport2,'WAW');

Ds = deg2km(distance(52.165802, 20.967240, flight_lat(idxs), flight_lon(idxs)));
Dl = deg2km(distance(52.165802, 20.967240, flight_lat(idxl), flight_lon(idxl)));

clf
hold on
subplot(2,1,1)
plot(Dl,flight_elev(idxl)/1000,'b.')
title('Planes landing @ WAW')
xlabel('Distance [km]')
ylabel('Elevation [km]')
subplot(2,1,2)
plot(Ds,flight_elev(idxs)/1000,'r.')
title('Planes taking off @ WAW')
xlabel('Distance [km]')
ylabel('Elevation [km]')

print(gcf, '-r360', '-dpng', 'wykres1.png');

| Komentarze (3)

Prosty problem jako zadanie z programowania

Każdy pomysł na fajne zadanie z programowania jest bardzo cenny. Dobre zadanie dla studentów powinno być ciekawe i powiązane z jakimś życiowym problemem, tak aby łatwiej było się skupić na samej istocie programowania a nie na samym problemie. Sam chodziłem kiedyś na zajęcia, gdzie prowadzący wymyślał abstrakcyjne problemy, które łatwo było oprogramować, ale bardzo trudno było zrozumieć o co chodzi i po co to wszystko. Wczoraj otworzyłem nowy wielopak zapałek i okazały się to być te z klasyczną łamigłówką z przekładaniem jednej zapałki:

img_1110

Pomyślałem, że mogło by to być fajne zadanie dla studentów I roku. Ja zazwyczaj uczę Pythona, więc podjąłem próbę napisania programu. W pierwszej wersji zadaniem programu jest po prostu znalezienie rozwiązania (lub rozwiązań) pojedynczego problemu:


plus = dict()
plus[0] = [8]
plus[1] = [7]
plus[2] = []
plus[3] = [9]
plus[4] = []
plus[5] = [9]
plus[6] = [8]
plus[7] = []
plus[8] = []
plus[9] = [8]

minus = dict()
minus[0] = []
minus[1] = []
minus[2] = []
minus[3] = []
minus[4] = []
minus[5] = []
minus[6] = []
minus[7] = [1]
minus[8] = [0, 6, 9]
minus[9] = [3, 5]

swap = dict()
swap[0] = [6, 9]
swap[1] = []
swap[2] = [3]
swap[3] = [2, 5]
swap[4] = []
swap[5] = [3]
swap[6] = [0, 9]
swap[7] = []
swap[8] = []
swap[9] = [0, 6]


def test_single(a, o1, b, o2, c):
	if o2 == "=":
		if o1 == "+":
			return a+b==c
		elif o1 == "-":
			return a-b==c
		else:
			print('ERROR')
	elif o1 == "=":
		if o2 == "+":
			return a+b==c
		elif o2 == "-":
			return a-b==c
		else:
			print('ERROR')
	else:
		print('ERROR')
def print_result(a, o1, b, o2, c,debug):
	print("{5}: {0}{1}{2}{3}{4}:".format(a,o1,b,o2,c,debug), test_single(a, o1, b, o2, c));


def all_comb(a, o1, b, o2, c):
	#print("SWAPY")
	for a1 in swap[a]:
		print_result(a1, o1, b, o2, c,1)
	for b1 in swap[b]:
		print_result(a, o1, b1, o2, c,2)
	for c1 in swap[c]:
		print_result(a, o1, b, o2, c1,3)
	if o1 == '-':
		print_result(a, "=", b, "-", c,4)
	#print("A minus")
	for a1 in minus[a]:
		for b1 in plus[b]:
			print_result(a1, o1, b1, o2, c,5)
		for c1 in plus[c]:
			print_result(a1, o1, b, o2, c1,6)
		if o1 == "-":
			print_result(a1, "+", b, o2, c,7)
		if o2 == "-":
			print_result(a1, o1, b, "+", c,8)
	#print("B minus")
	for b1 in minus[b]:
		for a1 in plus[a]:
			print_result(a1, o1, b1, o2, c,9)
		for c1 in plus[c]:
			print_result(a, o1, b1, o2, c1,10)
		if o1 == "-":
			print_result(a, "+", b1, o2, c,11)
		if o2 == "-":
			print_result(a, o1, b1, "+", c,12)
	#print("C minus")
	for c1 in minus[c]:
		for b1 in plus[b]:
			print_result(a, o1, b1, o2, c1,13)
		for a1 in plus[a]:
			print_result(a1, o1, b, o2, c1,14)
		if o1 == "-":
			print_result(a, "+", b, o2, c1,15)
		if o2 == "-":
			print_result(a, o1, b, "+", c1,16)

	#print("+ minus")
	if o1 == "+":
		for a1 in plus[a]:
			print_result(a1, "-", b, o2, c,17)
		for b1 in plus[b]:
			print_result(a, "-", b1, o2, c,18)
		for c1 in plus[c]:
			print_result(a, "-", b, o2, c1,19)


all_comb(1,"+",2,"=",8)

Nie jest to rozwiązanie w żaden sposób zoptymalizowane. Byłbym zadowolony, gdyby na zajęciach udało się dojść do takiego rozwiązania. Dla równania 1+2=8 program zwraca taki wynik:

screenshot_43

Działanie programu opiera się po pierwsze na 3 zdefiniowanych słownikach, które określają na jaką cyfrę można zmienić daną cyfrę po odjęciu, dodaniu i przełożeniu jednej zapałki. Po drugie program przewiduje wszystkie przypadki zamian: np. odjęcie zapałki z pierwszej cyfry i dodanie do cyfry drugiej lub trzeciej. W sumie przewidziane jest 19 przypadków (może da się to zoptymalizować?).

Drugą, równie ciekawą wersją programu jest wygenerowanie wszystkich możliwych łamigłówek. Tu należy zdecydować, czy rozważamy te równania, które są poprawne na wejściu. Ja zdecydowałem ich nie pomijać, bo wymagamy poprawności po przełożeniu jednej zapałki. Kod wygląda tak:


plus = dict()
plus[0] = [8]
plus[1] = [7]
plus[2] = []
plus[3] = [9]
plus[4] = []
plus[5] = [9]
plus[6] = [8]
plus[7] = []
plus[8] = []
plus[9] = [8]

minus = dict()
minus[0] = []
minus[1] = []
minus[2] = []
minus[3] = []
minus[4] = []
minus[5] = []
minus[6] = []
minus[7] = [1]
minus[8] = [0, 6, 9]
minus[9] = [3, 5]

swap = dict()
swap[0] = [6, 9]
swap[1] = []
swap[2] = [3]
swap[3] = [2, 5]
swap[4] = []
swap[5] = [3]
swap[6] = [0, 9]
swap[7] = []
swap[8] = []
swap[9] = [0, 6]


def test_single(a, o1, b, o2, c):
	if o2 == "=":
		if o1 == "+":
			return a+b==c
		elif o1 == "-":
			return a-b==c
		else:
			print('ERROR')
	elif o1 == "=":
		if o2 == "+":
			return a+b==c
		elif o2 == "-":
			return a-b==c
		else:
			print('ERROR')
	else:
		print('ERROR')
def print_result(wa,wo1,wb,wo2,wc,a, o1, b, o2, c):
	if test_single(a, o1, b, o2, c):
		print("{0}{1}{2}{3}{4} -> {5}{6}{7}{8}{9}".format(wa,wo1,wb,wo2,wc,a,o1,b,o2,c))
		return 1
	else:
		return 0;


def all_comb(a, o1, b, o2, c):
	count = 0
	for a1 in swap[a]:
		count = count + print_result(a,o1,b,o2,c,a1, o1, b, o2, c)
	for b1 in swap[b]:
		count = count + print_result(a,o1,b,o2,c,a, o1, b1, o2, c)
	for c1 in swap[c]:
		count = count + print_result(a,o1,b,o2,c,a, o1, b, o2, c1)
	if o1 == '-':
		count = count + print_result(a,o1,b,o2,c,a, "=", b, "-", c)
	for a1 in minus[a]:
		for b1 in plus[b]:
			count = count + print_result(a,o1,b,o2,c,a1, o1, b1, o2, c)
		for c1 in plus[c]:
			count = count + print_result(a,o1,b,o2,c,a1, o1, b, o2, c1)
		if o1 == "-":
			count = count + print_result(a,o1,b,o2,c,a1, "+", b, o2, c)
		if o2 == "-":
			count = count + print_result(a,o1,b,o2,c,a1, o1, b, "+", c)
	for b1 in minus[b]:
		for a1 in plus[a]:
			count = count + print_result(a,o1,b,o2,c,a1, o1, b1, o2, c)
		for c1 in plus[c]:
			count = count + print_result(a,o1,b,o2,c,a, o1, b1, o2, c1)
		if o1 == "-":
			count = count + print_result(a,o1,b,o2,c,a, "+", b1, o2, c)
		if o2 == "-":
			count = count + print_result(a,o1,b,o2,c,a, o1, b1, "+", c)
	for c1 in minus[c]:
		for b1 in plus[b]:
			count = count + print_result(a,o1,b,o2,c,a, o1, b1, o2, c1)
		for a1 in plus[a]:
			count = count + print_result(a,o1,b,o2,c,a1, o1, b, o2, c1)
		if o1 == "-":
			count = count + print_result(a,o1,b,o2,c,a, "+", b, o2, c1)
		if o2 == "-":
			count = count + print_result(a,o1,b,o2,c,a, o1, b, "+", c1)
	if o1 == "+":
		for a1 in plus[a]:
			count = count + print_result(a,o1,b,o2,c,a1, "-", b, o2, c)
		for b1 in plus[b]:
			count = count + print_result(a,o1,b,o2,c,a, "-", b1, o2, c)
		for c1 in plus[c]:
			count = count + print_result(a,o1,b,o2,c,a, "-", b, o2, c1)
	return count

for to1 in ["+", "-"]:
	for ta in range(10):
		for tb in range(10):
			for tc in range(10):
				tcount = all_comb(ta,to1,tb,"=",tc)
				#if tcount > 2:
				#	print("{0}{1}{2}{3}{4}".format(ta,to1,tb,"=",tc), tcount)

Wynik jest obliczany w mgnieniu oka. Dla rozwiązania dla łamigłówek z obrazka są następujące:

screenshot_44

Lista wszystkich kombinacji dla potomnych:

0+0=6 -> 6+0=6
0+0=6 -> 0+6=6
0+0=6 -> 0+0=0
0+0=8 -> 8-0=8
0+0=9 -> 9+0=9
0+0=9 -> 0+9=9
0+0=9 -> 0+0=0
0+1=7 -> 6+1=7
0+1=7 -> 8-1=7
0+1=8 -> 8+1=9
0+2=3 -> 0+3=3
0+2=3 -> 0+2=2
0+2=6 -> 8-2=6
0+2=8 -> 6+2=8
0+3=2 -> 0+2=2
0+3=2 -> 0+3=3
0+3=5 -> 0+5=5
0+3=5 -> 0+3=3
0+3=5 -> 8-3=5
0+3=8 -> 0+9=9
0+3=9 -> 6+3=9
0+4=4 -> 8-4=4
0+5=3 -> 0+3=3
0+5=3 -> 0+5=5
0+5=3 -> 8-5=3
0+5=8 -> 0+9=9
0+6=0 -> 0+0=0
0+6=0 -> 0+6=6
0+6=2 -> 8-6=2
0+6=9 -> 0+9=9
0+6=9 -> 0+6=6
0+7=1 -> 8-7=1
0+7=9 -> 8+1=9
0+8=0 -> 8-8=0
0+8=3 -> 0+9=9
0+8=5 -> 0+9=9
0+8=8 -> 8+0=8
0+9=0 -> 0+0=0
0+9=0 -> 0+9=9
0+9=6 -> 0+6=6
0+9=6 -> 0+9=9
1+0=7 -> 1+6=7
1+0=7 -> 7-0=7
1+0=8 -> 1+8=9
1+1=3 -> 1+1=2
1+1=6 -> 7-1=6
1+2=2 -> 1+2=3
1+2=4 -> 1+3=4
1+2=5 -> 1+2=3
1+2=5 -> 7-2=5
1+2=8 -> 7+2=9
1+3=3 -> 1+2=3
1+3=4 -> 7-3=4
1+3=6 -> 1+5=6
1+4=3 -> 1+4=5
1+4=3 -> 7-4=3
1+5=0 -> 1+5=6
1+5=2 -> 7-5=2
1+5=4 -> 1+3=4
1+5=9 -> 1+5=6
1+6=1 -> 1+0=1
1+6=1 -> 7-6=1
1+6=8 -> 1+8=9
1+7=0 -> 7-7=0
1+7=8 -> 7+1=8
1+8=0 -> 1+8=9
1+8=1 -> 1+6=7
1+8=6 -> 1+8=9
1+8=7 -> 7+0=7
1+9=1 -> 1+0=1
1+9=7 -> 1+6=7
1+9=8 -> 1+8=9
2+0=3 -> 3+0=3
2+0=3 -> 2+0=2
2+0=8 -> 2+6=8
2+1=2 -> 2+1=3
2+1=4 -> 3+1=4
2+1=5 -> 2+1=3
2+1=8 -> 2+7=9
2+2=5 -> 3+2=5
2+2=5 -> 2+3=5
2+3=3 -> 2+3=5
2+3=4 -> 2+2=4
2+3=6 -> 3+3=6
2+3=7 -> 2+5=7
2+4=0 -> 2+4=6
2+4=7 -> 3+4=7
2+4=9 -> 2+4=6
2+5=5 -> 2+3=5
2+5=8 -> 3+5=8
2+6=2 -> 2+0=2
2+6=9 -> 3+6=9
2+7=0 -> 2+7=9
2+7=6 -> 2+7=9
2+8=0 -> 2+6=8
2+8=6 -> 2+6=8
2+8=9 -> 2+6=8
2+9=1 -> 2+5=7
2+9=2 -> 2+0=2
2+9=8 -> 2+6=8
3+0=2 -> 2+0=2
3+0=2 -> 3+0=3
3+0=5 -> 5+0=5
3+0=5 -> 3+0=3
3+0=8 -> 9+0=9
3+0=9 -> 3+6=9
3+0=9 -> 9-0=9
3+1=3 -> 2+1=3
3+1=6 -> 5+1=6
3+1=8 -> 9-1=8
3+2=3 -> 3+2=5
3+2=4 -> 2+2=4
3+2=6 -> 3+3=6
3+2=7 -> 5+2=7
3+2=7 -> 9-2=7
3+3=0 -> 3+3=6
3+3=5 -> 2+3=5
3+3=5 -> 3+2=5
3+3=6 -> 9-3=6
3+3=8 -> 5+3=8
3+3=8 -> 3+5=8
3+3=9 -> 3+3=6
3+4=5 -> 9-4=5
3+4=6 -> 2+4=6
3+4=9 -> 5+4=9
3+5=4 -> 9-5=4
3+5=6 -> 3+3=6
3+5=7 -> 2+5=7
3+6=0 -> 3+6=9
3+6=3 -> 3+0=3
3+6=3 -> 9-6=3
3+6=6 -> 3+6=9
3+6=8 -> 2+6=8
3+7=2 -> 9-7=2
3+7=9 -> 2+7=9
3+8=1 -> 9-8=1
3+8=3 -> 3+6=9
3+8=5 -> 3+6=9
3+8=9 -> 9+0=9
3+9=0 -> 3+5=8
3+9=0 -> 9-9=0
3+9=3 -> 3+0=3
3+9=6 -> 3+5=8
3+9=9 -> 3+6=9
3+9=9 -> 3+5=8
4+1=3 -> 4+1=5
4+2=0 -> 4+2=6
4+2=7 -> 4+3=7
4+2=9 -> 4+2=6
4+3=6 -> 4+2=6
4+3=9 -> 4+5=9
4+5=0 -> 4+5=9
4+5=6 -> 4+5=9
4+5=7 -> 4+3=7
4+6=4 -> 4+0=4
4+9=1 -> 4+3=7
4+9=3 -> 4+5=9
4+9=4 -> 4+0=4
4+9=5 -> 4+5=9
5+0=3 -> 3+0=3
5+0=3 -> 5+0=5
5+0=8 -> 9+0=9
5+0=9 -> 9-0=9
5+1=0 -> 5+1=6
5+1=4 -> 3+1=4
5+1=8 -> 9-1=8
5+1=9 -> 5+1=6
5+2=5 -> 3+2=5
5+2=7 -> 9-2=7
5+2=8 -> 5+3=8
5+3=6 -> 3+3=6
5+3=6 -> 9-3=6
5+3=7 -> 5+2=7
5+4=0 -> 5+4=9
5+4=5 -> 9-4=5
5+4=6 -> 5+4=9
5+4=7 -> 3+4=7
5+5=4 -> 9-5=4
5+5=8 -> 3+5=8
5+5=8 -> 5+3=8
5+6=3 -> 9-6=3
5+6=5 -> 5+0=5
5+6=9 -> 3+6=9
5+7=2 -> 9-7=2
5+8=1 -> 9-8=1
5+8=9 -> 9+0=9
5+9=0 -> 5+3=8
5+9=0 -> 9-9=0
5+9=5 -> 5+0=5
5+9=6 -> 5+3=8
5+9=9 -> 5+3=8
6+0=0 -> 0+0=0
6+0=0 -> 6+0=6
6+0=8 -> 8-0=8
6+0=9 -> 9+0=9
6+0=9 -> 6+0=6
6+1=1 -> 0+1=1
6+1=7 -> 8-1=7
6+1=8 -> 8+1=9
6+2=2 -> 0+2=2
6+2=6 -> 8-2=6
6+2=9 -> 6+3=9
6+3=0 -> 6+3=9
6+3=3 -> 0+3=3
6+3=5 -> 8-3=5
6+3=6 -> 6+3=9
6+3=8 -> 6+2=8
6+4=4 -> 0+4=4
6+4=4 -> 8-4=4
6+5=3 -> 8-5=3
6+5=5 -> 0+5=5
6+5=9 -> 6+3=9
6+6=2 -> 8-6=2
6+6=6 -> 0+6=6
6+6=6 -> 6+0=6
6+7=1 -> 6+1=7
6+7=1 -> 8-7=1
6+7=7 -> 0+7=7
6+7=9 -> 8+1=9
6+8=0 -> 8-8=0
6+8=8 -> 0+8=8
6+8=8 -> 8+0=8
6+9=3 -> 6+3=9
6+9=5 -> 6+3=9
6+9=6 -> 6+0=6
6+9=9 -> 0+9=9
7+0=1 -> 7-0=7
7+0=9 -> 1+8=9
7+1=0 -> 7-7=0
7+1=8 -> 1+7=8
7+2=0 -> 7+2=9
7+2=6 -> 7+2=9
7+3=9 -> 7+2=9
7+6=1 -> 1+6=7
7+6=7 -> 7+0=7
7+6=9 -> 1+8=9
7+7=0 -> 1+7=8
7+7=0 -> 7+1=8
7+7=6 -> 1+7=8
7+7=6 -> 7+1=8
7+7=9 -> 1+7=8
7+7=9 -> 7+1=8
7+8=1 -> 7+0=7
7+8=3 -> 1+8=9
7+8=5 -> 1+8=9
7+9=7 -> 7+0=7
7+9=9 -> 1+8=9
8+0=0 -> 8-8=0
8+0=0 -> 8-0=8
8+0=3 -> 9+0=9
8+0=5 -> 9+0=9
8+0=6 -> 8-0=8
8+0=8 -> 0+8=8
8+0=9 -> 8-0=8
8+1=0 -> 8+1=9
8+1=1 -> 6+1=7
8+1=1 -> 8-7=1
8+1=1 -> 8-1=7
8+1=6 -> 8+1=9
8+1=7 -> 0+7=7
8+2=0 -> 6+2=8
8+2=6 -> 6+2=8
8+2=9 -> 6+2=8
8+3=3 -> 6+3=9
8+3=5 -> 6+3=9
8+3=9 -> 0+9=9
8+5=9 -> 0+9=9
8+6=0 -> 8-8=0
8+6=8 -> 8+0=8
8+6=8 -> 0+8=8
8+7=1 -> 0+7=7
8+7=3 -> 8+1=9
8+7=5 -> 8+1=9
8+8=0 -> 0+8=8
8+8=0 -> 8+0=8
8+8=6 -> 0+8=8
8+8=6 -> 8+0=8
8+8=9 -> 0+8=8
8+8=9 -> 8+0=8
8+9=0 -> 8-8=0
8+9=3 -> 0+9=9
8+9=5 -> 0+9=9
8+9=8 -> 8+0=8
8+9=8 -> 0+8=8
9+0=0 -> 0+0=0
9+0=0 -> 9+0=9
9+0=1 -> 9-8=1
9+0=3 -> 9-0=9
9+0=5 -> 9-0=9
9+0=6 -> 6+0=6
9+0=6 -> 9+0=9
9+0=8 -> 8-0=8
9+1=0 -> 9-1=8
9+1=1 -> 0+1=1
9+1=2 -> 9-7=2
9+1=6 -> 9-1=8
9+1=7 -> 6+1=7
9+1=7 -> 8-1=7
9+1=8 -> 8+1=9
9+1=9 -> 9-1=8
9+2=1 -> 5+2=7
9+2=1 -> 9-2=7
9+2=2 -> 0+2=2
9+2=6 -> 8-2=6
9+2=8 -> 6+2=8
9+3=0 -> 5+3=8
9+3=0 -> 9-9=0
9+3=3 -> 0+3=3
9+3=5 -> 8-3=5
9+3=6 -> 5+3=8
9+3=9 -> 6+3=9
9+3=9 -> 5+3=8
9+4=1 -> 3+4=7
9+4=3 -> 5+4=9
9+4=4 -> 0+4=4
9+4=4 -> 8-4=4
9+4=5 -> 5+4=9
9+5=0 -> 3+5=8
9+5=0 -> 9-9=0
9+5=3 -> 8-5=3
9+5=5 -> 0+5=5
9+5=6 -> 3+5=8
9+5=9 -> 3+5=8
9+6=1 -> 9-8=1
9+6=2 -> 8-6=2
9+6=3 -> 3+6=9
9+6=5 -> 3+6=9
9+6=6 -> 0+6=6
9+6=9 -> 9+0=9
9+7=1 -> 8-7=1
9+7=7 -> 0+7=7
9+7=9 -> 8+1=9
9+8=0 -> 8-8=0
9+8=3 -> 9+0=9
9+8=5 -> 9+0=9
9+8=8 -> 0+8=8
9+8=8 -> 8+0=8
9+9=1 -> 9-8=1
9+9=9 -> 0+9=9
9+9=9 -> 9+0=9
0-0=0 -> 0=0-0
0-0=6 -> 6-0=6
0-0=6 -> 0-0=0
0-0=8 -> 0+0=0
0-0=9 -> 9-0=9
0-0=9 -> 0-0=0
0-1=5 -> 6-1=5
0-1=7 -> 0+1=1
0-1=8 -> 9-1=8
0-2=4 -> 6-2=4
0-2=7 -> 9-2=7
0-2=8 -> 8-2=6
0-3=3 -> 6-3=3
0-3=6 -> 9-3=6
0-3=9 -> 0+3=3
0-3=9 -> 8-3=5
0-4=2 -> 6-4=2
0-4=5 -> 9-4=5
0-5=1 -> 6-5=1
0-5=4 -> 9-5=4
0-5=9 -> 8-5=3
0-5=9 -> 0+5=5
0-6=0 -> 6-6=0
0-6=0 -> 0-0=0
0-6=3 -> 9-6=3
0-6=8 -> 0+6=6
0-7=1 -> 0+1=1
0-7=2 -> 9-7=2
0-7=7 -> 8-1=7
0-7=7 -> 8-7=1
0-8=0 -> 0+0=0
0-8=1 -> 9-8=1
0-8=2 -> 8-6=2
0-8=6 -> 0+6=6
0-8=8 -> 8-0=8
0-8=8 -> 8-8=0
0-8=9 -> 0+9=9
0-9=0 -> 9-9=0
0-9=0 -> 0-0=0
0-9=3 -> 0+3=3
0-9=3 -> 8-5=3
0-9=5 -> 8-3=5
0-9=5 -> 0+5=5
0-9=8 -> 0+9=9
1-0=1 -> 1=0-1
1-0=7 -> 1+0=1
1-1=0 -> 1=1-0
1-1=6 -> 1-1=0
1-1=8 -> 7-1=6
1-1=9 -> 1-1=0
1-2=9 -> 1+2=3
1-2=9 -> 7-2=5
1-4=9 -> 7-4=3
1-4=9 -> 1+4=5
1-5=8 -> 1+5=6
1-6=1 -> 1-0=1
1-6=7 -> 7-6=1
1-7=2 -> 1+1=2
1-7=6 -> 7-1=6
1-7=8 -> 7-7=0
1-8=1 -> 1+0=1
1-8=1 -> 7-6=1
1-8=7 -> 7-0=7
1-8=7 -> 1+6=7
1-8=8 -> 1+8=9
1-9=1 -> 1-0=1
1-9=2 -> 7-5=2
1-9=4 -> 7-3=4
1-9=4 -> 1+3=4
1-9=6 -> 1+5=6
2-0=2 -> 2=0-2
2-0=3 -> 3-0=3
2-0=3 -> 2-0=2
2-1=1 -> 2=1-1
2-1=2 -> 3-1=2
2-1=9 -> 2+1=3
2-2=0 -> 2=2-0
2-2=1 -> 3-2=1
2-2=6 -> 2-2=0
2-2=9 -> 2-2=0
2-3=0 -> 3-3=0
2-3=0 -> 2-2=0
2-3=9 -> 2+3=5
2-4=8 -> 2+4=6
2-6=2 -> 2-0=2
2-7=3 -> 2+1=3
2-7=8 -> 2+7=9
2-8=2 -> 2+0=2
2-8=8 -> 2+6=8
2-9=2 -> 2-0=2
2-9=5 -> 2+3=5
2-9=7 -> 2+5=7
3-0=2 -> 2-0=2
3-0=2 -> 3-0=3
3-0=3 -> 3=0-3
3-0=5 -> 5-0=5
3-0=5 -> 3-0=3
3-0=8 -> 9-0=9
3-0=9 -> 3+0=3
3-1=1 -> 2-1=1
3-1=2 -> 3=1-2
3-1=3 -> 3-1=2
3-1=4 -> 5-1=4
3-2=0 -> 2-2=0
3-2=0 -> 3-3=0
3-2=1 -> 3=2-1
3-2=3 -> 5-2=3
3-2=9 -> 3+2=5
3-3=0 -> 3=3-0
3-3=1 -> 3-2=1
3-3=2 -> 5-3=2
3-3=6 -> 3-3=0
3-3=8 -> 9-3=6
3-3=8 -> 3+3=6
3-3=9 -> 3-3=0
3-4=1 -> 5-4=1
3-4=9 -> 9-4=5
3-5=0 -> 5-5=0
3-5=0 -> 3-3=0
3-6=3 -> 3-0=3
3-6=8 -> 3+6=9
3-6=9 -> 9-6=3
3-7=4 -> 3+1=4
3-7=8 -> 9-1=8
3-8=0 -> 9-9=0
3-8=3 -> 3+0=3
3-8=3 -> 9-6=3
3-8=7 -> 9-8=1
3-8=9 -> 9-0=9
3-8=9 -> 3+6=9
3-9=3 -> 3-0=3
3-9=4 -> 9-5=4
3-9=6 -> 9-3=6
3-9=6 -> 3+3=6
3-9=8 -> 3+5=8
3-9=8 -> 9-9=0
4-0=4 -> 4=0-4
4-1=2 -> 4-1=3
4-1=3 -> 4=1-3
4-1=5 -> 4-1=3
4-1=9 -> 4+1=5
4-2=1 -> 4-3=1
4-2=2 -> 4=2-2
4-2=3 -> 4-2=2
4-2=8 -> 4+2=6
4-3=1 -> 4=3-1
4-3=2 -> 4-2=2
4-4=0 -> 4=4-0
4-4=6 -> 4-4=0
4-4=9 -> 4-4=0
4-5=1 -> 4-3=1
4-5=8 -> 4+5=9
4-6=4 -> 4-0=4
4-7=5 -> 4+1=5
4-8=4 -> 4+0=4
4-9=4 -> 4-0=4
4-9=7 -> 4+3=7
4-9=9 -> 4+5=9
5-0=3 -> 3-0=3
5-0=3 -> 5-0=5
5-0=5 -> 5=0-5
5-0=8 -> 9-0=9
5-0=9 -> 5+0=5
5-1=2 -> 3-1=2
5-1=4 -> 5=1-4
5-1=8 -> 5+1=6
5-2=1 -> 3-2=1
5-2=2 -> 5-3=2
5-2=2 -> 5-2=3
5-2=3 -> 5=2-3
5-2=5 -> 5-2=3
5-3=0 -> 3-3=0
5-3=0 -> 5-5=0
5-3=2 -> 5=3-2
5-3=3 -> 5-2=3
5-3=3 -> 5-3=2
5-3=8 -> 9-3=6
5-4=1 -> 5=4-1
5-4=8 -> 5+4=9
5-4=9 -> 9-4=5
5-5=0 -> 5=5-0
5-5=2 -> 5-3=2
5-5=6 -> 5-5=0
5-5=9 -> 5-5=0
5-6=5 -> 5-0=5
5-6=9 -> 9-6=3
5-7=6 -> 5+1=6
5-7=8 -> 9-1=8
5-8=0 -> 9-9=0
5-8=3 -> 9-6=3
5-8=5 -> 5+0=5
5-8=7 -> 9-8=1
5-8=9 -> 9-0=9
5-9=4 -> 9-5=4
5-9=5 -> 5-0=5
5-9=6 -> 9-3=6
5-9=8 -> 5+3=8
5-9=8 -> 9-9=0
6-0=0 -> 0-0=0
6-0=0 -> 6-6=0
6-0=0 -> 6-0=6
6-0=6 -> 6=0-6
6-0=8 -> 6+0=6
6-0=9 -> 9-0=9
6-0=9 -> 6-0=6
6-1=3 -> 6-1=5
6-1=5 -> 6=1-5
6-1=8 -> 9-1=8
6-2=3 -> 6-3=3
6-2=4 -> 6=2-4
6-2=7 -> 9-2=7
6-2=8 -> 8-2=6
6-3=1 -> 6-5=1
6-3=2 -> 6-3=3
6-3=3 -> 6=3-3
6-3=4 -> 6-2=4
6-3=5 -> 6-3=3
6-3=6 -> 9-3=6
6-3=8 -> 6+3=9
6-3=9 -> 8-3=5
6-4=2 -> 6=4-2
6-4=3 -> 6-4=2
6-4=5 -> 9-4=5
6-5=1 -> 6=5-1
6-5=3 -> 6-3=3
6-5=4 -> 9-5=4
6-5=9 -> 8-5=3
6-6=0 -> 6=6-0
6-6=3 -> 9-6=3
6-6=6 -> 6-0=6
6-6=6 -> 6-6=0
6-6=9 -> 6-6=0
6-7=2 -> 9-7=2
6-7=7 -> 8-1=7
6-7=7 -> 6+1=7
6-7=7 -> 8-7=1
6-8=1 -> 9-8=1
6-8=2 -> 8-6=2
6-8=6 -> 6+0=6
6-8=8 -> 8-0=8
6-8=8 -> 8-8=0
6-9=0 -> 9-9=0
6-9=0 -> 6-6=0
6-9=3 -> 8-5=3
6-9=5 -> 8-3=5
6-9=6 -> 6-0=6
6-9=9 -> 6+3=9
7-0=1 -> 7-6=1
7-0=1 -> 1+0=1
7-0=7 -> 7=0-7
7-1=0 -> 7-1=6
7-1=2 -> 1+1=2
7-1=6 -> 7=1-6
7-1=8 -> 7-7=0
7-1=9 -> 7-1=6
7-2=3 -> 7-2=5
7-2=3 -> 1+2=3
7-2=4 -> 7-3=4
7-2=5 -> 7=2-5
7-2=8 -> 7+2=9
7-3=2 -> 7-5=2
7-3=4 -> 7=3-4
7-3=4 -> 1+3=4
7-3=5 -> 7-2=5
7-4=2 -> 7-4=3
7-4=3 -> 7=4-3
7-4=5 -> 7-4=3
7-4=5 -> 1+4=5
7-5=2 -> 7=5-2
7-5=3 -> 7-5=2
7-5=4 -> 7-3=4
7-5=6 -> 1+5=6
7-6=1 -> 7=6-1
7-6=7 -> 7-0=7
7-6=7 -> 1+6=7
7-7=0 -> 7=7-0
7-7=6 -> 7-7=0
7-7=8 -> 1+7=8
7-7=8 -> 7+1=8
7-7=9 -> 7-7=0
7-8=1 -> 7-0=7
7-8=7 -> 7+0=7
7-8=9 -> 1+8=9
7-9=1 -> 7-6=1
7-9=7 -> 7-0=7
8-0=0 -> 0+0=0
8-0=1 -> 9-8=1
8-0=2 -> 8-6=2
8-0=3 -> 9-0=9
8-0=5 -> 9-0=9
8-0=6 -> 6+0=6
8-0=8 -> 8=0-8
8-0=8 -> 8-8=0
8-0=9 -> 9+0=9
8-1=0 -> 9-1=8
8-1=1 -> 0+1=1
8-1=2 -> 9-7=2
8-1=6 -> 9-1=8
8-1=7 -> 8=1-7
8-1=7 -> 6+1=7
8-1=7 -> 8-7=1
8-1=8 -> 8+1=9
8-1=9 -> 9-1=8
8-2=0 -> 8-2=6
8-2=1 -> 9-2=7
8-2=2 -> 0+2=2
8-2=5 -> 8-3=5
8-2=6 -> 8=2-6
8-2=8 -> 6+2=8
8-2=9 -> 8-2=6
8-3=0 -> 9-9=0
8-3=3 -> 8-5=3
8-3=3 -> 8-3=5
8-3=3 -> 0+3=3
8-3=5 -> 8=3-5
8-3=6 -> 8-2=6
8-3=9 -> 6+3=9
8-4=4 -> 8=4-4
8-4=4 -> 0+4=4
8-5=0 -> 9-9=0
8-5=2 -> 8-5=3
8-5=3 -> 8=5-3
8-5=5 -> 8-3=5
8-5=5 -> 8-5=3
8-5=5 -> 0+5=5
8-6=1 -> 9-8=1
8-6=2 -> 8=6-2
8-6=3 -> 8-6=2
8-6=6 -> 0+6=6
8-6=8 -> 8-0=8
8-6=8 -> 8-8=0
8-7=1 -> 8=7-1
8-7=1 -> 8-1=7
8-7=7 -> 0+7=7
8-7=9 -> 8+1=9
8-8=0 -> 8=8-0
8-8=0 -> 8-0=8
8-8=6 -> 8-8=0
8-8=6 -> 8-0=8
8-8=8 -> 0+8=8
8-8=8 -> 8+0=8
8-8=9 -> 8-8=0
8-8=9 -> 8-0=8
8-9=1 -> 9-8=1
8-9=2 -> 8-6=2
8-9=8 -> 8-0=8
8-9=8 -> 8-8=0
8-9=9 -> 0+9=9
9-0=0 -> 0-0=0
9-0=0 -> 9-9=0
9-0=0 -> 9-0=9
9-0=3 -> 9-6=3
9-0=3 -> 3+0=3
9-0=5 -> 5+0=5
9-0=6 -> 6-0=6
9-0=6 -> 9-0=9
9-0=7 -> 9-8=1
9-0=8 -> 9+0=9
9-0=9 -> 9=0-9
9-1=4 -> 3+1=4
9-1=5 -> 6-1=5
9-1=6 -> 5+1=6
9-1=8 -> 9=1-8
9-2=4 -> 6-2=4
9-2=5 -> 3+2=5
9-2=6 -> 9-3=6
9-2=7 -> 9=2-7
9-2=7 -> 5+2=7
9-2=8 -> 8-2=6
9-3=0 -> 9-3=6
9-3=3 -> 6-3=3
9-3=4 -> 9-5=4
9-3=6 -> 9=3-6
9-3=6 -> 3+3=6
9-3=7 -> 9-2=7
9-3=8 -> 5+3=8
9-3=8 -> 9-9=0
9-3=9 -> 9-3=6
9-3=9 -> 8-3=5
9-4=2 -> 6-4=2
9-4=3 -> 9-4=5
9-4=5 -> 9=4-5
9-4=7 -> 3+4=7
9-4=9 -> 5+4=9
9-5=1 -> 6-5=1
9-5=4 -> 9=5-4
9-5=6 -> 9-3=6
9-5=8 -> 3+5=8
9-5=8 -> 9-9=0
9-5=9 -> 8-5=3
9-6=0 -> 6-6=0
9-6=0 -> 9-9=0
9-6=2 -> 9-6=3
9-6=3 -> 9=6-3
9-6=5 -> 9-6=3
9-6=7 -> 9-8=1
9-6=9 -> 9-0=9
9-6=9 -> 3+6=9
9-7=0 -> 9-1=8
9-7=2 -> 9=7-2
9-7=3 -> 9-7=2
9-7=6 -> 9-1=8
9-7=7 -> 8-1=7
9-7=7 -> 8-7=1
9-7=9 -> 9-1=8
9-8=1 -> 9=8-1
9-8=2 -> 8-6=2
9-8=3 -> 9-0=9
9-8=5 -> 9-0=9
9-8=8 -> 8-0=8
9-8=8 -> 8-8=0
9-8=9 -> 9+0=9
9-9=0 -> 9=9-0
9-9=3 -> 9-6=3
9-9=3 -> 8-5=3
9-9=5 -> 8-3=5
9-9=6 -> 9-9=0
9-9=7 -> 9-8=1
9-9=9 -> 9-0=9
9-9=9 -> 9-9=0

Mam nadzieję, że nie ma jakich drastycznych błędów w rozwiązaniu?

W ramach ćwiczeń polecam każdemu chętnemu rozwiązanie tego problemu w Waszym ulubionym języku programowania. Jeżeli twierdzicie, że da się prościej to zapewne macie racje, ale zawsze miło mi zobaczyć Wasz kod.

| Komentarze

Starsze wpisy »