My name is Paul Bissex, and e-scribe.com is my consulting business. I build web applications using as much open source software as possible. From September to June I teach web design and other important non-photographic professional skills to photographers. In the '90s I wrote technology commentary and reviews for magazines, newspapers, and web publications, including Wired, Salon.com, FamilyPC, the late lamented Web Review, and the Chicago Tribune. Feel free to email me.
I'm co-authoring a book, "Python Web Development with Django", with Jeff Forcier and Wesley Chun. It will be published by Prentice Hall in July 2008, but is available for pre-ordering on Amazon now.
This site is built on a fresh trunk checkout of Django, running on Python 2.5.1, served by Apache and mod_python. The database is SQLite. The operating system is FreeBSD, on a VPS hosted at Johncompanies.com. Comment-spam protection by Akismet. Vintage topo imagery from the Maptech archive.
Akismet, del.icio.us, Django, dpaste.com, Emacs, FreeBSD, Freenode, jQuery, LaunchBar, MacPorts, Markdown, Mercurial, OS X, Postfix, Python, SQLite, Subversion, TextMate, Trac, Ubuntu Linux, wmii
Copyright 2008
by Paul Bissex
and E-Scribe New Media
In November I wrote about rediscovering BASIC Computer Games, a book I had when I was learning programming in the '80s. Flipping through it recently I came across a simple game called "Reverse":
The game of REVERSE requires you to arrange a list of numbers in numerical order from left to right. To move, you tell the computer how many numbers (counting from the left) to reverse. For example, if the current list is 2 3 4 5 1 6 7 8 9 and you reverse 4, the result will be 5 4 3 2 1 6 7 8 9. Now if you reverse 5, you win.
The original BASIC version, including instructions and comments, is about 60 lines. It seemed like a good candidate for a quick language comparison; a chance to learn a little more Ruby and to see how it felt compared to two languages I'm more familiar with, Python and PHP. (If I had any skill in Perl or Tcl or Scheme they'd be included as well. Javascript should probably be here too.)
I also wanted to see just how much shorter these new versions would be -- the BASIC program takes about eight lines just to initialize the array of shuffled numbers.
The three programs are reproduced below; all were tested on my PowerBook running OS X 10.4.3, with Ruby 1.8.2, Python 2.4.1, and the PHP CLI 4.3.11. Their output looks identical in the shell. Here's the end of a game:
...
3 1 2 4 5 6 7 8 9
Reverse how many? 3
2 1 3 4 5 6 7 8 9
Reverse how many? 2
Done! That took you 9 steps.
Here's the code. I tried to be fair with the line and byte counts. I do think the counts represent the languages' respective verbosity levels in a general way.
numbers = (1..9).sort_by{ rand }
steps = 0
while numbers != numbers.sort
puts numbers.join(" ")
print "Reverse how many? "
flipcount = gets.to_i
numbers[0...flipcount] = numbers[0...flipcount].reverse
steps += 1
end
print "Done! That took you #{steps} steps.\n"
import random
numbers = random.sample(range(1,10), 9)
steps = 0
while numbers != sorted(numbers):
print " ".join(map(str, numbers))
flipcount = int(raw_input("Reverse how many? "))
numbers[:flipcount] = reversed(numbers[:flipcount])
steps += 1
print "Done! That took you %d steps." % steps
$numbers = range(1, 9);
shuffle($numbers);
$sorted = $numbers;
sort($sorted);
$steps = 0;
while ($numbers != $sorted)
{
print implode(" ", $numbers) . "\n";
print "Reverse how many? ";
$flipcount = (int)trim(fgets(STDIN));
array_splice($numbers, 0, $flipcount, array_reverse(array_slice($numbers, 0, $flipcount)));
$steps++;
}
print "Done! That took you $steps steps.\n";
Ruby's win on concision doesn't, to my eyes, come at the expense of readability. There are some flavors of Ruby code that I find frighteningly Perl-like, but none of that emerged here.
I was surprised that PHP didn't take more lines; it loses in the awkward initialization section, but nested functions allow the flip to happen in one line, albeit a long one.
Despite the warm feeling of cleverness I got from figuring out how to do the flip operation in one line of Python, the Ruby equivalent is inarguably easier to read.
PHP is just ugly-looking.
I am very tempted to actually learn Ruby. For simple things at least, it has a concision and consistency that remind me strangely of Forth. And I mean that in a nice way.
Some thing I would do differently in the python sample:
Initialization of data: numbers = random.sample(range(10),10)
Reversing the flip: numbers[:flipcount] = reversed(numbers[:flipcount])
All good suggestions, thanks! I've updated the code to reflect them. I had actually played with reversed() but was tripped up while experimenting by the fact that r = reversed([1,2,3]) gets the iterator object; you need to assign to a slice (e.g. r[:] = reversed([1,2,3])) to get a reversed list.
here is a way how to increase the number of lines in the python code:
numbers=range(1,10) random.shuffle(numbers)
(I find it a bit more readable than random.sample(range(1,10), 9))
Also, in python2.4 you can use
" ".join(str(n) for n in numbers)
in place of
" ".join([str(n) for n in numbers])
(saved 2 characters!). [to save even more characters, one could use
flipcount = input("Reverse how many? ")
but I think that's justly considered dangerous]
My Haskell version is here:
http://codepoetics.com/poetix/index.php?p=213
There's a certain amount of scaffolding that we have to put in ourselves, but the main loop's tolerably concise.
Metapundit > Ruby's type system is no weaker than Python's, both are strongly dynamically typed languages.
The difference is that Ruby's Array#join method calls to_s (string conversion) on every element of the array (which is explicitely stated in the documentation, check ri Array#join) while Python's str.join doesn't (here again, it's explicitely stated in the documentation that join takes an array of strings).
In the python version, you could do random.sample("123456789", 9) to get away with the cleaner " ".join(numbers) later on.
The initialization in the PHP version also looks a bit bulky, and could probably be cut down to something like:
$sorted = $numbers = range(1, 9);
shuffle($numbers);
$steps = 0;
More traditionally the way to reverse list elements in Python is
numbers[:flipcount] = numbers[flipcount-1::-1]
for example
>>> x=[1,2,3,4,5]
>>> i=2
>>> x[:i] = x[i-1::-1]
>>> x
[2, 1, 3, 4, 5]
>>> i=4
>>> x[:i] = x[i-1::-1]
>>> x
[4, 3, 1, 2, 5]
>>>
In Python, save some bytes with
print " ".join(map(str, numbers))
Bye ...
Python version could be somewhat shorter.
numbers[:flipcount] = numbers[flipcount-1::-1]
I find this actually clearer than reversed().
masklinn: > Rubys type system is no weaker than Pythons, both are strongly dynamically typed languages.
The difference is that Rubys Array#join method calls to_s (string conversion) on every element of the array (which is explicitely stated in the documentation, check ri Array#join) while Pythons str.join doesnt (here again, its explicitely stated in the documentation that join takes an array of strings).
My mistake. I was thinking ruby was weakly dynamically typed (like PHP is) and python was strongly dynamically typed... Aside from the typing issues, does seem like python's join should implicitly convert list elements to strings since join is a string only operation... Ruby definitely does the right thing here and I guess I have to chalk the terseness up to better library design rather than difference in typing...
Nobody buys the superiority of map over list comprehensions? I just see
print " ".join([str(n) for n in numbers])
as a little verbose to mentally unpack. I can't help seeing for as looping construct so working from inmost construct I see "okay, loop over list, converting current item to string, making a new list of the result", and join by a space.
" ".join(map(str,numbers))
I look at and think "map array to array of strings", and join by a space.
I buy it, and in fact I changed the code accordingly! (Somehow it didn't make it in with the changes I made last night.)
I'll have to say that the Ruby version isn't a good idom. sort_by{ rand } isn't a true random shuffle as done in Python.
http://eigenclass.org/hiki.rb?sort_by+rand+is+biased
Ruby loses points for an invalid program :) And on the point of ease of reading, the version : numbers = range(1,10) random.shuffle(numbers)
is the best in my opinion. Not everything needs to be a one liner.
In the Python code you can save 2 more lines at the expense of readability. Python can so several assignments on the same line so you could have
numbers, steps = random.sample(range(1,10), 9), 0
and
numbers[:flipcount], steps = reversed(numbers[:flipcount]), steps + 1
I don't know anyone who would do that in "real" code, however. :-)
From another angle, all three programs would benefit from being a few lines longer. Based on pure principles, if nothing else.
raw_input returns strings, input() returns whatever python thinks is appropriate; i.e. converting to int implicitly.
flipcount = input("Reverse how many? ")
Gets python below 300bytes.
Filip, I agree with you, at least on the Python and PHP -- when working on short little programs like this it's easy to get carried away with condensing things.
I'm not sure that I'd get much out of breaking down the Ruby version further, but I suppose the real test would be to see what I think of it in six months.
A variation of the Python one, more easy to follow, but a bit more verbose. And the reverse, while easy to guess what it does, not so easy to see why.
import random
reverse = -1
numbers = random.sample(range(1,9+1), 9)
steps = 0
while numbers != sorted(numbers):
print " ".join(map(str, numbers))
flipcount = input("Reverse how many? ")
numbers[:flipcount] = numbers[flipcount-1::reverse]
steps += 1
print "Done! That took you %d steps." % steps
Oops, the first two lines went into the paragraph there. Maybe there should be a preview function, if not even an edit function?
Again some more explicitness - adding to length seems to me to be a nice thing here
import random
base = range( 1, 10 )
numbers = random.sample( base, 9 )
steps = 0
while numbers != base:
I also like the avoiding of sorting to produce a constant list.
The Python version could get its byte count down - and be cleaner at the same time - by using flipcount = input("Reverse how many? ")
instead of
flipcount = int(raw_input("Reverse how many? "))
Funny game, here is a REBOL version of this game. An interesting thing is its readability.
numbers: random ordered: [1 2 3 4 5 6 7 8 9]
count: 0
while [numbers ordered][
reverse/part probe numbers to-integer ask "Reverse how many? "
count: count + 1
]
print reform ["Done! That took you" count "steps."]
Markdown ate the "different" operator in the "while" statement, which is a less-than followed by a greater-than
Try again: while [numbers < > ordered][
Or you can also use this: while [not-equal? numbers ordered][
Cheers
I love the REBOL version, thanks!
(The dropped angle brackets were due to some crude HTML-stripping I do for anti-spam and anti-XSS purposes, actually -- not Markdown's fault. I need to either clean it up or clarify my instructions.)
Hi,
Thanks for inspiring me to write a javascript version of REVERSE. You can play it online at:
http://www.arunrocks.com/blog/archives/2006/02/15/reverse-a-javascript-game-in-24-hours/
Here is a slightly tweaked Io version.
numbers := list(1,2,3,4,5,6,7,8,9) shuffle
count := 0
while (numbers != numbers clone sort,
count = count + 1
writeln(numbers join(" "))
"How many to swap? " print
howMany := File standardInput readLine asNumber
numbers = numbers slice(0, howMany - 1) reverse appendSeq(numbers slice(howMany))
)
writeln("Done! That took you ", count, " steps.")
Cool, thanks. (I took the liberty of combining your two comments, hope that's OK.)
I like the anonymous object trick there: "numbers clone sort".
I considered the one-line swap but found it a bit harder to read. Is it generally more, er, "Ionic" to leave long message chains unbroken?
Paul, here's a LISP version. I'm a bit rusty on the ol' LISP so I'm pretty sure someone out there will find a short/cleaner version.
(defun shuffle (l)
(loop for i below (length l) do (rotatef (elt l i) (elt l (random (length l)))))
l)
(defun prompt (l)
(format t "~{~a~^, ~}~%How many to swap? " l)
(read))
(defun playgame (n)
(let ((goal (loop for i from 1 to n collect i)))
(do ((count 0 (1+ count))
(state (shuffle (copy-list goal))
(let ((howmany (prompt state)))
(append (reverse (subseq state 0 howmany)) (subseq state howmany)))))
((equal state goal) (format t "Done! that took ~d steps.~%" count)))))
Here's my Prolog implementation. Check out: http://ozone.wordpress.com/2006/02/22/little-prolog-challenge/
Prolog "standard library" lacks the depth of languages like Ruby so a good part of the code is there to implement support predicates (shuffle, split). Prolog really shines for the game loop.
Olivier, thanks for those. I'm envious that you got to Prolog before I did!
Compared to my hacky Scheme version (link posted in update notice up top), your Lisp looks quite compact.
php in 9 lines...
$sorted = $numbers = range(1, 9);
shuffle($numbers);
$steps = 0;
while ($numbers != $sorted) {
print implode(" ", $numbers) . "\n Reverse how many? ";
$flipcount = (int)trim(fgets(STDIN));
array_splice($numbers, 0, $flipcount, array_reverse(array_slice($numbers, 0, $flipcount)));
$steps++;
}
print "Done! That took you $steps steps.\n";
Here is an implementation in F-Script:
http://www.fscript.org/reverseGame.htm
This page also shows the implementation of a program that plays to the game.
As awesome as that DHTML version is, here's a simpler javascript that fits the original format (type 'cscript whateverYouNamedIt.js' to run):
// inordinate amounts of scaffolding
Array.prototype.dupe = function() { return this.slice(0); } // hacky
Array.prototype.empty = function() { while (this.length > 0) this.pop(); return this; }
Array.prototype.equals = function(that) {
if (this.length != that.length) return false;
for (var i = 0; i < this.length; ++i) {
if (this[i] != that[i]) return false;
}
return true;
}
Array.prototype.appendAry = function(ary) {
var result = this.dupe();
for (var i = 0; i < ary.length; ++i) {
result.push(ary[i]);
}
return result;
}
Array.prototype.shuffle = function() {
var source = this.dupe();
this.empty();
while (source.length > 0) {
var idx = Math.floor(Math.random() * source.length);
this.push(source.splice(idx, 1));
}
return this;
}
Array.prototype.reverseLeft = function(count) {
return this.slice(0, count).reverse().appendAry(this.slice(count));
}
// main code
var solved = new Array(1,2,3,4,5,6,7,8,9);
var numbers = solved.dupe().shuffle();
var steps = 0;
while (!numbers.equals(solved)) {
WScript.StdOut.Write(numbers.join(" ") + "\nHow many numbers to swap? ");
var howMany = +(WScript.StdIn.ReadLine());
numbers = numbers.reverseLeft(howMany);
steps++;
}
WScript.Echo("Done! That took you " + steps + " steps!");
Meh. I decided it was a pinch too long, so I nixed appendAry() and reimplemented reverseLeft() like so:
Array.prototype.reverseLeft = function(count) {
var left = this.slice(0, count).reverse();
var right = this.slice(count);
while (right.length > 0) left.push(right.shift());
return left;
}
Triple post! I had some free time this weekend, so I decided to torture myself, er, that is, implement a false version. False doesn't provide a randomization faciility, so the ordering is hard-coded. It was the best I could do. You may now officially ph34r me:
0 3 1 4 2 5 9 7 8 6
0[$1+[$O@1-$][@]#%\%]d:s:
[10d;!1_1[@$][\$@=@&\1+]#%%~]
[9d;!.9[1-$][" "\.]#%
10,"How many to swap? "B^^B%'0-$1>
[$2>~[%\0]?$3=[%\@0]?$3>
[$c:[$1>][\10*\1-@@+\]#
c;[$1>][\10*\1-]#%[$1>]
[$@$@/@@\$@$@/@$@*@\-\10/]#
]?1s;+s:]?%]#[][]#s;
"Done! That took you "." steps!"
You gotta admit. It looks gorgeous. Oh, and I second comment previews.
Here's a perl version.
@array = (1..9); $steps = 0; shuffle( @array ); while (@array != sort {$a $b} @array){print join(" ", @array) . "nReverse how many?n"; $num = ; splice(@array, 0, $num, reverse(@array[0..($num-1)])); $steps++; } print "Done! That took you $steps steps.n"; sub shuffle {my $array = shift; my $i; for ($i = @$array; --$i; ) {my $j = int rand ($i+1); next if $i == $j; @$array[$i,$j] = @$array[$j,$i];}}
Sorry I was being lazy, now it's even better (just take everything between the lines):
Dangit!
I didn't actually test it, the last two had a flawed loop condition, this one actually works (tested it), still one line though, (would be smaller but I had to created a decent shuffle function).
Just keeps getting better, now it will tell you how long it took you as well as how many steps!
Here's another one. This one is for vbscript. Just put this into a file and give it an extension of .vbs (on a windows machine of course) and it should work on most new windows machines (uses wscript host).
I had a heck of a time with the shuffle on this one, I don't know vbscript that well.
Set wshShell = Wscript.Createobject("Wscript.shell")
If InStr(Ucase(wscript.FullName), "CSCRIPT") = 0 Then
wshShell.Run "cscript.exe //nologo " & Chr(34) & WScript.ScriptFullName & Chr(34)
Wscript.Quit
End If
do until (s = "q")
wscript.stdout.writeline "Press Enter to play!"
wscript.stdout.write "(Type 'q' at any time to quit the game)"
s = wscript.stdin.readline
if (s = "q") then
wscript.quit
end if
vs = time
vst = 0
for a=0 to 8
myArray(a)=(a+1)
next
randomize
do until (myarray(0) 1 and myarray(1) 2 and myarray(2) 3 and myarray(3) 4 and myarray(4) 5 and myarray(5) 6 and myarray(6) 7 and myarray(7) 8 and myarray(8) 9)
for b=8 to 0 step -1
randomize
c = Int((b+1)*Rnd)
if bc then
d=myarray(c)
myarray(c)=myarray(b)
myarray(b)=d
end if
next
loop
do until (myarray(0) = 1 and myarray(1) = 2 and myarray(2) = 3 and myarray(3) = 4 and myarray(4) = 5 and myarray(5) = 6 and myarray(6) = 7 and myarray(7) = 8 and myarray(8) = 9)
vst = vst + 1
for each e in myarray
wscript.stdout.write e & " "
next
wscript.stdout.writeline
wscript.stdout.writeline "Reverse how many?"
f = wscript.stdin.readline
if IsNumeric(f) = False then
if (f = "q") then
wscript.quit
end if
wscript.stdout.writeline "Pick a NUMBER!!!"
elseif f = 1 then
wscript.stdout.writeline "That's pointless, pick again!"
elseif f < 1 then
wscript.stdout.writeline "Ok, you know you can't do that!"
wscript.stdout.writeline "Pick again you fool!"
elseif f > 9 then
wscript.stdout.writeline "Ok, stop messing around!"
end if
if (IsNumeric(f) = False) then
f = 0
end if
if (f > 1 and f < 10) then
redim sarray(f-1)
for g = 0 to (f-1)
sarray(g)=myArray((f-1)-g)
next
for g = 0 to (f-1)
myArray(g)=sarray(g)
next
end if
loop
vtt = datediff("s",vs,time)
wscript.stdout.writeline "Congratulations, you have won!"
wscript.stdout.writeline "It only took you " & vst & " steps and " & vtt & " seconds!"
wscript.stdout.writeline
loop
Here is Tcl version (tcllib needed)
package require math
package require struct::list
set input {1 2 3 4 5 6 7 8 9}
set base $input
set steps 0
for {set i 0} {$i < [llength $base]} {incr i} {
set index [::math::random 0 [llength $input]]
lappend myarray [lindex $input $index]
set input [lreplace $input $index $index]
}
while {[::struct::list equal $myarray $base]==0} {
incr steps
puts -nonewline "$myarray \n($steps) How Many? "
flush stdout
set howmany [gets stdin]
set myarray [concat [::struct::list reverse [lrange $myarray 0 [expr $howmany-1]]] [lrange $myarray $howmany end]]
}
puts "$myarray \nDone in $steps steps"
Markus Gaelli offers a Squeak (Smalltalk) version:
steps := 0.
numbers := (1 to: 9) asArray shuffled.
[numbers isSorted] whileFalse:
[flipCount:= numbers indexOf: ((SelectionMenu selections: numbers)startUpWithCaption: \
'Revert up to which number?' at: Display center ).
1 to: flipCount//2 do: [:i | numbers swap: i with: flipCount-i+1].
steps := steps + 1].PopUpMenu inform: 'You needed ', steps asString,' \
steps to sort the list.'
He's also created a more elaborate eToys version (screenshot) that you can run in your browser if you've got the Squeak plugin.
PHP in 7 lines:
$sorted = $numbers = range(1, 9);
shuffle($numbers);
for ($steps = 0; $numbers != $sorted; $steps++) {
print implode(" ", $numbers) . "\n Reverse how many? ";
$flipcount = (int)trim(fgets(STDIN));
array_splice($numbers, 0, $flipcount, array_reverse(array_slice($numbers, 0, $flipcount)));
}
print "Done! That took you $steps steps.\n";
When you mentioned Forth, I took it for a challenge ^___^ I have seen the language just for a brief time back in school years, so the following code almost certainly sucks, but at least it works (under gForth).
I wrote in a simple PRNG (shamelessly lifted from the language manual) but you should update the seed variable at first if you don't want to play always the same game... For the "line count" part, line breaks and indentation are there for readability only; they don't matter in Forth.
Anyway, I just can't understand why that little neat piece of Ruby reminded you of this! o___O
=========================================================
hex
ff800000 constant ROL9MASK
decimal
variable seed
: ROL9 ( u1 -- u2 | rotate u1 left by 9 bits )
dup ROL9MASK and 23 rshift swap 9 lshift or ;
: RANDOM ( -- u ) seed @ 107465 * 234567 + rol9 dup seed ! ;
: RAND RANDOM abs swap mod ;
: NEWLINE 13 emit 10 emit ;
create workset 10 cells allot
: FILLNUMS
workset
10 0 DO
dup i dup cells rot + !
LOOP drop ;
: PRINTNUMS
workset
10 0 DO
dup i cells + @ .
LOOP NEWLINE drop ;
: MIXARRAY
1 9 -DO
workset i cells + dup @
workset i RAND cells + dup @
rot rot ! swap !
1 -LOOP ;
: REVERSE
dup 2 / 0
?DO
1 - dup cells workset + dup @
workset i cells + dup @
rot rot ! swap !
LOOP drop ;
: CHECK
1
9 0 DO
workset i cells + dup cell +
@ swap @ -
1 IF <> 1- unloop exit THEN
LOOP ;
: MESSAGE s" How many numbers do you want to reverse? (press 0 for 10) " type
key dup emit NEWLINE 48 -
dup 0= IF drop 10 THEN dup 2 11 within invert IF drop 0 THEN ;
: PLAY 0 NEWLINE FILLNUMS MIXARRAY
BEGIN PRINTNUMS MESSAGE REVERSE 1+ CHECK UNTIL
s" You solved the game in " type . s" steps" type ;
Well, I wrote the "unequal" operator and had it stripped just like another reader before... the last line of "check" was similar to this
1 < > IF 1- unloop exit THEN
Fixed -- sorry about that! It's a vestigal anti-comment-spam measure. Akismet is working pretty well so I may change my anti-angle-bracket policy.
In the words of David Letterman: It's an exhibition, not a competition!
$sorted = range(1, 9); $numbers = $sorted; shuffle($numbers); $steps = 0;
You can cut a line out of the PHP code if I understand the range() function correctly. Why shuffle it and resort it?
No good reason that I can see now. I think that sequence is an artifact of an earlier version.
Here's a readable perl version (9 lines, 365 chars):
use FreezeThaw qw(cmpStr);
my @sorted = (1 .. 9);
my @numbers = sort {rand(10) > $a} @sorted;
for (my $steps = 0; cmpStr(\@numbers, \@sorted) != 0; ++$steps) {
print join(" ", @numbers), "\nReverse how many? ";
my $flipcount = ;
splice(@numbers,0,$flipcount,reverse( @numbers[0..($flipcount-1)]));
}
print "Done! That took you $steps steps.\n";
More Perl 5 and Perl 6 versions (including Larry's) in the perl.perl6.users newgroup thread.
PHP looks really ugly, the python code seems to be the slimmest...
Here is one version in the Microsoft Command Processor, better known as the "DOS prompt".
@echo off
verify other 2>nul
setlocal enabledelayedexpansion
if errorlevel 1 echo Windows 2000/XP required.& goto :EOF
set sorted=%2
set sorted=%sorted:"=%
if "%sorted%"=="" (
set sorted=123456789
rem set sorted=ABCDE
)
set current=%1
set current=%current:"=%
if "%current%"=="" (
set current=%sorted%
call :Shuffle current
)
set steps=0
:Play
echo.%current%
if "%current%"=="%sorted%" goto Done
set /P flipcount=Reverse how many?
if /I "%flipcount:~0,1%"=="q" goto :EOF
set left=!current:~0,%flipcount%!
set right=!current:~%flipcount%!
call :Reverse left
set current=%left%%right%
set /A steps+=1
goto Play
:Done
:: The only way to print '!' is to disable DelayedExpansion.
:: Yes, cmd.exe is really flawed: there is no escape char for '!' in
:: DelayedExpansion mode.
setlocal disabledelayedexpansion
echo Done! That took you %steps% steps.
endlocal
goto :EOF
:: ======= FUNCTIONS =======
:: Compute number of characters of variable named %1
:: Result is returned in both %errorlevel% and %Length%
:Length
setlocal enabledelayedexpansion
set l=0
:LengthLoop
set _=!%1:~%l%,1!
if not "%_%"=="" set /A l+=1& goto LengthLoop
endlocal & set Length=%l%
:: Set errorlevel and return
exit /B %Length%
:: Reverse characters of variable named %1
:Reverse
setlocal enabledelayedexpansion
call :Length %1
set /A p=Length/2
set r=!%1:~%p%,-%p%!
:ReverseLoop
set /A q=p-1
set r=!%1:~-%p%,1!%r%!%1:~%q%,1!
set p=%q%
if not %p%==0 goto ReverseLoop
endlocal & set %1=%r%
goto :EOF
:: Shuffle characters in variable named %1
:Shuffle
setlocal enabledelayedexpansion
call :Length %1
set /A count=5*Length
set d=!%1!
for /L %%i in (1,1,%count%) do call :ShuffleSub d
endlocal & set %1=%d%
goto :EOF
:ShuffleSub
set /A n=^(Length * %RANDOM%^) / 32768
set right=!%1:~%n%!
set %1=!right:~1!!%1:~%n%,1!!%1:~0,%n%!
::echo.!%1!
goto :EOF
Thanks for that. Pretty funny to see, especially the workaround for the exclamation point!
This is a very nice post and I enjoyed the good-natured comments. It's been interesting looking at all the different solutions.
Here's my newLISP version (is newLISP - diabolus in programmatica - allowed? ;-))
(define (rev-slice n lst)
(append (reverse (0 n lst)) (n lst)))
(set 'steps 0 'goal (sequence 1 10) 'state (randomize goal))
(until (= goal state)
(println state "\nReverse how many?")
(inc 'steps)
(set 'state (rev-slice (int (read-line)) state)))
(string "\\nDone! That took you " steps " steps!")
newLISP is concise but not too terse (if there's a difference). A bit less punctuation and fewer abbreviations (but plenty of parentheses, of course) make it high on bytes but low on lines.
PS: i thought I had indented that code so that a Markdown pass would keep it formatted correctly, but I obviously goofed somewhere. I don't normally put every statement on a single line...! ;-) As originally written, there are 7 lines.
Neat, thanks -- I didn't know about newLISP.
I took the liberty of fixing the code formatting (Markdown wants four spaces at the beginning of a code line).
In regards to the PHP version (and probably other languages as well) the initialization line: $steps=0; is unnecessary in terms of minimizing code because the increment operator will assume a value of 0 for undefined variables. The error suppression operator can be used to ignore any PHP notices about undefined variables. ie: @$steps++;
Hello again! I'm the nut-job responsible for that awful False version above. I decided to give Haskell a shot:
module Reverse where
import System.Random
prompt q = catch (putStr q >> readLn) $ const $ prompt q
play s [1,2,3,4,5,6,7,8,9] =
putStrLn $ "Done! That took you " ++ show s ++ " steps!"
play s xs = do
putStrLn $ unwords $ map show xs
n <- prompt "Reverse how many? "
play (s + 1) $ reverse (take n xs) ++ drop n xs
shuffle [] = return []
shuffle xs = do
i <- randomRIO (0, length xs - 1)
rest <- shuffle $ take i xs ++ drop (i + 1) xs
return $ xs !! i : rest
main = play 0 =<< shuffle [1..9]
I like the shuffle function. It basically yanks a random element out of the list, sticks it in front, and shuffles the remainder recursively. I took a look at Dominic's version; his effectively pairs each element with a random number, sorts the list by that random number, and strips them off. I smooshed together his shuffle and shuffleWith functions, then yanked the stateful IO up into main:
module Reverse where
import System.Random
import Data.List
prompt q = catch (putStr q >> readLn) $ const $ prompt q
play s [1,2,3,4,5,6,7,8,9] =
putStrLn $ "Done! That took you " ++ show s ++ " steps!"
play s xs = do
putStrLn $ unwords $ map show xs
n <- prompt "Reverse how many? "
play (s + 1) $ reverse (take n xs) ++ drop n xs
shuffle xs gen = map snd $ sort $ zip (randoms gen :: [Int]) xs
main = play 0 . shuffle [1..9] . mkStdGen =<< randomIO
The only differences here are shuffle, main, and the addition of import Data.List (for sort).
Nice. I think Haskell's next for me.
(I fixed the "<" problem for you -- my tag-tripping code, an old antispam measure which I just removed, was getting in your way there.)
And I highly recommend it! It was one of the most painful, brain-breaking experiences I've had learning a new language, but I know I'm better for it. The type system alone is worth the headache. It's magic. Haskell.org has a great Getting Started guide. I used Hugs at first, and it's alright, but GHC's error messages are much friendlier. Unfortunately, it practically installs a Linux distro on my Windows box. *cries*
Work your way through Yet Another Haskell Tutorial until you have a loose grasp on the type system, before you try your hand at Monads. At some point, everyone seems to grumble about Monads in general and IO in particular. Writing Monad tutorials is a rite of passage for the Haskell newbie. The clearest that I've seen is You Could Have Invented Monads.
Dive in, and don't give up! Good luck!
(Oh, and you missed a few <'s in my code there. The first snippet has 3 lines that start n <-, i <-, and rest <-, the second snippet has a n <-. Could you please fix 'em?)
(define (rev-slice n lst) (append (reverse (0 n lst)) (n lst)))
(set 'steps 0 'goal (sequence 1 10) 'state (randomize goal))
(until (= goal state) (println state "nReverse how many?") (inc 'steps) (set 'state (rev-slice (int (read-line)) state))) (string "\nDone! That took you " steps " steps!")
This code don't work to me, something is wrong but i can't understand what :( Help me pls!
Works fine for me. MacOS X 10.4.8, newLISP v 9.0.4.
Best to take this discussion to rather than here!
:-) oops it swallowed the url (where's that preview? :-)
I meant 'take this discussion to http://www.alh.net/newlisp/phpbb/'
sorry Paul
What?
Why do I get so many inscrutable comments with German URLs?
Note: It may be hard to understand what I'm saying here since I've deleted dozens of spam comments in this thread. They're typically almost-relevant sounding short sentences like "thank you for sharing this script", with a username that doesn't match the name given in the comment body, and a URL pointing to a .de (German) domain -- never an internal page, always the top level. Weird.
I wonder whether someone is paying them to get their URL on other people's web pages... I presume that's what's behind it. Perhaps its a German thing...?
PHP is the best for me. In other words: The other script languages I don't understand ;-)
My professor told me: Many futurists would agree that, had it not been for symmetric encryption, the refinement of suffix trees might never have occurred. Here, we verify the analysis of spreadsheets, which embodies the important principles of algorithms. We explore an ambimorphic tool for studying wide-area networks, which we call TidEffort. This is an important point to understand.
Paul: The "german" links are spam comments - link farming. Someone has obviously written a bot that can reasonably reliably read captcha texts.
Looks to be the same bot that was spamming my site (I recognise the cableguy link, and the patterns used for the comment text).
I replaced my captcha with a simple question and haven't had any more spam comments.
A tidier Javascript implementation. Javascript doesn't have a standard console, so windows wsh is used. Save the code to reverse.js and then from the windows command shell run: cscript reverse.js
function flip(ar,n) { return ar.slice(0,n).reverse().concat(ar.slice(n,ar.length)); }
var goal = [1,2,3,4,5,6,7,8,9]; var numbers = goal; var steps = 0;
for (var i = 0; i < goal.length; ++i) { numbers = flip(numbers, Math.ceil(Math.random() * goal.length) ); }
while (numbers.join('') != goal.join('')) { WScript.StdOut.Write(numbers.join(' ') + 'nHow many numbers to swap? '); numbers = flip(numbers, WScript.StdIn.ReadLine()); steps++; }
WScript.StdOut.WriteLine('Done! That took you ' + steps + ' steps!');
Sorry, using proper markdown (I hope!):
function flip(ar,n) {
return ar.slice(0,n).reverse().concat(ar.slice(n,ar.length));
}
var goal = [1,2,3,4,5,6,7,8,9];
var numbers = goal;
var steps = 0;
for (var i = 0; i < goal.length; ++i) {
numbers = flip(numbers, Math.ceil(Math.random() * goal.length) );
}
while (numbers.join('') != goal.join('')) {
WScript.StdOut.Write(numbers.join(' ') + '\nHow many numbers to swap? ');
numbers = flip(numbers, WScript.StdIn.ReadLine());
steps++;
}
WScript.StdOut.WriteLine('Done! That took you ' + steps + ' steps!');
I just blogged about my implementation of Reverse in Cmd.exe (see the code above).
Very interesting, but it dont work.. Dominik, have you already found something?
There are other worlds .... 3 lines solution in k (www.kx.com) explanation: sequence unsorted if not every number is greater than its predecessor n draw -p generates n random numbers from 0..p-1 without repeated elems (|n#x),n x = (reverse n take x) concat (n drop x) c mx = while c(x) is true, new x is m(x) unsorted(try)x = while unsorted(x), new x is try x
unsorted:~&/>':
try:{`0:(5:x; "Reverse how many? "); n:0$0:; (|n#x),n _ x}0:"Done! That took you ",($-1+#unsorted(try)1+9_draw -9)," steps"
bad formatting.. The code is
unsorted:~&/>:
try:{0:(5:x; "Reverse how many? "); n:0$0:; (|n#x),n _ x}
`0:Done! That took you ,($-1+#unsorted(try)1+9_draw -9), steps
Hi, please can you add a summary next time, so that your visitors can get an overview of the end-length of every code? Thank you.
I prefer the code in JavaScript (like morris tidier Javascript implementation). That works good and sure.
I really like PHP and don´t understand nothing about the snake (python) but in my childhood i programmed with Basic too i enjoyed it much it was so easy but today the computerlanguages are so much more powerfull but also complicated. i´m looking back to the good old "Basic"-times.
I generally think of myself as too jaded to spend much time trying to understand the chaotic patterns of spam, but I have got to know why this post is the only one out of the 300+ posts on my blog that gets these German/Eastern-European one-liners with links to big boring German/Eastern-European company websites.
Again, this may seem like a bit of a non-sequitur since I've deleted most of the comments that appeared to be spam. The mystery remains, though.
hi paul, i think it´s because of the most are just lazy. one have found a good way to become a backlink from your good blog. the others just look in "backlinkcheckers" of good ranking pages and find the backlink from this article and try to do the same. but maybe one have just searched for backlinks and made a list which he sell over eBay. its crazy at all the most time i think twice if i add a comment i don´t want that it seems i spam too. but i know u can delete comments and thats the reason i added this here.
head up, i think in future it will be better, the software is gonna be much better to prevent spam. i hope so :-)
Hey, a non-spam comment from Germany! Thanks, Dan!
I suspect you're right about the backlink business.
genius: "From another angle, all three programs would benefit from being a few lines longer. Based on pure principles, if nothing else."
i am a webmaster...Lol
Hey, a non-spam comment from Germany! Thanks, Dan!
LOL
PS. Don't forget Poland, Paul :)
Hey, a non-spam comment from Poland! Thanks for article. I don't think that all comments from poland are spam! And not all comments from other country. It's bigger problem people from USA buy .pl domains and spam people from DE buy .pl domains and spam ... So please don't write that people from poland spam here.
Another VBScript implementation. Needed two custom procedures since VBScript does not have a Sort() method, or a way to reverse elements of an array (I compensated by converting array to string and reversing).
Randomize
Dim numbers(8)
For i = 0 To 8
numbers(i) = Int((9 * Rnd) + 1)
Next
steps = 0
Do While Join(numbers) <> Join(Sort(numbers))
WScript.Echo Join(numbers) & vbCrLf & "Reverse how many? "
flipcount = WScript.StdIn.ReadLine
ArrReverse numbers, flipcount
steps = steps + 1
Loop
WScript.Echo "Done! That took you " & steps & " steps." & vbCrLf
Sub ArrReverse(array, intCount)
str = Join(array)
tmp = Split(StrReverse(Mid(str, 1, intCount * 2 - 1)) & Mid(str, intCount * 2))
For i = 0 To 8
array(i) = tmp(i)
Next
End Sub
Function Sort(ByVal array)
For i = UBound(array) - 1 To LBound(array) Step -1
For j = 0 To i
If array(j) > array(j + 1) Then
temp = array(j)
array(j) = array(j + 1)
array(j + 1) = temp
End If
Next
Next
Sort = array
End Function
The big advantage of Perl is that you can write almost everything in only few lines. But after few weeks it is very hard to read. I wrote one project (using GoogleAPI) at my university, it takes 20 lines of code. But when 3 weeks later I was presenting this program to my professor, it took me some time to understand, what I have wriiten :D So...I don't recommend so short programs :D
That oncemore shows that Ruby and Python is better than PHP :) thanks for one more proof. nice test
@Warsow one task shows you which language is the best? oh no.....
Python version could be somewhat shorter.
numbers[:flipcount] = numbers[flipcount-1::-1]
I find this actually clearer than reversed()..
Good job! I like the game, found it from another post with it written in a few different languages. Yours is by far the most complete and enjoyable.
Thanks!
Jeremy
"Ruby. For simple things at least!"
and PHP vs Ruby i think Ruby ist very " Speed" ;-)
Good comparison. I saw something similar but with C, C++ and Ruby. I'll have to blog about it
You can save 3 more characters in Python like this.
exec"print "+numbers[1:-1]
Looks ugly but it works. :)
You can save 3 more characters in Python like this.
exec"print"+`numbers`[1:-1]
Looks ugly but it works. :)
Ignore my upper comment.
BIG LOOOOOOOLLLLLLLLL @ TOMEK!!!!!
TOMEK WRITE: "Hey, a non-spam comment from Poland! Thanks for article. I dont think that all comments from poland are spam! And not all comments from other country. Its bigger problem people from USA buy .pl domains and spam people from DE buy .pl domains and spam So please dont write that people from poland spam here."
That are the Words of the biggest SPAMER from POLAND!
TOMEK = www.blog.noclegi-lubuskie.pl = www.profesjonalna-reklama.pl = many more ......
he SPAM everywhere - if you wanna laugh just get his profesjonalna-reklama domain and look in a backlink-checker. i think there is no blog and no wiki out there without spam from him.
TOMEK: "...So please dont write that people from poland spam here."
LOL sorry Paul for this off-topic-post, as i read that from tomek i must write it :-) and for his great work i spend my backlink from this post for him too :-)
Hi, thanks a lot for all the useful hints and pieces of advice, kind reetings
Don't know if this is an active topic, but I used it as an excuse to learn Groovy. Here's a groovy version of the app:
list = [*0..9]
Collections.shuffle(list, new Random())
count = 0
sortedList = new ArrayList(list).sort()
while (list != sortedList) {
print list.join(" ") + " - How many to flip: "
flipCount = System.in.readLine().toInteger()
list[0..flipCount-1] = list[0..flipCount-1].reverse()
count++
}
println "It took you $count tries."
Hi Steve,
This thread seems to be evergreen. Thanks for posting!
Yet again proving that python and Ruby are better than PHP. Keep up the great work.
Yet again proving that python and Ruby are better than PHP. Keep up the great work.
Tomek AKA Tomekg. Do a google search, you'll see how good he spams over the net. Just wondering how many hours he spends a day for spamming.
LOL --> esw.w3.org/topic/tomekg swik.net/User:tomekg
This guy is BIG spammer!
Maybe I should just block comments on this post that don't contain code.
Make the script a little smaller from previous version by using permutations for shuffle 2nd try to make this horrible markdown look OK...
package require math
package require struct::list
set input {1 2 3 4 5 6 7 8 9}
set perms [::struct::list permutations $input]
set myarray [lindex $perms [::math::random 0 [llength $perms]] ]
for {set steps 1} {[::struct::list equal $myarray $input]==0} {incr steps} {
puts -nonewline "$myarray \n($steps) How Many? "
set howmany [gets stdin]
set myarray [concat [::struct::list reverse [lrange $myarray 0 [expr $howmany-1]]] [lrange $myarray $howmany end]]
}
puts "$myarray \nDone in [expr $steps-1] steps"
Here's another Common Lisp example:
(defun play (n)
(let* ((ordered-numbers (loop for i from 1 to n collect i))
(numbers (sort (copy-seq ordered-numbers) #'(lambda (x y) (elt '(nil 0) (random 2))))))
(loop for steps from 0
do
(format t "~{~a~^, ~} : Reverse how many? " numbers)
(let ((how-many (read)))
(replace numbers (nreverse (subseq numbers 0 how-many)) :start1 0 :end1 how-many))
until (equal numbers ordered-numbers)
finally (format t "Done in ~a steps!" steps))))
I made a bash version that, like the BASIC version, also takes several lines to randomize and reverse the prefix. I am a bash novice so maybe there are some built-in ways to shorten these parts. I'm almost afraid to know because I find the language so unlike what I'm used to. Here is a link because it's 42 lines and I don't trust my first try at Markdown to not screw it up: http://eloptoof.net/reverse.html
Some highlights:
Printing an array:
echo "${numbers[*]}"
(This also makes comparing two arrays a string compare.)
Prompting and reading a variable in one line:
read -p "Reverse how many? " rev
Array size:
n=${#numbers[@]}
I find most of the modern languages generally much easier to figure out. I am constantly checking the bash man page.
PHP in 5 lines ;)
for($sorted=$numbers=range(1,9), shuffle($numbers), $steps=0; $numbers!=$sorted; $steps++) { echo implode(' ', $numbers)."nReverse how many? "; array_splice($numbers, 0, $flipcount=(int)trim(fgets(STDIN)), array_reverse(array_slice($numbers,0,$flipcount))); } print "Done! That took you $steps steps.n";
PHP in 5 lines, Markdown version:
for($sorted=$numbers=range(1,9), shuffle($numbers), $steps=0; $numbers!=$sorted; $steps++) {
echo implode(' ', $numbers)."\nReverse how many? ";
array_splice($numbers, 0, $flipcount=(int)trim(fgets(STDIN)), array_reverse(array_slice($numbers,0,$flipcount)));
}
print "Done! That took you $steps steps.\n";
Yes i think to that this is a very interesting comparison. Thx
Im learning a bit of Ruby after some hard PHP Lessons. As someone said above i would, at my actual point of knowledge, prefer Ruby rather than python...
Here's a Python hack: 6 lines and 311 bytes.
import random
def guesses(numbers):
if numbers != sorted(numbers):
flipcount = int(raw_input("%s\nReverse how many? " % (numbers,)))
yield 1 + sum(guesses(numbers[flipcount - 1::-1] + numbers[flipcount:]))
print "Done! that took you %d steps." % sum(guesses(random.sample(range(1, 10), 9)))
Only works in Python 2.4 and later.
It seems that most implementations might be defective in that your initial randomization routines could produce a sorted list. the test on the outer while loop is on if the list is not sorted which would then fail, causing the programs to not ask for a reversal at all!
You would need to scramble the initial list of numbers until they are not sorted, then go on to the while loop.
Comments use Markdown syntax. Your comment will not appear until approved, which may take a few hours or more. Spammers will be torpedoed.
The iPhone keyboard doesn't suck
Python one-liner of the day
7 comments
How not to advocate via Google Code
2 comments
99 problems
3 comments
bitmonk
Obscure "svn mv" problem solved
54 days ago
Charlie
Book news: Rough Cuts and Amazon
55 days ago
Simon Griffee
Django Mercurial mirror tweaks
72 days ago
Jason Calleiro
From PHP to Python
73 days ago
Yuli
dpaste.com
76 days ago
bruce
Neat Python hack: infix operators
80 days ago
David Reynolds
The original Lego Star Wars
88 days ago
At least 31791 pieces of comment spam killed since January 12th. Thanks are mostly due to Akismet.
Hmm. I've been meaning to really learn python for a while now and have been resisting the ruby bandwagon. That ruby code sure is readable tho...
You can see how Python's strong typing adds a bit of verboseness with the explicit conversion to string in the join. I would've mapped tho (ie " ".join(map(str,numbers) seems more terse and more readable at the same time...)
Sadly, I write PHP full time for a living...