What about a 'series' type?

P

Peter Marsh

I'm sure everyone is fimilar with ranges:

(1..10).to_a = [1,2,3,4,5,6,7,8,9,10] (from manual)

But this isn't any good if you want a arithmetic [3,5,7,9] or geometric
[2,6,18,54] series. So I suggest this:


arithmetic_series = Arith.new(First_Term,Common_Difference)

aritmetic_series[9] will then generate the 10th term of the sequence (0
being the first to be consistent) and arithmetic_series[0..9] will
return an array with the first to tenth terms. The same goes for a
geometric series.

gemometric_series = Geometric.new(First_Term,Common_Ratio)


Now, there may be other ways to achieve this, but I think this way is
nicer.
 
R

Robert Dober

Honestly I do not believe that the core is the place to put such
things, furthermore I believe it is too specific a feature, a more
general approach might have better chances to be fit for the core; Yet
I do not think what follows is fit for the core either, but maybe you
find it interesting or helpful:

class Lazy
def initialize init, op, *args
@init = init
@op = op
@args = args.dup
end

def upto value
return [] if value < 1
(2..value).inject([@init]){ |acc,| acc << acc.last.send( @op, *@args ) }
end
end # class Lazy

505/6 > irb -r lazy.rb
irb(main):001:0> l = Lazy.new 1, :+, 2
=> #<Lazy:0xb7ddfa60 @args=[2], @init=1, @op=:+>
irb(main):002:0> l.upto 5
=> [1, 3, 5, 7, 9]
irb(main):003:0> m = Lazy.new 2, :*, 3
=> #<Lazy:0xb7dd4908 @args=[3], @init=2, @op=:*>
irb(main):004:0> m.upto 4
=> [2, 6, 18, 54]
irb(main):005:0>

Cheers
Robert

P.S. Implementations without #inject are theoretically possible ;)
R.
 
S

SonOfLilit

Actually, the syntax:

a = Series.new(8) {|i| 5*i**3}

a.collect{|i| i + 1}.select{|i| i % 2 == 0} # i.e. a is an enumerable object

is quite cool and might be useful in some cases. The only problem I'm
trying to solve is that of providing syntax for persistence (e.g. try
to implement a series of Fibonacci numbers with the iterative
algorithm using this Series syntax). Perhaps an optional hash argument
that is used to give initial values to instance variables?

With persistence, this could be a very useful idiom in many cases, and
maybe even deserves to be stdlib.

Aur

But this isn't any good if you want a arithmetic [3,5,7,9] or geometric
[2,6,18,54] series. So I suggest this:

arithmetic_series = Arith.new(First_Term,Common_Difference)

aritmetic_series[9] will then generate the 10th term of the sequence (0
being the first to be consistent) and arithmetic_series[0..9] will
return an array with the first to tenth terms. The same goes for a
geometric series.

gemometric_series = Geometric.new(First_Term,Common_Ratio)

Now, there may be other ways to achieve this, but I think this way is
nicer.

These seem like special cases of Series.new([Start Terms], Proc).

