seq 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENCE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2011 Joshua Hawxwell
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,95 @@
1
+ # seq
2
+
3
+ Defines a Seq class, which cycles over elements in an array.
4
+
5
+ ```ruby
6
+ s = Seq.new([1,2])
7
+ s.next #=> 1
8
+ s.next #=> 2
9
+ s.next #=> 1
10
+ # and so on forever
11
+ ```
12
+
13
+ They can be constrained to only return `n` items,
14
+
15
+ ```ruby
16
+ s = Seq.new([1,2,3], 2)
17
+ s.next #=> 1
18
+ s.next #=> 2
19
+ s.next #=> nil
20
+ s.next #=> nil
21
+ ```
22
+
23
+ and can start from an offset.
24
+
25
+ ```ruby
26
+ s = Seq.new([1,2,3], 2, 1)
27
+ s.next #=> 2
28
+ s.next #=> 3
29
+ s.next #=> nil
30
+ s.next #=> nil
31
+ ```
32
+
33
+ You can also provide a different default (as opposed to `nil`).
34
+
35
+ ```ruby
36
+ list = %w(a b c d)
37
+ s = Seq.new(list, 3, 0, list.last)
38
+ s.next #=> a
39
+ s.next #=> b
40
+ s.next #=> c
41
+ s.next #=> d
42
+ s.next #=> d
43
+ ```
44
+
45
+ Seq has `#each` defined and includes the `Enumerable` module so the usual stuff applies.
46
+
47
+ ```ruby
48
+ s = Seq.new([1,2,3,4], 10)
49
+ s.map {|i| i * 2 } #=> [2, 4, 6, 8, 2, 4, 6, 8, 2, 4]
50
+ s.next #=> nil
51
+
52
+ # Standard methods will increment the number of items that have been returned meaning
53
+ # you have to call #reset
54
+ s.reset
55
+ s.next #=> 1
56
+
57
+ # You can use special ! versions to avoid incrementing the number of cycles, but note
58
+ # this will start and finish based on the current index and items returned. So here
59
+ # we are on the second item in the original list, ie. "2".
60
+
61
+ s.map! {|i| i * 2 } #=> [4, 6, 8, 2, 4, 6, 8, 2, 4]
62
+
63
+ # Note this only returned 9 items as we had already called #next, and it started on 2
64
+ # which was mapped to 4.
65
+
66
+ s.reset
67
+ s.entries #=> [1, 2, 3, 4, 1, 2, 3, 4, 1, 2]
68
+ s.reject! {|i| i < 3 } #=> [3, 4, 3, 4]
69
+
70
+ s.take(4) #=> [1, 2, 3, 4]
71
+ s.entries #=> [1, 2, 3, 4, 1, 2]
72
+ s.reject! {|i| i < 3 } #=> [3, 4]
73
+ ```
74
+
75
+ ## Seq::Random
76
+
77
+ A subclass of Seq which returns random elements.
78
+
79
+ ```ruby
80
+ r = Seq::Random.new([1,2,3], 5)
81
+ r.entries #=> [2, 1, 1, 3, 2]
82
+ r.entries #=> [1, 3, 2, 3, 3]
83
+ ```
84
+
85
+ ## Seq::Lazy
86
+
87
+ Lazily evaluates a block using starting list given.
88
+
89
+ ```ruby
90
+ fibs = Seq::Lazy.new([1,1]) {|list| list[-1] + list[-2] }
91
+ fibs.take(10) #=> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
92
+ fibs.take(20) #=> [89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040]
93
+ fibs.next! #=> 832040
94
+ fibs.reset
95
+ ```
@@ -0,0 +1,10 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new(:test) do |t|
4
+ t.libs << 'lib' << 'test'
5
+ t.pattern = 'test/**/*_test.rb'
6
+ t.verbose = true
7
+ end
8
+
9
+ task :default => :test
10
+
@@ -0,0 +1,137 @@
1
+ unless defined?(Float::INFINITY)
2
+ # Define Float::INFINITY if not previously defined
3
+ Float::INFINITY = 1.0/0
4
+ end
5
+
6
+ # A Seq will cycle over a list returning a certain number of items.
7
+ #
8
+ # @example With number of items to return
9
+ #
10
+ # s = Seq.new([1, 2], 4)
11
+ # s.next #=> 1
12
+ # s.next #=> 2
13
+ # s.next #=> 1
14
+ # s.next #=> 2
15
+ # s.next #=> nil
16
+ #
17
+ # @example With an offset
18
+ #
19
+ # s = Seq.new([1, 2, 3, 4, 5], 5, 3)
20
+ # s.next #=> 4
21
+ # s.next #=> 5
22
+ # s.next #=> 1
23
+ # # etc
24
+ #
25
+ # @example With default value
26
+ #
27
+ # s = Seq.new([1, 2], 2, 0, 2)
28
+ # s.next #=> 1
29
+ # s.next #=> 2
30
+ # s.next #=> 2
31
+ # s.next #=> 2
32
+ #
33
+ class Seq
34
+
35
+ # Creates a new instance of Seq.
36
+ #
37
+ # @param list [Array] List of values to cycle over
38
+ # @param items [Integer] Number of values to return
39
+ # @param offset [Integer] Index of item in +list+ to start at
40
+ # @param default [Object] Value to return when finished cycling
41
+ def initialize(list=[], items=Float::INFINITY, offset=0, default=nil)
42
+ @list = list
43
+ @items = items
44
+ @offset = offset
45
+ @default = default
46
+
47
+ self.reset
48
+ end
49
+
50
+ # Resets the Seqs position to the same as when initialized.
51
+ def reset
52
+ @cycles = 0
53
+ @index = @offset
54
+ end
55
+
56
+ # @return Until ended it returns the next item from the list, when ended it returns
57
+ # the default item.
58
+ def next
59
+ if ended?
60
+ @default
61
+ else
62
+ @list[@index].tap { inc }
63
+ end
64
+ end
65
+
66
+ # Increment the list index, the number of cycles completed and if at the end of
67
+ # the list returns to the first item.
68
+ #
69
+ # @return [Integer] Number of items that have been returned.
70
+ def inc
71
+ if @index+1 == @list.size
72
+ @index = 0
73
+ else
74
+ @index += 1
75
+ end
76
+ @cycles += 1
77
+ end
78
+
79
+ # Iterates over each item as returned by #next until #ended?.
80
+ def each
81
+ until ended?
82
+ yield self.next
83
+ end
84
+ end
85
+
86
+ include Enumerable
87
+
88
+ # @return [Array] The items that would be returned by repeated calls to #next
89
+ # until #ended?.
90
+ # @raise [RangeError] If #infinite?, otherwise it creates an infinite loop!
91
+ def entries
92
+ raise RangeError if infinite?
93
+
94
+ i, c = @index, @cycles
95
+ r = super
96
+ @index, @cycles = i, c
97
+ r
98
+ end
99
+
100
+ # @return [Array] Returns the original list
101
+ def to_a
102
+ @list
103
+ end
104
+
105
+ # @return Whether the Seq returns infinite items.
106
+ def infinite?
107
+ @items == Float::INFINITY
108
+ end
109
+
110
+ # @return Whether the Seq has returned enough items.
111
+ def ended?
112
+ @cycles >= @items
113
+ end
114
+
115
+ # Any method called on a Seq with ending with !, will be caught by method
116
+ # missing, it will then attempt to call the non ! version but will not
117
+ # alter the index or number of cycles completed.
118
+ def method_missing(sym, *args, &block)
119
+ if sym.to_s[-1] == "!" &&
120
+ self.respond_to?(sym.to_s[0..-2].to_sym) &&
121
+ ! [:infinite?, :ended?, :to_a, :entries, :inc, :reset].include?(sym.to_s[0..-2].to_sym)
122
+
123
+ begin
124
+ i, c = @index, @cycles
125
+ self.send(sym.to_s[0..-2].to_sym, *args, &block)
126
+ ensure
127
+ @index, @cycles = i, c
128
+ end
129
+ else
130
+ super
131
+ end
132
+ end
133
+ end
134
+
135
+ require 'seq/lazy'
136
+ require 'seq/random'
137
+ require 'seq/version'
@@ -0,0 +1,73 @@
1
+ require 'seq'
2
+
3
+ class Seq
4
+
5
+ # Lazy seqs evaluate a block to determine the next value when needed,
6
+ # this value is then saved.
7
+ #
8
+ # @example
9
+ #
10
+ # s = Seq::Lazy.new([1]) { 1 }
11
+ # s.take(3) #=> [1, 1, 1]
12
+ #
13
+ # ns = Seq::Lazy.new([1]) {|l| l[-1] + 1 }
14
+ # ns.take(5) #=> [1, 2, 3, 4, 5]
15
+ #
16
+ # fibs = Seq::Lazy.new([1, 1]) {|l| l[-1] + l[-2] }
17
+ # fibs.take(7) #=> [1, 1, 2, 3, 5, 8, 13]
18
+ # fibs.take!(7) #=> [21, 34, 55, 89, 144, 233, 377]
19
+ # fibs.take!(2) #=> [21, 34]
20
+ #
21
+ class Lazy < Seq
22
+
23
+ # Creates a new Lazy seq instance.
24
+ #
25
+ # @param list [Array] Starting values
26
+ # @param items [Integer] Number of values to return
27
+ # @param offset [Integer] Index of item to start at
28
+ # @param default [Object] Value to return when finished cycling
29
+ #
30
+ # @yield [list] Block to be called which returns the next value in the list
31
+ # @yieldparam list [Array] The list calculated up to the current point
32
+ def initialize(list=[], items=Float::INFINITY, offset=0, default=nil, &block)
33
+ @list = list
34
+ @items = items
35
+ @block = block
36
+ @offset = offset
37
+ @default = default
38
+
39
+ self.reset
40
+ end
41
+
42
+ # Resets the state of the lazy seq. It also calculates any values
43
+ # necessary to get to the offset.
44
+ def reset
45
+ @index = @list.size
46
+ @cycles = 0
47
+
48
+ until @list.size >= @offset
49
+ @list[@index] = @block.call(@list[0..@index-1])
50
+ @index += 1
51
+ end
52
+
53
+ @index = @offset
54
+ end
55
+
56
+ # @return [Object]
57
+ # Until ended it returns the next item from +list+ if it exists or calculates
58
+ # it then stores it in +list+, if ended it returns the default value.
59
+ def next
60
+ if ended?
61
+ @default
62
+ else
63
+ @index += 1
64
+ if @index-1 < @list.size
65
+ @list[@index-1]
66
+ else
67
+ @list[@index-1] = @block.call(@list)
68
+ end
69
+ end
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,34 @@
1
+ require 'seq'
2
+
3
+ class Seq
4
+
5
+ # A Random seq chooses items from the list randomly.
6
+ #
7
+ # @example
8
+ #
9
+ # s = Seq::Random.new([1,2,3,4,5])
10
+ # s.take(6) #=> [1, 4, 5, 3, 1, 2]
11
+ #
12
+ class Random < Seq
13
+
14
+ # Creates a new Random seq instance.
15
+ #
16
+ # @param list [Array] List of values to cycle over
17
+ # @param items [Integer] Number of values to return
18
+ # @param default [Object] Value to return when finished cycling
19
+ def initialize(list=[], items=Float::INFINITY, default=nil)
20
+ super(list, items, 0, default)
21
+ end
22
+
23
+ # @return Until ended it returns a randomly selected item from the list, when
24
+ # ended it returns the default value.
25
+ def next
26
+ if ended?
27
+ @default
28
+ else
29
+ @list[rand(@list.size).to_i].tap { inc }
30
+ end
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,4 @@
1
+ class Seq
2
+ # Current version
3
+ VERSION = "0.1.0"
4
+ end
@@ -0,0 +1,8 @@
1
+ $: << File.dirname(__FILE__)
2
+ $: << File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'seq'
5
+
6
+ require 'rubygems'
7
+ require 'minitest/unit'
8
+ require 'minitest/autorun'
@@ -0,0 +1,25 @@
1
+ $: << File.dirname(__FILE__) + '/..'
2
+ require 'helper'
3
+
4
+ class LazySeqTest < MiniTest::Unit::TestCase
5
+
6
+ def setup
7
+ @seq = Seq::Lazy.new([1, 1]) {|l| l[-1] + l[-2] }
8
+ end
9
+
10
+ def test_defines_next
11
+ assert_equal 1, @seq.next
12
+ assert_equal 1, @seq.next
13
+ assert_equal 2, @seq.next
14
+ assert_equal 3, @seq.next
15
+ assert_equal 5, @seq.next
16
+ assert_equal 8, @seq.next
17
+ assert_equal 13, @seq.next
18
+ end
19
+
20
+ def test_offset_works
21
+ s = Seq::Lazy.new([1], 1.0/0, 5) {|l| l.last + 1 }
22
+ assert_equal 6, s.next
23
+ end
24
+
25
+ end
@@ -0,0 +1,24 @@
1
+ $: << File.dirname(__FILE__) + '/..'
2
+ require 'helper'
3
+
4
+ class RandomSeqTest < MiniTest::Unit::TestCase
5
+
6
+ def setup
7
+ @seq = Seq::Random.new([1, 2, 3], 6)
8
+ end
9
+
10
+ def test_defines_next
11
+ assert_includes [1,2,3], @seq.next
12
+ assert_includes [1,2,3], @seq.next
13
+ assert_includes [1,2,3], @seq.next
14
+ assert_includes [1,2,3], @seq.next
15
+ assert_includes [1,2,3], @seq.next
16
+ assert_includes [1,2,3], @seq.next
17
+ assert_equal nil, @seq.next
18
+ end
19
+
20
+ def test_cycles_correct_times
21
+ assert_equal 6, @seq.entries.size
22
+ end
23
+
24
+ end
@@ -0,0 +1,65 @@
1
+ $: << File.dirname(__FILE__)
2
+ require 'helper'
3
+
4
+ class SeqTest < MiniTest::Unit::TestCase
5
+
6
+ def setup
7
+ @empty = Seq.new
8
+ @seq = Seq.new([1, 2, 3, 4], 7, 1, "nope")
9
+ end
10
+
11
+ def test_can_become_array
12
+ assert_equal [], @empty.to_a
13
+ assert_equal [1, 2, 3, 4], @seq.to_a
14
+ end
15
+
16
+ def test_can_become_expanded_array
17
+ assert_raises RangeError do
18
+ @empty.entries
19
+ end
20
+
21
+ assert_equal [2, 3, 4, 1, 2, 3, 4], @seq.entries
22
+ end
23
+
24
+ def test_can_iterate
25
+ @empty.each_with_index do |a,i|
26
+ assert_equal a, nil
27
+ break if i > 3
28
+ end
29
+
30
+ @seq.each_with_index do |a,i|
31
+ assert_equal a, case i
32
+ when 0 then 2
33
+ when 1 then 3
34
+ when 2 then 4
35
+ when 3 then 1
36
+ when 4 then 2
37
+ when 5 then 3
38
+ when 6 then 4
39
+ end
40
+ end
41
+ end
42
+
43
+ def test_gets_next_item
44
+ assert_equal nil, @empty.next
45
+ assert_equal nil, @empty.next
46
+
47
+ assert_equal 2, @seq.next
48
+ assert_equal 3, @seq.next
49
+ assert_equal 4, @seq.next
50
+ assert_equal 1, @seq.next
51
+ assert_equal 2, @seq.next
52
+ assert_equal 3, @seq.next
53
+ assert_equal 4, @seq.next
54
+ assert_equal "nope", @seq.next
55
+ assert_equal "nope", @seq.next
56
+ end
57
+
58
+ def test_can_be_reset
59
+ 10.times { @seq.next }
60
+ assert_equal "nope", @seq.next
61
+ @seq.reset
62
+ assert_equal 2, @seq.next
63
+ end
64
+
65
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: seq
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Joshua Hawxwell
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-07-22 00:00:00.000000000 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: minitest
17
+ requirement: &2156880320 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 2.3.1
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: *2156880320
26
+ description: ! " A Seq is created with an array, and optionally a number of elements\n
27
+ \ to return, an offset to start at and a default item to return when\n ended.
28
+ Call #next to return the next item.\n \n A Seq::Random will return randomly
29
+ selected elements from the array.\n A Seq::Lazy will lazily evaluate a block
30
+ to get the next element.\n"
31
+ email: m@hawx.me
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - README.md
37
+ - Rakefile
38
+ - LICENCE
39
+ - lib/seq/lazy.rb
40
+ - lib/seq/random.rb
41
+ - lib/seq/version.rb
42
+ - lib/seq.rb
43
+ - test/helper.rb
44
+ - test/seq/lazy_test.rb
45
+ - test/seq/random_test.rb
46
+ - test/seq_test.rb
47
+ has_rdoc: true
48
+ homepage: http://github.com/hawx/seq
49
+ licenses: []
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ! '>='
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubyforge_project:
68
+ rubygems_version: 1.6.2
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: Seqs cycle over elements of an array.
72
+ test_files:
73
+ - test/helper.rb
74
+ - test/seq/lazy_test.rb
75
+ - test/seq/random_test.rb
76
+ - test/seq_test.rb