An elegant way...

F

F. Senault

Hello everybody.

I've written a method to solve a little problem, but I find my solution
really ugly. So, I'm trying to find ways to improve it.

The method takes one argument, an array containing a sorted list of
strings representing episodes numbers. The episodes numbers are either
a number ('1', '12') or prefixed with a letter ('S1' for special 1). My
goal is to find sequences in the numbers and join them with dashes :
RAniDBTools.format_episodes_list([ '1', '2', '3', '4', '6', '7', '9', 'S1', 'S2' ]) => "1-4, 6-7, 9, S1-S2"
RAniDBTools.format_episodes_list([ '1', '2', 'S3', 'S4', 'S5', 'O6' ])
=> "1-2, S3-S5, O6"

Here's the code ; what can I do to improve this ?

module RAniDBTools
def RAniDBTools.format_episodes_list(list)
lt = []
le = []
list.each do |epno|
if ('0'..'9').include? epno[0,1]
t = ''
e = epno.to_i
else
t = epno[0..0]
e = epno[1..-1].to_i
end
if lt.last == t
max = le.last.max rescue le.last
min = le.last.min rescue le.last
if e == max + 1
le[-1] = (min..e)
else
le << e
lt << t
end
else
le << e
lt << t
end
end
f = []
le.each_with_index do |e, i|
if e.is_a? Range
f << "#{lt}#{e.min}-#{lt}#{e.max}"
else
f << "#{lt}#{e}"
end
end
f.join(', ')
end
end

TIA,

Fred
 
J

Jesús Gabriel y Galán

Hello everybody.

I've written a method to solve a little problem, but I find my solution
really ugly. =A0So, I'm trying to find ways to improve it.

