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/tsplab.rb CHANGED
@@ -6,24 +6,15 @@
6
6
 
7
7
  =begin
8
8
  todo unit tests
9
- todo set up options, defaults as in mars, spheres (@@options...)
10
- todo :trace as one of the options, array of things to print
11
- TODO test predigree counts
12
- TODO add "phylogeny" links -- keep refs to parent(s)
13
- TODO test: selection only, no mutations
14
- TODO test: random draws only, no selection
15
- TODO plot space: get all 180K costs, order lexicographically, plot (smoothed)
16
-
17
- TODO hide mutate, cross methods -- but accept optional params in call to reproduce constructor so students can control effects to see how method works
18
- TODO (idea is that genes are fixed when critter made, don't change after that)
19
- =end
20
-
21
- =begin
22
- TODO clean up interface
23
- todo two constructors (mutate, cross) instead of one
24
- todo distances are floats, not ints
25
- todo wider histogram, up to 100 bins 3 pixels wide
26
- todo line color = blue if bin width 4 or less
9
+ todo keep track of how tour made, color those from crosses a different color
10
+ todo ? later -- hightlight changes when drawing tour
11
+ todo possible error in call to m.make_tour(:mutate,t) when t is not full size tour of m
12
+ todo kinda brittle with popsize param -- interaction between histogram and popsize needs
13
+ to be stabilized (see esp need to pass popsize to rebuild_population)
14
+ todo fix labels -- top level methods need to erase labels from rsearch or other experiments
15
+ todo to_s for maps should print : in front of symbol labels
16
+ todo clean up rsearch, add :begin/:end wrapper
17
+ todo catch ^C interrupt in esearch? each_permutation?
27
18
  =end
28
19
 
29
20
  module RubyLabs
@@ -31,170 +22,276 @@ module RubyLabs
31
22
  module TSPLab
32
23
 
33
24
  require 'set'
25
+ require "permute.rb"
26
+
27
+ MapView = Struct.new(:cities, :nodes, :links, :labels, :histogram, :options)
28
+ ItemWithDirection = Struct.new(:value, :direction)
29
+
30
+ class ItemWithDirection
31
+ def inspect
32
+ (self.direction ? "<" : ">") + self.value.inspect
33
+ end
34
+ end
34
35
 
35
- MapView = Struct.new(:cities, :nodes, :links, :histogram, :options)
36
-
37
36
  =begin rdoc
38
-
39
- Objects of the CityList class store city names and distances between pairs of
40
- cities. The instance variables are a hash associating a key with a
41
- city name and a matrix of distances. The matrix is a hash of
42
- hashes, indexed by city, where distances are driving times in
43
- minutes.
44
-
37
+ A Map is a 2D array of distances between pairs of cities. A new Map object
38
+ will initially be empty. Call m[i,j] = x to assign the distance between i and
39
+ j. Maps are symmetric (i.e. m[i,j] == m[j,i] for all i and j) so assigning a
40
+ value to m[i,j] automatically assigns the same value to m[j,i]. Indices can be
41
+ strings, symbols, or integers.
45
42
  =end
43
+
44
+ class Map
46
45
 
47
- # Public methods:
48
- # new(f) initializes an object using names and distances in file f
49
- # size() returns number of cities
50
- # keys() returns an array of keys
51
- # values() returns an array of city names
52
- # [x] returns the name of the city with key x
53
- # distance(x,y) returns the distance between cities with keys x and y
54
- # coords(x) returns x, y coordinates of city
46
+ # Make a new distance matrix.
55
47
 
56
- class CityList
57
-
58
- attr_reader :cities, :matrix
59
-
60
48
  def initialize(arg)
49
+ @labels = Array.new
50
+ @dist = Array.new
51
+ @coords = Array.new
61
52
  if arg.class == String || arg.class == Symbol
62
53
  read_file(arg)
63
- elsif arg.class == Array
64
- make_map(arg)
65
54
  elsif arg.class == Fixnum
66
55
  make_random_map(arg)
67
- else
68
- raise "CityList: parameter must be a file name, array of points, or an integer"
56
+ elsif arg.class == Array
57
+ make_map(arg)
58
+ elsif arc.class != NilClass
59
+ raise "Map.new: parameter must be a file name, array of points, or an integer"
69
60
  end
70
61
  end
71
62
 
72
- def inspect()
73
- n = @matrix.length
74
- return sprintf "TSPLab::CityList (%d x %d)", n, n-1
63
+ def inspect
64
+ sprintf "#<TSPLab::Map [%s]>", @labels.join(",")
75
65
  end
76
-
77
- def dump
78
- (?A..@key-1).each do |i|
79
- (i+1..@key-1).each do |j|
80
- printf "%s %s %d\n", @cities[i], @cities[j], @matrix[i][j]
66
+
67
+ alias to_s inspect
68
+
69
+ # print the current state of the metric; the parameter is the field width (number
70
+ # of chars in each matrix entry)
71
+
72
+ def display(fw = nil)
73
+ if fw.nil?
74
+ lw = labels.inject(0) { |max, x| n = x.to_s.length; (n > max) ? n : max }
75
+ dw = (log10(@maxdist).ceil+4)
76
+ fw = max(lw+1,dw)
77
+ end
78
+ res = " " * fw
79
+ @labels.each { |name| res << sprintf("%#{fw-1}s ", name.to_s[0..fw-1]) }
80
+ res += "\n"
81
+ @dist.each_with_index do |a,i|
82
+ res += sprintf("%#{fw-1}s ", @labels[i].to_s[0..fw-1])
83
+ a.each { |x| res += (x.nil? ? " -- " : sprintf("%#{fw}.2f", x)) }
84
+ res += "\n"
85
+ end
86
+ puts res
87
+ end
88
+
89
+ # method to make tours
90
+
91
+ def make_tour(*args)
92
+ begin
93
+ raise "usage" if args.length == 0
94
+ case args[0]
95
+ when :random
96
+ tour = Tour.new(self, permute!(labels)) # note labels returns clone of @labels array...
97
+ when :mutate
98
+ raise "usage" unless args.length >= 2 && args[1].class == Tour && (args[2].nil? || args[2].class == Fixnum)
99
+ child = args[1].clone
100
+ dmax = args[2] ? args[2] : 1
101
+ i = rand(size)
102
+ d = 1 + rand(dmax)
103
+ # puts "mutate #{i} #{d}"
104
+ tour = child.mutate!(i,d)
105
+ child.parent = args[1]
106
+ child.op = [:mutate, i, d]
107
+ when :cross
108
+ raise "usage" unless args.length == 3 && args[1].class == Tour && args[2].class == Tour
109
+ child = args[1].clone
110
+ i = rand(size)
111
+ n = 1 + rand(size-1)
112
+ # puts "cross #{i} #{n}"
113
+ tour = child.cross!(args[2], i, n)
114
+ child.parent = args[1]
115
+ child.op = [:cross, args[2], i, n]
116
+ else
117
+ raise "usage" unless args[0].class == Array
118
+ a = args[0]
119
+ errs = 0
120
+ a.each do |x|
121
+ if ! @labels.include?(x)
122
+ puts "unknown city: #{x}"
123
+ errs += 1
124
+ end
125
+ end
126
+ raise "errors in list of cities" if errs > 0
127
+ tour = Tour.new(self, a)
128
+ end
129
+ rescue Exception => e
130
+ if e.message == "usage"
131
+ puts "Usage:"
132
+ puts " make_tour( [x,y,..] )"
133
+ puts " make_tour( :random )"
134
+ puts " make_tour( :mutate, t [,n] )"
135
+ puts " make_tour( :cross, t1, t2 )"
136
+ else
137
+ puts "make_tour: #{e.message}"
138
+ end
139
+ return nil
140
+ end
141
+
142
+ return tour
143
+
144
+ end
145
+
146
+ # Generate all possible tours using the Johnson-Trotter algorithm. The method
147
+ # works by generating all permutations of array indices, then for each permutation
148
+ # generating an array of labels.
149
+
150
+ def each_tour
151
+ a = []
152
+ n = @labels.length
153
+ for i in 1...n do
154
+ a << ItemWithDirection.new(i, true)
155
+ end
156
+ loop do
157
+ # yield [0] + a
158
+ yield Tour.new(self, [@labels[0]] + a.map { |x| @labels[x.value] })
159
+ mover = nil
160
+ for i in 0...a.length
161
+ mover = i if movable(a,i) && (mover.nil? || a[i].value > a[mover].value)
81
162
  end
82
- end
83
- return true
84
- end
85
-
86
- def print_matrix
87
- # colarray =
88
- puts " " + (?B..(@key-1)).map { |x| x.chr }.join(" ")
89
- (?A..(@key-2)).each_with_index do |i, n|
90
- s = sprintf "%2s: ", i.chr
91
- s += " " * n
92
- (i+1..@key-1).each do |j|
93
- s += sprintf " %3d", @matrix[i][j]
163
+ break if mover.nil?
164
+ k = a[mover].value
165
+ # puts "mover = #{mover} k = #{k}"
166
+ break if k == 2
167
+ adj = a[mover].direction ? mover-1 : mover+1
168
+ a[adj], a[mover] = a[mover], a[adj]
169
+ for i in 0...a.length
170
+ if a[i].value > k
171
+ a[i].direction ^= true
172
+ end
94
173
  end
95
- puts s
96
- end
97
- return true
98
- end
99
-
100
- def size()
101
- return @key - ?A
174
+ end
175
+ end
176
+
177
+ # return the number of rows in the matrix
178
+
179
+ def size
180
+ return @dist.length
181
+ end
182
+
183
+ # return the first pair of city labels
184
+
185
+ def first
186
+ return @labels[0..1]
102
187
  end
103
188
 
104
- def keys()
105
- return @cities.keys.sort.map{|x| x.chr}
189
+ # this iterator will generate the remaining pairs of labels
190
+
191
+ def rest
192
+ n = @labels.length
193
+ @labels.each_with_index do |x,i|
194
+ @labels.last(n-i-1).each_with_index do |y,j|
195
+ next if i == 0 && j == 0
196
+ yield(x,y)
197
+ end
198
+ end
199
+ end
200
+
201
+ # get/set the distance between labels i and j (which can be)
202
+ # given in either order, i.e. d[i,j] is the same as d[j,i]
203
+
204
+ def [](i,j)
205
+ ix = @labels.index(i) or return nil
206
+ jx = @labels.index(j) or return nil
207
+ ix, jx = jx, ix if ix < jx
208
+ @dist[ix][jx]
106
209
  end
107
210
 
108
- def values()
109
- return @cities.keys.sort.collect{|x| @cities[x]}
211
+ def []=(i,j,val)
212
+ raise "Map: can't assign to diagonal" if i == j
213
+ ix = index_of(i)
214
+ jx = index_of(j)
215
+ ix, jx = jx, ix if ix < jx
216
+ @dist[ix][jx] = val.to_f
110
217
  end
111
218
 
112
- def [](x)
113
- x = x[0] if x.class == String
114
- return nil unless x >= ?A && x < @key
115
- return @cities[x]
219
+ def labels
220
+ return @labels.clone
116
221
  end
117
222
 
118
- def distance(x,y)
119
- x = x[0] if x.class == String
120
- y = y[0] if y.class == String
121
- return nil unless x >= ?A && x < @key
122
- return nil unless y >= ?A && y < @key
123
- return @matrix[x][y]
223
+ def dist
224
+ d = Array.new
225
+ @dist.each { |row| d << row.clone }
226
+ return d
124
227
  end
125
228
 
126
- def coords(name)
127
- return @coords[name]
229
+ def coords(x)
230
+ return @coords[index_of(x)]
231
+ end
232
+
233
+ # method left over from Metric class, probably not useful for TSP, but...
234
+
235
+ def delete(i)
236
+ ix = @labels.index(i) or return nil
237
+ (ix...@labels.length).each { |x| @dist[x].slice!(ix) }
238
+ @dist.slice!(ix)
239
+ @labels.slice!(ix)
128
240
  end
129
-
130
- # private methods -- return the key for a city (make a new one if necessary),
131
- # get a time value from an input line, save a distance
132
241
 
133
242
  private
134
243
 
244
+ def index_of(i)
245
+ if (ix = @labels.index(i)) == nil
246
+ ix = @labels.length
247
+ @labels << i
248
+ @dist[ix] = Array.new
249
+ @dist[ix][ix] = 0.0
250
+ end
251
+ return ix
252
+ end
253
+
135
254
  def read_file(fn)
136
255
  matrixfilename = fn.to_s + ".txt"
137
- matrixfilename = File.join(@@dataDirectory, matrixfilename)
256
+ matrixfilename = File.join(@@tspDirectory, matrixfilename)
138
257
  raise "Matrix not found: #{matrixfilename}" unless File.exists?(matrixfilename)
139
258
 
140
- @key = ?A
141
- @cities = Hash.new # initially a map from name string to one-letter key
142
- @matrix = Hash.new
143
- @coords = Hash.new
144
259
  readingLocations = true
260
+ @maxdist = 0.0
145
261
 
146
262
  File.open(matrixfilename).each do |line|
147
263
  line.chomp!
148
264
  next if line.length == 0
265
+ next if line[0] == ?#
149
266
  rec = line.split
150
267
  if rec[0][0] == ?:
151
268
  readingLocations = false if rec[0] == ":matrix"
152
269
  # tbd -- deal with other directives
153
270
  elsif readingLocations
154
- @coords[getKey(rec[2]).chr] = [rec[0].to_i, rec[1].to_i]
271
+ x = rec[2].to_sym
272
+ # printf "loc #{x} at #{rec[0]}, #{rec[1]}\n"
273
+ @coords[index_of(x)] = [rec[0].to_i, rec[1].to_i]
274
+ # p index_of(x)
275
+ # p @coords[index_of(x)]
155
276
  else
156
- i = getKey(rec[0])
157
- j = getKey(rec[1])
158
- dist = rec[2].to_i
159
- assign(i,j,dist)
160
- assign(j,i,dist)
277
+ i = rec[0].to_sym
278
+ j = rec[1].to_sym
279
+ d = rec[2].to_f
280
+ # printf "dist #{rec[0]} to #{rec[1]} = #{rec[2]}\n"
281
+ self[i,j] = d
282
+ @maxdist = d if d > @maxdist
161
283
  end
162
284
  end
163
285
 
164
- @cities = @cities.invert # from now on @cities is a map from key to name string
165
-
166
- errs = 0
167
- (?A...@key).each do |i|
168
- (i+1...@key).each do |j|
169
- unless @matrix[i][j] && @matrix[j][i]
170
- errs += 1
171
- puts "CityList.new: missing distance between #{@cities[i]} and #{@cities[j]}"
172
- end
173
- end
286
+ errs = []
287
+ i, j = first
288
+ errs << [i,j] if j.nil?
289
+ self.rest do |i,j|
290
+ errs << [i,j] unless self[i,j] != nil
174
291
  end
175
- raise "CityList.new: errors in input file" if errs > 0
292
+ raise "Map.new: missing distances #{errs.inspect}" if errs.length > 0
176
293
  end
177
-
178
- def make_map(a)
179
- @key = ?A + a.length
180
- @cities = Hash.new
181
- @matrix = Hash.new
182
- @coords = Hash.new
183
- for i in 0...a.length
184
- chi = ?A + i
185
- @cities[chi] = chi.chr
186
- for j in (i+1)...a.length
187
- x = a[i][0] - a[j][0]
188
- y = a[i][1] - a[j][1]
189
- d = Math.sqrt(x**2 + y**2).round
190
- chj = ?A + j
191
- assign(chi,chj,d)
192
- assign(chj,chi,d)
193
- end
194
- @coords[chi.chr] = a[i]
195
- end
196
- end
197
-
294
+
198
295
  def make_random_map(n)
199
296
  h = Hash.new
200
297
  a = Array.new
@@ -209,326 +306,413 @@ minutes.
209
306
  make_map(a)
210
307
  end
211
308
 
212
- def getKey(s)
213
- if @cities[s] == nil
214
- @cities[s] = @key
215
- @key += 1
309
+ def make_map(a)
310
+ @maxdist = 0.0
311
+ for i in 0...a.length
312
+ for j in (i+1)...a.length
313
+ x = a[i][0] - a[j][0]
314
+ y = a[i][1] - a[j][1]
315
+ d = sqrt(x**2 + y**2)
316
+ self[i,j] = d
317
+ @maxdist = d if d > @maxdist
318
+ end
319
+ @coords[i] = a[i]
216
320
  end
217
- return @cities[s]
218
321
  end
219
-
220
- def getValue(s)
221
- a = s.scan(/(\d+)/)
222
- if a.length == 1
223
- return a[0][0].to_i
322
+
323
+ # helper method for each_tour iterator, used by Johnson-Trotter algorithm
324
+
325
+ def movable(a, i)
326
+ if a[i].direction
327
+ return i > 0 && a[i].value > a[i-1].value
224
328
  else
225
- return 0
329
+ return i < a.length-1 && a[i].value > a[i+1].value
226
330
  end
227
331
  end
228
332
 
229
- def assign(i,j,v)
230
- if @matrix[i] == nil
231
- @matrix[i] = Hash.new
232
- end
233
- @matrix[i][j] = v
234
- end
235
-
236
- end # class CityList
333
+ end # class Map
334
+
237
335
 
238
336
  =begin rdoc
239
-
240
- Each Tour object is a string where each letter represents a city, along with a cost
241
- defined by a CityList matrix that must be specified when the tour is created. If
242
- an initial string is given verify it contains only valid keys. If no initial
243
- string is given make a tour with a random permutation of all city keys.
244
-
245
- The +nm+ and +nx+ attributes keep track of a tour's "pedigree." The counters are
246
- inherited from a parent, and incremented whenever a mutation or crossover is applied.
247
-
337
+ A Tour object is an array of city names, corresponding to the cities visited, in order,
338
+ by the salesman. Attributes are the path, its cost, a unique tour ID, and a reference to
339
+ the matrix used to define the distance between pairs of cities.
340
+
341
+ Class methods access the number of tours created, or reset the tour counter to 0.
248
342
  =end
249
343
 
250
- =begin
251
- TODO don't give access to path (unless there's a way to make it read-only -- freeze it?)
252
- =end
344
+ # TODO don't give access to path (unless there's a way to make it read-only -- freeze it?)
253
345
 
