rubylabs 0.7.5 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/permute.rb ADDED
@@ -0,0 +1,30 @@
1
+ =begin rdoc
2
+ Permute the order of items in x. Does not copy x -- shuffles the
3
+ items in place. Works for strings, arrays, any container that responds to
4
+ length, [], and []=
5
+ =end
6
+
7
+ # :begin :permute! :random
8
+ def permute!(x)
9
+ for i in 0..x.length-2
10
+ r = random(i, x.length-1)
11
+ x[i], x[r] = x[r], x[i]
12
+ end
13
+ return x
14
+ end
15
+ # :end :permute!
16
+
17
+ =begin rdoc
18
+ A "helper method" for permute! that makes it easier to see which two
19
+ locations are being swapped. A call to random(i,j) returns a random
20
+ integer in the range i..j. See also PRNG::random
21
+ =end
22
+
23
+ # :begin :random
24
+ def random(min, max)
25
+ return nil if max < min
26
+ range = max - min + 1
27
+ return rand(range) + min
28
+ end
29
+ # :end :random
30
+
data/lib/randomlab.rb CHANGED
@@ -7,10 +7,17 @@ Random number generators and associated methods.
7
7
 
8
8
  =end
9
9
 
10
+ =begin
11
+ TODO number line -- mini-histogram, e.g. second tick drawn above first? else explain in lab manual / reference why two values per tickmark
12
+ TODO another thing for documentation: diff between p.random(x,y) and random(x,y) [latter uses Ruby's PRNG]
13
+ =end
14
+
10
15
  module RubyLabs
11
16
 
12
17
  module RandomLab
13
18
 
19
+ require "permute.rb"
20
+
14
21
  =begin rdoc
15
22
  Pseudo-random number generator. Constraints on a, c, and m:
16
23
  * c and m must be relatively prime
@@ -80,39 +87,13 @@ module RandomLab
80
87
  (0..51).map { |i| Card.new(i) }
81
88
  end
82
89
 
83
- =begin rdoc
84
- Permute the order of items in x. Does not copy x -- shuffles the
85
- items in place. Works for strings, arrays, any container that responds to
86
- length, [], and []=
87
- =end
88
-
89
- # :begin :permute
90
- def permute(x)
91
- for i in 0..x.length-2
92
- r = random(i, x.length-1)
93
- x[i], x[r] = x[r], x[i]
94
- end
95
- return x
96
- end
97
- # :end :permute
98
-
99
- =begin rdoc
100
- A "helper method" for permute, that makes it easier to see which two
101
- locations are being swapped. A call to random(i,j) returns a random
102
- integer in the range i..j. See also PRNG::random
103
- =end
104
-
105
- def random(min, max)
106
- return nil if max < min
107
- range = max - min + 1
108
- return rand(range) + min
109
- end
110
-
111
90
  =begin rdoc
112
91
  A "helper method" that can be called via a probe, to print the contents
113
- of an array during the execution of the permute method
92
+ of an array during the execution of the permute! method
114
93
  =end
115
94
 
95
+ # Note: permute! moved to own source file, permute.rb
96
+
116
97
  def brackets(a, i, r)
117
98
  res = "#{r}: "
118
99
  if i <= 0
data/lib/recursionlab.rb CHANGED
@@ -64,18 +64,18 @@ Recursive implementation of binary search.
64
64
 
65
65
  =end
66
66
 
67
- # :begin :rsearch
68
- def rsearch(a, k, lower = -1, upper = a.length)
67
+ # :begin :rbsearch
68
+ def rbsearch(a, k, lower = -1, upper = a.length)
69
69
  mid = (lower + upper) / 2
70
70
  return nil if upper == lower + 1 # search fails if the region is empty
71
71
  return mid if k == a[mid] # search succeeds if k is at the midpoint
72
72
  if k < a[mid]
73
- return rsearch(a, k, lower, mid)
73
+ return rbsearch(a, k, lower, mid)
74
74
  else
75
- return rsearch(a, k, mid, upper)
75
+ return rbsearch(a, k, mid, upper)
76
76
  end
77
77
  end
78
- # :end :rsearch
78
+ # :end :rbsearch
79
79
 
80
80
  =begin rdoc
81
81
  A helper method that can be called from a probe to display the contents
@@ -116,30 +116,38 @@ combine successively bigger pieces of the input array.
116
116
 
117
117
  =end
118
118
 
119
- # :begin :msort :merge :less
120
- def msort(a)
121
- g = 1 # group size
122
- while g < a.length
123
- tmp = [] # append merged groups to this array
124
- i = 0 # first group starts here
125
- while i < a.length
126
- tmp += merge(a, i, g) # merge groups at a[i] and a[i+g], append to tmp
127
- i += 2*g # next groups starts 2*g places to the right of i
128
- end
129
- g *= 2 # double the group size
130
- a = tmp # a now refers to array just built
131
- end
132
- return a
133
- end
119
+ # :begin :msort :merge :merge_groups :less
120
+ def msort(array)
121
+ a = array.clone
122
+ size = 1
123
+ while size < a.length
124
+ merge_groups(a, size)
125
+ size = size * 2
126
+ end
127
+ return a
128
+ end
134
129
  # :end :msort
135
130
 
136
- # "Helper function" to merge two blocks. A call of the form merge(a, i, n) creates
137
- # a new list by merging n-element lists at a[i] and a[i+n].
131
+ # "Helper method" to merge all groups of size g
132
+
133
+ # :begin :merge_groups
134
+ def merge_groups(a, gs)
135
+ i = 0 # first group starts here
136
+ while i < a.length
137
+ j = i + 2*gs - 1 # end of second group
138
+ a[i..j] = merge(a, i, gs) # merge groups at a[i] and a[i+g]
139
+ i += 2*gs # next groups starts 2*g places to the right
140
+ end
141
+ end
142
+ # :end :merge_groups
143
+
144
+ # "Helper method" to merge two blocks. A call of the form merge(a, i, n) creates
145
+ # a new list by merging n-element lists starting at a[i] and a[i+n].
138
146
 
139
147
  # :begin :merge
140
- def merge(a, i, n) # :nodoc:
141
- ix = j = min(i + n, a.length)
142
- jx = min(j + n, a.length)
148
+ def merge(a, i, gs) # :nodoc:
149
+ ix = j = min(i + gs, a.length)
150
+ jx = min(j + gs, a.length)
143
151
  res = []
144
152
  while i < ix || j < jx
145
153
  if j == jx || i < ix && less( a[i], a[j] )
data/lib/rubylabs.rb CHANGED
@@ -10,8 +10,7 @@ Methods used to monitor execution of programs during experiments.
10
10
 
11
11
  SCRIPT_LINES__ = Hash.new unless defined? SCRIPT_LINES__
12
12
 
13
- # $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'bin'))
14
-
13
+ autoload :IntroLab, "introlab.rb"
15
14
  autoload :SieveLab, "sievelab.rb"
16
15
  autoload :IterationLab, "iterationlab.rb"
17
16
  autoload :RecursionLab, "recursionlab.rb"
@@ -24,6 +23,10 @@ autoload :ElizaLab, "elizalab.rb"
24
23
  autoload :SphereLab, "spherelab.rb"
25
24
  autoload :TSPLab, "tsplab.rb"
26
25
 
26
+ autoload :Demos, "demos.rb"
27
+
28
+ include Math
29
+
27
30
  module RubyLabs
28
31
 
29
32
  =begin rdoc
@@ -43,7 +46,7 @@ Log base 2.
43
46
  =end
44
47
 
45
48
  def log2(x)
46
- Math.log(x) / Math.log(2.0)
49
+ log(x) / log(2.0)
47
50
  end
48
51
 
49
52
  =begin rdoc
@@ -55,31 +58,17 @@ Log base 2.
55
58
  end
56
59
 
57
60
  =begin rdoc
58
- Return the smaller of a and b
61
+ Return the smaller of +a+ and +b+
59
62
  =end
60
63
 
61
64
  def min(a,b)
62
65
  a < b ? a : b
63
66
  end
64
-
65
- # =begin rdoc
66
- # Return a copy of object x with the elements in a new, scrambled order. The
67
- # parameter x can be any object that has an index operator (e.g. strings or
68
- # arrays).
69
- # =end
70
- #
71
- # def permutation(x)
72
- # res = x.clone
73
- # for i in 0..res.length-2
74
- # r = rand(res.length-i) + i
75
- # res[i], res[r] = res[r], res[i]
76
- # end
77
- # return res
78
- # end
67
+
79
68
 
80
69
  =begin rdoc
81
70
 
82
- Call time { foo(...) } to measure the execution time of a call to foo. This
71
+ Call +time { foo(...) }+ to measure the execution time of a call to +foo+. This
83
72
  method will time any arbitrary Ruby expression.
84
73
 
85
74
  =end
@@ -157,9 +146,6 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
157
146
  # to keep around for very few calls, but it's efficient enough -- making an array
158
147
  # of 100K items takes less than a second.
159
148
 
160
- # The @spread variable controls the average spacing between items. The 6.667 for
161
- # small arrays means an array of 15 will have numbers between 0 and 99.
162
-
163
149
  # An earlier version used a method named test_array to make a regular Array object
164
150
  # and augment it with the location method, but the singleton's methods were not passed
165
151
  # on to copies made by a call to sort:
@@ -169,72 +155,93 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
169
155
  # NoMethodError: undefined method `random' for [4, 13, 16]:Array
170
156
 
171
157
  class TestArray < Array
172
-
173
- def initialize(size)
174
- @spread = (size > 50) ? 10 : 6.667
175
- @h = Hash.new
176
158
 
177
- while @h.size < size
178
- @h[ rand( size * @spread ) ] = 1
179
- end
159
+ data = File.join(File.dirname(__FILE__), '..', 'data', 'arrays')
160
+
161
+ @@sources = {
162
+ :cars => "#{data}/cars.txt",
163
+ :colors => "#{data}/colors.txt",
164
+ :fruits => "#{data}/fruit.txt",
165
+ :words => "#{data}/wordlist.txt",
166
+ }
180
167
 
181
- @h.keys.each do |k|
182
- self << k
183
- end
168
+ def initialize(size, src = nil)
169
+
170
+ if src.nil? || src.class == Fixnum
171
+ raise "TestArray: array size must be an integer" unless size.class == Fixnum
172
+ if src.nil?
173
+ @max = (size < 50) ? 100 : (10 * size)
174
+ else
175
+ @max = src
176
+ raise "TestArray: max must be at least 2x larger than size" unless @max >= 2 * size
177
+ end
178
+ else
179
+ raise "TestArray: array size must be an integer or :all" unless size.class == Fixnum || size == :all
180
+ end
181
+
182
+ @h = Hash.new
183
+
184
+ # if @max is defined make an array of integers, otherwise src defines the type of data;
185
+ # size might be :all, in which case return the whole file, and set @all to true so random
186
+ # doesn't try to make a random value not in the array.
187
+
188
+ if @max
189
+ while @h.size < size
190
+ @h[ rand( @max ) ] = 1
191
+ end
192
+ else
193
+ fn = @@sources[src] or raise "TestArray: undefined source: #{src}"
194
+ @words = File.open(fn).readlines
195
+ if size != :all
196
+ max = @words.length
197
+ raise "TestArray: size must be less than #{max} for an array of #{src}" unless size < max
198
+ while @h.size < size
199
+ @h[ @words[ rand(max) ].chomp ] = 1
200
+ end
201
+ end
202
+ end
203
+
204
+ if size == :all
205
+ self.concat @words.map { |s| s.chomp! }
206
+ @all = true
207
+ else
208
+ self.concat @h.keys
209
+ for i in 0..length-2
210
+ r = rand(length-i) + i # i <= r < length
211
+ self[i],self[r] = self[r],self[i]
212
+ end
213
+ end
214
+
184
215
  end
185
216
 
186
217
  def random(outcome)
187
218
  if outcome == :success
188
219
  return self[ rand(self.length) ]
189
220
  elsif outcome == :fail
221
+ raise "TestArray#random: array is universal set" if @all
190
222
  loop do
191
- i = rand( self.length * @spread )
192
- return i if @h[i] == nil
223
+ if @max
224
+ x = rand( @max )
225
+ else
226
+ x = @words[ rand( @words.length ) ].chomp
227
+ end
228
+ return x if @h[x] == nil
193
229
  end
194
230
  else
195
231
  return nil
196
232
  end
197
233
  end
198
-
199
- end # class TestArray
200
-
201
- =begin rdoc
202
-
203
- === RandomArray
204
-
205
- Similar to TestArray, but draws random words from a file.
206
-
207
- =end
208
-
209
- class RandomArray < Array
210
-
211
- data = File.join(File.dirname(__FILE__), '..', 'data', 'arrays')
212
-
213
- @@sources = {
214
- :cars => "#{data}/cars.txt",
215
- :colors => "#{data}/colors.txt",
216
- :fruit => "#{data}/fruit.txt",
217
- :words => "#{data}/wordlist.txt",
218
- }
219
234
 
220
- def initialize(src, n)
221
- fn = @@sources[src] or raise "RandomArray: undefined array type: #{src}"
222
- words = File.open(fn).readlines
223
- a = Hash.new
224
- while a.size < n
225
- w = words[rand(words.length)].chomp
226
- a[w] = 1
227
- end
228
- a.keys.each do |w|
229
- self << w
230
- end
231
- end
232
-
233
- def RandomArray.sources
235
+ def TestArray.sources
234
236
  return @@sources.keys.sort { |a,b| a.to_s <=> b.to_s }
235
237
  end
236
-
237
- end # RandomArray
238
+
239
+ end # class TestArray
240
+
241
+
242
+ def TestArray(n, type = nil)
243
+ TestArray.new(n, type)
244
+ end
238
245
 
239
246
 
240
247
  =begin
@@ -406,7 +413,8 @@ Similar to TestArray, but draws random words from a file.
406
413
  next
407
414
  end
408
415
  end
409
- if s =~ /:end\s+:#{id.to_s}/
416
+ # if s =~ /:end\s+:#{id.to_s}\b/
417
+ if s =~ /:end\s+:#{id.to_s}\s/
410
418
  size = line_num - base
411
419
  throw :found
412
420
  end
@@ -565,11 +573,25 @@ Similar to TestArray, but draws random words from a file.
565
573
  # synch the drawing thread....
566
574
 
567
575
  def Canvas.sync
568
- if RUBY_VERSION =~ %r{^1\.8} && RUBY_PLATFORM =~ %r{darwin} && caller[2] =~ %r{workspace}
576
+ if RUBY_VERSION =~ %r{^1\.8} && RUBY_PLATFORM =~ %r{darwin} && caller[1].index("(irb)") == 0
569
577
  sleep(0.1)
570
578
  end
571
579
  end
572
580
 
581
+ =begin rdoc
582
+ Add text at (x, y). Note -- looks like :anchor is required, otherwise runtime error
583
+ from Tk (something about illegal coords).
584
+ =end
585
+
586
+ def Canvas.text(s, x, y, opts = {})
587
+ return nil unless @@canvas
588
+ opts[:anchor] = :nw
589
+ opts[:text] = s
590
+ text = TkcText.new( @@canvas, x, y, opts)
591
+ @@objects << text
592
+ return text
593
+ end
594
+
573
595
  =begin rdoc
574
596
  Draw a line from (x0,y0) to (x1,y1)
575
597
  =end
@@ -666,8 +688,8 @@ Similar to TestArray, but draws random words from a file.
666
688
  (0...a.length).step(2) do |i|
667
689
  x = a[i] - x0
668
690
  y = a[i+1] - y0
669
- a[i] = x0 + x * Math.cos(theta) - y * Math.sin(theta)
670
- a[i+1] = y0 + x * Math.sin(theta) + y * Math.cos(theta)
691
+ a[i] = x0 + x * cos(theta) - y * sin(theta)
692
+ a[i+1] = y0 + x * sin(theta) + y * cos(theta)
671
693
  end
672
694
  obj.coords = a
673
695
  return a
data/lib/sievelab.rb CHANGED
@@ -10,19 +10,18 @@ filtering step until no more composite numbers are left in the worklist.
10
10
 
11
11
  =end
12
12
 
13
- include Math
14
-
15
13
  module RubyLabs
16
14
 
17
15
  module SieveLab
18
-
19
- # Call sieve(n) to create an array of prime numbers between 2 and n
16
+
17
+ =begin rdoc
18
+ Call +sieve(n)+ to create an array of prime numbers between +2+ and +n+
19
+ =end
20
20
 
21
21
  # :begin :sieve
22
22
  def sieve(n)
23
- return [] if n < 2
24
- worklist = []
25
- (n-1).times { |i| worklist << i+2 }
23
+ return [] if n < 2
24
+ worklist = Array(2..n)
26
25
  primes = []
27
26
 
28
27
  while worklist.first < sqrt(n)
@@ -34,25 +33,6 @@ module SieveLab
34
33
  end
35
34
  # :end :sieve
36
35
 
37
-
38
- # A first version of the sieve, iterates until the worklist is empty
39
-
40
- # :begin :proto_sieve
41
- def proto_sieve(n)
42
- return [] if n < 2
43
- worklist = []
44
- (n-1).times { |i| worklist << i+2 }
45
- primes = []
46
-
47
- while worklist.length > 0
48
- primes << worklist.first
49
- worklist.delete_if { |x| x % primes.last == 0 }
50
- end
51
-
52
- return primes
53
- end
54
- # :end :proto_sieve
55
-
56
36
  end # SieveLab
57
37
 
58
38
  end # RubyLabs
data/lib/spherelab.rb CHANGED
@@ -7,8 +7,6 @@ Definition of Vector and Body objects used for n-body simulations.
7
7
 
8
8
  =end
9
9
 
10
- include Math
11
-
12
10
  module RubyLabs
13
11
 
14
12
  module SphereLab
@@ -18,7 +16,7 @@ module SphereLab
18
16
  global values.
19
17
  =end
20
18
 
21
- @@dataDirectory = File.join(File.dirname(__FILE__), '..', 'data', 'spheres')
19
+ @@sphereDirectory = File.join(File.dirname(__FILE__), '..', 'data', 'spheres')
22
20
 
23
21
  @@viewerOptions = {
24
22
  :dotColor => '#000080',
@@ -210,6 +208,17 @@ module SphereLab
210
208
  end
211
209
  @force = Vector.new(0.0, 0.0, 0.0)
212
210
  end
211
+
212
+ def clone
213
+ copy = super
214
+ if graphic
215
+ copy.position = position.clone
216
+ copy.velocity = velocity.clone
217
+ copy.force = force.clone
218
+ copy.graphic = Canvas.circle(prevx, prevy, size, :fill => color)
219
+ end
220
+ return copy
221
+ end
213
222
 
214
223
  def inspect
215
224
  name = @name ? @name : ""
@@ -398,7 +407,8 @@ module SphereLab
398
407
  end
399
408
 
400
409
  def set_flag(fx, fy)
401
- Canvas.circle( fx, fy, 3, :fill => 'darkblue' )
410
+ r = 3.0
411
+ Canvas.circle( fx + r/2, fy + r/2, r, :fill => 'darkblue' )
402
412
  @reference = [ fx, fy ]
403
413
  end
404
414
 
@@ -475,8 +485,8 @@ module SphereLab
475
485
  def falling_bodies(n)
476
486
  raise "n must be 5 or more" unless n >= 5
477
487
  a = random_bodies(n-1, n-1)
478
- # b = Body.new(1e13, (a[0].position + a[1].position), Vector.new(0,0,0))
479
- b = Body.new(1e13, (a[0].position + a[1].position)*0.85, Vector.new(0,0,0))
488
+ b = Body.new(1e13, (a[0].position + a[1].position), Vector.new(0,0,0))
489
+ # b = Body.new(1e13, (a[0].position + a[1].position)*0.85, Vector.new(0,0,0))
480
490
  # pos = a[0].position
481
491
  # (1..(n-2)).each { |i| pos.add( a[i].position ) }
482
492
  # b = Body.new(1e14, pos * (1.0 / n), Vector.new(0,0,0))
@@ -506,7 +516,7 @@ module SphereLab
506
516
  end
507
517
  filename = args[0]
508
518
  if filename.class == Symbol
509
- filename = File.join(@@dataDirectory, filename.to_s + ".txt")
519
+ filename = File.join(@@sphereDirectory, filename.to_s + ".txt")
510
520
  end
511
521
  File.open(filename).each do |line|
512
522
  line.strip!
@@ -521,7 +531,7 @@ module SphereLab
521
531
  b.color = a[-1]
522
532
  bodies << b
523
533
  end
524
- if args[0] == :urey
534
+ if args[0] == :melon
525
535
  class <<bodies[0]
526
536
  def height
527
537
  return 0 if prevy.nil?
@@ -538,6 +548,27 @@ module SphereLab
538
548
  return bodies
539
549
  end
540
550
 
551
+ =begin rdoc
552
+ Write the mass, position, and velocity for each body to a file. Not intended to
553
+ be used by students, but used to save interesting data sets they can load and use.
554
+ =end
555
+
556
+ # d 4.5e16 300 -75 0 -5 2 0 6 #ff6666
557
+
558
+
559
+ def save_system(b, fn)
560
+ raise "file exists" if File.exists?(fn)
561
+ File.open(fn, "w") do |f|
562
+ b.each do |x|
563
+ f.printf "%s %g %g %g %g %g %g %g %d %s\n",
564
+ x.name, x.mass,
565
+ x.position.x, x.position.y, x.position.z,
566
+ x.velocity.x, x.velocity.y, x.velocity.z,
567
+ x.size, x.color
568
+ end
569
+ end
570
+ end
571
+
541
572
  =begin rdoc
542
573
  Initialize the drawing canvas by drawing a circle for each body in list b.
543
574
  =end
@@ -571,31 +602,26 @@ module SphereLab
571
602
  Demonstrate adding force vectors by moving only one body
572
603
  =end
573
604
 
574
- def update_one(bodies, time)
575
- b = bodies[0]
576
- if b.graphic.nil?
605
+ def update_one(falling, stationary, time)
606
+ if falling.graphic.nil?
577
607
  puts "display the system with view_system"
578
608
  return nil
579
609
  end
580
- for j in 1...bodies.length
581
- Body.interaction( b, bodies[j] )
610
+ stationary.each do |x|
611
+ Body.interaction( falling, x )
582
612
  end
583
- b.move(time)
613
+ falling.move(time)
584
614
  if @@drawing.options.has_key?(:dash)
585
615
  @@drawing.options[:dashcount] = (@@drawing.options[:dashcount] + 1) % @@drawing.options[:dash]
586
616
  if @@drawing.options[:dashcount] == 0
587
617
  @@drawing.options[:pendown] = @@drawing.options[:pendown].nil? ? :track : nil
588
618
  end
589
619
  end
590
- newx, newy = scale(b.position, @@drawing.origin, @@drawing.scale)
591
- Canvas.move(b.graphic, newx-b.prevx, newy-b.prevy, @@drawing.options[:pendown])
592
- b.prevx = newx
593
- b.prevy = newy
594
- b.clear_force
595
- # puts b.velocity.norm
596
- # if (speed = b.velocity.norm) > 7.5
597
- # b.velocity.scale(7.5 / speed)
598
- # end
620
+ newx, newy = scale(falling.position, @@drawing.origin, @@drawing.scale)
621
+ Canvas.move(falling.graphic, newx-falling.prevx, newy-falling.prevy, @@drawing.options[:pendown])
622
+ falling.prevx = newx
623
+ falling.prevy = newy
624
+ falling.clear_force
599
625
  Canvas.sync
600
626
  return true
601
627
  end