rubylabs 0.7.5 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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