254
346
  class Tour
255
- attr_reader :id, :path, :cost, :matrix, :nm, :nx
347
+ attr_reader :id, :path, :cost, :matrix
348
+ attr_accessor :parent, :op
256
349
 
257
350
  @@id = 0
258
-
259
- def initialize(m, s = nil, nm = 0, nx = 0)
260
- if s == nil
261
- @path = m.keys.to_s.permute!
262
- else
263
- skeys = Set.new(m.keys)
264
- used = Set.new
265
- s.each_byte do |x|
266
- raise "Tour.new: invalid character in tour: #{x}" unless skeys.member?(x.chr)
267
- raise "Tour.new: duplicate character in tour: #{x.chr}" if used.member?(x)
268
- used.add(x)
269
- end
270
- @path = s.clone
271
- end
272
- @cost = pathcost(m,@path)
273
- @matrix = m # need matrix to update cost after mutation...
274
- @id = @@id
275
- @nm = nm
276
- @nx = nx
277
- @@id += 1
351
+
352
+ def Tour.reset
353
+ @@id = 0
278
354
  end
279
-
280
- # An alterntaive constructor -- either copy a single parent and add
281
- # a point mutation, or cross two parents
282
-
283
- def Tour.reproduce(x, y = nil, trace = nil)
284
- if y.nil? || y == :trace
285
- i = rand(x.path.length)
286
- j = (i+1) % x.path.length
287
- # j = rand(x.path.length)
288
- trace = :trace if y == :trace # handle calls like reproduce(x, :trace)
289
- x.clone.mutate(i, j, trace)
290
- else
291
- i = rand(x.path.length)
292
- loop { j = rand(x.path.length); break if j != i }
293
- i, j = j, i if i > j
294
- x.clone.cross(y, i, j, trace)
295
- end
296
- end
297
-
298
- # Another constructor -- return the next tour lexicographically
299
- # following tour t
300
355
 
