[rcr] String#first / String#last

S

Simon Strandgaard

problem:
I often use a range to extract the beginning/ending letters..
like this "string"[-4, 4] #-> "ring"
It feels somewhat redundant that I have to type the length 2 times.
A very simple operation that easily can confuse people if they
are browsing through others code.. this is a place where one easily can
introduce off-by-one errors.


motivation:
When looking at the Array class I see that it has #first and #last
methods.. just for this purpose, to extract the sub-array either
from the beginning or the ending.
Why not also use #first and #last on String ?


proposal:
"ruby".first(2) #=> "ru"
"ruby".last(3) #=> "uby"


implementation:
class String
def first(n=nil)
n ||= 1
raise TypeError, "cannot convert #{n.class} to Integer" unless n.kind_of?
(Integer)
raise ArgumentError, "negative string size" if n < 0
n = [n, self.size].min
self[0, n]
end
def last(n=nil)
n ||= 1
raise TypeError, "cannot convert #{n.class} to Integer" unless n.kind_of?
(Integer)
raise ArgumentError, "negative string size" if n < 0
n = [n, self.size].min
self[-n, n]
end
end


open questions:

Some encodings has variable length chars, where a char may
span between 1 and 5 bytes. This raises the question:
how should the argument to #first be interpreted?
should it interpret as bytes or chars?


Is this a good or a bad proposal?
 
T

ts

S> raise TypeError, "cannot convert #{n.class} to Integer" unless n.kind_of?
S> (Integer)

svg% ruby -e 'class A; def to_int() 2 end; end; p [1,2,3].first(A.new)'
[1, 2]
svg%


Guy Decoux
 
T

trans. (T. Onoma)

On Sunday 24 October 2004 09:32 am, Simon Strandgaard wrote:
| class String
|   def first(n=nil)
n = (n || 1).to_i
|     raise ArgumentError, "negative string size" if n < 0
|     n = [n, self.size].min
|     self[0, n]
|   end
|   def last(n=nil)
n = (n || 1).to_i
|     raise ArgumentError, "negative string size" if n < 0
|     n = [n, self.size].min
|     self[-n, n]
|   end
| end

Would you say that's better, or no?

Thanks,
T.
 
S

Simon Strandgaard

S> raise TypeError, "cannot convert #{n.class} to Integer" unless
n.kind_of? S> (Integer)

svg% ruby -e 'class A; def to_int() 2 end; end; p [1,2,3].first(A.new)'
[1, 2]
svg%


Guy Decoux


Ok.. I think I have fixed this issue now.


