The Big Book of Small EK9 Programs
Adapted from Al Sweigart's The Big Book of Small Python Projects: 81 Easy Practice Programs (No Starch Press, 2021; copyright © 2021 Al Sweigart), which he makes freely available under a Creative Commons Attribution-NonCommercial-ShareAlike licence at inventwithpython.com/bigbookpython. Changes were made: each program was ported from Python to EK9 and remodelled in EK9's idioms.
Project 24 — Factor Finder
This is project 24 of the book, ported to EK9. Its chapter is still being written; until then, this page shows the port itself: a run of it, the whole program, and how it keeps to the original.
A sample run
The player's answers are piped in:
24
7
36
QUIT
Run it with:
ek9 factorfinder.ek9 < input.txt
It prints:
Factor Finder, by Al Sweigart al@inventwithpython.com
A number's factors are two numbers that, when multiplied with each
other, produce the number. For example, 2 x 13 = 26, so 2 and 13 are
factors of 26. 1 x 26 = 26, so 1 and 26 are also factors of 26. We
say that 26 has four factors: 1, 2, 13, and 26.
If a number only has two factors (1 and itself), we call that a prime
number. Otherwise, we call it a composite number.
Can you discover some prime numbers?
Enter a positive whole number to factor (or QUIT):
1, 2, 3, 4, 6, 8, 12, 24
Enter a positive whole number to factor (or QUIT):
1, 7
Enter a positive whole number to factor (or QUIT):
1, 2, 3, 4, 6, 9, 12, 18, 36
Enter a positive whole number to factor (or QUIT):
The program
#!ek9 defines module bigbook.factorfinder defines class //THE GAME's state: the divisors still to try for one number. //The divisors worth trying: 1, 2, ... while divisor * divisor <= number. Every factor above //the square root is number / divisor for one of these, so the search stops at the root. CandidateDivisors number as Integer? divisor as Integer: 1 default private CandidateDivisors() as pure CandidateDivisors() as pure -> forNumber as Integer number :=? forNumber hasNext() as pure <- rtn as Boolean: divisor * divisor <= number //A copy: `divisor += 1` changes the Integer in place, so handing out `divisor` itself //would change the value the stream had already passed on. next() <- rtn as Integer: Integer(divisor) divisor += 1 default operator ? //THE input idiom of this book, copied unchanged from lib/LineInput.ek9 (the reasons are //there): read with next(), and every input loop also stops when ended() is true. LineInput stdin as Stdin: Stdin() inputEnded as Boolean: false next() <- rtn as String: stdin.next() if not rtn? inputEnded: true ended() as pure <- rtn as Boolean: Boolean(inputEnded) override operator ? as pure <- rtn as Boolean: true //THE INPUT AND OUTPUT: everything the player sees or types goes through here, worded by Say. Console stdout as Stdout: Stdout() input as LineInput: LineInput() say as Say: Say("en") Console() -> words as Say say: words showIntroduction() stdout.println(say.introduction()) //Asks until the answer is a number to factor; unset when the player quits or the input ends. nextNumber() <- rtn as Integer: Integer() quitting <- false while not rtn? and not quitting and not input.ended() stdout.println(say.askNumber()) if entry <- input.next() response <- entry.trim() if response.upperCase() == say.quit() quitting: true else rtn: numberToFactor(response) showFactors() -> factors as List of Integer listed <- cat factors | map with asText | join with withComma | collect as String stdout.println(listed) ended() as pure <- rtn as Boolean: input.ended() override operator ? as pure <- rtn as Boolean: true defines function //THE GAME: the arithmetic, with no input or output. //A positive whole number, or unset for anything else: Integer(text) is unset for anything //that is not a whole number, so this one guard does the original's isdecimal() check. numberToFactor() as pure -> entry as String <- rtn as Integer: Integer() if number <- Integer(entry) then number > 0 rtn: number //The book's algorithm as one pipeline: each divisor up to the root gives a pair of factors, //and `sort | uniq` does the work of the original's set() and sort(). A perfect square's //root pairs with itself (6 x 6 = 36), and `uniq` keeps one of the two. factorsOf() as pure -> number as Integer <- rtn as List of Integer: List() of Integer divides <- (number) is Predicate of Integer as pure function r: number rem t == 0 withPartner <- (number) is Function of (Integer, List of Integer) as pure function r: [t, number / t] rtn: cat CandidateDivisors(number) | filter by divides | map with withPartner | flatten | sort | uniq | collect as List of Integer defines function asText() as pure -> factor as Integer <- rtn as String: $factor withComma() as pure -> left as String right as String <- rtn as String: `${left}, ${right}` //THE language of the words, copied unchanged from lib/Language.ek9: BIGBOOK_LANG when it //is set (fr, zh, ...), otherwise en. language() <- rtn as String: EnvVars().get("BIGBOOK_LANG") ?: "en" //THE WORDS: every word the player sees. defines text for "en" Say introduction() `Factor Finder, by Al Sweigart al@inventwithpython.com A number's factors are two numbers that, when multiplied with each other, produce the number. For example, 2 x 13 = 26, so 2 and 13 are factors of 26. 1 x 26 = 26, so 1 and 26 are also factors of 26. We say that 26 has four factors: 1, 2, 13, and 26. If a number only has two factors (1 and itself), we call that a prime number. Otherwise, we call it a composite number. Can you discover some prime numbers? ` askNumber() "Enter a positive whole number to factor (or QUIT):" quit() "QUIT" defines program //Factor Finder (Big Book project 24). Shows all the factors of each number typed, until QUIT. //The game decides; the Console asks and tells, in the player's words. FactorFinder() //A language with no words of its own is unset: ?: falls back to English. console <- Console(Say(language()) ?: Say("en")) console.showIntroduction() running <- true while running and not console.ended() if number <- console.nextNumber() console.showFactors(factorsOf(number)) else running: false
About this port
- Input:
Integer(text)is unset for anything that is not a whole number, so one guard (if number <- Integer(entry) then number > 0) replaces the original'sisdecimal()check. QUIT (any case) ends the loop, and so does the end of input.