301
- def Tour.next(t)
302
- p = t.path.clone
303
- n = p.length
304
- # find largest j s.t. path[j] < path[j+1]
305
- j = n-2
306
- while j >= 0
307
- break if p[j] < p[j+1]
308
- j -= 1
309
- end
310
- return nil if j < 0
311
- # find largest i s.t. path[j] < path[i]
312
- i = n-1
313
- loop do
314
- break if p[j] < p[i]
315
- i -= 1
316
- end
317
- # exchange path[j], path[i]
318
- p[j], p[i] = p[i], p[j]
319
- # reverse path from j+1 to end
320
- tmp = p.slice!(j+1, n-1)
321
- p += tmp.reverse
322
- return Tour.new(t.matrix, p)
356
+ def Tour.count
357
+ return @@id
323
358
  end
324
359
 
325
- def to_s()
326
- return "\##{@id}: #{@path} / #{@cost}"
360
+ def initialize(m, s)
361
+ @matrix = m
362
+ @path = s.clone
363
+ @cost = pathcost
364
+ @id = @@id
365
+ @@id += 1
327
366
  end
328
367
 
329
368
  def inspect
330
- return to_s
369
+ sprintf "#<TSPLab::Tour %s (%.2f)>", @path.inspect, @cost
331
370
  end
