I find this type of code an anti pattern of how good code should be. This solution has a high "cognitive load" imho: http://chrismm.com/blog/how-to-reduce-the-cognitive-load-of-...
I'd much rather see a 15-20 line solution that's clear and readable, than a 4 line solution that I have to reverse engineer.
This whole HN attitude of pristine code is killing me. This is a piece of code in a GIST and solves the problem in three lines, it is clearly not intended for production use in a large code base.
Please stop searching for something wrong and enjoy what is presented in all of its glory.
It should be called "0,5kb scala contest" than "solving x with scala", to avoid misunderstanding.
Another way this code could be useful would be to showcase good readability practices, but it doesn't.
OTOH, the gist has a low cognitive load provided one understands funtional notation. I can look at his four lines of code and see that this is a correct solution, because each atom of the function can be reasoned about separately and then composed into the whole. The same is much more difficult with the looping solutions that have been presented as peers to this comment. These solutions have a higher cognitive load because proving correctness requires a full trace of the algorithm, while keeping all of the variables in working memory as you go.
tl;dr; The OP presented a clear and clever solution. I'll allow it. ;)
I think there's a difference between the cognitive load of the solution and the cognitive load of the implementation. The cognitive load of the general solution for queens is somewhat high, but that's why it's a well-known puzzle. But this implementation is practically a description of the problem itself. It's literally selecting (filter) from all queen arrangements (permutations) where the queens aren't conflicting (described via map and zipWithIndex).
Said in other words: 15 line solutions cannot break into the higher-order pattern-recognition space, since these solutions rely on context specific naming.
Naming is one of the most difficult parts of software engineering, so we should try to rely on contextual patterns in order to minimize naming altogether.
The same issue arises when factoring Forth code into tiny "words" (one of the advantages of a concatenative language): you have to name sequences of operations that don't have an obvious problem-space concept behind them.
#!/usr/local/bin/perl
#borrowed from Nick Holloway <alfie@dcs.warwick.ac.uk>, 27th May 1993.
my $size=8;my @cols=(0..$size-1);
sub nextq {
(print( "@_\n" ),return) if @_ == $size;
foreach my $col(@cols) {
next if grep($_ == $col, @_);
my $row=@_;next if grep ($_ == $col-$row--, @_);
my $row=@_;next if grep ($_ == $col+$row--, @_);
nextq(@_,$col);
}
}
nextq();
Not sure if it's slower or more resource intensive than the Scala solution. I'm sure it could be golfed down to be smaller than the Scala counterpart. It has fewer characters already, even with the comments intact. -l /.{@ARGV}/?print:map/((.).*)(.)(??{$2!=$3&&length$1!=abs$2-$3&&0})/||do$0,$_.1.."$_@ARGV" my $size=8;my @cols=(0..$size-1);
my $row=@_;next if grep ($_ == $col-$row--, @_);
my $row=@_;next if grep ($_ == $col+$row--, @_);
Strictly speaking that's 6 lines of code concatenated into 3.But as you said, it would be relatively easy to golf that down and still keep the program logic.
That "print and return with postfix" is the gold star. /s
You can of course then go on and get increasingly clever, but I think you should at least aim for backtracking.
For someone completely unfamiliar with FP, the cognitive load might be high. However, even basic familiarity with FP constructs is enough to understand and appreciate the code.
I would always prefer a map/fold to a for-loop. Removing the "i" and its book keeping reduces cognitive load. However, a Python list comprehension often becomes too big an expression if you use too much filtering. Extracting the filter into its own function an giving it a good name compartmentalizes the complexity, though. All in all, it is quite subjective.
If you are used to premutations, mapping, zipping, filters the code becomes quite readable. I'm pretty sure there is C code which confuses a Haskell programmer but would be considered "clear and readable" by the average C programmer.
from itertools import permutations
def n_queens(n):
for p in permutations(range(n)):
if len({c + i for i, c in enumerate(p)}) == len({c - i for i, c in enumerate(p)}) == n:
yield p def n_queens(n):
yield from (p for p in permutations(range(n))
if len({c + i for i, c in enumerate(p)}) ==
len({c - i for i, c in enumerate(p)}) == n)
It also illustrates how much neater list comprehensions can be in Python versus map/filter since its lambda functions are fairly verbose (even though there's a straightforward correspondence between them): def n_queens(n):
return filter(lambda p:
len(set(map(lambda x: x[1] + x[0], enumerate(p)))) ==
len(set(map(lambda x: x[1] - x[0], enumerate(p)))) == n,
permutations(range(n)))There is next to no cognitive load in this 4-line solution. Brute-force attack on every permutation. I would say that anyone find there to be an excessive cognitive load would be someone that is not familiar with scala nor functional programming.
I am not going to spoil the fun by giving you the details here, just the hint: start on the right square, depending on whether N is even or odd. Then place successive queens in a knight jump pattern.
It is fascinating to me that whoever invented chess a long time ago may have been aware of these subtle complementary relationships between the different types of moves?
_=[__import__('sys').stdout.write("\n".join('.' * i + 'Q' + '.' * (8-i-1) for i in vec) + "\n===\n") for vec in __import__('itertools').permutations(xrange(8)) if 8 == len(set(vec[i]+i for i in xrange(8))) == len(set(vec[i]-i for i in xrange(8)))]
#!/usr/bin/env python3
from itertools import permutations as p
for vec in p(range(8)):
if 8 == len(set(vec[i]+i for i in range(8))) \
== len(set(vec[i]-i for i in range(8))):
print("\n".join( '.' * i + 'Q' + '.' * (8-i-1) for i in vec),
end="\n===\n")
The string construction could/should probably also be refactored a bit, but at least this version I have a hope of following the logic... :-)It took 21 minutes to find and print all the solutions, if I remember right. But that was in Basic on a machine that ran at 10MHz.
queenst =: (, #: I.@,@(</)&i.)~ (] #"1~ [ */@:~:&(|@-/) {)&.|: ! A.&i. ]
This is the calculation, and you can output all 92 solutions for the 8 queens problems like this: queenst 8
0 4 7 5 2 6 1 3
0 5 7 2 6 3 1 4
0 6 3 5 7 1 4 2
....
each column is a row number, and the integer is which column the queen is in, or vice versa on rows and columns.,or print how many solutions
$queens
92 8
$ gives the 'shape' of the array, that is 92 rows (solutions) x 8 columns (8-queens problem).I always return to J, because I enjoy mathematics and functional programming. Like math, you work with symbols you learn to think as you code in an interpreted window, somewhat like when working in the Lisp REPL. Ken Iverson created the APL language in 1960, and won an ACM Turing Award and gave a lecture 'Notation as a Tool of Thought', which sums it up better than I could ever hope to express it [3].
J was a collaboration between him, Arthur Whitney (of finance's k language, and kdb fame), and Roger Hui, in an attempt to improve on APL, and to do away with needing a special character keyboard. It is under the GPL3 license today. J uses the ASCII character set, and is very functional and composable.
[1] http://code.jsoftware.com/wiki/Essays/N_Queens_Problem
[2] http://code.jsoftware.com/wiki/Essays/Queens_and_Knights
[3] http://www.jsoftware.com/papers/tot.htm queens n = q n n [[]] where
q 0 m ss = ss
q n m ss = q (n-1) m (concatMap (\s->(map (:s)(filter (f 1 s) [0..m-1]))) ss)
f _ [] a = True
f n (b:s) a = a /= b && a /= b+n && a /= b-n && f (n+1) s a
main = print $ queens 8 import Data.List
queens n = q [] [0..n-1] where
q s [] = [[]]
q s as = concatMap (\a -> q (a:s) (delete a as)) (filter (f 1 s) as)
f _ [] a = True
f n (b:s) a = a /= b+n && a /= b-n && f (n+1) s a
main = print $ length (queens 8)
Undoubtedly functional, the question is whether it is improved eg. by using folds instead of explicit recursion, or replacing concatMap with monadic join.https://github.com/davidad/8queens/blob/1989666c45baa639f152...
#include <nqueensolver.h>
int main(){int n=5;solve(n);}
Please include the binaries for nqueen solver when compiling and running.
The point I am trying to make is that, in such a high label language like scala, this kind of claim sounds foolish.
tr -d '\n'
and call it one line! Again, many will find this pedantic and unsatisfactory but programmers should really be more precise in what they mean by lines of code.What I think most people are really referring to is information content. To achieve the same program in shorter length, the rest of the information has to lie somewhere else (in the scala implementation or nqeensolver.h). We can increase the density of the information needed to specify a program, but not decrease it.
I actually just wrote something about this concept yesterday:
https://aconz2.github.io/programming/2016/04/09/power_of_pl....
1. Yes, it is O(n!) - it says so in the description but not on the title of the post on HN.
2. It is 4 lines of code for `nQueen` function. I did not include the printing code in the line count.
3. And no, I did not cheat by using semicolons in same lines - these are 4 legit lines IMO. You can put the `&` clause in 1 line to reduce it further.
from itertools import permutations
def nQueens(n):
return (p for p in permutations(range(n))
if (len(set(c+d for c,d in enumerate(p))) == n and
len(set(c-d for c,d in enumerate(p))) == n))
for num,solution in enumerate(nQueens(8)):
print("Solution #", num+1)
print("\n".join((" ".join("Q" if i == col else "-" for i in range(8))) for col in solution))http://citeseerx.ist.psu.edu/viewdoc/download;jsessionid=4DC...
Rosetta posts should always include a one liner from APL, so here is a link: