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 56 — Prime Numbers
This is project 56 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:
0
Run it with:
ek9 primenumbers.ek9 < input.txt
It prints:
Prime Numbers, by Al Sweigart al@inventwithpython.com
Prime numbers are numbers that are only evenly divisible by
one and themselves. They are used in a variety of practical
applications, but cannot be predicted. They must be
calculated one at a time.
Enter a number to start searching for primes from:
(Try 0 or 1000000000000 (12 zeros) or another number.)
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47,
The program
#!ek9 defines module bigbook.primenumbers defines constant SmallestPrime <- 2 //The original prints primes until Ctrl-C. A golden needs an end, so the port shows this many. PrimesShown <- 15 defines class //THE GAME's state: the two counts the search walks through, each a stream source. //Every whole number from a start upwards, for ever: the original's `while True: num += 1` //as a stream source. A stream only pulls what a later stage asks for, so `head` ends it. NumbersFrom current as Integer? default private NumbersFrom() as pure NumbersFrom() as pure -> start as Integer current :=? Integer(start) hasNext() as pure <- rtn as Boolean: true //A copy: `current += 1` changes the Integer in place, so handing out `current` itself //would change the number the stream had already passed on. next() <- rtn as Integer: Integer(current) current += 1 default operator ? //The divisors worth trying: 2, 3, ... while divisor * divisor <= number, the same bound as //the original's range(2, int(math.sqrt(number)) + 1), with no Float square root. TrialDivisors number as Integer? divisor as Integer: Integer(SmallestPrime) default private TrialDivisors() as pure TrialDivisors() as pure -> forNumber as Integer number :=? forNumber hasNext() as pure <- rtn as Boolean: divisor * divisor <= number 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 game accepts a number to start from; unset only if the input ends first. nextStart() <- rtn as Integer: Integer() while not rtn? and not input.ended() stdout.println(say.askStart()) if entry <- input.next() rtn: startOf(entry) //The original prints `str(num) + ', '` for each prime, so the line ends with ", " too. showPrimes() -> primes as List of Integer listing <- cat primes | map with listed | collect as String stdout.println(listing) override operator ? as pure <- rtn as Boolean: true defines function //THE GAME: the arithmetic, with no input or output. //A whole number of zero or more, or unset for anything else: Integer(text) is unset for //anything that is not a whole number, so one guard does the original's isdecimal() check. startOf() as pure -> entry as String <- rtn as Integer: Integer() if number <- Integer(entry.trim()) then number >= 0 rtn: number //Prime when no trial divisor divides it. `head 1` stops at the first divisor found, which //is the original's early `return False`, so the test can be pure: no counter to mutate. isPrime() as pure -> number as Integer <- rtn as Boolean: number >= SmallestPrime if rtn divides <- (number) is Predicate of Integer as pure function r: number rem t == 0 firstDivisor <- cat TrialDivisors(number) | filter by divides | head 1 | collect as List of Integer rtn: firstDivisor.empty() //The original's endless search, ended by `head`. primesFrom() as pure -> start as Integer <- rtn as List of Integer: cat NumbersFrom(start) | filter by isPrime | head PrimesShown | collect as List of Integer defines function listed() as pure -> prime as Integer <- rtn as String: `${prime}, ` //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() `Prime Numbers, by Al Sweigart al@inventwithpython.com Prime numbers are numbers that are only evenly divisible by one and themselves. They are used in a variety of practical applications, but cannot be predicted. They must be calculated one at a time. ` askStart() `Enter a number to start searching for primes from: (Try 0 or 1000000000000 (12 zeros) or another number.)` defines program //Prime Numbers (Big Book project 56). Finds the prime numbers from a number the player chooses. //The game decides; the Console asks and tells, in the player's words. PrimeNumbers() //A language with no words of its own is unset: ?: falls back to English. console <- Console(Say(language()) ?: Say("en")) console.showIntroduction() //No start means the input ended first: there is nothing to search from. if start <- console.nextStart() console.showPrimes(primesFrom(start))
About this port
- Input:
Integer(text)is unset for anything that is not a whole number, so one guard replaces the original'sisdecimal()loop. If the input ends before a start number is given, there is nothing to search from and the port stops after the prompt.