332
371
 
333
- # Reset the id to 0 (e.g. to count number of objects)
334
-
335
- def Tour.reset
336
- @@id = 0
337
- end
338
-
339
- def Tour.count
340
- @@id
341
- end
372
+ alias to_s inspect
342
373
 
343
374
  # A copy of a tour needs its own new id and copy of the path and pedigree
344
375
 
345
376
  def clone
346
- return Tour.new(@matrix, @path, @nm, @nx)
377
+ # return Tour.new(@matrix, @path, @nm, @nx)
378
+ return Tour.new(@matrix, @path)
347
379
  end
348
380
 
349
- # Exchange mutation (called EM by Larranaga et al). Simply swaps two
350
- # cities at the specified locations.
381
+ # Exchange mutation (called 'EM' by Larranaga et al). Swaps node i with one
382
+ # d links away (d = 1 means neighbor). An optimization (has a big impact when
383
+ # tours are 20+ cities) computes new cost by subtracting and adding single
384
+ # link costs instead of recomputing full path length. Notation: path
385
+ # through node i goes xi - i - yi, and path through j is xj - j - yj.
351
386
 
352
- def mutate(i, j, trace = nil)
353
- puts "mutate: #{i} <-> #{j}" if ! trace.nil?
387
+ def mutate!(i, d = 1)
388
+ raise "mutate!: index #{i} out of range 0..#{@path.length}" unless (i >=0 && i < @path.length)
389
+ return if d == 0 # these two special cases won't occur when
390
+ if d == @path.length-1 # mutate! called from evolve, but....
391
+ i = (i-1) % @path.length
392
+ d = 1
393
+ end
394
+
395
+ j = (i+d) % @path.length # location of swap
396
+
397
+ xi = (i-1) % @path.length # locations before, after i
398
+ yi = (i+1) % @path.length
399
+ xj = (j-1) % @path.length # locations before, after j
400
+ yj = (j+1) % @path.length
401
+
402
+ if d == 1
403
+ @cost -= @matrix[ @path[xi], @path[i] ]
404
+ @cost -= @matrix[ @path[j], @path[yj] ]
405
+ @cost += @matrix[ @path[xi], @path[j] ]
406
+ @cost += @matrix[ @path[i], @path[yj] ]
407
+ else
408
+ @cost -= @matrix[ @path[xi], @path[i] ]
409
+ @cost -= @matrix[ @path[i], @path[yi] ]
410
+ @cost -= @matrix[ @path[xj], @path[j] ]
411
+ @cost -= @matrix[ @path[j], @path[yj] ]
412
+ @cost += @matrix[ @path[xi], @path[j] ]
413
+ @cost += @matrix[ @path[j], @path[yi] ]
414
+ @cost += @matrix[ @path[xj], @path[i] ]
415
+ @cost += @matrix[ @path[i], @path[yj] ]
416
+ end
417
+
354
418
  @path[i], @path[j] = @path[j], @path[i]
355
- @cost = pathcost(@matrix,@path)
356
- @nm += 1
419
+
420
+ # uncomment to verify path cost logic
421
+ # if (@cost - pathcost).abs > 0.001
422
+ # puts "#{i} #{j}" + self.to_s + " / " + @cost.to_s
423
+ # end
424
+
357
425
  self
358
426
  end
359
427
 
360
- # Order cross-over (called OX1 by Larranaga et al). Save a chunk of the
428
+ # Order cross-over (called 'OX1' by Larranaga et al). Save a chunk of the
361
429
  # current tour, then copy the remaining cities in the order they occur in
362
- # tour t.
430
+ # tour t. i is the index of the place to start copying, n is the number to
431
+ # copy.
363
432
 
