Index: lib/README
===================================================================
RCS file: /src/ruby/lib/README,v
retrieving revision 1.22
diff -u -u -r1.22 README
--- lib/README	1 Dec 2003 06:28:35 -0000	1.22
+++ lib/README	1 Nov 2006 19:41:38 -0000
@@ -1,6 +1,7 @@
 English.rb	lets Perl'ish global variables have English names
 Env.rb		loads importenv.rb
 README		this file
+array.rb	extensions to the core array class
 base64.rb	encodes/decodes base64 (obsolete)
 benchmark.rb	a benchmark utility
 cgi-lib.rb	simple CGI support library (old style)
Index: lib/array.rb
===================================================================
RCS file: lib/array.rb
diff -N lib/array.rb
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ lib/array.rb	1 Nov 2006 19:41:38 -0000
@@ -0,0 +1,118 @@
+class Array
+
+  # Randomly permutes the receiver in place and returns it.
+  def shuffle!
+    (length - 1).downto 1 do |slot|
+      source = rand(slot + 1)
+      self[slot], self[source] = self[source], self[slot]
+    end
+    self
+  end
+
+  # Returns a random permutation of the receiver.
+  def shuffle
+    self.dup.shuffle!
+  end
+
+end
+
+if __FILE__ == $0
+  require 'test/unit'
+
+  class ArrayShuffleTest < Test::Unit::TestCase # :nodoc:
+
+    def test_smoke
+      assert_equal [], [].shuffle
+      assert_equal ["foo"], ["foo"].shuffle
+    end
+
+    def test_content_preservation
+      source = (0..100).to_a
+      assert_equal source.sort, source.shuffle.sort
+    end
+
+    def test_self_modification
+      source = (0..100).to_a
+      result = source.shuffle
+      assert_equal (0..100).to_a, source
+      assert_not_equal source, result
+      source.shuffle!
+      assert_not_equal (0..100).to_a, source    
+    end
+
+    def test_shuffle_pair
+      n = 1000
+      source = ["foo", "bar"]
+      ans = Hash.new(0)
+      (2*n).times do
+        ans[source.shuffle] += 1
+      end
+      permutations = [["foo", "bar"], ["bar", "foo"]]
+      permutations.each do |perm|
+        assert_in_delta n, ans[perm], n * 0.15, "#{perm.inspect} ill represented"
+      end
+      # For a correct implementation of shuffle, with n = 1000
+      # and a 15% error tolerance, this test will fail with
+      # probability at most 2.112e-11 by sheer chance.
+    end
+
+    def test_shuffle_triple
+      n = 1000
+      source = ["foo", "bar", "baz"]
+      ans = Hash.new(0)
+      (6*n).times do
+        ans[source.shuffle] += 1
+      end
+      permutations = [["foo", "bar", "baz"],
+                      ["foo", "baz", "bar"],
+                      ["bar", "foo", "baz"],
+                      ["bar", "baz", "foo"],
+                      ["baz", "foo", "bar"],
+                      ["baz", "bar", "foo"]]
+      permutations.each do |perm|
+        assert_in_delta n, ans[perm], n * 0.15, "#{perm.inspect} ill represented"
+      end
+      # Even for a correct implementation of shuffle, with n = 1000
+      # and a 15% error tolerance, this test will fail with
+      # probability at most 1.47e-06 by sheer chance.
+    end
+
+    def test_distribution
+
+      # Another way to check whether shuffle does what it's meant to
+      # do, particularly on an array whose permutations are too large
+      # to enumerate, is to shuffle such an array a bunch of times and
+      # check that every index receives every element with about the
+      # right frequency.  This is not perfect, of course, (in fact,
+      # rotate(rand(length)) instead of shuffle will pass this test),
+      # but it does catch some kinds of shuffling errors.  In this
+      # case, we let the elements be their own indecies, so the
+      # average of the elements that find themselves at any given
+      # index should be about equal to the average of all the elements
+      # in the source.
+
+      # For the parameter settings here, to wit 1000 shuffles of an
+      # array of size 30, the sum at each index will fall within 10%
+      # of the expected value 0.99999988688 of the time, which means
+      # this test as a whole should pass at least 0.9999966 of the
+      # time (i.e. fail no more than 3.4 times out of a million) for
+      # a correct implementation of shuffle.
+
+      n = 1000
+      ct = 30
+      avg = (ct - 1) / 2.0
+      ans = Array.new(ct, 0)
+      source = (0...ct).to_a
+      n.times do
+        result = source.shuffle
+        ans.each_index do |i|
+          ans[i] += result[i]
+        end
+      end
+      ans.each_index do |i|
+        assert_in_delta n*avg, ans[i], n*avg*0.1, "#{ans.inspect} is not close enough to even: Position #{i} is off"
+      end
+    end
+
+  end
+end