(Consider Fibbonacci sequences, for instance, or Pascal's Triangle.)

-s
 
A

ara.t.howard

I'm sure everyone is fimilar with ranges:

(1..10).to_a = [1,2,3,4,5,6,7,8,9,10] (from manual)

But this isn't any good if you want a arithmetic [3,5,7,9] or
geometric
[2,6,18,54] series. So I suggest this:


arithmetic_series = Arith.new(First_Term,Common_Difference)

aritmetic_series[9] will then generate the 10th term of the
sequence (0
being the first to be consistent) and arithmetic_series[0..9] will
return an array with the first to tenth terms. The same goes for a
geometric series.

gemometric_series = Geometric.new(First_Term,Common_Ratio)


Now, there may be other ways to achieve this, but I think this way is
nicer.

it can be done with lambdas in ruby on a case by case basis:

cfp:~ > cat a.rb
arithmetic = lambda do |i|
if i.respond_to? :map
i.map{|j| arithmetic[j]}
else
i >= 1 ? (2 + arithmetic[i-1]) : 1
end
end

p arithmetic[0]
p arithmetic[1]
p arithmetic[4]

p arithmetic[0..9]


cfp:~ > ruby a.rb
1
3
9
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

kind regards.

-a
 
P

Peter Marsh

SonOfLilit said:
Actually, the syntax:

a = Series.new(8) {|i| 5*i**3}

a.collect{|i| i + 1}.select{|i| i % 2 == 0} # i.e. a is an enumerable
object

is quite cool and might be useful in some cases. The only problem I'm
trying to solve is that of providing syntax for persistence (e.g. try
to implement a series of Fibonacci numbers with the iterative
algorithm using this Series syntax). Perhaps an optional hash argument
that is used to give initial values to instance variables?

With persistence, this could be a very useful idiom in many cases, and
maybe even deserves to be stdlib.

Aur

The two examples I gave are fairly specific, I didn't really think about
that. However, I still think a 'series' type would be useful, even if
the implimentation is different to my example.

Taking prime numbers as an example using the current 'prime' class you
have to use:

require 'mathn'

primes = Prime.new

etc

With a seires type you could do something like this:

primes = Series.new(Starting_value,Some_block_to_define_serires)

primes[0] = 2
primes[0..2]

Giving definate advantages, I feel.
 
P

Peter Marsh

primes = Series.new(Starting_value,Some_block_to_define_serires)

Just wanted to be a bit clearer about this, the second argument is a
block which can generate nth term in a series. This would probally take
a while for primes, but if it were recursive then it would be easier...
 
R

Robert Klemme

Just wanted to be a bit clearer about this, the second argument is a
block which can generate nth term in a series. This would probally take
a while for primes, but if it were recursive then it would be easier...

IMHO for a series it would be more natural to let the block calculate
a[n+1] from a[n] wouldn't it? Of course, for Fibonacci this would only
work if you allow for multiple arguments.

Something like

#!ruby
class Serial
include Enumerable

def initialize(*init, &f)
@init = init
@f = f
end

def each(&b)
a = b.arity
current = @init
loop do
b[*current[0 ... a]]
current = Array(@f[*current])
end
self
end
end

s1 = Serial.new 0 do |x| x+1 end
s1.each {|x| p x; break if x > 10}

puts

s2 = Serial.new 0,1 do |a,b| [b,a+b] end
s2.each {|x| p x; break if x > 40}


Kind regards

robert
 
R

Robert Dober

Just wanted to be a bit clearer about this, the second argument is a
block which can generate nth term in a series. This would probally take
a while for primes, but if it were recursive then it would be easier...

IMHO for a series it would be more natural to let the block calculate
a[n+1] from a[n] wouldn't it? Of course, for Fibonacci this would only
work if you allow for multiple arguments.

Something like

#!ruby
class Serial
include Enumerable

def initialize(*init, &f)
@init = init
@f = f
end

def each(&b)
a = b.arity
current = @init
loop do
b[*current[0 ... a]]
current = Array(@f[*current])
end
self
end
end

s1 = Serial.new 0 do |x| x+1 end
s1.each {|x| p x; break if x > 10}

puts

s2 = Serial.new 0,1 do |a,b| [b,a+b] end
s2.each {|x| p x; break if x > 40}

Robert I hope you do not mind my fantasy about/over/at??? your theme.

class Serial
include Enumerable

def initialize(*init, &f)
@init = init
@f = f
@arity = f.arity
end

def get_some( some = nil, &b)
@current = @init.dup
if some && b.nil? then
(0...some).inject([]) do |acc,|
compute_next
acc << @current.first
end
else
a = b.arity
loop do
break if some && ( some -= 1 ) < 0
b[*@current.first( a )]
compute_next
end
self
end
end

private
def compute_next
@current += Array( @f[*@current] )
@current = @current.last @arity
end
end

s1 = Serial.new 0 do |x| x+1 end
s1.get_some {|x| p x; break if x > 10}
puts "-"*42
puts s1.get_some(6)

puts "-"*42
f = Serial.new 1, 1 do |x, y| x + y end
f.get_some(6){ |x,| puts x }

puts "-"*42
a = Serial.new 1, 1, 1 do |x, y, z| x + y + z end
puts a.get_some(6)

Cheers
Robert
 
F

fREW

primes = Series.new(Starting_value,Some_block_to_define_serires)

Just wanted to be a bit clearer about this, the second argument is a
block which can generate nth term in a series. This would probally take
a while for primes, but if it were recursive then it would be easier...

IMHO for a series it would be more natural to let the block calculate
a[n+1] from a[n] wouldn't it? Of course, for Fibonacci this would only
work if you allow for multiple arguments.

Something like

#!ruby
class Serial
include Enumerable

def initialize(*init, &f)
@init = init
@f = f
end

def each(&b)
a = b.arity
current = @init
loop do
b[*current[0 ... a]]
current = Array(@f[*current])
end
self
end
end

s1 = Serial.new 0 do |x| x+1 end
s1.each {|x| p x; break if x > 10}

puts

s2 = Serial.new 0,1 do |a,b| [b,a+b] end
s2.each {|x| p x; break if x > 40}

Robert I hope you do not mind my fantasy about/over/at??? your theme.

class Serial
include Enumerable

def initialize(*init, &f)
@init = init
@f = f
@arity = f.arity
end

def get_some( some = nil, &b)
@current = @init.dup
if some && b.nil? then
(0...some).inject([]) do |acc,|
compute_next
acc << @current.first
end
else
a = b.arity
loop do
break if some && ( some -= 1 ) < 0
b[*@current.first( a )]
compute_next
end
self
end
end

private
def compute_next
@current += Array( @f[*@current] )
@current = @current.last @arity
end
end

s1 = Serial.new 0 do |x| x+1 end
s1.get_some {|x| p x; break if x > 10}
puts "-"*42
puts s1.get_some(6)

puts "-"*42
f = Serial.new 1, 1 do |x, y| x + y end
f.get_some(6){ |x,| puts x }

puts "-"*42
a = Serial.new 1, 1, 1 do |x, y, z| x + y + z end
puts a.get_some(6)

Cheers
Robert

Isn't ruby cool?
 
R

Robert Klemme

Robert I hope you do not mind my fantasy about/over/at??? your theme.

Not at all. I had thought about the limit myself but did not want to
bother implementing it. Two remarks about your code:

You include Enumerable but do not provide #each which is required.

You use instance variables for the iteration which is a bad thing
because this needless restricts usability (namely in the light of
multiple threads). Array and other's also do not store the iteration
state in instance variables but in local variables in method #each. You
can try it with something like this which would not work if instance
variables would be used for storing iteration state

a=(1..10).to_a
2.times {|i| Thread.new(i) {|j| a.each {|x| puts "[#{j}-#{x}]"; sleep 0.5}}}

Kind regards

robert
 
R

Robert Dober

Not at all. I had thought about the limit myself but did not want to
bother implementing it. Two remarks about your code:

You include Enumerable but do not provide #each which is required.
Sure, bad error; that was a leftover of your code, I do not really
want to include it.
You use instance variables for the iteration which is a bad thing
because this needless restricts usability (namely in the light of
multiple threads).
I am not sure I understand this point, but we can get rid of that by
expanding compute_next, but this will make the code much less readable
:(
The whole beast is not thread safe at all I guess, even without the
instance var.

It would be a nice challenge to make it thread safe, I guess we would
need to cache values and synchronize the computation part, sounds
*very* expansive, time and memory wise; probably not worth it .
Array and other's also do not store the iteration
state in instance variables but in local variables in method #each.
Would that not be for performance reasons?
You
can try it with something like this which would not work if instance
variables would be used for storing iteration state

a=(1..10).to_a
2.times {|i| Thread.new(i) {|j| a.each {|x| puts "[#{j}-#{x}]"; sleep 0.5}}}
I guess I do not know enough to understand this :(
How is concurrent access to the method not a problem with local variables?
However it would be sufficient to synchronize the access to the method
only and not the access to the ivar. Is that what you are worried
about?

Cheers
Robert
 
R

Robert Klemme

Sure, bad error; that was a leftover of your code, I do not really
want to include it.
I am not sure I understand this point, but we can get rid of that by
expanding compute_next, but this will make the code much less readable
:(

You can as well add parameters and return values to compute_next.
The whole beast is not thread safe at all I guess, even without the
instance var.

Oh, it's perfectly thread safe if you change the use of instance variables.
It would be a nice challenge to make it thread safe, I guess we would
need to cache values and synchronize the computation part, sounds
*very* expansive, time and memory wise; probably not worth it .

Caching is only needed if you want to make it faster or more efficient.
But for a general implementation I would not do it as it can have all
sorts of unwanted side effects.
Would that not be for performance reasons?

That's another advantage.
You
can try it with something like this which would not work if instance
variables would be used for storing iteration state

a=(1..10).to_a
2.times {|i| Thread.new(i) {|j| a.each {|x| puts "[#{j}-#{x}]"; sleep
0.5}}}
I guess I do not know enough to understand this :(
How is concurrent access to the method not a problem with local variables?

Because then there is iteration state per method invocation. Compare
these two variants of #each:

class Foo
# a must be an Array
def initialize(a) @a=a.dup end

# thread safe
def each_1
for i in (e-mail address removed)
yield @a
end
self
end

# not thread safe
def each_2
for @i in (e-mail address removed)
yield @a[@i]
end
self
end
end

Now, think about what happens if two threads invoke #each_1 and #each_2.
However it would be sufficient to synchronize the access to the method
only and not the access to the ivar.

That would work but it would limit usability - and there is no need to
do that.

Kind regards

robert
 
R

Robert Dober

First of all thx for your time but I am not a quick learner :(

Obviously I am (or rather was )missing something very basic and if I
understand correctly that very basic is that method_invocations are on
copies of the local data, in each thread, is this *really* true? Well
you already showed with an example.

Hopefully I can put this into work tonight, gotta go back to work
here, hopefully this weeks Ruby Quiz will be boooring, please James ;)

Robert
 
R

Robert Klemme

First of all thx for your time but I am not a quick learner :(

Obviously I am (or rather was )missing something very basic and if I
understand correctly that very basic is that method_invocations are on
copies of the local data, in each thread, is this *really* true? Well
you already showed with an example.

Even more fine granular: there is one copy of local variables /per
invocation/ of a method. You can play a bit with this to see the effect:

def recurse(i = 5)
s = "#{Thread.current.inspect} #{i} "
print s, "enter\n"
recurse(i - 1) if i > 0
print s, "leave\n"
end

If you then do something like

2.times { Thread.new { recurse } }

You'll notice how values for "enter" decrease nicely and increase for
"leave" per thread. Internally Ruby has a stack that stores all local
variables (including method arguments) and every time a method is
invoked a new stack frame is put on the stack which stores all those
values. This is basically how most popular programming languages work.
Hopefully I can put this into work tonight, gotta go back to work
here, hopefully this weeks Ruby Quiz will be boooring, please James ;)

:)

Kind regards

robert
 
R

Robert Dober

That's a dialog, right, I wonder if OP lost interest or did I just
hitchhike another thread?

let us see what I came up with under your tutorship.

require 'test/unit'
class Serial

def initialize(*init, &f)
@init = init
@f = f
@arity = f.arity
end

def get_some( some = nil, &b)
current = @init.dup
if some && b.nil? then
r = @init.dup
(some - r.size).times do
current = compute_next( current )
r << current.last
end
r
else
a = b.arity
loop do
break if some && ( some -= 1 ) < 0
b[ *current.first( a ) ]
current = compute_next( current )
end
self
end
end

private
def compute_next current
(current + Array( @f[*current] ) ).last @arity
end
end
if __FILE__ == $0 then

class Testee < Test::Unit::TestCase
def test_1
s1 = Serial.new 0 do |x| x+1 end
assert_equal [*0..3], s1.get_some( 4 )
a = []
s1.get_some{ |x| a << x; break if x > 10 }
assert_equal [*0..11], a
end # def test_1

def test_2
f = Serial.new 1, 1 do |x, y| x + y end
assert_equal [1,1,2,3,5,8], f.get_some(6)
a = []
f.get_some{ |x| a << x; break if x > 10 }
assert_equal [1,1,2,3,5,8,13], a
a = []
f.get_some(4){ |x| a.unshift x }
assert_equal [3,2,1,1], a
end # def test_2


def test_3
f = Serial.new 0, 0, 1 do |a, b, c| 3*a + 2*b + c end
assert_equal [0,0,1,1,3,8,17,42,100], f.get_some(9)
end # def test_3
end # class Testee < Test::Unit::TestCase
end

Thanks again

Robert
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,261
Messages
2,571,308
Members
47,968
Latest member
SerenaRusc

Latest Threads

Top