irb(main):001:0> 'abc'.last(3)
=> "abc"
irb(main):002:0> class A; def to_int; 2 end; end
=> nil
irb(main):003:0> 'abc'.last(A.new)
=> "bc"
irb(main):004:0> class B; def to_int; 'a' end; end
=> nil
irb(main):005:0> 'abc'.last(B.new)
TypeError: B#to_int should return Integer
from ./a.rb:22:in `last'
from (irb):5
irb(main):006:0>



New implementation is here:

class String
def first(length=nil)
length ||= 1
unless length.respond_to?:)to_int)
raise TypeError, "cannot convert #{length.class} to Integer"
end
n = length.to_int
unless n.kind_of?(Integer)
raise TypeError, "#{length.class}#to_int should return Integer"
end
raise ArgumentError, "negative string size" if n < 0
n = [n, self.size].min
self[0, n]
end
def last(length=nil)
length ||= 1
unless length.respond_to?:)to_int)
raise TypeError, "cannot convert #{length.class} to Integer"
end
n = length.to_int
unless n.kind_of?(Integer)
raise TypeError, "#{length.class}#to_int should return Integer"
end
raise ArgumentError, "negative string size" if n < 0
n = [n, self.size].min
self[-n, n]
end
end




btw: Is this better?
 
S

Simon Strandgaard

I will vote for it. May I propose aliasing left and right to first and
last?


I have submitted this RCR here.. (and I have mentioned you idea)
http://rcrchive.net/rcr/RCR/RCR283



On IRC, Florian Gross suggested to add #first= and #last=,
that works this way:

"string".last="foo" #=> "strfoo"

It seems as a good idea.
 
F

Florian Gross

Simon said:
On IRC, Florian Gross suggested to add #first= and #last=,
that works this way:

"string".last="foo" #=> "strfoo"

That is what Christian Neukirchen suggested, in my opinion
"string".last="foo" should change the string to "strinfoo". That would
also allow "string".last *= 2 to work as expected.

Regards,
Florian Gross
 
G

Gavin Kistner

I will vote for it. May I propose aliasing left and right to first and
last?

Given the presence of right-to-left languages in the world, and
possible some-day support for them, I think first/last is a better
choice.
 
T

trans. (T. Onoma)

|
| class String
| def first(length=nil)
| length ||= 1
| unless length.respond_to?:)to_int)
| raise TypeError, "cannot convert #{length.class} to Integer"
| end
| n = length.to_int
| unless n.kind_of?(Integer)
| raise TypeError, "#{length.class}#to_int should return Integer"
| end
| raise ArgumentError, "negative string size" if n < 0
| n = [n, self.size].min
| self[0, n]
| end
| def last(length=nil)
| length ||= 1
| unless length.respond_to?:)to_int)
| raise TypeError, "cannot convert #{length.class} to Integer"
| end
| n = length.to_int
| unless n.kind_of?(Integer)
| raise TypeError, "#{length.class}#to_int should return Integer"
| end
| raise ArgumentError, "negative string size" if n < 0
| n = [n, self.size].min
| self[-n, n]
| end
| end
|
|
|
|
| btw: Is this better?

Yes, I think so, but this might be even more so,

begin
n = length.to_int
rescue NoMethodError
raise TypeError, "cannot convert #{length.class} to Integer"
end

also

| unless n.kind_of?(Integer)
| raise TypeError, "#{length.class}#to_int should return Integer"
| end

I don't think this is really needed, since such a bug would be worked out else
where. (That #to_int might be used usefully for something other then
returning an integer is extremely unlikely and thus not worth the check).

T.
 
T

ts

t> On Sunday 24 October 2004 09:52 am, Simon Strandgaard wrote:

t> | unless n.kind_of?(Integer)
t> | raise TypeError, "#{length.class}#to_int should return Integer"
t> | end

t> I don't think this is really needed, since such a bug would be worked out else
t> where. (That #to_int might be used usefully for something other then
t> returning an integer is extremely unlikely and thus not worth the check).

svg% ruby -e 'class A; def to_int() "a" end end; [1,2].first(A.new)'
-e:1:in `first': A#to_int should return Integer (TypeError)
from -e:1
svg%


Guy Decoux
 
S

Shashank Date

Gavin said:
Given the presence of right-to-left languages in the world, and possible
some-day support for them, I think first/last is a better choice.

Hmmm... never thought of that. Nice catch, Gavin.
So, how about:

str.left(6)

should show the left 6 chars/codes of the string, regardless.

Another RCR then ?

-- shanko
 
T

trans. (T. Onoma)

