charlie 0.6.0 → 0.7.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/History.txt +14 -0
- data/Manifest.txt +13 -22
- data/README.txt +3 -3
- data/Rakefile +1 -1
- data/TODO.txt +11 -21
- data/data/BENCHMARK +25 -23
- data/data/CROSSOVER +5 -1
- data/data/GENOTYPE +6 -6
- data/data/MUTATION +19 -7
- data/data/SELECTION +2 -1
- data/data/template.html +2 -1
- data/examples/EXAMPLES_README.txt +70 -0
- data/examples/bitstring.rb +72 -0
- data/examples/{gladiatorial_sunburn.rb → coevolution.rb} +80 -22
- data/examples/function_optimization.rb +113 -0
- data/examples/output/{royalroad1_report.html → bitstring_royalroad.html} +822 -655
- data/examples/output/function_optimization_sombrero.html +2289 -0
- data/examples/output/function_optimization_twopeak.csv +210 -0
- data/examples/output/function_optimization_twopeak.html +2477 -0
- data/examples/output/string_weasel.html +513 -0
- data/examples/output/tsp.html +633 -882
- data/examples/{money.rb → permutation.rb} +20 -8
- data/examples/string.rb +98 -0
- data/examples/tree.rb +37 -12
- data/examples/tsp.rb +34 -22
- data/lib/charlie.rb +5 -1
- data/lib/charlie/1.9fixes.rb +46 -0
- data/lib/charlie/crossover.rb +31 -14
- data/lib/charlie/etc/minireport.rb +5 -4
- data/lib/charlie/etc/monkey.rb +11 -8
- data/lib/charlie/gabenchmark.rb +230 -0
- data/lib/charlie/genotype.rb +4 -0
- data/lib/charlie/list/list_crossover.rb +25 -5
- data/lib/charlie/mutate.rb +34 -7
- data/lib/charlie/permutation/permutation.rb +34 -6
- data/lib/charlie/population.rb +12 -122
- data/lib/charlie/selection.rb +1 -0
- data/lib/charlie/tree/tree.rb +179 -17
- data/test/t_common.rb +1 -1
- data/test/test_benchmark.rb +19 -5
- data/test/test_cross.rb +23 -1
- data/test/test_evolve.rb +14 -1
- data/test/test_mutator.rb +28 -2
- data/test/test_permutation.rb +23 -1
- data/test/test_sel.rb +3 -1
- data/test/test_tree.rb +63 -1
- metadata +17 -25
- data/examples/bit.rb +0 -10
- data/examples/function_opt_2peak.rb +0 -24
- data/examples/function_opt_sombero.rb +0 -38
- data/examples/gladiatorial_simple.rb +0 -17
- data/examples/gridwalk.rb +0 -29
- data/examples/output/flattened_sombero.html +0 -6400
- data/examples/output/flattened_sombero2_.html +0 -3576
- data/examples/output/fopt1_dblopt.html +0 -2160
- data/examples/output/hill10.html +0 -5816
- data/examples/output/hill2.csv +0 -24
- data/examples/output/hill2.html +0 -384
- data/examples/output/royalroad2_report.html +0 -1076
- data/examples/output/royalroadquick_report.html +0 -504
- data/examples/output/weasel1_report.html +0 -1076
- data/examples/output/weasel2_report.html +0 -240
- data/examples/royalroad.rb +0 -26
- data/examples/royalroad2.rb +0 -18
- data/examples/simple_climb_hill2.rb +0 -47
- data/examples/weasel.rb +0 -36
@@ -1,5 +1,10 @@
|
|
1
|
+
begin
|
2
|
+
require '../lib/charlie'
|
3
|
+
rescue LoadError
|
4
|
+
require 'rubygems'
|
5
|
+
require 'charlie'
|
6
|
+
end
|
1
7
|
|
2
|
-
require '../lib/charlie'
|
3
8
|
|
4
9
|
module MoneyBase
|
5
10
|
def words
|
@@ -17,19 +22,26 @@ module MoneyBase
|
|
17
22
|
end
|
18
23
|
end
|
19
24
|
|
20
|
-
|
25
|
+
puts "send+more=money problem as a permutation genotype"
|
26
|
+
|
27
|
+
class MoneyP < PermutationGenotype(10) # send+more=money problem as a permutation
|
28
|
+
include MoneyBase
|
29
|
+
end
|
30
|
+
|
31
|
+
Population.new(MoneyP).evolve_on_console
|
32
|
+
|
33
|
+
|
34
|
+
=begin
|
35
|
+
# can also be done with StringGenotype, more effectively too.
|
21
36
|
# remember, string genotypes are just arrays with elements from some set, not necessarily chars.
|
37
|
+
|
22
38
|
class Money < StringGenotype(8,0..9)
|
23
39
|
include MoneyBase
|
24
40
|
def fitness # no permutation, so need to have a penalty for reusing chars
|
25
41
|
super - 100*(8 - genes.uniq.size)
|
26
42
|
end
|
27
43
|
end
|
28
|
-
Population.new(Money).evolve_on_console
|
29
|
-
|
30
|
-
class MoneyP < PermutationGenotype(10) # send+more=money problem as a permutation
|
31
|
-
include MoneyBase
|
32
|
-
end
|
33
44
|
|
34
|
-
Population.new(
|
45
|
+
Population.new(Money).evolve_on_console
|
35
46
|
|
47
|
+
=end
|
data/examples/string.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
begin
|
2
|
+
require '../lib/charlie'
|
3
|
+
rescue LoadError
|
4
|
+
require 'rubygems'
|
5
|
+
require 'charlie'
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
if ARGV[0].nil?
|
10
|
+
puts "Several examples for string genotypes."
|
11
|
+
puts "Usage: ruby string.rb weasel|gridwalk"
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
elsif ARGV[0].downcase.gsub(/[^a-z]/,'') == 'weasel'
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
STR = 'methinks it is like a weasel'
|
22
|
+
Schars = ('a'..'z').to_a << ' '
|
23
|
+
|
24
|
+
puts "A version of the weasel program. Fitness is # of characters that match '#{STR}'."
|
25
|
+
puts "Useful as a simple test for convergence speed."
|
26
|
+
|
27
|
+
|
28
|
+
class Weasel < StringGenotype(STR.size,Schars)
|
29
|
+
def fitness
|
30
|
+
genes.zip(STR.chars).find_all{|a,b|a==b}.size
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
genes.join.inspect
|
35
|
+
end
|
36
|
+
use BestOnlySelection, UniformCrossover, ListMutator(:single_point,:replace[*Schars])
|
37
|
+
end
|
38
|
+
|
39
|
+
Population.new(Weasel).evolve_on_console(200)
|
40
|
+
|
41
|
+
|
42
|
+
output_file = 'output/string_weasel.html'
|
43
|
+
|
44
|
+
puts "Running benchmark. Takes about a minute on Ruby 1.9. Output in #{output_file}"
|
45
|
+
puts "Press [Enter] to continue, or Ctrl-C to abort."
|
46
|
+
STDIN.gets
|
47
|
+
|
48
|
+
GABenchmark.benchmark(Weasel,output_file){
|
49
|
+
selection TruncationSelection(0.2), BestOnlySelection, Elitism(ScaledRouletteSelection)
|
50
|
+
crossover NullCrossover, SinglePointCrossover, UniformCrossover
|
51
|
+
mutator *(1..4).map{|n| ListMutator(:n_point[n],:replace[*Schars]) }
|
52
|
+
generations 100
|
53
|
+
}
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
|
58
|
+
|
59
|
+
elsif ARGV[0].downcase.gsub(/[^a-z]/,'') == 'gridwalk'
|
60
|
+
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
puts "This example tries to find a tour through a grid, in such a way that each square is visited only once."
|
67
|
+
puts "Often needs to restart multiple times, each time getting stuck in a local maximum, before finding the full solution."
|
68
|
+
|
69
|
+
DIRS = [[-1,0],[1,0],[0,-1],[0,1]]
|
70
|
+
class Walk < StringGenotype(25,DIRS) # Walk with steps in 4 directions
|
71
|
+
|
72
|
+
def fitness
|
73
|
+
grid = Array.new(5){Array.new(5)}
|
74
|
+
x,y=0,0
|
75
|
+
genes.each{|dx,dy|
|
76
|
+
grid[x][y] = :visited
|
77
|
+
nx,ny = x+dx,y+dy
|
78
|
+
x,y=nx,ny if (0..4)===nx && (0..4)===ny && grid[nx][ny].nil? # on grid and haven't been there before
|
79
|
+
}
|
80
|
+
grid[x][y] = :visited
|
81
|
+
grid.flatten.compact.size
|
82
|
+
end
|
83
|
+
use TournamentSelection(4)
|
84
|
+
end
|
85
|
+
|
86
|
+
# multiple runs of 100 generations until a solution is found. This is better for problems that get stuck in local maxima a lot
|
87
|
+
pop = gen = nil
|
88
|
+
loop{
|
89
|
+
pop, gen = Population.new(Walk,20).evolve_until(100){|b| b.fitness == 25 }
|
90
|
+
break if gen
|
91
|
+
puts "Did not converge in 100 generations, length = #{pop.max.fitness}/25"
|
92
|
+
}
|
93
|
+
puts 'Found walk of length 25:', pop[-1].genes.inspect
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
end
|
data/examples/tree.rb
CHANGED
@@ -1,28 +1,44 @@
|
|
1
|
+
begin
|
2
|
+
require '../lib/charlie'
|
3
|
+
rescue LoadError
|
4
|
+
require 'rubygems'
|
5
|
+
require 'charlie'
|
6
|
+
end
|
1
7
|
require 'pp'
|
2
|
-
require '../lib/charlie'
|
3
8
|
|
4
9
|
# several examples of genetic programming
|
5
10
|
|
6
11
|
if ARGV[0]=='cos'
|
7
12
|
|
13
|
+
$size_fac = 0.0
|
8
14
|
# approximate cos(x) by a polynomial.on [0,3]. usually results in some kind of linear approximation.like 1.3-0.8x
|
9
|
-
class Cos < TreeGenotype([proc{3*rand-1.5},:x], [
|
15
|
+
class Cos < TreeGenotype([proc{3*rand-1.5},:x], [:-@], [:+,:*,:-])
|
10
16
|
def fitness
|
11
|
-
|
17
|
+
#-(0..10).map{|x| (eval_genes(:x=>0.3*x) - Math.cos(0.3 * x) ).abs }.sum - 0.1*size # last term used to counter bloat
|
12
18
|
# also possible, use infinity norm instead of L1 norm
|
13
19
|
#-(0..10).map{|x| (eval_genes(:x=>0.3*x) - Math.cos(0.3 * x) ).abs }.max
|
14
20
|
|
15
21
|
# smaller range [0,1.5] and inf norm give higher order approximations
|
16
|
-
|
22
|
+
-(0..10).map{|x| (eval_genes(:x=>0.1*x) - Math.cos(0.1 * x) ).abs }.max
|
17
23
|
end
|
24
|
+
use PMutate(0.5,TreeReplaceMutator.dup,TreeNumTerminalMutator.dup)
|
18
25
|
end
|
19
26
|
|
20
|
-
pop = Population.new(Cos)
|
21
|
-
|
27
|
+
pop = Population.new(Cos)
|
28
|
+
|
29
|
+
loop{
|
30
|
+
pop.evolve_on_console(500)
|
31
|
+
pp pop.best.genes
|
32
|
+
puts "q to quit, enter to continue"
|
33
|
+
break if $stdin.gets =~ /q/
|
34
|
+
}
|
35
|
+
|
36
|
+
|
22
37
|
|
23
38
|
elsif ARGV[0]=='pors'
|
24
39
|
|
25
|
-
|
40
|
+
|
41
|
+
# plus one recall store, generate the number ARGV[1] || 31 using +, 1 and recall/store operations (as few ops as possible).
|
26
42
|
|
27
43
|
class Fixnum
|
28
44
|
def sto
|
@@ -30,7 +46,7 @@ class Fixnum
|
|
30
46
|
end
|
31
47
|
end
|
32
48
|
|
33
|
-
class PORS < TreeGenotype([1,:rec], [
|
49
|
+
class PORS < TreeGenotype([1,:rec], [:sto], [:+])
|
34
50
|
N = (ARGV[1] || 32).to_i
|
35
51
|
def fitness
|
36
52
|
$pors_store = 0 # reset store
|
@@ -41,8 +57,13 @@ end
|
|
41
57
|
pop = Population.new(PORS).evolve_on_console(500)
|
42
58
|
pp pop.max.genes
|
43
59
|
|
60
|
+
# n=31 1 + sto(sto(sto(1+1) + rec + 1) + rec + rec) + rec , size 18
|
61
|
+
|
62
|
+
|
44
63
|
elsif ARGV[0]=='porsx'
|
45
64
|
|
65
|
+
|
66
|
+
|
46
67
|
# variant on pors, multiply T by some N, with only one access to T allowed
|
47
68
|
|
48
69
|
class Fixnum
|
@@ -51,7 +72,7 @@ class Fixnum
|
|
51
72
|
end
|
52
73
|
end
|
53
74
|
|
54
|
-
class PORS < TreeGenotype([:rec,:T], [
|
75
|
+
class PORS < TreeGenotype([:rec,:T], [:sto], [:+])
|
55
76
|
N = (ARGV[1] || 31).to_i # quite complicated optimal tree for 31
|
56
77
|
def fitness
|
57
78
|
-(0..5).map{|t|
|
@@ -73,8 +94,11 @@ pp pop.max.genes
|
|
73
94
|
|
74
95
|
|
75
96
|
elsif ARGV[0]=='bloat'
|
97
|
+
|
98
|
+
|
99
|
+
|
76
100
|
# just generates huge trees.
|
77
|
-
class Bloat < TreeGenotype([1], [
|
101
|
+
class Bloat < TreeGenotype([1], [:+@], [:+])
|
78
102
|
def fitness
|
79
103
|
size
|
80
104
|
end
|
@@ -86,5 +110,6 @@ end
|
|
86
110
|
pop = Population.new(Bloat).evolve_on_console(200)
|
87
111
|
|
88
112
|
else
|
89
|
-
puts "
|
90
|
-
|
113
|
+
puts "Several examples for tree genotypes."
|
114
|
+
puts "Usage: ruby tree.rb cos|pors|porsx|bloat"
|
115
|
+
end
|
data/examples/tsp.rb
CHANGED
@@ -1,15 +1,14 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
#CITIES = (0...N).map{|i| th = i * 2 * Math::PI / N; [Math.cos(th),Math.sin(th)] }
|
1
|
+
begin
|
2
|
+
require '../lib/charlie'
|
3
|
+
rescue LoadError
|
4
|
+
require 'rubygems'
|
5
|
+
require 'charlie'
|
6
|
+
end
|
8
7
|
|
9
|
-
|
10
|
-
#CITIES = (0...N).map{|i| [0,i] }
|
8
|
+
puts "Travelling salesperson problem."
|
11
9
|
|
12
|
-
|
10
|
+
N=7
|
11
|
+
# NxN cities on a grid
|
13
12
|
CITIES = (0...N).map{|i| (0...N).map{|j| [i,j] } }.inject{|a,b|a+b}
|
14
13
|
|
15
14
|
class TSP < PermutationGenotype(CITIES.size)
|
@@ -21,22 +20,35 @@ class TSP < PermutationGenotype(CITIES.size)
|
|
21
20
|
}
|
22
21
|
-d # higher (less negative) fitness is better. This breaks RouletteSelection (use 1.0/d instead), but most other methods can handle this
|
23
22
|
end
|
23
|
+
use TournamentSelection(4), PCross(0.01,EdgeRecombinationCrossover), PMutateN(InversionMutator=>0.4,InsertionMutator=>0.4)
|
24
|
+
|
24
25
|
cache_fitness # benchmark ~ 20% faster, tournament selection 25+% faster
|
25
26
|
end
|
26
27
|
|
28
|
+
puts "Running GA until the optimal solution has been found."
|
29
|
+
gen = nil
|
30
|
+
pop, gen = Population.new(TSP,30).evolve_until(1000,100){|b| puts b.fitness; b.fitness > -(N*N+1) } while gen.nil?
|
31
|
+
puts "Fitness: #{pop.max.fitness}"
|
32
|
+
puts "Generations needed: #{gen.inspect}"
|
33
|
+
p pop.max.genes.map{|a| CITIES[a]}
|
34
|
+
|
35
|
+
|
36
|
+
puts "Running benchmark. Output in output/tsp.html. Takes about 50 minutes on Ruby 1.9."
|
37
|
+
puts "Press [Enter] to continue, or Ctrl-C to abort."
|
38
|
+
STDIN.gets
|
27
39
|
|
28
|
-
|
29
|
-
|
30
|
-
|
40
|
+
GABenchmark.benchmark(TSP,'output/tsp.html') {
|
41
|
+
selection TruncationSelection(1), Elitism(ScaledRouletteSelection), TournamentSelection(4)
|
42
|
+
crossover EdgeRecombinationCrossover, PermutationCrossover,
|
43
|
+
PCross(0.5,EdgeRecombinationCrossover,PermutationCrossover),
|
44
|
+
PCross(0.01,EdgeRecombinationCrossover), NullCrossover
|
31
45
|
|
46
|
+
mutator TranspositionMutator, InversionMutator, InsertionMutator,
|
47
|
+
PMutateN(InversionMutator=>0.4,InsertionMutator=>0.4),
|
48
|
+
PMutateN(InversionMutator=>0.4,InsertionMutator=>0.4,RotationMutator=>0.2)
|
32
49
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
InversionMutator, PMutate(0.5,InversionMutator,PermutationMutator)
|
38
|
-
self.generations = 30
|
39
|
-
self.repeat = 25
|
40
|
-
self.population_size = 20
|
41
|
-
} if ARGV[0]=='bm' # takes about 7 minutes on ruby 1.9. lower the repeat number for a quicker run
|
50
|
+
generations 400
|
51
|
+
repeat 8
|
52
|
+
population_size 20
|
53
|
+
}
|
42
54
|
|
data/lib/charlie.rb
CHANGED
@@ -4,7 +4,7 @@ $:.unshift File.dirname(__FILE__)
|
|
4
4
|
|
5
5
|
# This is just a dummy module to avoid making the VERSION constant a global.
|
6
6
|
module Charlie
|
7
|
-
VERSION = '0.
|
7
|
+
VERSION = '0.7.0'
|
8
8
|
end
|
9
9
|
|
10
10
|
require 'charlie/etc/monkey'
|
@@ -28,5 +28,9 @@ require 'charlie/permutation/permutation'
|
|
28
28
|
|
29
29
|
require 'charlie/tree/tree'
|
30
30
|
|
31
|
+
require 'charlie/gabenchmark'
|
31
32
|
|
33
|
+
if RUBY_VERSION >= '1.9'
|
34
|
+
require 'charlie/1.9fixes'
|
35
|
+
end
|
32
36
|
|
@@ -0,0 +1,46 @@
|
|
1
|
+
#1.9 versions of some functions, to avoid bug #16493.
|
2
|
+
#TODO: remove on bugfix/1.9.1
|
3
|
+
|
4
|
+
|
5
|
+
|
6
|
+
def Elitism(sel_module,elite_n=1) # :nodoc:
|
7
|
+
ng_name = sel_module.to_s.gsub(/[^A-Za-z0-9]/,'_')
|
8
|
+
Module.new{
|
9
|
+
include sel_module
|
10
|
+
alias_method ng_name, :next_generation
|
11
|
+
@@elite_n = elite_n
|
12
|
+
define_method :next_generation, ->(population,&block){
|
13
|
+
population = population.sort_by(&:fitness)
|
14
|
+
best = population[-@@elite_n..-1]
|
15
|
+
population = send(ng_name,population,&block)
|
16
|
+
# reset old best elite_n, but don't overwrite better ones
|
17
|
+
population[-@@elite_n..-1] = best.zip_with(population[-@@elite_n..-1]){|old,new| [old,new].max }
|
18
|
+
population
|
19
|
+
}
|
20
|
+
self.name= "Elitism(#{sel_module.to_s},#{elite_n})"
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def SingleChild(crossover_module) # :nodoc:
|
26
|
+
crs_name = '_cross_' + crossover_module.to_s.gsub(/[^A-Za-z0-9]/,'')
|
27
|
+
Module.new{
|
28
|
+
include crossover_module
|
29
|
+
alias_method crs_name, :cross
|
30
|
+
|
31
|
+
define_method(:cross){|*args| send(crs_name,*args).at_rand }
|
32
|
+
self.name= "SingleChild(#{crossover_module})"
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
module Enumerable
|
37
|
+
undef_method :sum
|
38
|
+
def sum # faster than both r=0; each; r and {|a,b|a+b}
|
39
|
+
inject(0,:+)
|
40
|
+
end
|
41
|
+
|
42
|
+
undef_method :zip_with
|
43
|
+
def zip_with(a2) # avoid Enumerable#zip in 1.9
|
44
|
+
r=[]; each_with_index{|e,i| r << yield(e,a2[i]) }; r
|
45
|
+
end
|
46
|
+
end
|
data/lib/charlie/crossover.rb
CHANGED
@@ -23,27 +23,44 @@ end
|
|
23
23
|
|
24
24
|
# Takes crossover c1 with probability p, and crossover c2 with probability 1-p
|
25
25
|
def PCross(p,c1,c2=NullCrossover)
|
26
|
+
return c1 if c1==c2
|
27
|
+
c1_name, c2_name = [c1,c2].map{|c| '_cross_' + c.to_s.gsub(/[^A-Za-z0-9]/,'') }
|
26
28
|
Module.new{
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
include c1.dup # dup to avoid bugs on use PCross(..,c1) .. use c1
|
30
|
+
alias_method c1_name, :cross
|
31
|
+
include c2.dup
|
32
|
+
alias_method c2_name, :cross
|
33
|
+
|
34
|
+
define_method(:cross) {|*args|
|
35
|
+
rand < p ? send(c1_name,*args) : send(c2_name,*args)
|
36
|
+
}
|
34
37
|
self.name= "PCross(#{p},#{c1},#{c2})"
|
35
38
|
}
|
36
39
|
end
|
37
40
|
|
38
|
-
|
39
|
-
#a
|
40
|
-
|
41
|
-
|
41
|
+
# Variant of PCross for more than 2 crossovers.
|
42
|
+
# * Pass a hash of Module=>probability pairs. If sum(probability) < 1, NullCrossover will be used for the remaining probability.
|
43
|
+
# * example: PCrossN(SinglePointCrossover=>0.33,UniformCrossover=>0.33) for NullCrossover/SinglePointCrossover/UniformCrossover all with probability 1/3
|
44
|
+
def PCrossN(hash)
|
45
|
+
tot_p = hash.inject(0){|s,(m,p)| s+p }
|
46
|
+
if (tot_p - 1.0).abs > 0.01 # close to 1?
|
47
|
+
raise ArgumentError, "PCrossN: sum of probabilities > 1.0" if tot_p > 1.0
|
48
|
+
hash[NullCrossover] = (hash[NullCrossover] || 0.0) + (1.0 - tot_p)
|
49
|
+
end
|
50
|
+
partial_sums = hash.sort_by{|m,p| -p } # max probability first
|
51
|
+
s = 0.0
|
52
|
+
partial_sums.map!{|m,p| ['_cross_' + m.to_s.gsub(/[^A-Za-z0-9]/,'') , s+=p, m] }
|
53
|
+
|
42
54
|
Module.new{
|
55
|
+
partial_sums.each{|name,p,mod|
|
56
|
+
include mod.dup
|
57
|
+
alias_method name, :cross
|
58
|
+
}
|
43
59
|
define_method(:cross) {|*args|
|
44
|
-
|
60
|
+
r = rand
|
61
|
+
c_name = partial_sums.find{|name,p,mod| p >= r }.first
|
62
|
+
send(c_name,*args)
|
45
63
|
}
|
46
|
-
self.name= "
|
64
|
+
self.name= "PCrossN(#{hash.inspect})"
|
47
65
|
}
|
48
66
|
end
|
49
|
-
=end
|
@@ -16,7 +16,7 @@ class Table < Array
|
|
16
16
|
def to_html
|
17
17
|
"<table>\n" +
|
18
18
|
"<tr>" + @colnames.map{|t| "<th>#{t}</th>" }.join + "</tr>\n" +
|
19
|
-
map{|r| "<tr>\n" + r.map{|e| "\t<td>#{e.gsub('<','<')}</td>\n" }.join + "</tr>\n" }.join +
|
19
|
+
map{|r| "<tr>\n" + r.map{|e| "\t<td>#{e.gsub('<','<').gsub("\r\n",'<br>')}</td>\n" }.join + "</tr>\n" }.join +
|
20
20
|
"</table>"
|
21
21
|
end
|
22
22
|
|
@@ -25,12 +25,13 @@ class Table < Array
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def to_s(csz=nil)
|
28
|
-
|
29
|
-
|
28
|
+
console_size = 80
|
29
|
+
rsz = at(0).size
|
30
|
+
csz ||= [(console_size-rsz) / rsz]*rsz
|
30
31
|
pad = ' ' * csz.max
|
31
32
|
sep = csz.map{|x|'-'*x}.join('+')
|
32
33
|
([sep,@colnames.zip_with(csz){|str,sz| (str+pad)[0...sz] }.join('|'),sep]+
|
33
|
-
map{|r| r.zip_with(csz){|str,sz| (str+pad)[0...sz] }.join('|') } << sep).join("\n")
|
34
|
+
map{|r| r.zip_with(csz){|str,sz| (str.gsub("\r\n","\\")+pad)[0...sz] }.join('|') } << sep).join("\n")
|
34
35
|
end
|
35
36
|
|
36
37
|
end
|