Python – Collatz-sekvensen (Svenska)
On december 1, 2020 by adminKan jag få en recension av min kod för Collatz-sekvensen från kapitel tre av Automatisera de tråkiga sakerna med Python ?
Collatz-sekvensen
Skriv en funktion med namnet collatz () som har en parameter med namnet nummer. Om talet är jämnt, ska collatz () skriva ut nummer // 2 och returnera detta värde. Om talet är udda ska collatz () skriva ut och returnera 3 * nummer + 1.
Skriv sedan ett program som låter användaren skriva in ett heltal och som fortsätter att ringa collatz () på det numret tills funktionen returnerar värdet 1. (Otroligt nog fungerar den här sekvensen faktiskt för alla heltal – förr eller senare, med denna sekvens kommer du till 1! Även matematiker är inte säkra på varför. Ditt program utforskar vad som kallas Collatz-sekvensen , ibland kallat ”det enklaste omöjliga matematiska problemet.”)
Kom ihåg att konvertera returvärdet från ingång () till ett heltal med funktionen int (); annars kommer det att vara ett strängvärde.
Tips: Ett heltal är även om siffran% 2 == 0, och det är udda om antalet% 2 == 1.
Programmets utdata kan se ut så här:
Enter number: 3 10 5 16 8 4 2 1
Ingångsvalidering
Lägg till försöks- och undantagsuttalanden till det föregående projektet för att upptäcka om användaren skriver in en icke-nummersträng. Normalt kommer funktionen int () att höja ett ValueError-fel om det skickas till en icke-nummersträng, som i int (”valp”). I undantagssatsen kan du skriva ut ett meddelande till användaren som säger att de måste ange ett heltal.
Jag undrar främst om det finns ett renare sätt att skriva mitt lösning.
def collatz(num): while num > 1: if num % 2 == 0: print(num//2) num = num //2 elif num % 2 ==1: print(3*num+1) num = 3*num+1 else: print(num) def getNum(): global num num = (input("> ")) try: num = int(num) except ValueError: print("plese enter a number") getNum() getNum() collatz(num)
Kommentarer
- Lägg till en beskrivning som indikerar vad denna kapitel 3 rekommenderar med exempel på in- och utdata, ingen ’ kommer att gissa vad det är
- @Edmad Broctor och @ Carcigenicate Tack för din feedback. Jag gick vidare och reviderade mitt inlägg.
- ” Otroligt nog, den här sekvensen fungerar faktiskt för alla heltal – förr eller senare, med den här sekvensen kommer du vid 1! ” Vi vet faktiskt inte ’ att det fungerar för alla heltal, idén att det alltid kommer att nå 1 är en gissning .
- @PierreCath é Jag har en verkligt underbar demonstration av detta förslag som den här kommentarrutan är för liten för att innehålla.
- @Accumulation Wow ! Att ’ är imponerande, i så fall bör du uppdatera Wikipedia-artikeln och kontakta International Mathematical Union för att samla in din Fields-medalj.
Svar
Observera först hur du duplicerar beräkningar:
print(num//2) num = num //2
Detta kan inte orsaka problem med den här specifika koden, men det är inte god praxis. Du gör dubbelt så mycket arbete som du behöver, vilket kan orsaka prestandaproblem när du börjar skriva mer komplicerad kod. Gör beräkningen en gång och spara resultatet. I det här fallet är allt du behöver göra dock att vända dessa rader och använd num
:
num = num // 2 print(num)
Se också till att du har rätt avstånd runt operatörer och var konsekvent .
Din if
och elif
fall är exklusiva från varandra och ditt else
ska aldrig hända. Om det första villkoret är sant måste andra vara falska och tvärtom. Det finns inget behov av andra kontrollen. När du har skrivit om kommer du att se att utskrift i alla fall inte är nödvändig. Du kan bara skriva ut efter:
while num > 1: if num % 2 == 0: num = num // 2 else: num = 3 * num + 1 print(num)
Eftersom du bara omprövar num
ett av två alternativ baserat på en villkor kan ett villkorligt uttryck också användas här rent:
while num > 1: num = (num // 2) if num % 2 == 0 else (3 * num + 1) print(num)
Hängslen är inte nödvändiga, men jag tror att de är användbara här på grund av antal aktörer som är inblandade.
Att skriva ut siffrorna är inte perfekt här. I de flesta koder måste du kunna använda de data som du producerar. Om du vill analysera den producerade sekvensen måste du göra något för att fånga upp stdout, vilket är dyrt och alltför komplicerat. Gör det till en funktion som ackumuleras och returnerar en lista.I följande exempel lade jag också till några -tips för att göra det tydligare vad typen av data är:
from typing import List def collatz(starting_num: int) -> List[int]: nums = [starting_num] num = starting_num while num > 1: num = (num // 2) if num % 2 == 0 else (3 * num + 1) nums.append(num) return nums
Eller, ett mycket renare tillvägagångssätt är att göra det till en -generator som ger siffrorna :
# Calling this function returns a generator that produces ints # Ignore the two Nones, as they aren"t needed for this kind of generator def collatz_gen(starting_num: int) -> Generator[int, None, None]: yield starting_num num = starting_num while num > 1: num = (num // 2) if num % 2 == 0 else (3 * num + 1) yield num >>> list(collatz_gen(5)) [5, 16, 8, 4, 2, 1]
Det finns några få anmärkningsvärda saker om getNum
:
Python använder ”snake_case” , inte ”camelCase”.
Din användning av global num
här är onödig och förvirrande Precis som tidigare uttryckligen return
alla data som funktionen producerar:
def get_num() -> int: raw_num = input("> ") try: return int(raw_num) except ValueError: print("Please enter a number") return get_num()
Observera hur istället för att tilldela en global num
, vi returnerar bara numret. Jag placerade också saker lite och använde mer lämpliga namn. Konceptuellt skulle jag säga att num = input("> ")
är fel. Vid den tiden som körs gör num
inte innehålla ett nummer (det innehåller en sträng).
Detta är inte en bra användning av rekursion. Det sannolikt kommer inte att orsaka några problem, men om din användare är riktigt dum och anger fel data ~ 1000 gånger, kommer ditt program att krascha. Använd bara en slinga:
def get_num() -> int: while True: raw_num = input("> ") try: return int(raw_num) except ValueError: print("Please enter a number")
På språk som Python, var försiktig med rekursion i fall där du inte har några garantier för hur många gånger funktionen kommer att återgå.
Jag skulle också ha förmodligen namnge detta något närmare ask_for_num
. ”get” gör det inte mycket tydligt om var data kommer ifrån.
Sammantaget kommer du att sluta med:
from typing import Generator def collatz_gen(starting_num: int) -> Generator[int, None, None]: yield starting_num num = starting_num while num > 1: num = (num // 2) if num % 2 == 0 else (3 * num + 1) yield num def ask_for_num() -> int: while True: raw_num = input("> ") try: return int(raw_num) except ValueError: print("Please enter a number")
Vilket kan användas som:
num = ask_for_num() for n in collatz_gen(num): print(n)
Svar
Fråga
Den mest uppenbara dåliga praxis här är användningen av en global variabel. Istället för att ställa in num
som en bieffekt, bör din funktion return
resultatet.
getNum()
är inte ett så bra namn för funktionen. PEP 8 , den officiella stilguiden för Python, säger att funktionsnamn ska vara lower_case_with_underscores
. Dessutom innebär ”get” att funktionen hämtar en bit data som redan är lagrad någonstans, vilket inte är fallet här. Slutligen bör ”Num” vara mer specifikt.
Användningen av rekursion är inte lämplig. Om du vill ha en slinga, skriv en slinga.
def ask_integer(): """ Return an integer entered by the user (repeatedly prompting if the input is not a valid integer). """ while True: try: return int(input("> ")) except ValueError: print("Please enter an integer") num = ask_integer()
collatz
-funktion
Strikt talade följde du inte instruktionerna. Din lösning är inte fel eller dålig – du implementerade bara inte collatz
-funktionen enligt specifikationen som gavs, som säger att du ska skriva ut och returnera ett enda nummer.
def collatz(num): """ Given a number, print and return its successor in the Collatz sequence. """ next = num // 2 if num % 2 == 0 else 3 * num + 1 print(next) return next num = ask_integer() while num > 1: num = collatz(num)
Kommentarer
- Kanske vända testet och eliminera jämförelsen med 0:
next = 3 * num + 1 if num % 2 else num // 2
- @RootTwo Ärligt talat tycker jag att det ler med avsikten. Vi ’ kontrollerar inte om
num % 2
är falskt; det ’ är inte ett predikat. Vi ’ kontrollera om det ’ är lika med 0. Det händer så att de ’ motsvarar i Python. -
next = ...
överskuggar den inbyggdanext
. I detta sammanhang är det ’ förmodligen inte farligt, men bara bra att vara medveten om - Även om du tekniskt sett kan använda
if num % 2 else
, jag håller med om att det är viktigare att visa avsikten här. ochif num % 2 == 0 else
kommunicerar den avsikten bättre. Jag håller med om att du bör byta namn pånext
variabeln, dock. Det ’ är en dålig vana. - @ 200_success Tack för att du påpekade att jag inte kunde returnera numret och inte bara skriva ut det. Det var till stor hjälp.
Svar
För fullständighetens skull, en rekursiv implementering för collatz
(du har redan tillräckligt bra förslag för att mata in num
):
def collatz(num): print(num) if num == 1: return num if num % 2 == 0: return collatz(num // 2) return collatz(3 * num + 1) collatz(3)
Utgångar
3 10 5 16 8 4 2 1
Kommentarer
- Jag don ’ t tror att det här svaret hjälper OP. Även om en rekursiv definition verkligen är möjlig, passar den inte ’ till specifikationen de försöker uppfylla. Dessutom har den här koden viss redundans: funktionen returnerar alltid 1 (när den returnerar alls), så varför ens ha ett returvärde?
- @MeesdeVries
the function always returns 1 (when it returns at all), so why even have a return value
Jag ’ är inte säker på att jag följer. Rekursionen måste ha ett basfall. Det händer så att basfallet i detta exempel är omnum == 1
. Exempelutgången visar att funktionen inte alltid returnerar 1. - Funktionen skriver ut andra värden, men den returnerar bara värdet 1.
- Jag är inte säker på vad din poäng är . Min ursprungliga kommentar konstaterade att det inte finns något behov av att funktionen du skrev ska returnera någonting, för det kommer alltid att vara nummer 1, oavsett ingång. Därför skulle ett bättre genomförande undvika att returnera något värde alls, för att undvika förslaget att det är meningsfullt. Jag skulle till exempel säga att en bättre implementering (av rekursionsdelen) är
if num % 2 == 0: collatz(num//2); elif num > 1: collatz(3 * num + 1)
. - Jag uppmanar dig att prova koden jag skrev ut själv för att se att det gör det inte (åtminstone för positivt heltal). Om du vill diskutera detta ytterligare föreslår jag att vi tar det för att chatta.
Lämna ett svar