How (Not?) to Use Python’s List Comprehensions
moderndescartes.com
moderndescartes.com
import Control.Monad (guard)
import Data.Ratio ((%))
results :: [[Int]]
results =
do th <- u
o <- u
r <- u
guard $ 2*th^2 + o^2 + r^2 == 1000
s <- u
z <- u
guard $ th^2 * z^2 * (s - z) + s^2 == 7225
g <- u
d <- u
xi <- u
guard $ (g - 1)^2 + d^2 + xi^2 - xi == 600
(et, i) <- [(xi - 7, xi - 11), (xi - 11, xi - 7),
(xi + 7, xi + 11), (xi + 11, xi + 7)]
guard $ 0 < et && et <= 26 && 0 < i && i <= 26
(a, mu) <- [(4, 1), (2, 2), (1, 4)]
pi <- u
guard $ a * (a + pi) == 4 * g
b <- u
n <- u
guard $ b^3 + z^3 + n^3 + o^9 == 1997
k <- [3, 4]
e <- u
guard $ (a % k)^2 + (b % n)^2 + (d % xi)^2 +
(e % pi)^2 + (et % mu)^2 + (i % s)^2 == 6
return [a, b, g, d, e, z, et, th, i, k, th, mu, n, xi, o, pi, r, s]
where u = [1..26]
main :: IO ()
main = print results (et, i) <- [(xi - 7, xi - 11), (xi - 11, xi - 7),
(xi + 7, xi + 11), (xi + 11, xi + 7)]
Could be written as (et,i) <- do (a,b) <- let tuple = (7,11) in [tuple, swap tuple]
sign <- [id,negate]
[(xi+sign a,xi+sign b)] f <- [(+), (-)]
let t = (xi `f` 7, xi `f` 11)
(et, i) <- [t, swap t]It's so much easier to see all this by just working out a small example for yourself with pen and paper.
So these two are equivalent, and the list comprehension version is very close to what nested loops (read it from left to right) would give you, except for the laziness.
do x <- [1, 2, 3]; guard (f x); y <- [1, 2, 3]; guard (g x y); z <- [1, 2, 3]; guard (h x y z); return (x, y, z)
[(x, y, z) | x <- [1, 2, 3], f x, y <- [1, 2, 3], g x y, z <- [1, 2, 3], h x y z]
for x in [1, 2, 3] { if (f(x)) { for y in [1, 2, 3] { if (g(x,y)) { for z in [1, 2, 3] { if (h(x,y,z)) { yield((x, y, z)) }}}}}} -- "pseudocode"
The guard uses the following idea: guard True = [()]
guard False = []
So _ <- guard True is a noop, while _ <- guard False short-circuits the list comprehension the way "continue" would (just like "for x in [] { ... }"), so guard (f x) becomes the test f x in the list comprehension.This isn't an accident. Python's list comprehensions were modeled on Haskell's list comprehensions, which were in turn modeled on the much more general concepts of monads. Haskell's do notation provides a nice syntactic sugar for monadic code. Since lists form a monad, such code is quite naturally expressed using do notation.
Haskell's list comprehensions can be directly translated to do notation
[x+y | x <- [1..10], y <- [1..10], x+y<5]
can be translated into do x <- [1..10]
y <- [1..10]
guard (x+y<5)
return (x+y)
which is syntactic sugar for [1..10] >>= (\x ->
[1..10] >>= (\y ->
guard (x+y<5) >>
return (x+y)))
where (>>=) is the monadic bind operator.It's true that Python borrowed the concept from Haskell.
But Haskell in turn took the concept from set theory, not category theory.
List comprehensions are modeled after set comprehensions. Here's an example that uses set-builder notation [0] to define the set of Pythagorean triples with sides bounded by 10:
{ (x,y,z) | x,y,z \in {1,2,...,10}, x^2 + y^2 = z^2 }
In Haskell that becomes let u=[1..10] in [ (x,y,z) | x<-u,y<-u,z<-u, x^2 + y^2 = z^2 ]
The creators of Haskell worked hard to make sure the syntax was a spitting image of set theory.For a motivation behind the creation of Haskell was to use it to as a lightweight calculator / computer algebra system (sans heavy algebra) to teach set theory to students.
List comprehensions have been around since the dawn of Haskell. It was around even before Haskell, viz. in Miranda circa mid-80s.
Moggi's work on PL monads was only published in 1991.
I think you've mixed up monad comprehensions--which came much later--which is the observation that this notation and concept can be generalized to monads other than lists. Monad comprehensions segue into topos theory, understood as a monadic generalization of set theory, quite nicely.
Hopefully, the next 700 monad tutorials will give more visibility to topos theory.
results is an array of Int, but do the double [[ ]] brackets mean something special? I hope they do, otherwise why planting doubts in people's mind.
<- is assignment or pattern matching, probably the latter.
the $ after guard is... what? Maybe a noop like Python's :
Maybe
guard $ expression
something
is equivalent to if expression
something
If this is the case, it's much less readable because people are left guessing. The lack of indentation doesn't help either.u is probably the variable that's iterated over in where u = [1..26]
Not very readable to put that after every use of u. This is easier to read
where u = [1..26] do
which is many languages is for u in [1..26] do
I can imagine that where is more general and is used in other contexts.Don't take these as criticisms of Haskell, only as a UX test. Python's list comprehensions have never been very readable to me. This Haskell loop is somewhat better.
I'm sure there are good reason for designing Haskell's syntax like that. Programming languages belong to lineages and tend to perpetrate the syntax of their parents. It makes them easier to adopt to developers well versed in their parent languages but that could also make them difficult to grasp for developers of other language families. Maybe luring imperative developers into the functional world is not one of the goals of Haskell and Haskell has achieved its goals as it is.
Still I believe that the first functional language that looks like an imperative language will move many developers from OOP to functional. Elixir attempted that, consciously or not, but it's not enough. The barrier there are the OTP patterns, which are not easy to use properly. There are many different choices for writing a process (what in OOP would be an object with its own CPU) and its hard to chose the proper one. GenServer, Agent, Task? Nobody is asking these kind of questions in Ruby or Python https://groups.google.com/forum/#!topic/elixir-lang-talk/DCT...
There is also the code to add them to the supervision tree (the cool systemd-inside-the-language thing of Erlang and Elixir). I can't help thinking that Elixir has to be streamlined.
This is because there could be more than one set of solutions to the simultaneous eqn
Guard $ foo
Is equivalent to filtering out the values in u in each variable that fails foo. They're staged that way for the sake of performance - filter out possible values of x now so that the next guard statement has fewer possibilities to iterate over.Then, once all guard statements have been put into effect, the full set of combinations of values that pass each guard are placed in the array.
Critically, it is not "for u in [0..26]" but "u=[0..26]", which gives me the idea that this isn't a loop but a filter.
> where u = [1..26] do
This isn't valid Haskell, but this is
let u = [1..26] in
do ...
Also ($) is just sugar to avoid parenthesis.Instead of writing
f (1+2*3)
you can write f $ 1+2*3
Haskellers have a massive fetish for eliminating parenthesis, perhaps in order to avoid their code looking like lisp.Neither really, though I guess it's kind of like assignment, but it's usually read "draw from". Do-notation is sugar for working with monadic values, which are kind of like containers that hold a value. It's a bit hard to explain, but basically it means to pull the enclosed value out of the container on the right hand side and reference it according to the name on the left hand side.
> the $ after guard is... what?
$ is function application. It basically means to evaluate the right hand side before passing the result to the left hand side. It's used to avoid parentheses, `f $ g h` is like `f (g h)` (Haskell doesn't use parenthesis for function evaluation).
Haskell syntax is lovely when you're used to it, but it's also subtle. IMO it can be easy for people who aren't familiar to get the general idea of what's going on but the specifics are often not what you think.
That's supposed to be the deal with javascript (but please don't ask me for a source; I was first introduced to the idea that js is functional in a book in an old workplace's library, where I was waiting for my boss to chew me up. Needless to say, I haven't used javascript much).
for b in range(1, 27)
for n in range(1, 27)
if b**3 + z**3 + n**3 + o**9 == 1997
for k in (3,4)
for e in range(1, 27)
This reduces the number of innermost tests and on my laptop it runs about 18x faster than the blog's solution. That was: for k in (3,4)
for b in range(1, 27)
for e in range(1, 27)
for n in range(1, 27)
if b**3 + z**3 + n**3 + o**9 == 1997
Then I see that @eutectic did the same optimization in his Haskell code. Nice! guard $ (a % k)^2 + (b % n)^2 + (d % xi)^2 +
(e % pi)^2 + (et % mu)^2 + (i % s)^2 == 6
can be written as guard $ (6 ==) $
sum $ map (^2) $
zipWith (%)
[a ,b ,d ,e ,et,i ]
[k ,n ,xi,pi,mu,s ] sum $ map (^2)
Is pretty clear as a sum of squares is a quite common mathematical idiom. The zipWith part could probably be avoided. guard $ (6 ==) $
sum $ map (^2)
[(a%k),...]The normal benefit of generators is that they don't store the intermediate result in memory, but that isn't particularly necessary in this case
The only drawbacks I can see:
- if you want to debug that with PDB, you are out of luck.
- anybody not wanting to spend the time understanding what it does will freak out.
Unrelated, but you can do:
def get_ranges(i): return product(*(range(1, 27) for _ in range(i)))
To replaced stuff like:
for th in range(1, 27)
for o in range(1, 27)
for r in range(1, 27)
With: for th, o, r in get_ranges(3)
But again, no sure it makes things better. Shorter yes, but the benefit of your version is that each step is very clear.I still have no idea what's this code is for though :)
from fractions import Fraction as F
def thor():
for th in range(1, 27):
for o in range(1, 27):
for r in range(1, 27):
if 2*(th**2) + o**2 + r**2 == 1000:
yield th, o, r
def sz(thors):
for th, o, r in thors:
for s in range(1, 27):
for z in range(1, 27):
if (th**2 * z**2 * (s - z) + s**2) == 7225:
yield th, o, r, s, z
def gdxi(szs):
for th, o, r, s, z in szs:
for g in range(1, 27):
for d in range(1, 27):
for xi in range(1, 27):
if (g-1)**2 + d**2 + xi**2 - xi == 600:
yield th, o, r, s, z, g, d, xi
def eti(gdxis):
for th, o, r, s, z, g, d, xi in gdxis:
for (et, i) in (
(xi-7, xi-11), (xi-11,xi-7), (xi+7, xi+11), (xi+11, xi+7)):
if 0 < et <= 26 and 0 < i <= 26:
yield th, o, r, s, z, g, d, xi, et, i
def amupi(etis):
for th, o, r, s, z, g, d, xi, et, i in etis:
for (a, mu) in ((4, 1), (2, 2), (1, 4)):
for pi in range(1, 27):
if a*(a+pi) == 4 * g:
yield th, o, r, s, z, g, d, xi, et, i, a, mu, pi
def kben(amupis):
for th, o, r, s, z, g, d, xi, et, i, a, mu, pi in amupis:
for k in (3,4):
for b in range(1, 27):
for e in range(1, 27):
for n in range(1, 27):
if b**3 + z**3 + n**3 + o**9 == 1997:
if (F(a, k)**2 + F(b, n)**2 + F(d, xi)**2 +
F(e, pi)**2 + F(et, mu)**2 +
F(i, s)**2 == 6):
yield (a, b, g, d, e, z, et, th, i,
k, th, mu, n, xi, o, pi, r, s)
thors = thor()
szs = sz(thors)
gdxis = gdxi(szs)
etis = eti(gdxis)
amupis = amupi(etis)
results = kben(amupis)
print('\t'.join(('a', 'b', 'g', 'd', 'e', 'z', 'et', 'th',
'i', 'k', 'l', 'mu', 'n', 'xi', 'o', 'pi', 'r', 's')))
for r in results:
print('\t'.join(map(str, r))) { a : 2*a for a in range(1, 10)} #produces {1:2, 2:4, ...}
{ 2*a for a in range(1, 10)} #produces set([2, 4, ...])My PhD code (which was too slow and RAM-hungry, having been written in Python...) was a monster, and was a lot less of a monster for order-2 and order-3 nested list/dict comps.
It started out as a 200 line proof of concept (which worked perfectly on an ideal of the system I was studying), and it evolved into a 6,000 line behemoth (~2,000 lines for OpenGL rendering error cases) which would have been 10,000 lines without nested list and dict comps.
By the way, never use networkx on anything you expect to have a large number of vertices or edges. It uses a lot of RAM (esp in undirected graphs where you have backrefs for each edge.) Wrap a C++ graph lib like boost::graphs or something, and code against that interface from the beginning.
https://mathieularose.com/choosing-the-right-data-structures...
This is definitely not a good way to use list comprehensions.
I appreciate the OP's effort to break the language. Well done.
But no one does this in practical reality.
result = [x
for sublist in nested_list
for x in sublist
if x % 3 == 0
]
I rarely nest comprehensions, but I will occasionally have more than one condition in the filter at the end, or some condition in the selection at the front.Except for the simplest comprehensions, I tend to write mine similar to above, with indents to highlight what part of the comprehension is what.
The utility of a comprehension is not so much concise notation. Instead it's that a comprehension is an expression (that has an associated value), rather than the for loop which is a statement.
Similar to the trinary operator: its advantage is not merely that it replaces an if statement visually, it's that it's a directly usable expression (that can be composed in a (preferably short) chain of trinaries).
This highlights the four things that fizzbuzz requires:
print '\n'.join([
'fizzbuzz' if x%15 == 0 else
'fizz' if x%3 == 0 else
'buzz' if x%5 == 0 else
str(x) for x in xrange(1, 101)])
Gratuitous demo filter conditions: print '\n'.join([
'fizzbuzz' if x%15 == 0 else
'fizz' if x%3 == 0 else
'buzz' if x%5 == 0 else
str(x) for x in xrange(1, 101)
if x%2 == 0 or x%2 != 0])
If nested comprehensions make things harder to understand, I'll sometimes move an inner comprehension out: subrange = {x for x in range(1, 101)}
print '\n'.join([
'fizzbuzz' if x%15 == 0 else
'fizz' if x%3 == 0 else
'buzz' if x%5 == 0 else
str(x) for x in subrange
if x%2 == 0 or x%2 != 0])
EDIT: OCD forces me to rewrite: subrange = {x for x in range(1, 101)}
print '\n'.join([
'fizzbuzz' if x%15 == 0 else
'fizz' if x%3 == 0 else
'buzz' if x%5 == 0 else
str(x)
for x in subrange
if x%2 == 0 or x%2 != 0])
FURTHER EDIT: argh! subrange = (x for x in range(1, 101))
Original was a set comprehension. That it worked "in order" was a happy accident. Further edit is a generator. subrange = {x for x in range(1, 101)}
print '\n'.join(
'fizzbuzz' if x % 15 == 0 else
'fizz' if x % 3 == 0 else
'buzz' if x % 5 == 0 else
str(x)
for x in subrange
if x % 2 == 0 or x % 2 != 0)
Notice how the '[' and ']' are missing in the code above, yet it is still both correct python and correct fizzbuzz solution and runs faster because the code does less work.But without the [] it appears to produce a generator expression (which is immediately consumed) rather than a list (which is immediately consumed).
In [9]: print ' '.join(str(x) for x in range(3))
0 1 2
In [10]: type(str(x) for x in range(3))
Out[10]: generator
In [11]: type([str(x) for x in range(3)])
Out[11]: list
Same effect between the two, but different and interesting things. Thanks.EDIT: Also:
In [3]: %timeit ' '.join(str(x) for x in range(3))
100000 loops, best of 3: 3.11 µs per loop
In [4]: %timeit ' '.join([str(x) for x in range(3)])
1000000 loops, best of 3: 1.47 µs per loop
The list version, with the [], is about twice as fast as the generator version (without the brackets). I suspect because the generator version has to "go back to the well" for each value, but I don't really know. And this is one small test case, maybe it's different for larger sequences. Testing is everything. :)"Transforming and Reducing Data at the Same Time"
I highly recommend David Beazley's excellent book, it made me a much better Python developer, helping me produce both idiomatic and efficient solutions over the last year. That book is programming poetry.
Python goes to extensive lengths to work around its mediocre lambdas. I'll take a language with good lambda syntax and higher-order-functions like map and filter (or select and where in C#).
List comprehensions have always struck me as a very ugly mechanism which is convenient in simple instances and which composes together poorly. This kind of special-casing is all over the place in Python's design. Consider, for example, all the unintuitive syntactic and semantic contortions introduced to support interval comparisons[2]. The overall result is a language which is apparently simple but in practice surprisingly complex.
[1] http://www.artima.com/weblogs/viewpost.jsp?thread=98196
[2] https://docs.python.org/2/reference/expressions.html#not-in
I mean, Python could offer an alternate sexpr syntax that makes it a lisp, and that would provide nice higher-order-function support for map and filter and foldr, but that wouldn't be a "pythonic" solution to the problem.
I want a "pythonic" answer for map and filter and reduce. List Comprehensions are supposed to provide that, but imho they're a failed attempt. They're ugly and restrictive.
While what he does works, it can just as easily be written as
list(itertools.chain(*[[x for x in sublist if x%3 == 0] for sublist in nested_list]))
Which uses a bit of magic to massage the end result, but it is much clearer for the inner portions, and much similar to your 'lispy' style. (`itertools.chain(` is equivalent to a 1-level flatten)You can do the same thing in the bigger case, and you end up nesting your structures such that you get something like
results = [[[[[[[[[[[[[[[(a, b, g, d, e, z, et, th, i, k, th, mu, n, xi, o, pi, r, s)
for th in range(1, 27) if (2*(th**2) + o**2 + r**2 == 1000) and ((th**2 * z**2 * (s - z) + s**2) == 7225)]
for o in range(1, 27) if (b**3 + z**3 + n**3 + o**9 == 1997)]
for r in range(1, 27)]
for z in range(1, 27)]
for n in range(1, 27) if F(a, k)**2 + F(b, n)**2 + F(d, xi)**2 + F(e, pi)**2 + F(et, mu)**2 + F(i, s)**2 == 6]
for b in range(1, 27)]
for e in range(1, 27)]
for s in range(1, 27)]
for (a, mu) in ((4, 1), (2, 2), (1, 4)) if a*(a+pi) == 4 * g]
for pi in range(1, 27)]
for g in range(1, 27) if (g-1)**2 + d**2 + xi**2 - xi == 600]
for d in range(1, 27)]
for (et, i) in ((xi-7, xi-11), (xi-11,xi-7), (xi+7, xi+11), (xi+11, xi+7)) if 0 < et <= 26 and 0 < i <= 26]
for xi in range(1, 27)]
for k in (3,4)]
Which admittedly isn't better*, but you can build up over time (ie. you can start with [f(k) for k in range(3, 4)], and then provide f(k) in a closure, and repeat, to build this up over time.I've always thought they are basically an implementation of set builder notation, e.g.:
X = {a | a ∈ Y}
Equivalent to: X = [a for a in Y]
And so on? X = {a | A ∈ Y, a ∈ A }
X = {a | a ∈ A, A ∈ Y}
But in Python, changing the order changes the meaning: >>> Y = (('a','b','c'), ('d','e','f'))
>>> X = [a for A in Y for a in A]
>>> X
['a', 'b', 'c', 'd', 'e', 'f']
>>> X = [a for a in A for A in Y]
>>> X
[('a', 'b', 'c'), ('a', 'b', 'c'), ('d', 'e', 'f'), ('d', 'e', 'f')]
It's the difference between declarative and imperative semantics- a kind of declarative-imperative impedance mismatch, if you like. The set-builder notation tells you how things are, timeless and unchanging. The imperative notation tells you how to get there.Consequently, you can't just throw any set of comprehension elements together and hope that you'll get something that makes sense. This messes up the elegance of the notation considerably, and it also makes it very, very fiddly to write some things that you might conceivably want to write, so much so that it ends up being much more readable to write a traditional for-loop, than use a comprehension.
There might even be orderings that are not allowed by the Python compiler, even though they might make sense in a declarative formalism. I can't think of any right now, but I'm pretty sure there are plenty- and lambdas are not going to be a get-out-of-jail-free card either.
from fractions import Fraction as F
def thor():
for th in range(1, 27):
for o in range(1, 27):
for r in range(1, 27):
if 2*(th**2) + o**2 + r**2 == 1000:
yield th, o, r
def sz(thors):
th, o, r = thors
for s in range(1, 27):
for z in range(1, 27):
if (th**2 * z**2 * (s - z) + s**2) == 7225:
yield th, o, r, s, z
def gdxi(szs):
th, o, r, s, z = szs
for g in range(1, 27):
for d in range(1, 27):
for xi in range(1, 27):
if (g-1)**2 + d**2 + xi**2 - xi == 600:
yield th, o, r, s, z, g, d, xi
def eti(gdxis):
th, o, r, s, z, g, d, xi = gdxis
for (et, i) in (
(xi-7, xi-11), (xi-11,xi-7), (xi+7, xi+11), (xi+11, xi+7)):
if 0 < et <= 26 and 0 < i <= 26:
yield th, o, r, s, z, g, d, xi, et, i
def amupi(etis):
th, o, r, s, z, g, d, xi, et, i = etis
for (a, mu) in ((4, 1), (2, 2), (1, 4)):
for pi in range(1, 27):
if a*(a+pi) == 4 * g:
yield th, o, r, s, z, g, d, xi, et, i, a, mu, pi
def kben(amupis):
th, o, r, s, z, g, d, xi, et, i, a, mu, pi = amupis
for k in (3,4):
for b in range(1, 27):
for e in range(1, 27):
for n in range(1, 27):
if b**3 + z**3 + n**3 + o**9 == 1997:
if (F(a, k)**2 + F(b, n)**2 + F(d, xi)**2 +
F(e, pi)**2 + F(et, mu)**2 +
F(i, s)**2 == 6):
yield (a, b, g, d, e, z, et, th, i,
k, th, mu, n, xi, o, pi, r, s)
flatten = lambda l: [item for sublist in l for item in sublist]
thors = thor()
szs = flatten(filter(None, map(lambda x: list(sz(x)), thors)))
gdxis = flatten(filter(None, map(lambda x: list(gdxi(x)), szs)))
etis = flatten(filter(None, map(lambda x: list(eti(x)), gdxis)))
amupis = flatten(filter(None, map(lambda x: list(amupi(x)), etis)))
results = flatten(filter(None, map(lambda x: list(kben(x)), amupis)))
print('\t'.join(('a', 'b', 'g', 'd', 'e', 'z', 'et', 'th',
'i', 'k', 'l', 'mu', 'n', 'xi', 'o', 'pi', 'r', 's')))
for r in results:
print('\t'.join(map(str, r))) thors = thor()
szs = flatten(map(list, map(sz, thors)))
gdxis = flatten(map(list, map(gdxi, szs)))
etis = flatten(map(list, map(eti, gdxis)))
amupis = flatten(map(list, map(amupi, etis)))
results = flatten(map(list, map(kben, amupis)))
# print resultshttps://gist.github.com/lispm/7c009676fe6b86cff851bc8a1ba82d...
CL-USER 20 > [x (y <- '((0 1) (2 3) (4 5) (6 7) (8 9)))
(x <- y)
(zerop (mod x 3))]
(0 3 6 9)https://classroom.udacity.com/courses/cs212/lessons/48632874...
result = [x for sublist in nested_list for x in sublist if x % 3 == 0]
This explanation still confuses people initially. I suggest this one: result = [a for b in c for a in b if a % 3 == 0]
Here it's more obvious that the outer loop arguments are to be reversed in the latter part of the nested comprehension.However I would advise against any further nesting than this, you will not want to debug that if anything goes wrong with one of the inputs here, especially if you also do any conditional logic here in the comprehension.
for A in range(1, 27):
for B in range(1, 27):
if A == 2 * B:
Why iterate over anything here? for A in range(1, 27):
if A % 2 == 0:
B = A / 2
Or for A in range(2, 27, 2):
B = A / 2
Is there a full version of the naive nested loops? I'd like to see just how readable the final version is with applying more rules like this. (defpackage solver (:use :cl :iterate) (:shadow pi))
(in-package :solver)
(defmacro defsolver (name default constraints result)
(flet ((normal-var (var) `(iter (for ,var ,@(rest default))))
(custom-var (var spec) `(iter (for ,var in ,spec)))
(guard (expr) `(when ,expr))
(solved () `(return-from ,name ,result)))
(macrolet ((yield (form) `(append ,form (list (constraint (rest cs))))))
(labels ((constraint (cs &aux (c (first cs)))
(cond ((null cs) nil)
((eq c :solved) (solved))
((symbolp c) (yield (normal-var c)))
((eq (first c) 'for) (yield (custom-var (second c) (third c))))
(t (yield (guard c))))))
`(defun ,name () ,(constraint `(,@constraints :solved)))))))
(defsolver result
(:default from 1 to 26)
(th
o
r
(= (+ (* 2 (expt th 2)) (expt o 2) (expt r 2)) 1000)
s
z
(= (+ (* (expt th 2) (expt z 2) (- s z)) (expt s 2)) 7225)
g
d
xi
(= (+ (expt (1- g) 2) (expt d 2) (expt xi 2) (- xi)) 600)
(for (et i) `((,(- xi 7) ,(- xi 11)) (,(- xi 11) ,(- xi 7))
(,(+ xi 7) ,(+ xi 11)) (,(+ xi 11) ,(+ xi 7))))
(and (< 0 et) (<= et 26) (< 0 i) (<= i 26))
(for (a mu) '((4 1) (2 2) (1 4)))
pi
(= (* 4 g) (* a (+ a pi)))
b
n
(= (+ (expt b 3) (expt z 3) (expt n 3) (expt o 9)) 1997)
(for k '(3 4))
e
(= (+ (expt (/ a k) 2) (expt (/ b n) 2) (expt (/ d xi) 2)
(expt (/ e pi) 2) (expt (/ et mu) 2) (expt (/ i s) 2)) 6))
(list a b g d e z et th i k th mu n xi o pi r s))
SOLVER> (time (result))
Evaluation took:
0.010 seconds of real time
0.012000 seconds of total run time (0.012000 user, 0.000000 system)
120.00% CPU
23,160,723 processor cycles
851,968 bytes consed
(4 9 19 12 15 3 1 20 5 4 20 1 9 12 2 15 14 5)
Edit: formatting, simplificationThe b, n, k, e definitions are reordered better as in @eutectic's haskell example.
(defmacro defsolver (name default constraints result)
(flet ((normal-var (var) `(iter (for ,var ,@(rest default))))
(custom-var (var spec) `(iter (for ,var in ,spec)))
(guard (expr) `(when ,expr))
(solved () `(return-from ,name ,result)))
(labels ((constraint (cs &aux (c (first cs)))
(flet ((yield (form) (append form (list (constraint (rest cs))))))
(cond ((null cs) nil)
((eq c :solved) (solved))
((symbolp c) (yield (normal-var c)))
((eq (first c) 'for) (yield (custom-var (second c) (third c))))
(t (yield (guard c)))))))
`(defun ,name () ,(constraint `(,@constraints :solved)))))) for a in range(1,27) for b in range(1,27) if a == 2*b ...
you could try for b in range(1,27) for a in [2*b] if a < 27 ...ever heard of gauss algorithm?
So I could go back to math class and learn some more formal proofs. What part of my programming knowledge am I going to drop to learn that? Will it actually help my career?
I have found little to no use for advanced mathematics in 10 years of programming. I am not saying it had no use, but I have not needed it.
Most academic types majorly overestimate the usefulness of math in programming. I can code circled around people who rock out at math.
> I have found little to no use for advanced mathematics in 10 years of programming.
I reckon you use or stumble upon the algorithms often, perhaps all the time. Be it with Boolean logic, or network design, or optimal stopping, or fuzzy matching, or statistics in logging, or what have you. These principles show up often. My point wasn't you can't or shouldn't be able to code circles around people who rock out at math, but that maths education often fails to emphasize the logical methods of figuring things out from first principles over the already solved proofs, methods, or equations.
> Most academic types majorly overestimate the usefulness of math in programming.
To some degree, I agree here. If there are well developed libraries and mature paradigms needed (RDBMS, web dev, etc.) then there is little need for mathematical forte. Nothing wrong with that!
From the article: "Don't do this at work, adults!"