The method takes one argument, an array containing a sorted list of
strings representing episodes numbers. =A0The episodes numbers are either
a number ('1', '12') or prefixed with a letter ('S1' for special 1). =A0M= y
goal is to find sequences in the numbers and join them with dashes :
RAniDBTools.format_episodes_list([ '1', '2', '3', '4', '6', '7', '9', '=
S1', 'S2' ])
=3D> "1-4, 6-7, 9, S1-S2"
RAniDBTools.format_episodes_list([ '1', '2', 'S3', 'S4', 'S5', 'O6' ])
=3D> "1-2, S3-S5, O6"

Here's the code ; what can I do to improve this ?

module RAniDBTools
=A0def RAniDBTools.format_episodes_list(list)
=A0 =A0lt =3D []
=A0 =A0le =3D []
=A0 =A0list.each do |epno|
=A0 =A0 =A0if ('0'..'9').include? epno[0,1]
=A0 =A0 =A0 =A0t =3D ''
=A0 =A0 =A0 =A0e =3D epno.to_i
=A0 =A0 =A0else
=A0 =A0 =A0 =A0t =3D epno[0..0]
=A0 =A0 =A0 =A0e =3D epno[1..-1].to_i
=A0 =A0 =A0end
=A0 =A0 =A0if lt.last =3D=3D t
=A0 =A0 =A0 =A0max =3D le.last.max rescue le.last
=A0 =A0 =A0 =A0min =3D le.last.min rescue le.last
=A0 =A0 =A0 =A0if e =3D=3D max + 1
=A0 =A0 =A0 =A0 =A0le[-1] =3D (min..e)
=A0 =A0 =A0 =A0else
=A0 =A0 =A0 =A0 =A0le << e
=A0 =A0 =A0 =A0 =A0lt << t
=A0 =A0 =A0 =A0end
=A0 =A0 =A0else
=A0 =A0 =A0 =A0le << e
=A0 =A0 =A0 =A0lt << t
=A0 =A0 =A0end
=A0 =A0end
=A0 =A0f =3D []
=A0 =A0le.each_with_index do |e, i|
=A0 =A0 =A0if e.is_a? Range
=A0 =A0 =A0 =A0f << "#{lt}#{e.min}-#{lt}#{e.max}"
=A0 =A0 =A0else
=A0 =A0 =A0 =A0f << "#{lt}#{e}"
=A0 =A0 =A0end
=A0 =A0end
=A0 =A0f.join(', ')
=A0end
end


I have no time to propose a complete solution. I'll leave to you
adding the "Sx" and "Ox" processing. This is how I'd do it it they
were all numbers:

irb(main):004:0> list =3D [1,2,3,4,6,7,9,16,17]
=3D> [1, 2, 3, 4, 6, 7, 9, 16, 17]
irb(main):018:0> result =3D [[list.first]]
=3D> [[1]]
irb(main):023:0> list.each_cons(2) do |x,y|
irb(main):024:1* if (x+1) =3D=3D y
irb(main):025:2> result.last << y
irb(main):026:2> else
irb(main):027:2* result << [y]
irb(main):028:2> end
irb(main):029:1> end
=3D> nil
irb(main):030:0> result
=3D> [[1, 2, 3, 4], [6, 7], [9], [16, 17]]
irb(main):031:0> s =3D ""
=3D> ""
irb(main):034:0> res =3D result.map {|x| x.size =3D=3D 1 ? x.to_s :
"#{x.first}-#{x.last}"}
=3D> ["1-4", "6-7", "9", "16-17"]
irb(main):035:0> res.join(",")
=3D> "1-4,6-7,9,16-17"

For the other stuff, I would have a flag that tells me if I'm in the
middle of a number, "S" or "O" run, and act accordingly.

Hope this helps,

Jesus.
 
J

James Edward Gray II

The method takes one argument, an array containing a sorted list of
strings representing episodes numbers. The episodes numbers are = either
a number ('1', '12') or prefixed with a letter ('S1' for special 1). = My
goal is to find sequences in the numbers and join them with dashes :
=20
RAniDBTools.format_episodes_list([ '1', '2', '3', '4', '6', '7', =
'9', 'S1', 'S2' ])
=3D> "1-4, 6-7, 9, S1-S2"
RAniDBTools.format_episodes_list([ '1', '2', 'S3', 'S4', 'S5', 'O6' =
])
=3D> "1-2, S3-S5, O6"

Dave Thomas and I were playing around with an idea like this recently:

http://gist.github.com/570434

http://gist.github.com/570556

I hope those give you some fresh ideas.

James Edward Gray II=
 
B

brabuhr

The method takes one argument, an array containing a sorted list of
strings representing episodes numbers. =A0The episodes numbers are either
a number ('1', '12') or prefixed with a letter ('S1' for special 1). =A0M= y
goal is to find sequences in the numbers and join them with dashes :
RAniDBTools.format_episodes_list([ '1', '2', '3', '4', '6', '7', '9', '=
S1', 'S2' ])
=3D> "1-4, 6-7, 9, S1-S2"
RAniDBTools.format_episodes_list([ '1', '2', 'S3', 'S4', 'S5', 'O6' ])
=3D> "1-2, S3-S5, O6"

Here's the code ; what can I do to improve this ?

I wouldn't call this an improvement:

def format_episodes_list a
z =3D []
z << a.inject([""]){|i,j| i.last.succ=3D=3Dj ? i << j : (z << i ; [j])}
z[1..-1].map{|b| b.size=3D=3D1?b:"#{b.first}-#{b.last}"}.join(", ")
end

puts format_episodes_list([ '1', '2', '3', '4', '6', '7', '9', 'S1', 'S2' ]=
)
#=3D> 1-4, 6-7, 9, S1-S2
puts format_episodes_list([ '1', '2', 'S3', 'S4', 'S5', 'O6' ])
#=3D> 1-2, S3-S5, O6
puts format_episodes_list([ '1', '2', '9', '10', 'S9', 'S10' ])
#=3D> 1-2, 9-10, S9, S10

(And, I'm guessing that the last one there isn't correct :)
 
J

Joel VanderWerf

Hello everybody.

I've written a method to solve a little problem, but I find my solution
really ugly. So, I'm trying to find ways to improve it.

The method takes one argument, an array containing a sorted list of
strings representing episodes numbers. The episodes numbers are either
a number ('1', '12') or prefixed with a letter ('S1' for special 1). My
goal is to find sequences in the numbers and join them with dashes :

a = [ '1', '2', 'S3', 'S4', 'S5', 'O6' ]

r0 = nil
r1 = nil
result = []

a.each_cons 2 do |prev, s|
case s
when prev.succ
r0 ||= prev
r1 = s
else
if r0
result << "#{r0}-#{r1}"
else
result << prev
end
r0 = r1 = nil
end
end

if r0
result << "#{r0}-#{r1}"
else
result << a.last
end

p result # ==> ["1-2", "S3-S5", "O6"]
 
J

Joel VanderWerf


Refactoring that:

a = [ '1', '2', '3', '4', '6', '7', '9', 'S1', 'S2', "10", "11", "12" ]
#a = [ '1', '2', 'S3', 'S4', 'S5', 'O6' ]

module Enumerable
def each_run(cond)
run = nil
last = nil

each_cons 2 do |prev, s|
if cond[prev, s]
run ||= [prev]
run << s
else
yield run || prev
run = nil
end
last = s
end

yield run || [last]

self
end

end

result = []

a.each_run(proc {|prev, s| prev.succ == s}) do |run|
if run.size > 1
result << "#{run.first}-#{run.last}"
else
result << run.first
end
end

p result
 
F

F. Senault

Le 20 septembre à 19:38, Jesús Gabriel y Galán a écrit :

/.../
I have no time to propose a complete solution. I'll leave to you
adding the "Sx" and "Ox" processing. This is how I'd do it it they
were all numbers:
irb(main):023:0> list.each_cons(2) do |x,y|

Ahha ! each_cons...
irb(main):024:1* if (x+1) == y
irb(main):025:2> result.last << y
irb(main):026:2> else
irb(main):027:2* result << [y]
irb(main):028:2> end
irb(main):029:1> end

M'kay. My current iteration looks like this :

def RAniDBTools.format_episodes_list(list)
list.group_by { |e| ('A'..'Z') === e[0,1] ? e[0,1] : '' }.collect do |p, l|
l = l.map { |e| e.gsub(/^[A-Z]/, '').to_i }
format_consecutive_numbers(p, l)
end.join(", ")
end

(I'd definitely need a group_and_map function here.)

def RAniDBTools.format_consecutive_numbers(prefix, list)
result = [[ list.first ]]
list.each_cons(2) do |x, y|
if y == x + 1
result.last << y
else
result << [ y ]
end
ends
result.map { |x| "#{prefix}#{x.first}" + (x.size == 1 ? "" : "-#{prefix}#{x.last}") }.join(", ")
end

Better ! I'm still reading the other answers in the thread, though...

Thanks a lot for the ideas !
s
Fred
--
If I don't think about the fact that she left me,
If I don't see the pearls fall from the sky,
If I don't hear the accusations of blasphemy,
If I don't feel the tears in my eyes,
This is the best day of my life. (Prince, 3 chains o' gold)
 
F

F. Senault

Le 20 septembre à 20:17, Joel VanderWerf a écrit :
a.each_run(proc {|prev, s| prev.succ == s}) do |run|

There's a catch with succ :
=> "T0"

Definitely not what I want ! :)

Fred
 
J

Joel VanderWerf

Le 20 septembre à 20:17, Joel VanderWerf a écrit :


There's a catch with succ :

=> "T0"

Definitely not what I want ! :)

Fred

Ow! Yup. Maybe you could use this:
=> "S10"
 
J

James Edward Gray II

Le 20 septembre =E0 19:44, James Edward Gray II a =E9crit :
=20
=20
Where does the slice_before method comes from ?
=3D> true

It was added in 1.9.2, I believe.

James Edward Gray II=
 
F

F. Senault

Le 20 septembre à 20:59, James Edward Gray II a écrit :
=> true

It was added in 1.9.2, I believe.
=> false

Indeed. Explains why I can't find it in my third edition Pick Axe...

Fred
--
The problem with defending the purity of the English language is that
English is about as pure as a cribhouse whore. We don't just borrow
words; on occasion, English has pursued other languages down alleyways
to beat them unconscious and rifle their pockets for new vocabulary.
(James D. Nicoll, rasfw)
 
H

Harry Kakueki

Hello everybody.

=A0My
goal is to find sequences in the numbers and join them with dashes :

This is not a complete solution but *maybe* it is something worth looking a=
t.
It depends on your specs.
I'll leave it to you to work out the details.

arr =3D [ '1', '2', '3', '4', '6', '7', '9', 'S1', 'S2' ]
s =3D ('1'..'20').to_a + ('S1'..'S5').to_a

t =3D []
s.each do |x|
t << x if arr.include?(x)
t << "*" if arr.include?(x) =3D=3D false
end

p t.join(" ").split("*").map{|y| y.strip.split(" ")}.select{|z| z.size
0}.map{|w| "#{w[0]}-#{w[-1]}"}

#> ["1-4", "6-7", "9-9", "S1-S2"]


Harry
 
H

Harry Kakueki

arr =3D [ '1', '2', '3', '4', '6', '7', '9', 'S1', 'S2' ]
s =3D ('1'..'20').to_a + ('S1'..'S5').to_a

t =3D []
s.each do |x|
=A0t << x if arr.include?(x)
=A0t << "*" if arr.include?(x) =3D=3D false
end

p t.join(" ").split("*").map{|y| y.strip.split(" ")}.select{|z| z.size
0}.map{|w| "#{w[0]}-#{w[-1]}"}

#> ["1-4", "6-7", "9-9", "S1-S2"]


A slightly shorter but still unreadable version :)

r =3D [ '1', '2', '3', '4', '6', '7', '9', 'S1', 'S2' ]
s =3D ('1'..'20').to_a + ('S1'..'S5').to_a

p [].tap{|m| s.each {|x| m << (r.include?(x) ? x : "*")}}.join("
").split("*").map{|y| y.strip.split(" ")}.select{|z| z.size >
0}.map{|w| "#{w[0]}-#{w[-1]}"}

#> ["1-4", "6-7", "9-9", "S1-S2"]


Harry
 
H

Harry Kakueki

A slightly shorter but still unreadable version :)

r = [ '1', '2', '3', '4', '6', '7', '9', 'S1', 'S2' ]
s = ('1'..'20').to_a + ('S1'..'S5').to_a

p [].tap{|m| s.each {|x| m << (r.include?(x) ? x : "*")}}.join("
").split("*").map{|y| y.strip.split(" ")}.select{|z| z.size >
0}.map{|w| "#{w[0]}-#{w[-1]}"}

#> ["1-4", "6-7", "9-9", "S1-S2"]


r = [ '1', '2', '3','4', '6', '7', '9', 'S1', 'S2' ]
s = ('1'..'20').to_a + ('S1'..'S5').to_a

p [].tap{|m| s.each{|x| m << (r.include?(x) ? x : "*")}}.join("
").split("*").map{|y| y.strip.split(" ")}.select{|z| z.size >
0}.map{|w| (w.size>1 ? "#{w[0]}-#{w[-1]}" : "#{w[0]}")}

#> ["1-4", "6-7", "9", "S1-S2"]


I'll shut up, now.

Harry
 
B

Brian Candler

Here is a very traditional imperative solution.

def format_episodes_list(src)
res = []
j = 0
while j < src.size
i = j
j += 1 while j+1 < src.size &&
src[j+1] == src[j].sub(/\d+/) { $&.succ }
res << ((i == j) ? src : "#{src}-#{src[j]}")
j += 1
end
res.join(", ")
end

puts format_episodes_list(['1', '2', '3', '4', '6', '7', '9', 'S1', 'S2'
])
puts format_episodes_list([ '1', '2', 'S3', 'S4', 'S5', 'O6' ])
 
B

Brian Candler

In fact, I forgot it's OK to run off the end of an Array in ruby :) So
the inner loop can simplify to this:

j += 1 while src[j+1] == src[j].sub(/\d+/) { $&.succ }
 
R

Robert Klemme

Fred, for the contrast here's my engineering approach. It may not be
elegant (you decide) but you get modularity and reusability.

First I'd start out with a proper representation. If your numbers are
a central part of your application you may want to spend a class for
them:

Num = Struct.new :pre, :val do
def succ
self.class.new pre, val.succ
end

def to_s; "#{pre}#{val}" end
def inspect; "#{self.class.name}(#{self})" end

include Comparable
def <=>(o) to_a <=> o.to_a end
end

def Num(s)
%r{\A(\D*)(\d+)\z} =~ s or raise "bad arg %p" % s
Num.new $1, $2.to_i
end

Then we can set up a generic mechanism for this

module Enumerable
def rangify(&bl)
bl ||= lambda {|x, y| y ? (x..y) : x}

res = []
a = b = nil

each do |x|
case
when a.nil?
a = b = x
when x == b.succ
b = x
else
res << bl[a, b]
a = b = x
end
end

res
end
end


Full story is here http://gist.github.com/589427

Kind regards

robert
 
R

Robert Klemme

Fred, for the contrast here's my engineering approach. =A0It may not be
elegant (you decide) but you get modularity and reusability.

First I'd start out with a proper representation. =A0If your numbers are
a central part of your application you may want to spend a class for
them:

Num =3D Struct.new :pre, :val do
=A0def succ
=A0 =A0self.class.new pre, val.succ
=A0end

=A0def to_s; "#{pre}#{val}" end
=A0def inspect; "#{self.class.name}(#{self})" end

=A0include Comparable
=A0def <=3D>(o) to_a <=3D> o.to_a end
end

def Num(s)
=A0%r{\A(\D*)(\d+)\z} =3D~ s or raise "bad arg %p" % s
=A0Num.new $1, $2.to_i
end

Then we can set up a generic mechanism for this

Forgot the end handling and mixed up two versions. Sorry for that.
Here's the proper one

module Enumerable
def rangify(&bl)
bl ||=3D lambda {|x, y| x =3D=3D y ? x : (x..y)}

res =3D []
a =3D b =3D nil

each do |x|
case
when a.nil?
a =3D b =3D x
when x =3D=3D b.succ
b =3D x
else
res << bl[a, b]
a =3D b =3D x
end
end

res << bl[a, b]
end
end

Cheers

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 

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

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,011
Latest member
AjaUqq1950

Latest Threads

Top