364
- def cross(t, i, j, trace = nil)
365
- puts "cross: copy #{i}..#{j}" if ! trace.nil?
366
- @path = @path[i..j]
367
- t.path.each_byte do |c|
433
+ def cross!(t, i, n)
434
+ j = (i + n - 1) % @path.length
435
+ if i <= j
436
+ p = @path[i..j]
437
+ else
438
+ p = @path[i..-1]
439
+ p += @path[0..j]
440
+ end
441
+ @path = p
442
+ t.path.each do |c|
368
443
  @path << c unless @path.include?(c)
369
444
  end
370
- @cost = pathcost(@matrix,@path)
371
- @nx += 1 + t.nx
372
- @nm += t.nm
445
+ @cost = pathcost
373
446
  self
374
447
  end
375
448
 
376
- # private methods -- compute the cost of a path, given a matrix; apply
377
- # mutations (point mutations, cross-overs)
378
-
379
- private
380
-
381
- def pathcost(m,s)
382
- sum = m.distance(s[0],s[-1]) # link from end to start
383
- for i in 0..s.length-2
384
- sum += m.distance(s[i],s[i+1])
449
+ # Not used normally, but is use in unit tests to make sure mutate! is computing
450
+ # the right costs with its optimized strategy
451
+
452
+ def pathcost
453
+ sum = @matrix[ @path[0], @path[-1] ]
454
+ for i in 0..@path.length-2
455
+ sum += @matrix[ @path[i], @path[i+1] ]
385
456
  end
386
457
  return sum
387
458
  end
388
459
 
389
460
  end # class Tour
461
+
462
+ # Combinatorics... tempting to write factorial with inject, but this is for the
463
+ # students to look at....
390
464
 
391
- # Make a population of size n -- n instances of tours made from a set
392
- # of cities
465
+ def factorial(n)
466
+ f = 1
467
+ for i in 2..n
468
+ f *= i
469
+ end
470
+ return f
471
+ end
472
+
473
+ def ntours(n)
474
+ return factorial(n-1) / 2
475
+ end
476
+
477
+ # Random search
478
+
479
+ def rsearch( matrix, nsamples )
480
+ Tour.reset
481
+ best = matrix.make_tour(:random)
482
+ if @@drawing
483
+ make_histogram([]) # clear histogram display
484
+ view_tour(best)
485
+ init_labels(["tours:", "cost:"])
486
+ end
487
+ (nsamples-1).times do |i|
488
+ t = matrix.make_tour(:random)
489
+ update = t.cost < best.cost
490
+ best = t if update
491
+ if @@drawing
492
+ view_tour(t) if update
493
+ labels = []
494
+ labels << sprintf( "#tours: %d", Tour.count )
495
+ labels << sprintf( "cost: %.2f", best.cost )
496
+ update_labels( labels )
497
+ end
498
+ end
499
+ Canvas.sync
500
+ return best
501
+ end
502
+
503
+ =begin rdoc
504
+ Make a set of +n+ random tours from map +m+
505
+ =end
393
506
 
394
- def initPopulation(n,m)
507
+ # :begin :init_population
508
+ def init_population( m, n )
395
509
  a = Array.new
396
510
 
397
511
  n.times do
398
- a.push(Tour.new(m))
512
+ a << m.make_tour(:random)
399
513
  end
400
514
 
401
515
  a.sort! { |x,y| x.cost <=> y.cost }
402
-
516
+
517
+ if @@drawing
518
+ make_histogram(a)
519
+ Canvas.sync
520
+ end
521
+
403
522
  return a
404
523
  end
524
+ # :end :init_population
525
+
526
+
527
+ =begin rdoc
528
+ +select_survivors(p, options)+ Apply "natural selection" to population +p+. Sort by
529
+ fitness, then remove individual +i+ with probability +i/n+ where +n+ is the population
530
+ size. Note the first item in the array is always kept since +0/n = 0+.
531
+ =end
405
532
 
406
- # Evolve a population. Sort by fitness, then remove individuals with
407
- # a probability based on their ranking (the ith individual survives
408
- # with p = (n-i)/n). Then Build back up to the original population
409
- # size via copies with mutations or crossovers. An optional second
410
- # argument is a collection of parameters that can override various
411
- # options (e.g. the probability of survival).
412
-
413
- def evolve(p, param={})
414
- debug = param[:trace] ? true : false
415
- pcross = param[:pcross] ? param[:pcross] : 0.25
416
- plotci = param[:plotci]
417
-
418
- p.sort! { |x,y| x.cost <=> y.cost }
419
- i = 0
420
- n = p.length
421
-
422
- if plotci
423
- m = 0.0
424
- p.each { |t| m += t.cost }
425
- m = m / p.length
426
- printf "%.2f %.2f %.2f\n", m, p[0].cost, p[-1].cost
427
- end
428
-
429
- # phase 1 -- delete random tours
533
+ # p.sort! { |x,y| (x.cost == y.cost) ? (x.id <=> y.id) : (x.cost <=> y.cost) }
430
534
 
431
- while i < p.length
432
- pk = (n-i).to_f / n
433
- if rand < pk
434
- puts "keep #{p[i]}" if debug
435
- i += 1 # keep this tour, skip to next one
436
- else
437
- puts "zap #{p[i]}" if debug
438
- p.delete_at(i) # zap this one; keep i at same value
535
+ # :begin :select_survivors
536
+ def select_survivors( population, toplevel = true )
537
+ population.sort! { |x,y| x.cost <=> y.cost }
538
+ n = population.size
539
+
540
+ (n-1).downto(1) do |i|
541
+ if ( rand < (i.to_f / n) )
542
+ # population.delete_at(i)
543
+ population[i].op = :zap
439
544
  end
440
545
  end
546
+
547
+ if @@drawing && toplevel
548
+ show_survivors( population )
549
+ Canvas.sync
550
+ end
551
+
552
+ return population
553
+ end
554
+ # :end :select_survivors
441
555
 
442
- # phase 2 -- build back up to n tours; prev is the index of the last
443
- # tour from the previous generation (candidates for cloning/crossing)
556
+ =begin rdoc
557
+ +rebuild_population(p, n, options)+ Add new Tour objects to population +p+ until the size
558
+ of the population reaches +n+. Each new Tour is a mutation of one or two Tours currently
559
+ in +p+. The hash +options+ has mutation parameters, e.g. the probability of doing a crossover
560
+ vs. point mutation.
561
+ =end
444
562
 