On Sunday 24 October 2004 12:12 pm, ts wrote:
|
| t> On Sunday 24 October 2004 09:52 am, Simon Strandgaard wrote:
|
| t> | unless n.kind_of?(Integer)
| t> | raise TypeError, "#{length.class}#to_int should return Integer"
| t> | end
|
| t> I don't think this is really needed, since such a bug would be worked
| out else t> where. (That #to_int might be used usefully for something other
| then t> returning an integer is extremely unlikely and thus not worth the
| check).
|
| svg% ruby -e 'class A; def to_int() "a" end end; [1,2].first(A.new)'
| -e:1:in `first': A#to_int should return Integer (TypeError)
| from -e:1
| svg%

I see --to behave like Array's methods.

Seems like a lot overhead though when one considers this kind of thing is
going on throughout the system.

I added to my lib, but I did this instead:

begin
n = n.to_int.abs
rescue NoMethodError
raise TypeError, "cannot convert #{length.class} to positive Integer"
end

which allowed me to get rid of the negative check too. In the very very very
unlikely case that a non-integer slips through (a String for instance) one
still just gets:

ArgumentError: comparison of Fixnum with String failed

T.
 
T

trans. (T. Onoma)

On Sunday 24 October 2004 12:48 pm, trans. (T. Onoma) wrote:
| Seems like a lot overhead though when one considers this kind of thing is
| going on throughout the system.

Doh. I forgot my main point. I wanted to say that one might think Integer()
would take care of all this, but imagine my suprise when I did this:

irb(main):001:0> Integer:)a)
=> 12169

T.
 
D

Daniel Berger

Simon Strandgaard said:
problem:
I often use a range to extract the beginning/ending letters..
like this "string"[-4, 4] #-> "ring"
It feels somewhat redundant that I have to type the length 2 times.
A very simple operation that easily can confuse people if they
are browsing through others code.. this is a place where one easily can
introduce off-by-one errors.


motivation:
When looking at the Array class I see that it has #first and #last
methods.. just for this purpose, to extract the sub-array either
from the beginning or the ending.
Why not also use #first and #last on String ?

Why not just turn String into an Array of characters? It feels like
that's how everyone wants a String to behave anyway.

Dan
 
H

Hal Fulton

Daniel said:
Why not just turn String into an Array of characters? It feels like
that's how everyone wants a String to behave anyway.

I think I'd be opposed to that. But adding a to_a might be good -- I
frequently do a gratuitous split operation for that purpose.

Hal
 
H

Hal Fulton

Hal said:
I think I'd be opposed to that. But adding a to_a might be good -- I
frequently do a gratuitous split operation for that purpose.

Pardon me: Of course, there already is a to_a -- but it is line-oriented
like #each. Makes sense in many ways, but I more often need the other.

Hal
 
Y

Yukihiro Matsumoto

Hi,

In message "Re: [rcr] String#first / String#last"

|I think I'd be opposed to that. But adding a to_a might be good -- I
|frequently do a gratuitous split operation for that purpose.

It already has "to_a" which works line-wise. Perhaps something called
"explode" in other language is what you want.

matz.
 
T

trans. (T. Onoma)

On Sunday 24 October 2004 11:54 pm, Yukihiro Matsumoto wrote:
| Hi,
|
| In message "Re: [rcr] String#first / String#last"
|
| on Mon, 25 Oct 2004 09:18:51 +0900, Hal Fulton
| |I think I'd be opposed to that. But adding a to_a might be good -- I
| |frequently do a gratuitous split operation for that purpose.
|
| It already has "to_a" which works line-wise. Perhaps something called
| "explode" in other language is what you want.

Believe their is an RCR for #chars

def chars
split(//)
end

T.
 
M

Markus

String#chars would solve the original point as well, at least
semantically:

"my favorite test string".chars.last # => "g"

...but unless some magic were done, this could be needlessly
inefficient.

-- Markus
 
R

Robert Klemme

Simon Strandgaard said:
"S" == Simon Strandgaard <[email protected]> writes:

S> raise TypeError, "cannot convert #{n.class} to Integer" unless
n.kind_of? S> (Integer)

svg% ruby -e 'class A; def to_int() 2 end; end; p [1,2,3].first(A.new)'
[1, 2]
svg%


Guy Decoux


Ok.. I think I have fixed this issue now.


irb(main):001:0> 'abc'.last(3)
=> "abc"
irb(main):002:0> class A; def to_int; 2 end; end
=> nil
irb(main):003:0> 'abc'.last(A.new)
=> "bc"
irb(main):004:0> class B; def to_int; 'a' end; end
=> nil
irb(main):005:0> 'abc'.last(B.new)
TypeError: B#to_int should return Integer
from ./a.rb:22:in `last'
from (irb):5
irb(main):006:0>



New implementation is here:

class String
def first(length=nil)
length ||= 1
unless length.respond_to?:)to_int)
raise TypeError, "cannot convert #{length.class} to Integer"
end
n = length.to_int
unless n.kind_of?(Integer)
raise TypeError, "#{length.class}#to_int should return Integer"
end
raise ArgumentError, "negative string size" if n < 0
n = [n, self.size].min
self[0, n]
end
def last(length=nil)
length ||= 1
unless length.respond_to?:)to_int)
raise TypeError, "cannot convert #{length.class} to Integer"
end
n = length.to_int
unless n.kind_of?(Integer)
raise TypeError, "#{length.class}#to_int should return Integer"
end
raise ArgumentError, "negative string size" if n < 0
n = [n, self.size].min
self[-n, n]
end
end


btw: Is this better?


I'd prefer

class String
def first(n)
n = n.to_int
raise ArgumentError, "negative string size" if n < 0
self[0,n]
end

def last(n)
n = n.to_int
raise ArgumentError, "negative string size" if n < 0
self[-n,n] || self[-length,length]
end
end

Notes:

- You don't need the type check for n because #to_int will throw if n
does not have it.

- You don't need the min in first, because String#[] takes care of that
already.

- A default value does not make sense IMHO, because with #first() (i.e.
without arg) I'd
expect to get the first character ("foo"[0]) which is something
different).

Kind regards

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

Forum statistics

Threads
473,766
Messages
2,569,569
Members
45,043
Latest member
CannalabsCBDReview

Latest Threads

Top