cheap_random 0.9.2

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/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ doc/
3
+ random/
4
+
data/LICENSE.md ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2006 - 2012 Bardi Einarsson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,85 @@
1
+ CheapRandom
2
+ ============
3
+
4
+ Description
5
+ -----------
6
+ **CheapRandom** is a set of tools for pseudo random number generation from arbitrary data. The properties of the **CheapRandom seed** make convenient random number generation possible -- useful for easily repeatable software testing. The **CheapRandom algorithm** is information conserving and generally appears to produce lower chi-squared statistics than **Kernel::rand** i.e. it appears to be more random. The **CheapRandom algorithm**, an original work by Bardi Einarsson, has been in use for 6 years.
7
+
8
+ Why should one use CheapRandom?
9
+ -------------------------------
10
+ Simple and powerful: The **CheapRandom algorithm** is fast, fast enough to be practical to use in ruby as ruby. The properties of the **CheapRandom seed** (see below) make management of seeds and random data easy and verifiable.
11
+
12
+ Version
13
+ -------
14
+ v0.9.1 with comprehensive tests, developed using ruby 1.9.3 and rspec 2.10.0.
15
+
16
+ Installation
17
+ ------------
18
+ Clone the repository. Create a directory called **random** in the repository root. Use **examples/make\_seed.rb** to make a **the.seed** file - (a 256 byte permutation file) from arbitrary data using the **CheapRandom default seed**.
19
+
20
+ Usage and documentation
21
+ -----------------------
22
+ Study the programs in the **examples** and **spec** directories and use the **.rb** files in the **lib** directory. See below for more information.
23
+
24
+ License
25
+ -------
26
+ Copyright (c) 2006 - 2012 Bardi Einarsson. Released under the MIT License. See the [LICENSE][license] file for further details.
27
+
28
+ [license]: https://github.com/bardibardi/cheap_random/blob/master/LICENSE.md
29
+
30
+ Create a large file of random bytes
31
+ -----------------------------------
32
+ Copy (or link) some large file (with a known hash) to the **random** directory. Use **examples/cr.rb** to randomize it into a **.random** file (the rspec tests expect **test.zip** and **test.zip.random**).
33
+
34
+ Use a large file of random bytes as a source of random numbers
35
+ --------------------------------------------------------------
36
+ Use **examples/cb.rb** to see how to use **lib/cheap\_bits.rb** to create a random number generator which uses that large file. **spec/using\_cheap\_bits\_cheap\_random\_spec.rb** uses **lib/cheap\_bits.rb** to generate random numbers. It should be compared with **spec/cheap\_random\_spec.rb** which uses **Kernel::rand**.
37
+
38
+ Manage a large file used as the source of random numbers
39
+ --------------------------------------------------------
40
+ The large file does not need to be kept if it is something like **jruby-bin-1.6.7.2.zip**. For example, one can keep a reference to its internet location, its sha1 hash and the **CheapRandom seed** file used when creating its **.random** file.
41
+
42
+ Manage the seed file used when generating .random files
43
+ -------------------------------------------------------
44
+ The **CheapRandom seed** file does not need to be kept if it is generated from the **CheapRandom default seed** and some arbitrary file, say a picture of a pet cat. One can simply keep the picture of the pet cat.
45
+
46
+ CheapBits#random(n) in lib/cheap\_bits.rb
47
+ -----------------------------------------
48
+ **random(n)**, where n is an integer greater than zero, behaves like **Kernel::rand(n)**. It is only dependent on the **.random** file, not on how the **.random** file was generated. Note that the **.random** file is treated like a ring buffer of random bits.
49
+
50
+ CheapRandom::cheap\_random3!(is\_randomizing, perm, s)
51
+ ------------------------------------------------------
52
+ **cheap\_random3!** updates **s**, a byte string. **perm** is a **CheapRandom seed**, a byte string of 256 bytes all different. **is\_randomizing** is a boolean which determines whether or **s** is being randomized or un-randomized. **cheap\_random3!** returns another perm / **CheapRandom seed**. **spec/cheap\_random\_spec.rb** is used to test **cheap\_random3!**. **spec/using\_cheap\_bits\_cheap\_random\_spec.rb** is also used to test **cheap\_random3!** and to demonstrate the use of **lib/cheap\_bits.rb**.
53
+
54
+ Properties of the CheapRandom seed
55
+ ----------------------------------
56
+ **CheapRandom seeds** are easy to identify. All the **CheapRandom seeds** are 256 bytes, all different.
57
+
58
+ **CheapRandom seeds** can be used to identify files. **CheapRandom seeds** are a type of hash function result. When **test.zip** is processed into **test.zip.random**, **test.zip.seed** is also produced. **test.zip.seed** is the result of **cheap\_random3!** on **test.zip**.
59
+
60
+ Given the same start seed -- **the.seed**, the result seed files **test.zip.seed** and **test.zip.random.seed** are always identical. (**test.zip.random.seed** is the result of **cheap\_random3!** on **test.zip.random**.)
61
+
62
+ make\_seed.rb
63
+ -------------
64
+ **ruby examples/make\_seed.rb pet\_cat.png** => **random/pet\_cat.png.seed** which should be copied to **random/the.seed**
65
+
66
+ cr.rb
67
+ -----
68
+ **ruby examples/cr.rb test.zip** => **random/test.zip.random** and **random/test.zip.seed**
69
+
70
+ cb.rb
71
+ -----
72
+ **ruby examples/cb.rb** => listing of byte frequencies for **random/test.zip.random**
73
+
74
+ chi\_squared.rb
75
+ ---------------
76
+ **ruby examples/chi\_squared.rb test.zip** => a listing of a chi-squared statistic for **random/test.zip.random** followed by a listing of a chi-squared statistic for the same amount of data generated by **Kernel::rand 256**
77
+
78
+ Test
79
+ ----
80
+ Make sure that the **random** directory exists and contains the files **pet\_cat.png**, **pet\_cat.png.seed**, **the.seed**, **test.zip** and **test.zip.random** as described above. Run **rspec**. The tests are quite comprehensive.
81
+
82
+ Other possible uses of CheapRandom
83
+ ----------------------------------
84
+ There are a number of intriguing possible uses for the **CheapRandom algorithm** and the **CheapRandom seed** properties beyond random number generation.
85
+
@@ -0,0 +1,38 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'cheap_random'
3
+ s.version = '0.9.2'
4
+ s.date = '2012-06-12'
5
+ s.summary = 'pseudo random number generation from arbitrary data'
6
+ s.description = <<-EOT
7
+ **CheapRandom** is a set of tools for pseudo random number generation from arbitrary data. The properties of the **CheapRandom seed** make convenient random number generation possible -- useful for easily repeatable software testing. The **CheapRandom algorithm** is information conserving and generally appears to produce lower chi-squared statistics than **Kernel::rand** i.e. it appears to be more random. The **CheapRandom algorithm**, an original work by Bardi Einarsson, has been in use for 6 years.
8
+ EOT
9
+
10
+ s.authors = ['Bardi Einarsson']
11
+ s.email = ['bardi_e@hotmail.com']
12
+ s.homepage = 'https://github.com/bardibardi/cheap_random'
13
+ s.required_ruby_version = '>= 1.9.2'
14
+ s.add_development_dependency('rspec', '~> 2.2')
15
+ s.files = %w(
16
+ cheap_random.gemspec
17
+ .gitignore
18
+ LICENSE.md
19
+ README.md
20
+ examples/cb.rb
21
+ examples/chi_squared.rb
22
+ examples/cr.rb
23
+ examples/make_seed.rb
24
+ lib/cheap_big_file.rb
25
+ lib/cheap_bits.rb
26
+ lib/cheap_byte_count.rb
27
+ lib/cheap_dependency.rb
28
+ lib/cheap_file.rb
29
+ lib/cheap_random.rb
30
+ lib/cheap_random/version.rb
31
+ lib/cheap_test.rb
32
+ spec/cheap_big_file_spec.rb
33
+ spec/cheap_bits_spec.rb
34
+ spec/cheap_random_spec.rb
35
+ spec/using_cheap_bits_cheap_random_spec.rb
36
+ )
37
+ end
38
+
data/examples/cb.rb ADDED
@@ -0,0 +1,18 @@
1
+ p File.absolute_path(__FILE__)
2
+ CHEAP_DEPENDENCY_ENV_NAME = 'CD'
3
+ load File.expand_path('../lib/cheap_dependency.rb', File.dirname(__FILE__))
4
+ CheapDependency.cd_get('cheap_bits')
5
+
6
+ BASE_DIR = File.expand_path('../random', File.dirname(__FILE__))
7
+ RANDOM_FILE_SOURCE = "test.zip"
8
+ XLAT_EXT = '.random'
9
+ CB = CheapBits.new(9, BASE_DIR, RANDOM_FILE_SOURCE, XLAT_EXT)
10
+
11
+ def l
12
+ load File.absolute_path(__FILE__)
13
+ end
14
+
15
+ if !CheapDependency.cd_test?
16
+ p CB.get_many_random 441241, 256
17
+ end
18
+
@@ -0,0 +1,48 @@
1
+ p File.absolute_path(__FILE__)
2
+ CHEAP_DEPENDENCY_ENV_NAME = 'CD'
3
+ load File.expand_path('../lib/cheap_dependency.rb', File.dirname(__FILE__))
4
+ CheapDependency.cd_get('cheap_byte_count')
5
+
6
+ BASE_DIR = File.expand_path('../random', File.dirname(__FILE__))
7
+ XLAT_EXT = '.random'
8
+
9
+ def byte_count_array(file_name)
10
+ afn = "#{BASE_DIR}/#{file_name}#{XLAT_EXT}"
11
+ CheapByteCount.byte_count_array_from_file afn
12
+ end
13
+
14
+ def chi(a)
15
+ d = a.length
16
+ n = a.reduce(:+)
17
+ p n
18
+ e = (n*1.0)/d
19
+ p e
20
+ c = a.reduce(0) {|acc, r| acc + (r - e)*(r - e)/e}
21
+ [d - 1, c]
22
+ end
23
+
24
+ def rand_array(a)
25
+ d = a.length
26
+ n = a.reduce(:+)
27
+ rand_a = []
28
+ d.times {rand_a << 0}
29
+ n.times {i = rand(d); rand_a[i] += 1}
30
+ rand_a
31
+ end
32
+
33
+ def chi_of_bytes(file_name)
34
+ bca = byte_count_array file_name
35
+ chi bca
36
+ end
37
+
38
+ def l
39
+ load File.absolute_path(__FILE__)
40
+ end
41
+
42
+ if !CheapDependency.cd_test?
43
+ file_name = ARGV[0]
44
+ bca = byte_count_array file_name
45
+ p chi(bca)
46
+ p chi(rand_array(bca))
47
+ end
48
+
data/examples/cr.rb ADDED
@@ -0,0 +1,33 @@
1
+ p File.absolute_path(__FILE__)
2
+ CHEAP_DEPENDENCY_ENV_NAME = 'CD'
3
+ load File.expand_path('../lib/cheap_dependency.rb', File.dirname(__FILE__))
4
+ CheapDependency.cd_get(
5
+ 'cheap_random',
6
+ 'cheap_file',
7
+ 'cheap_big_file'
8
+ )
9
+ if CheapDependency.cd_test?
10
+ CheapDependency.cd_get 'cheap_test'
11
+ end
12
+
13
+ BASE_DIR = File.expand_path('../random', File.dirname(__FILE__))
14
+ # SEED = CheapRandom.cheap_seed!('secret' * 100)
15
+ # CheapFile.to_file "#{BASE_DIR}/the.seed", SEED
16
+ SEED = CheapFile.from_file "#{BASE_DIR}/the.seed"
17
+ XLAT = lambda {|is_do, perm, s| CheapRandom.cheap_random3! is_do, perm, s}
18
+ XLAT_EXT = '.random'
19
+ # CheapFile.new(BASE_DIR, XLAT_EXT, SEED, XLAT).xlat_small_file file_name
20
+ CF = CheapBigFile.new(9, BASE_DIR, XLAT_EXT, SEED, XLAT)
21
+
22
+ def l
23
+ load File.absolute_path(__FILE__)
24
+ end
25
+
26
+ if !CheapDependency.cd_test?
27
+ file_name = ARGV[0]
28
+ generated_seed = CF.xlat_big_file file_name
29
+ seed_file_name = "#{BASE_DIR}/#{file_name}.seed"
30
+ CheapFile.to_file seed_file_name, generated_seed
31
+ p generated_seed.each_byte.inject([], :<<)
32
+ end
33
+
@@ -0,0 +1,23 @@
1
+ p File.absolute_path(__FILE__)
2
+ CHEAP_DEPENDENCY_ENV_NAME = 'CD'
3
+ load File.expand_path('../lib/cheap_dependency.rb', File.dirname(__FILE__))
4
+ CheapDependency.cd_get(
5
+ 'cheap_random',
6
+ 'cheap_file',
7
+ 'cheap_big_file'
8
+ )
9
+
10
+ BASE_DIR = File.expand_path('../random', File.dirname(__FILE__))
11
+ SEED = CheapRandom.reverse_perm
12
+ XLAT = lambda {|is_do, perm, s| CheapRandom.cheap_random3! is_do, perm, s}
13
+ XLAT_EXT = '.random'
14
+ CF = CheapBigFile.new(9, BASE_DIR, XLAT_EXT, SEED, XLAT)
15
+
16
+ if !CheapDependency.cd_test?
17
+ file_name = ARGV[0]
18
+ generated_seed = CF.xlat_big_file file_name, false
19
+ seed_file_name = "#{BASE_DIR}/#{file_name}.seed"
20
+ CheapFile.to_file seed_file_name, generated_seed
21
+ p generated_seed.each_byte.inject([], :<<)
22
+ end
23
+
@@ -0,0 +1,70 @@
1
+ class CheapBigFile < CheapFile
2
+
3
+ def self.readblock(fd_in, half_block_size)
4
+ fd_in.readpartial half_block_size
5
+ rescue EOFError
6
+ nil
7
+ end
8
+
9
+ def self.write(fd_out, s)
10
+ fd_out.write s if fd_out
11
+ end
12
+
13
+ def self.wipe!(s)
14
+ (0...s.length).each {|i| s.setbyte i, 255}
15
+ end
16
+
17
+ def self.eof_sout_from_blocks(half_block_size, s0, s1)
18
+ return [true, nil] unless s0
19
+ return [true, s0] unless s1
20
+ return [true, s0 + s1] if half_block_size > s1.length
21
+ [false, s0]
22
+ end
23
+
24
+ def self.xlat(fd_in, fd_out, half_block_size, is_do, seed, xlat_lambda)
25
+ perm = seed
26
+ s0 = readblock fd_in, half_block_size
27
+ eof = false
28
+ while !eof do
29
+ s1 = readblock fd_in, half_block_size
30
+ eof, sout = eof_sout_from_blocks half_block_size, s0, s1
31
+ if sout
32
+ perm = xlat_lambda.call is_do, perm, sout
33
+ write fd_out, sout
34
+ end
35
+ if sout.length > half_block_size
36
+ wipe! s0
37
+ wipe! s1
38
+ end
39
+ s0 = s1
40
+ end
41
+ perm
42
+ end
43
+
44
+ def initialize(block_size_exponent, base_dir, xlat_ext, seed, xlat_lambda)
45
+ super base_dir, xlat_ext, seed, xlat_lambda
46
+ @block_size = 1 << block_size_exponent
47
+ @half_block_size = @block_size >> 1
48
+ end
49
+
50
+ def xlat_big(fd_in, fd_out, is_do)
51
+ self.class.xlat(fd_in, fd_out, @half_block_size, is_do, @seed, @xlat)
52
+ end
53
+
54
+ def xlat_big_file(fn, should_write = true)
55
+ is_do, afn, new_afn = self.class.is_do_afn_new_afn @base_dir, fn, @xlat_ext
56
+ perm = nil
57
+ File.open(afn, 'rb') do |fd_in|
58
+ if should_write
59
+ File.open(new_afn, 'wb') do |fd_out|
60
+ perm = xlat_big(fd_in, fd_out, is_do)
61
+ end
62
+ else
63
+ perm = xlat_big(fd_in, nil, is_do)
64
+ end
65
+ end
66
+ perm
67
+ end
68
+
69
+ end #CheapBigFile
70
+
data/lib/cheap_bits.rb ADDED
@@ -0,0 +1,127 @@
1
+ class CheapBits
2
+
3
+ def self.readblock(fd_in, block_size)
4
+ fd_in.readpartial block_size
5
+ rescue EOFError
6
+ nil
7
+ end
8
+
9
+ def self.getbit(random_block, bit_offset)
10
+ byte_offset = bit_offset >> 3
11
+ byte_bit_offset = bit_offset - (byte_offset << 3)
12
+ byte = random_block.getbyte(byte_offset)
13
+ 1 & (byte >> (7 - byte_bit_offset))
14
+ end
15
+
16
+ def initialize(block_size_exponent, base_dir, fn, xlat_ext)
17
+ @afn = "#{base_dir}/#{fn}#{xlat_ext}"
18
+ @block_size = 1 << block_size_exponent
19
+ @fd_in = nil
20
+ @current_block = ''
21
+ @bits_total = 0
22
+ @bit_offset = 0
23
+ end
24
+
25
+ def readblock
26
+ open unless @fd_in
27
+ s = self.class.readblock @fd_in, @block_size
28
+ if !s
29
+ rewind
30
+ s = self.class.readblock @fd_in, @block_size
31
+ end
32
+ @current_block = s
33
+ @bits_total = @current_block.length << 3
34
+ @bit_offset = 0
35
+ s
36
+ end
37
+
38
+ def close
39
+ @fd_in.close if @fd_in
40
+ end
41
+
42
+ def open
43
+ @fd_in = File.open(@afn)
44
+ end
45
+
46
+ def rewind
47
+ close
48
+ open
49
+ @current_block = ''
50
+ @bits_total = 0
51
+ @bit_offset = 0
52
+ end
53
+
54
+ def getbit
55
+ readblock if @bit_offset == @bits_total
56
+ bit = self.class.getbit(@current_block, @bit_offset)
57
+ @bit_offset += 1
58
+ bit
59
+ end
60
+
61
+ def getbits_as_number(how_many)
62
+ return nil unless how_many > 0
63
+ first_one_bit_found = false
64
+ bits = 0
65
+ how_many.times do
66
+ bit = getbit
67
+ if first_one_bit_found
68
+ bits = bits << 1
69
+ bits += bit
70
+ else
71
+ if 1 == bit
72
+ bits = 1
73
+ first_one_bit_found = true
74
+ end
75
+ end
76
+ end
77
+ bits
78
+ end
79
+
80
+ def random(n)
81
+ return nil unless n > 0
82
+ return 0 if 1 == n
83
+ bits_needed = 0
84
+ power_of_two = 1
85
+ while n > power_of_two
86
+ bits_needed += 1
87
+ power_of_two = power_of_two << 1
88
+ end
89
+ bits = power_of_two
90
+ while bits >= n
91
+ bits = getbits_as_number bits_needed
92
+ end
93
+ bits
94
+ end
95
+
96
+ def broken_random(n)
97
+ current = n
98
+ acc = 0
99
+ while current > 0
100
+ odd = 1 == 1 & current
101
+ current = current >> 1
102
+ if current > 0
103
+ acc += current if 1 == getbit
104
+ end
105
+ if odd
106
+ if (1 == getbit)
107
+ acc += 1
108
+ else
109
+ current += 1
110
+ end
111
+ end
112
+ end
113
+ acc - 1
114
+ end
115
+
116
+ def get_many_random(how_many, what)
117
+ a = []
118
+ what.times {a << 0}
119
+ how_many.times do
120
+ r = random(what)
121
+ a[r] += 1
122
+ end
123
+ a
124
+ end
125
+
126
+ end #CheapBits
127
+
@@ -0,0 +1,30 @@
1
+ class CheapByteCount
2
+
3
+ def self.readblock(fd_in)
4
+ fd_in.readpartial 4096
5
+ rescue EOFError
6
+ nil
7
+ end
8
+
9
+ def self.byte_count_array_from_file(file_name)
10
+ bca = nil
11
+ File.open(file_name, 'rb') do |fd_in|
12
+ bca = new(fd_in).byte_count_array
13
+ end
14
+ bca
15
+ end
16
+
17
+ attr_reader :byte_count_array
18
+
19
+ def initialize(fd_in)
20
+ @byte_count_array = []
21
+ 256.times {@byte_count_array << 0}
22
+ while s = self.class.readblock(fd_in) do
23
+ s.each_byte do |b|
24
+ @byte_count_array[b] += 1
25
+ end
26
+ end
27
+ end
28
+
29
+ end
30
+
@@ -0,0 +1,40 @@
1
+ module CheapDependency
2
+
3
+ def self.cd_test?
4
+ 'test' == ENV[CHEAP_DEPENDENCY_ENV_NAME]
5
+ end
6
+
7
+ def self.cd_test(load)
8
+ ENV[CHEAP_DEPENDENCY_ENV_NAME] = 'test' if load
9
+ ENV[CHEAP_DEPENDENCY_ENV_NAME] = 'no_test' unless load
10
+ end
11
+
12
+ def self.cd_exists_absolute_fn(relative_fn_base)
13
+ afn = File.expand_path("#{relative_fn_base}.rb", File.dirname(__FILE__))
14
+ [File.exists?(afn), afn]
15
+ end
16
+
17
+ def self.cd_require_relative(relative_fn_base)
18
+ exists, afn = cd_exists_absolute_fn relative_fn_base
19
+ require_relative relative_fn_base if exists
20
+ require relative_fn_base unless exists
21
+ end
22
+
23
+ def self.cd_load_relative(relative_fn_base)
24
+ exists, afn = cd_exists_absolute_fn relative_fn_base
25
+ load afn if exists
26
+ require relative_fn_base unless exists
27
+ end
28
+
29
+ def self.cd_get(*relative_fn_base_array)
30
+ relative_fn_base_array.each do |relative_fn_base|
31
+ if cd_test?
32
+ cd_load_relative relative_fn_base
33
+ else
34
+ cd_require_relative relative_fn_base
35
+ end
36
+ end
37
+ end
38
+
39
+ end
40
+
data/lib/cheap_file.rb ADDED
@@ -0,0 +1,47 @@
1
+ class CheapFile
2
+
3
+ def self.file?(afn)
4
+ File.file? afn
5
+ end
6
+
7
+ def self.from_file(afn)
8
+ f = File.new afn, 'rb'
9
+ s = f.read
10
+ s.force_encoding 'ASCII-8BIT'
11
+ end
12
+
13
+ def self.to_file(afn, s)
14
+ f = File.new afn, "wb"
15
+ f.write s
16
+ f.close
17
+ end
18
+
19
+ def self.is_do_afn_new_afn(base_dir, fn, xlat_ext)
20
+ afn = "#{base_dir}/#{fn}"
21
+ xlat_match = afn =~ Regexp.new("\\#{xlat_ext}$")
22
+ new_afn = afn[0, xlat_match] if xlat_match
23
+ new_afn = afn + xlat_ext unless xlat_match
24
+ [!xlat_match, afn, new_afn]
25
+ end
26
+
27
+ def initialize(base_dir, xlat_ext, seed, xlat_lambda)
28
+ @base_dir = base_dir
29
+ @xlat_ext = xlat_ext
30
+ @seed = seed
31
+ @xlat = xlat_lambda
32
+ end
33
+
34
+ def xlat_small(is_do, s)
35
+ @xlat.call is_do, @seed, s
36
+ end
37
+
38
+ def xlat_small_file(fn, should_write = true)
39
+ is_do, afn, new_afn = self.class.is_do_afn_new_afn @base_dir, fn, @xlat_ext
40
+ s = self.class.from_file afn
41
+ perm = xlat_small is_do, s
42
+ self.class.to_file new_afn, s if should_write
43
+ perm
44
+ end
45
+
46
+ end #CheapFile
47
+
@@ -0,0 +1,175 @@
1
+ module CheapRandom
2
+
3
+ def self.subperm(perm, length)
4
+ result = ' ' * length
5
+ idx = 0
6
+ (0..255).each do |x|
7
+ if perm.getbyte(x) < length
8
+ result.setbyte idx, perm.getbyte(x)
9
+ idx += 1
10
+ end
11
+ end
12
+ result
13
+ end
14
+
15
+ def self.permute(perm, buffer, offset, length)
16
+ disp = 0
17
+ temp = 0
18
+ y = 0
19
+ (0...length).each do |x|
20
+ while perm.getbyte(y) >= length do
21
+ y += 1
22
+ end
23
+ disp = offset + perm.getbyte(y)
24
+ y += 1
25
+ temp = buffer.getbyte disp
26
+ buffer.setbyte disp, buffer.getbyte(offset + x)
27
+ buffer.setbyte(offset + x, temp)
28
+ end
29
+ nil
30
+ end
31
+
32
+ def self.unpermute(perm, buffer, offset, length)
33
+ disp = 0
34
+ temp = 0
35
+ y = 255
36
+ (1..length).each do |x|
37
+ while perm.getbyte(y) >= length do
38
+ y -= 1
39
+ end
40
+ disp = offset + perm.getbyte(y)
41
+ y -= 1
42
+ temp = buffer.getbyte disp
43
+ buffer.setbyte disp, buffer.getbyte(offset + length - x)
44
+ buffer.setbyte(offset + length - x, temp)
45
+ end
46
+ nil
47
+ end
48
+
49
+ # is_randomizing is a boolean
50
+ # cheap_random with is_randomizing true, randomizes
51
+ # cheap_random with is_randomizing false, un-randomizes
52
+ # perm is a read only string of length 256 with each
53
+ # byte represented once
54
+ # perm is cheap_random's seed
55
+ # nextperm is a writeable string of length 256
56
+ # comes in as a copy of perm
57
+ # it is the next seed for use in chain seeding
58
+ # translation is a buffer needed for perm reversed as a substitution transformation
59
+ # buffer is read as unrandomized text
60
+ # and written as randomized text
61
+ # offset is a pointer into the buffer
62
+ # length is from 1 to 256
63
+ # it is the number of bytes to process
64
+ # starting at offset in buffer
65
+ def self.cheap_random7(is_randomizing, perm, nextperm, translation, buffer, offset, length)
66
+ if is_randomizing then
67
+ random_cheap_random(perm, nextperm, buffer, offset, length)
68
+ else
69
+ (0..255).each do |x|
70
+ translation.setbyte perm.getbyte(x), x
71
+ end
72
+ unrandom_cheap_random(perm, nextperm, translation, buffer, offset, length)
73
+ end
74
+ return nil
75
+ end
76
+
77
+ def self.random_cheap_random(perm, nextperm, buffer, offset, length)
78
+ disp = 0
79
+ temp = 0
80
+ y = 0
81
+ (0...length).each do |x|
82
+ disp = offset + x
83
+ y = buffer.getbyte(disp) ^ perm.getbyte(x)
84
+ y = perm.getbyte((y + x + x + x) & 255)
85
+ buffer.setbyte disp, y
86
+ temp = nextperm.getbyte x
87
+ nextperm.setbyte x, nextperm.getbyte(y)
88
+ nextperm.setbyte y, temp
89
+ end
90
+ y = 0
91
+ (0...length).each do |x|
92
+ while perm.getbyte(y) >= length do
93
+ y += 1
94
+ end
95
+ disp = offset + perm.getbyte(y)
96
+ y += 1
97
+ temp = buffer.getbyte disp
98
+ buffer.setbyte disp, buffer.getbyte(offset + x)
99
+ buffer.setbyte(offset + x, temp)
100
+ end
101
+ nil
102
+ end
103
+
104
+ def self.unrandom_cheap_random(perm, nextperm, translation, buffer, offset, length)
105
+ disp = 0
106
+ temp = 0
107
+ y = 255
108
+ (1..length).each do |x|
109
+ while perm.getbyte(y) >= length do
110
+ y -= 1
111
+ end
112
+ disp = offset + perm.getbyte(y)
113
+ y -= 1
114
+ temp = buffer.getbyte disp
115
+ buffer.setbyte disp, buffer.getbyte(offset + length - x)
116
+ buffer.setbyte(offset + length - x, temp)
117
+ end
118
+ y = 0
119
+ (0...length).each do |x|
120
+ disp = offset + x
121
+ y = buffer.getbyte disp
122
+ buffer.setbyte(disp, ((translation.getbyte(y) + 768 - x - x - x) & 255) ^ perm.getbyte(x))
123
+ temp = nextperm.getbyte(x)
124
+ nextperm.setbyte x, nextperm.getbyte(y)
125
+ nextperm.setbyte y, temp
126
+ end
127
+ nil
128
+ end
129
+
130
+ def self.next_block_size(size)
131
+ return 256 if size > 511
132
+ return size if size <= 256
133
+ size - (size >> 1)
134
+ end
135
+
136
+ # length > 0
137
+ def self.cheap_random5!(is_randomizing, startperm, buffer, offset, length)
138
+ nextperm = startperm + 'NEXT'
139
+ perm = (' ' * 256) + 'PERM'
140
+ translation = (' ' * 256) + 'TRAN'
141
+ len = length
142
+ off = offset
143
+ while len > 0 do
144
+ bs = next_block_size len
145
+ (0..255).each do |x|
146
+ perm.setbyte x, nextperm.getbyte(x)
147
+ end
148
+ cheap_random7(is_randomizing, perm, nextperm, translation, buffer, off, bs)
149
+ off += bs
150
+ len -= bs
151
+ end
152
+ nextperm[0..255]
153
+ end
154
+
155
+ def self.reverse_perm
156
+ s = ' ' * 256
157
+ (0..255).each do |x|
158
+ s.setbyte(x, 255 - x)
159
+ end
160
+ s
161
+ end
162
+
163
+ def self.cheap_random3!(is_randomizing, perm, s)
164
+ cheap_random5!(is_randomizing, perm, s, 0, s.length)
165
+ end
166
+
167
+ def self.cheap_seed!(s)
168
+ ip = reverse_perm
169
+ result = cheap_random3!(true, ip, s)
170
+ cheap_random3!(false, ip, s)
171
+ result
172
+ end
173
+
174
+ end # CheapRandom
175
+
@@ -0,0 +1,4 @@
1
+ module CheapRandom
2
+ VERSION = '0.9.1'
3
+ end
4
+
data/lib/cheap_test.rb ADDED
@@ -0,0 +1,52 @@
1
+ module CheapTest
2
+
3
+ def self.random(n)
4
+ rand(n)
5
+ end
6
+
7
+ def self.cheap_perm_check!(perm, s)
8
+ return nil if length > 256
9
+ CheapRandom::permute perm, s, 0, length
10
+ CheapRandom::unpermute perm, s, 0, length
11
+ end
12
+
13
+ def self.random_string(len)
14
+ s = ' ' * len
15
+ (0...len).each do |x|
16
+ s.setbyte x, random(256)
17
+ end
18
+ s
19
+ end
20
+
21
+ def self.identity_perm
22
+ s = ' ' * 256
23
+ (0..255).each do |x|
24
+ s.setbyte x, x
25
+ end
26
+ s
27
+ end
28
+
29
+ def self.random_perm
30
+ s = identity_perm
31
+ i = 256
32
+ (0..255).each do |x|
33
+ temp = s.getbyte x
34
+ y = x + random(i)
35
+ s.setbyte x, s.getbyte(y)
36
+ s.setbyte y, temp
37
+ i -= 1
38
+ end
39
+ s
40
+ end
41
+
42
+ def self.is_reversible?
43
+ s = random_string(random(10000))
44
+ x = s + 'X'
45
+ ip = random_perm
46
+ CheapRandom::cheap_random3!(true, ip, s)
47
+ CheapRandom::cheap_random3!(false, ip, s)
48
+ s == x[0...(s.length)]
49
+ end
50
+
51
+ end #CheapTest
52
+
@@ -0,0 +1,50 @@
1
+ load 'cheap_file.rb'
2
+ load 'cheap_big_file.rb'
3
+ require 'stringio'
4
+
5
+ module CheapTest
6
+
7
+ FAKE_XLAT = lambda do |is_do, perm, s|
8
+ return [s.length] unless perm
9
+ perm << s.length
10
+ end
11
+
12
+ CF = CheapBigFile.new(9, nil, nil, nil, FAKE_XLAT)
13
+
14
+ end
15
+
16
+ describe "CheapBigFile's block handling for CheapRandom" do
17
+ it "should return blocks usable as 256 byte chunks" do
18
+ (257..3000).each do |i|
19
+ fd_in = StringIO.new('x' * i)
20
+ a = CheapTest::CF.xlat_big fd_in, nil, CheapTest::FAKE_XLAT
21
+ len = a.length
22
+ exist = len > 0
23
+ exist.should == true
24
+ total = a.reduce(:+)
25
+ total.should == i
26
+ last_block_big_enough = a[-1] > 255
27
+ last_block_big_enough.should == true
28
+ a[0..-2].each do |size|
29
+ big_enough = size > 255
30
+ big_enough.should == true
31
+ multiple_of_256 = 0 == (size % 256)
32
+ multiple_of_256.should == true
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ describe "CheapBigFile's block handling for CheapRandom" do
39
+ it "should return small blocks less than size 257" do
40
+ (1..256).each do |i|
41
+ fd_in = StringIO.new('x' * i)
42
+ a = CheapTest::CF.xlat_big fd_in, nil, CheapTest::FAKE_XLAT
43
+ len = a.length
44
+ len.should == 1
45
+ same_size = a[-1] == i
46
+ same_size.should == true
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,65 @@
1
+ load 'cheap_file.rb'
2
+ load 'cheap_big_file.rb'
3
+ load 'cheap_bits.rb'
4
+ load 'cheap_byte_count.rb'
5
+
6
+ module CheapTest
7
+
8
+ BASE_DIR ||= File.expand_path('../random', File.dirname(__FILE__))
9
+ RANDOM_FILE_SOURCE ||= "test.zip"
10
+ XLAT_EXT ||= '.random'
11
+ CB ||= CheapBits.new(9, BASE_DIR, RANDOM_FILE_SOURCE, XLAT_EXT)
12
+ FILE_NAME = "#{BASE_DIR}/#{RANDOM_FILE_SOURCE}#{XLAT_EXT}"
13
+
14
+ def self.random(n)
15
+ CB.random n
16
+ end
17
+
18
+ end
19
+
20
+ describe "CheapBits random(1)" do
21
+ it "should return 0" do
22
+ is_zero = 0 == CheapTest.random(1)
23
+ is_zero.should == true
24
+ end
25
+ end
26
+
27
+ describe "CheapBits random" do
28
+ it "should get an in bounds number" do
29
+ inbounds = 256 > CheapTest.random(256)
30
+ inbounds.should == true
31
+ end
32
+ end
33
+
34
+ describe "CheapBits getbit" do
35
+ fake_random_block = ' ' * 8
36
+ (0..7).each {|i| fake_random_block.setbyte(7 - i, 1 << i) }
37
+ it "should get the correct bit" do
38
+ (0..7).each do |i|
39
+ bit = CheapBits.getbit fake_random_block, ((i << 3) + i)
40
+ bit.should == 1
41
+ end
42
+ end
43
+ end
44
+
45
+ describe "CheapBits get_many_random" do
46
+ it "should be plausibly random" do
47
+ a = CheapTest::CB.get_many_random 30000, 3
48
+ a.each do |i|
49
+ plausible = i > 9750
50
+ plausible.should == true
51
+ end
52
+ end
53
+ end
54
+
55
+ describe "CheapBits get_many_random" do
56
+ it "should process all bytes in file" do
57
+ bca = CheapByteCount.byte_count_array_from_file CheapTest::FILE_NAME
58
+ how_many = File.new(CheapTest::FILE_NAME).size
59
+ CheapTest::CB.rewind
60
+ a = CheapTest::CB.get_many_random how_many, 256
61
+ same_byte_count_profile = bca == a
62
+ same_byte_count_profile.should == true
63
+ end
64
+ end
65
+
@@ -0,0 +1,10 @@
1
+ load 'cheap_random.rb'
2
+ load 'cheap_test.rb'
3
+
4
+ describe "CheapRandom randomizer" do
5
+ it "should reversibly randomize arbitrary strings when using arbitrary seed permutations" do
6
+ reversed = CheapTest.is_reversible?
7
+ reversed.should == true
8
+ end
9
+ end
10
+
@@ -0,0 +1,35 @@
1
+ load 'cheap_random.rb'
2
+ load 'cheap_test.rb'
3
+
4
+ load 'cheap_file.rb'
5
+ load 'cheap_big_file.rb'
6
+ load 'cheap_bits.rb'
7
+
8
+ module CheapTest
9
+
10
+ BASE_DIR ||= File.expand_path('../random', File.dirname(__FILE__))
11
+ RANDOM_FILE_SOURCE ||= "test.zip"
12
+ XLAT_EXT ||= '.random'
13
+ CB ||= CheapBits.new(9, BASE_DIR, RANDOM_FILE_SOURCE, XLAT_EXT)
14
+
15
+ def self.random(n)
16
+ CB.random n
17
+ end
18
+
19
+ end
20
+
21
+ describe "CheapTest random" do
22
+ it "should be using the CheapBits RANDOM_FILE_SOURCE" do
23
+ bit = CheapTest.random 2
24
+ is_a_bit = 2 > bit
25
+ is_a_bit.should == true
26
+ end
27
+ end
28
+
29
+ describe "CheapRandom randomizer" do
30
+ it "should reversibly randomize arbitrary strings when using arbitrary seed permutations" do
31
+ reversed = CheapTest.is_reversible?
32
+ reversed.should == true
33
+ end
34
+ end
35
+
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cheap_random
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Bardi Einarsson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-06-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '2.2'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '2.2'
30
+ description: ! ' **CheapRandom** is a set of tools for pseudo random number generation
31
+ from arbitrary data. The properties of the **CheapRandom seed** make convenient
32
+ random number generation possible -- useful for easily repeatable software testing.
33
+ The **CheapRandom algorithm** is information conserving and generally appears to
34
+ produce lower chi-squared statistics than **Kernel::rand** i.e. it appears to be
35
+ more random. The **CheapRandom algorithm**, an original work by Bardi Einarsson,
36
+ has been in use for 6 years.
37
+
38
+ '
39
+ email:
40
+ - bardi_e@hotmail.com
41
+ executables: []
42
+ extensions: []
43
+ extra_rdoc_files: []
44
+ files:
45
+ - cheap_random.gemspec
46
+ - .gitignore
47
+ - LICENSE.md
48
+ - README.md
49
+ - examples/cb.rb
50
+ - examples/chi_squared.rb
51
+ - examples/cr.rb
52
+ - examples/make_seed.rb
53
+ - lib/cheap_big_file.rb
54
+ - lib/cheap_bits.rb
55
+ - lib/cheap_byte_count.rb
56
+ - lib/cheap_dependency.rb
57
+ - lib/cheap_file.rb
58
+ - lib/cheap_random.rb
59
+ - lib/cheap_random/version.rb
60
+ - lib/cheap_test.rb
61
+ - spec/cheap_big_file_spec.rb
62
+ - spec/cheap_bits_spec.rb
63
+ - spec/cheap_random_spec.rb
64
+ - spec/using_cheap_bits_cheap_random_spec.rb
65
+ homepage: https://github.com/bardibardi/cheap_random
66
+ licenses: []
67
+ post_install_message:
68
+ rdoc_options: []
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: 1.9.2
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubyforge_project:
85
+ rubygems_version: 1.8.23
86
+ signing_key:
87
+ specification_version: 3
88
+ summary: pseudo random number generation from arbitrary data
89
+ test_files: []