445
- best = p[0]
446
- prev = p.length
563
+ # :begin :rebuild_population
564
+ def rebuild_population( population, popsize, userOptions = {} )
565
+ options = @@tourOptions.merge(userOptions)
566
+ tracing = (options[:trace] == :on)
567
+
568
+ map = population[0].matrix # assume they're all from the same map...
569
+
570
+ dist = options[:distribution]
571
+ psmall, plarge, pcross = options[:profiles][dist]
572
+ sdmax = options[:sdmax] || ((map.size > 10) ? (map.size / 10) : 1)
573
+ ldmax = options[:ldmax] || ((map.size > 10) ? (map.size / 4) : 1)
447
574
 
448
- while p.length < n
449
- mom = p[rand(prev)]
450
- if rand < pcross
451
- dad = p[rand(prev)]
452
- kid = Tour.reproduce(mom,dad)
453
- puts "#{mom} x #{dad} => #{kid}" if debug
575
+ (popsize-1).downto(1) do |i|
576
+ population.delete_at(i) if population[i].op == :zap
577
+ end
578
+
579
+ prev = population.length # items before this index are from previous generation
580
+
581
+ while population.length < popsize
582
+ r = rand
583
+ if r < 1.0 - (psmall + plarge + pcross)
584
+ kid = map.make_tour( :random )
585
+ elsif r < 1.0 - (plarge + pcross)
586
+ mom = population[ rand(prev) ]
587
+ kid = map.make_tour( :mutate, mom, sdmax )
588
+ elsif r < 1.0 - pcross
589
+ mom = population[ rand(prev) ]
590
+ kid = map.make_tour( :mutate, mom, ldmax )
454
591
  else
455
- kid = Tour.reproduce(mom)
456
- puts "#{mom} => #{kid}" if debug
592
+ mom = population[ rand(prev) ]
593
+ dad = population[ rand(prev) ]
594
+ kid = map.make_tour( :cross, mom, dad )
457
595
  end
458
- p.push(kid)
459
- best = kid if kid.cost < best.cost
460
- end
461
-
462
- return best
463
-
464
- end
465
-
466
- # High level interface -- make a population for a set of cities, then
467
- # call evolve() until the best tour doesn't change after some number of
468
- # iterations (also passed as a parameter). Print the tour when done.
469
-
470
- def bestTour(matrix, param={})
471
- popsize = param[:popsize] ? param[:popsize] : 10
472
- maxgen = param[:maxgen] ? param[:maxgen] : 25
473
- maxstatic = param[:maxstatic] ? param[:maxstatic] : maxgen/2
474
-
475
- p = initPopulation(popsize,matrix)
476
- best = p[0]
477
- nstatic = 0
478
- ngen = 0
479
-
480
- if param[:verbose]
481
- puts "popsize: #{popsize}"
482
- puts "maxgen: #{maxgen}"
483
- puts "maxstatic: #{maxstatic}"
484
- puts "pcross: #{param[:pcross]}"
596
+ population << kid
485
597
  end
486
598
 
487
599
  if @@drawing
488
- view_tour(best)
489
- make_histogram(p)
600
+ update_histogram(population)
601
+ Canvas.sync
490
602
  end
491
603
 
492
- while ngen < maxgen && nstatic < maxstatic
493
- puts "best tour: #{p[0]}" if param[:verbose]
494
- evolve(p, param)
495
- if p[0].cost < best.cost
496
- best = p[0]
497
- nstatic = 0
498
- view_tour(best) if @@drawing
604
+ return population
605
+ end
606
+ # :end :rebuild_population
607
+
608
+ # :begin :evolve
609
+ def evolve( population, maxgen, ngen = 0, options = {} )
610
+ popsize = population.length
611
+ best = population[0]
612
+ while ngen < maxgen
613
+ select_survivors( population, false )
614
+ rebuild_population( population, popsize, options)
615
+ if (population[0].cost - best.cost).abs > 1e-10
616
+ best = population[0]
617
+ updated = true
618
+ # @@bestGeneration = ngen
499
619
  else
500
- nstatic += 1
620
+ updated = false
501
621
  end
502
- update_histogram(p) if @@drawing
503
622
  ngen += 1
504
- end
623
+ update_tour_display( population, ngen, updated ) if @@drawing
624
+ end
625
+ Canvas.sync if @@drawing
626
+ return best
627
+ end
628
+ # :end :evolve
505
629
 
506
- # puts "#{ngen} generations (#{nstatic} at best value)"
507
- puts "#{ngen} generations"
508
-
509
- if param[:verbose]
510
- puts "best cost: #{best.cost}"
511
- puts "pedigree: #{best.nx} cross #{best.nm} mutate"
512
- puts "best tour:"
513
- best.path.each_byte do |x|
514
- puts " " + matrix[x]
630
+ =begin rdoc
631
+ +esearch( map, n, options)+ Top level method for the genetic algorithm. +m+ is a Map
632
+ containing pairwise distances between a set of cities. The hash named
633
+ +options+ contains tour parameters, e.g. population size and mutation probabilities.
634
+ Make an initial population of random tours, then iterate +n+ times, calling +select_survivors+
635
+ and +rebuild_population+ to evolve an optimal tour. The return value is the lowest cost
636
+ tour after the last round of evolution.
637
+ =end
638
+
639
+ def esearch( matrix, maxgen, userOptions = {} )
640
+ options = @@tourOptions.merge(userOptions)
641
+
642
+ if options[:continue]
643
+ if @@previousPopulation
644
+ population = @@previousPopulation
645
+ options = @@previousOptions
646
+ ngen = @@previousNGen
647
+ else
648
+ puts "no saved population"
649
+ return nil
650
+ end
651
+ else
652
+ Tour.reset
653
+ population = init_population( matrix, options[:popsize] )
654
+ ngen = 0
655
+ if @@drawing
656
+ init_labels(["generations:", "tours:", "cost:"])
657
+ view_tour( population[0] )
515
658
  end
516
659
  end
660
+
661
+ begin
662
+ check_mutation_parameters(options)
663
+ rescue Exception => e
664
+ puts "esearch: " + e
665
+ return false
666
+ end
667
+
668
+ evolve( population, maxgen, ngen, options )
669
+
670
+ @@previousPopulation = population
671
+ @@previousOptions = options
672
+ @@previousNGen = maxgen
673
+
674
+ Canvas.sync if @@drawing
517
675
 
518
- return best
676
+ return population[0]
519
677
  end
520
678
 
521
- # Make a random set of n cities on a 1000 x 1000 grid
679
+ def update_tour_display(population, ngen, draw_tour)
680
+ view_tour(population[0]) if draw_tour
681
+ update_histogram(population)
682
+ labels = []
683
+ labels << sprintf( "generations: %d", ngen )
684
+ labels << sprintf( "#tours: %d", Tour.count )
685
+ labels << sprintf( "cost: %.2f", population[0].cost )
686
+ update_labels(labels)
687
+ end
522
688
 
523
- def random_cities(n)
524
- a = Array.new
525
- n.times { x = rand(1000); y = rand(1000); a << [x,y] }
526
- return a
689
+ =begin rdoc
690
+ Make sure the mutation parameters are valid
691
+ =end
692
+
693
+ def check_mutation_parameters(options)
694
+ dist = options[:distribution]
695
+ profiles = options[:profiles]
696
+ raise "specify mutation probabilities with :distribution option" unless dist
697
+ floaterr = "distribution must be an array of three numbers between 0.0 and 1.0"
698
+ symbolerr = "distribution must be one of #{profiles.keys.inspect}"
699
+ if dist.class == Symbol
700
+ raise symbolerr unless profiles[dist]
701
+ elsif dist.class == Array
702
+ raise floaterr unless dist.length == 3
703
+ sum = 0.0
704
+ dist.each { |x| raise floaterr if (x < 0.0 || x > 1.0); sum += x}
705
+ raise "sum of probabilities must be 1.0" unless (sum - 1).abs < Float::EPSILON
706
+ profiles[:user] = dist
707
+ options[:distribution] = :user
708
+ else
709
+ raise "#{floaterr} or #{symbolerr}"
710
+ end
527
711
  end
528
712
 
529
713
  def checkout(matrix, filename = nil)
530
714
  matrixfilename = matrix.to_s + ".txt"
531
- matrixfilename = File.join(@@dataDirectory, matrixfilename)
715
+ matrixfilename = File.join(@@tspDirectory, matrixfilename)
532
716
  if !File.exists?(matrixfilename)
533
717
  puts "Matrix not found: #{matrixfilename}"
534
718
  return nil
@@ -548,14 +732,16 @@ inherited from a parent, and incremented whenever a mutation or crossover is app
548
732
 
549
733
  def view_map(m, userOptions = {} )
550
734
  options = @@mapOptions.merge(userOptions)
551
- Canvas.init(700, 500, "TSPLab")
735
+ Canvas.init(800, 500, "TSPLab")
552
736
  links = Array.new
553
737
  nodes = Array.new
554
- m.keys.each do |loc|
738
+ r = options[:dotRadius]
739
+ m.labels.each do |loc|
555
740
  x, y = m.coords(loc)
556
- nodes << Canvas.circle( x, y, options[:dotRadius], :fill => options[:dotColor] )
741
+ nodes << Canvas.circle( x, y, r, :fill => options[:dotColor] )
742
+ Canvas.text(loc.to_s, x+r, y+r)
557
743
  end
558
- @@drawing = MapView.new(m, nodes, links, Array.new, options)
744
+ @@drawing = MapView.new(m, nodes, links, [], [], options)
559
745
  Canvas.sync
560
746
  return true
561
747
  end
@@ -565,105 +751,211 @@ inherited from a parent, and incremented whenever a mutation or crossover is app
565
751
  puts "call view_map to initialize the canvas"
566
752
  return nil
567
753
  end
754
+ options = @@tourOptions.merge(userOptions)
568
755
  map = @@drawing.cities
569
756
  @@drawing.links.each { |x| x.delete }
570
757
  @@drawing.links.clear
571
- x0, y0 = map.coords(t.path[-1].chr)
758
+ x0, y0 = map.coords(t.path[-1])
572
759
  for i in 0...t.path.length
573
- x1, y1 = map.coords(t.path[i].chr)
760
+ x1, y1 = map.coords(t.path[i])
574
761
  @@drawing.links << Canvas.line(x0, y0, x1, y1)
575
762
  x0, y0 = x1, y1
576
763
  end
577
764
  @@drawing.nodes.each { |x| x.raise }
578
765
  Canvas.sync
766
+ return true
767
+ end
768
+
769
+ def init_labels(a)
770
+ labelx = 525
771
+ labely = 350
772
+ dy = 20
773
+ labels = @@drawing.labels
774
+ if labels.length > 0
775
+ labels.each { |x| x.text = "" }
776
+ end
777
+ labels.clear
778
+ for i in 0...a.length
779
+ if i == labels.length
780
+ labels << Canvas.text("", labelx, labely)
781
+ labely += dy
782
+ end
783
+ labels[i].text = a[i]
784
+ end
785
+ end
786
+
787
+ def update_labels(a)
788
+ labels = @@drawing.labels
789
+ for i in 0...labels.length
790
+ labels[i].text = a[i]
791
+ end
579
792
  end
580
793
 
581
- # todo -- get drawing params from options
582
- # todo -- make_histogram and update_histogram are private
583
- # todo -- check to see if new histogram has same number of bins as old one -- throw some out or make some new ones
584
- # todo -- scale w to fit number of tours, min size = 1
585
- # todo -- max bins 100 -- if pop size over that just show first 100 tours
586
- # todo! color bin red if new tour comes from crossover
794
+ # todo! color bin red if new tour comes from crossover? but what if > 1 tour per bin?
795
+
796
+ # note: calling make_histogram with an empty list erases the previous histogram
587
797
 
588
798
  def make_histogram(pop)
589
799
  if @@drawing.histogram.length > 0
590
- update_histogram(pop)
591
- else
592
- x = 520
593
- y = 100
594
- ymax = 200.0
595
- w = 5
596
- scale = ymax / pop[-1].cost
597
- pop[0..29].each do |t|
598
- h = t.cost*scale
599
- @@drawing.histogram << Canvas.rectangle(x, y+ymax-h, x+w, y+ymax, :fill => 'darkblue' )
600
- x += w
601
- end
602
- @@drawing.options[:scale] = scale
603
- end
800
+ @@drawing.histogram.each { |box| box.delete }
801
+ @@drawing.histogram.clear
802
+ end
803
+ return if pop.length == 0
804
+ x = @@histogramOptions[:x]
805
+ y = @@histogramOptions[:y]
806
+ ymax = @@histogramOptions[:ymax]
807
+ w = @@histogramOptions[:binwidth]
808
+ scale = ymax / pop[-1].cost
809
+ npb = (pop.length / @@histogramOptions[:maxbins]).ceil
810
+ nbins = pop.length / npb
811
+ (0..pop.length-1).step(npb) do |i|
812
+ h = pop[i..(i+npb-1)].inject(0.0) { |sum, tour| sum + tour.cost }
813
+ h = (h/npb)*scale
814
+ @@drawing.histogram << Canvas.rectangle(x, y+ymax-h, x+w, y+ymax, :fill => 'darkblue' )
815
+ x += w
816
+ end
817
+ @@drawing.options[:scale] = scale
818
+ @@histogramOptions[:npb] = npb
819
+ @@histogramOptions[:nbins] = nbins
820
+ @@recolor = false
604
821
  end
605
822
 
606
823
  def update_histogram(pop)
607
- y = 100
608
- ymax = 200.0
824
+ y = @@histogramOptions[:y]
825
+ ymax = @@histogramOptions[:ymax]
826
+ npb = @@histogramOptions[:npb]
609
827
  scale = @@drawing.options[:scale]
610
828
  @@drawing.histogram.each_with_index do |box, i|
611
- h = pop[i].cost * scale
829
+ j = i*npb
830
+ if j > pop.length
831
+ h = 0
832
+ else
833
+ h = pop[j..(j+npb-1)].inject(0.0) { |sum, tour| sum + tour.cost }
834
+ h = (h/npb)*scale
835
+ end
612
836
  x0, y0, x1, y1 = box.coords
613
837
  box.coords = x0, y+ymax-h, x1, y1
838
+ box.fill = 'darkblue' if @@recolor
839
+ end
840
+ end
841
+
842
+ def show_survivors(pop)
843
+ hist = @@drawing.histogram
844
+ return unless pop.length == hist.length
845
+ hist.each_with_index do |box, i|
846
+ box.fill = 'gray' if pop[i].op == :zap
614
847
  end
848
+ @@recolor = true
615
849
  end
616
850
 
851
+ def show_lineage(tour, userOptions = {} )
852
+ options = @@lineageOptions.merge(userOptions)
853
+ dt = options[:dt]
854
+ a = []
855
+ while tour.parent
856
+ a << tour
857
+ tour = tour.parent
858
+ end
859
+ init_labels(["id:", "cost:", "op:"]) if @@drawing
860
+ a.reverse.each do |x|
861
+ puts x.inspect + ": " + x.op.inspect
862
+ if @@drawing
863
+ view_tour(x)
864
+ labels = []
865
+ labels << sprintf( "id: %d", x.id )
866
+ labels << sprintf( "cost: %.2f", x.cost )
867
+ labels << sprintf( "op: %s", x.op.inspect )
868
+ update_labels(labels)
869
+ sleep(dt)
870
+ end
871
+ end
872
+ return true
873
+ end
874
+
617
875
  # Values accessible to all the methods in the module
618
876
 
619
- @@dataDirectory = File.join(File.dirname(__FILE__), '..', 'data', 'tsp')
877
+ @@tspDirectory = File.join(File.dirname(__FILE__), '..', 'data', 'tsp')
620
878
 
621
879
  @@mapOptions = {
622
880
  :dotColor => '#cccccc',
623
881
  :dotRadius => 5.0,
624
882
  }
625
883
 
884
+ # @@rsearchOptions = {
885
+ # :pause => 0.0,
886
+ # }
887
+
626
888
  @@tourOptions = {
627
-
889
+ :popsize => 10,
890
+ :maxgen => 25,
891
+ :profiles => {
892
+ :mixed => [0.5, 0.25, 0.25],
893
+ :random => [0.0, 0.0, 0.0],
894
+ :all_small => [1.0, 0.0, 0.0],
895
+ :all_large => [0.0, 1.0, 0.0],
896
+ :all_cross => [0.0, 0.0, 1.0],
897
+ },
898
+ :distribution => :all_small,
899
+ :pause => 0.0,
900
+ }
901
+
902
+ @@histogramOptions = {
903
+ :x => 520.0,
904
+ :y => 100.0,
905
+ :ymax => 200.0,
906
+ :maxbins => 50.0,
907
+ :binwidth => 5,
908
+ }
909
+
910
+ @@lineageOptions = {
911
+ :dt => 2.0,
912
+ :first => 0,
913
+ :last => -1,
628
914
  }
629
915
 
630
916
  @@drawing = nil
631
-
917
+ @@previousPopulation = nil
918
+
632
919
  end # module TSPLab
633
920
 
634
921
  end # module RubyLabs
635
922
 
636
- # Additions to the String class
637
923
 
638
- class String
924
+ module Enumerable
639
925
 
640
926
  =begin rdoc
641
- Call <tt>s.permute</tt> to scramble the characters in string +s+.
927
+ <tt>each_permtation</tt> is an iterator that makes all permutations of
928
+ a container in lexicographic order.
642
929
  =end
643
930
 
644
- def permute!
645
- for i in 0..length-2
646
- r = rand(length-i) + i # i <= r < length
647
- self[i],self[r] = self[r],self[i]
648
- end
649
- self
650
- end
651
-
652
-
653
- =begin rdoc
654
- Call <tt>s.rotate</tt> to "rotate" the characters in string +s+, i.e. detach
655
- the last character in +s+ and reinsert it at the front.
656
- =end
657
-
658
- def rotate!(n)
659
- if n > 0
660
- tail = self[n..-1]
661
- self[n..-1] = ""
662
- self.insert(0,tail)
931
+ def each_permutation
932
+ p = self.clone
933
+ n = p.length
934
+ res = []
935
+ loop do
936
+ yield p if block_given?
937
+ res << p.clone
938
+ # find largest j s.t. path[j] < path[j+1]
939
+ j = n-2
940
+ while j >= 0
941
+ break if p[j] < p[j+1]
942
+ j -= 1
943
+ end
944
+ break if j < 0
945
+ # find largest i s.t. path[j] < path[i]
946
+ i = n-1
947
+ loop do
948
+ break if p[j] < p[i]
949
+ i -= 1
950
+ end
951
+ # exchange path[j], path[i]
952
+ p[j], p[i] = p[i], p[j]
953
+ # reverse path from j+1 to end
954
+ tmp = p.slice!(j+1, n-1)
955
+ p += tmp.reverse
663
956
  end
664
- self
957
+ return block_given? ? nil : res
665
958
  end
666
-
959
+
667
960
  end
668
961
 
669
-