Is there a replacement for sub?

M

Michael W. Ryder

I was trying to come up with a way to remove x instances of a character
from a string and came up with a problem. If I enter:

a = "a b c d e f"
for i in 1..3
a = a.sub!(' ', '')
end
puts a ==> returns 'abcd e f' which is correct.

But if I enter:

a = "a b c d e f"
for i in 1..10
a = a.sub!(' ', '')
end
puts a ==> returns error.rb:3: private method `sub!' called for
nil:NilClass (NoMethodError, and a is now nil.

What I am looking for is a way to remove the first n instances of a
blank from the string without wiping out the string if it does not
contain at least n blanks. I assume there is a way to do this with
regular expressions, but have not found it yet. This is something an
editor I liked, UCEDIT, on the CDC Cyber had in the 70's.
 
C

Chris Shea

I was trying to come up with a way to remove x instances of a character
from a string and came up with a problem. If I enter:

a = "a b c d e f"
for i in 1..3
a = a.sub!(' ', '')
end
puts a ==> returns 'abcd e f' which is correct.

But if I enter:

a = "a b c d e f"
for i in 1..10
a = a.sub!(' ', '')
end
puts a ==> returns error.rb:3: private method `sub!' called for
nil:NilClass (NoMethodError, and a is now nil.

What I am looking for is a way to remove the first n instances of a
blank from the string without wiping out the string if it does not
contain at least n blanks. I assume there is a way to do this with
regular expressions, but have not found it yet. This is something an
editor I liked, UCEDIT, on the CDC Cyber had in the 70's.

sub! modifies the string in place, so you don't need to say a = a.sub!
(' ',''). a is already changing. And since sub! is modifying in
place, it returns nil if no changes are being made, and you end up
setting a to nil when that happens.

a = 'a b c d e f'
10.times { a.sub!(' ','')}
puts a # 'abcdef'

HTH,
Chris
 
P

Pete Yandell

Michael said:
I was trying to come up with a way to remove x instances of a character
from a string and came up with a problem. If I enter:

a = "a b c d e f"
for i in 1..3
a = a.sub!(' ', '')
end
puts a ==> returns 'abcd e f' which is correct.

But if I enter:

a = "a b c d e f"
for i in 1..10
a = a.sub!(' ', '')
end
puts a ==> returns error.rb:3: private method `sub!' called for
nil:NilClass (NoMethodError, and a is now nil.

a.sub! returns nil if no matches are found. You want either:

a = a.sub(' ', '')

or

a.sub!(' ', '')

And try using times rather than a for loop and a useless variable:

3.times { a.sub!(' ', '') }

You could also replace the whole thing with:

"a b c d e f".split(' ', 4).join

Pete Yandell
http://notahat.com/
 
K

Ken Bloom

I was trying to come up with a way to remove x instances of a character
from a string and came up with a problem. If I enter:

a = "a b c d e f"
for i in 1..3
a = a.sub!(' ', '')
end
puts a ==> returns 'abcd e f' which is correct.

But if I enter:

a = "a b c d e f"
for i in 1..10
a = a.sub!(' ', '')
end
puts a ==> returns error.rb:3: private method `sub!' called for
nil:NilClass (NoMethodError, and a is now nil.

What I am looking for is a way to remove the first n instances of a
blank from the string without wiping out the string if it does not
contain at least n blanks. I assume there is a way to do this with
regular expressions, but have not found it yet. This is something an
editor I liked, UCEDIT, on the CDC Cyber had in the 70's.

a.sub! modifies the string in place, changing only the first occurance.
if it finds something, it returns self, if it finds nothing, it returns
nil (so you can find out whether it changed anything.) So there's no need
to do the a=a.sub!. Just use a.sub! on its own.

a.sum (without the!) always returns a string, whether something was found
or not (in which case it returns the unmodified string), and never
modifies the string itself -- it always returns a copy.

gsub! and gsub work similarly, but they change all matching strings in
one fell swoop. (The g in the name is a holdover from standard UNIX
utilities such as sed, ex, and vi that use the letter g as a modifier to
mean "replace all matches")

--Ken
 
M

Morton Goldberg

I was trying to come up with a way to remove x instances of a
character from a string and came up with a problem. If I enter:

a = "a b c d e f"
for i in 1..3
a = a.sub!(' ', '')
end
puts a ==> returns 'abcd e f' which is correct.

But if I enter:

a = "a b c d e f"
for i in 1..10
a = a.sub!(' ', '')
end
puts a ==> returns error.rb:3: private method `sub!' called for
nil:NilClass (NoMethodError, and a is now nil.

What I am looking for is a way to remove the first n instances of a
blank from the string without wiping out the string if it does not
contain at least n blanks. I assume there is a way to do this with
regular expressions, but have not found it yet. This is something
an editor I liked, UCEDIT, on the CDC Cyber had in the 70's.

How about this?

n = 3
"a b c d e f".sub(/(\S\s){#{n}}/) { |m| m.delete(" ") } # => "abcd e f"
n = 10
"a b c d e f".sub(/(\S\s){#{n}}/) { |m| m.delete(" ") } # => "a b c d
e f"

Regards, Morton
 
M

Michael W. Ryder

Chris said:
sub! modifies the string in place, so you don't need to say a = a.sub!
(' ',''). a is already changing. And since sub! is modifying in
place, it returns nil if no changes are being made, and you end up
setting a to nil when that happens.

a = 'a b c d e f'
10.times { a.sub!(' ','')}
puts a # 'abcdef'

HTH,
Chris

I think this is where I am having problems understanding Ruby. I have
to use a.sub(' ', '') in a for loop but use a.sub!(' ', '') when using a
times loop. Why the difference?
 
M

Michael W. Ryder

Morton said:
How about this?

n = 3
"a b c d e f".sub(/(\S\s){#{n}}/) { |m| m.delete(" ") } # => "abcd e f"
n = 10
"a b c d e f".sub(/(\S\s){#{n}}/) { |m| m.delete(" ") } # => "a b c d e f"

Regards, Morton

Is there nothing in regular expressions where you can tell it to do
something up to n times?
 
R

Robert Klemme

2007/7/20 said:
I think this is where I am having problems understanding Ruby. I have
to use a.sub(' ', '') in a for loop but use a.sub!(' ', '') when using a
times loop. Why the difference?

There is none. a.sub! is an alternative to a = a.sub - wherever you use it.

robert
 
R

Robert Klemme

2007/7/20 said:
Is there nothing in regular expressions where you can tell it to do
something up to n times?

There is - kind of. You can use {} to give repetition counts. You can do this

irb(main):004:0> a = "a b c d e f"
=> "a b c d e f"
irb(main):005:0> a.sub(/(?: [^ ]*){3}/) {|m| m.gsub(/ /, '') }
=> "abcd e f"
irb(main):006:0>

Kind regards

robert
 
D

dblack

Hi --

There is none. a.sub! is an alternative to a = a.sub - wherever you use it.

a.sub! will modify the same string, though, whereas a = a.sub
changes the binding of 'a' to a new string. (Which you probably
didn't mention because it doesn't matter in a given case, but could be
misunderstood out of context :)


David

--
* Books:
RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242)
RUBY FOR RAILS (http://www.manning.com/black)
* Ruby/Rails training
& consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
 
M

Michael W. Ryder

Robert said:
2007/7/20 said:
Is there nothing in regular expressions where you can tell it to do
something up to n times?

There is - kind of. You can use {} to give repetition counts. You can
do this

irb(main):004:0> a = "a b c d e f"
=> "a b c d e f"
irb(main):005:0> a.sub(/(?: [^ ]*){3}/) {|m| m.gsub(/ /, '') }
=> "abcd e f"
irb(main):006:0>

Kind regards

robert
Unfortunately this is much more complicated and much harder to
understand, and debug. The editor I mentioned had an argument you
passed to the expression that told it to do it one time if it was
absent, n number of times, or till the end. Since this was 30 years ago
I expected that something like this hadn't been dropped in the interim.
 
M

Michael W. Ryder

Robert said:
There is none. a.sub! is an alternative to a = a.sub - wherever you use
it.

Except where you use b = a.sub!(' ', ''). Then if you loop through this
statement 10 times b is nil, not the abcdef I expected. This is what is
so confusing. At the same time I can not use b = a.sub(' ', '') as it
always returns ab c d e f regardless of how many times I execute it,
which is what I would expect. So, how would I do this?
10.times { b = a.sub!(' ', '')} doesn't work, it errors out.
b = a.split(' ', 10).join works for this contrived example but I am not
sure if it would work for something like replacing the first 12
occurrences of \","\ with \\t\.
 
M

Martin DeMello

Except where you use b = a.sub!(' ', ''). Then if you loop through this
statement 10 times b is nil, not the abcdef I expected. This is what is
so confusing. At the same time I can not use b = a.sub(' ', '') as it
always returns ab c d e f regardless of how many times I execute it,
which is what I would expect. So, how would I do this?
10.times { b = a.sub!(' ', '')} doesn't work, it errors out.

Remember that a variable is not an object, it is just a label pointing
to an object.

sub: returns a *copy* of the string, with the substitution made (i.e.
a new object)
sub!: modifies the string in place (i.e. modifies the object itself)

Therefore

a = "a b c d e f"
a.sub(" ", "") # => returns "ab c d e f", but leaves a unchanged.
also, the return value will be garbage collected, since nothing is
pointing to it

b = a.sub(" ", "") # => b is now "ab c d e f", leaves a unchanged

a = a.sub(" ", "") # => a is now "ab c d e f", the old string is
garbage collected

a = "a b c d e f"
b = a
a = a.sub(" ", "") # => a is now "ab c d e f", b is still "a b c d e f"

a = "a b c d e f"
a.sub!("", '') #=> a is now "ab c d e f"

a = "a b c d e f"
b = a.sub!("", '') #=> a is now "ab c d e f", a and b point to the same object

a = "abcdef"
b = a.sub!("", '') #=> a is still "abcdef", b is nil

Note the last bit carefully - sub! modifies the object, and *returns*
either another pointer to the same object if it was modified, or nil
if it wasn't. Therefore the return value of sub! should ideally only
be used to check if the string was modified or not, and then
discarded.

As for running in a loop, you want either

10.times { a.sub!(" ", '')} #=> modifies a string 10 times

or

10.times {a = a.sub(" ", '')} # creates a series of 10 strings, the
intermediate ones being GCd

try this:

a = "a b c d e f"
intermediate = [a]
10.times {|i| intermediate[i+1] = intermediate.sub(" ", '')}
p intermediate

# => ["a b c d e f", "ab c d e f", "abc d e f", "abcd e f", "abcde f",
"abcdef", "abcdef", "abcdef", "abcdef", "abcdef", "abcdef"]


martin
 
P

Peña, Botp

From: Michael W. Ryder [mailto:[email protected]]=20
# a =3D "a b c d e f"
# for i in 1..3
# a =3D a.sub!(' ', '')
^^^^^
lose that

# end
# puts a =3D=3D> returns 'abcd e f' which is correct.
#=20
# But if I enter:
#=20
# a =3D "a b c d e f"
# for i in 1..10
# a =3D a.sub!(' ', '')
^^^^^
same. lose it.

# end
# puts a =3D=3D> returns error.rb:3: private method `sub!' called for=20
# nil:NilClass (NoMethodError, and a is now nil.

a.sub! will modify a. so, do NOT use a=3Da.sub!, it's NOT right. just =
use plain a.sub!. ruby has already simplified it, do not make it simpler =
:)

eg,

C:\family\ruby>cat -n test.rb
1 puts "---test 3 subs using for---"
2 a =3D "a b c d e f"
3 for i in 1..3
4 a.sub!(' ', '')
5 end
6 puts a
7
8 puts "---test 10 subs using for---"
9 a =3D "a b c d e f"
10 for i in 1..10
11 a.sub!(' ', '')
12 end
13 puts a
14
15
16 puts "---test 3 subs using times---"
17 a =3D "a b c d e f"
18 3.times do
19 a.sub!(' ', '')
20 end
21 puts a
22
23
24 puts "---test 10 subs using times---"
25 a =3D "a b c d e f"
26 10.times do
27 a.sub!(' ', '')
28 end
29 puts a

C:\family\ruby>ruby test.rb
---test 3 subs using for---
abcd e f
---test 10 subs using for---
abcdef
---test 3 subs using times---
abcd e f
---test 10 subs using times---
abcdef

C:\family\ruby>

is that clear enough?

kind regards -botp
 
R

Robert Klemme

2007/7/20 said:
Hi --



a.sub! will modify the same string, though, whereas a = a.sub
changes the binding of 'a' to a new string. (Which you probably
didn't mention because it doesn't matter in a given case, but could be
misunderstood out of context :)

Yes. I was just referring to the assumed different usage in for and
while loops (see question). Thanks for clarifying!

Kind regards

robert
 
F

F. Senault

Le 20 juillet à 11:12, Peña, Botp a écrit :
a.sub! will modify a. so, do NOT use a=a.sub!, it's NOT right.
just use plain a.sub!. ruby has already simplified it, do not make
it simpler :)

And, while you're at it, depending on the situation, sub! returning nil
may be quite useful :

a = "a b c d e f"
for i in 1..1_000
break unless a.sub!(' ', '')
end

(I.e. breaking if there is nothing else to substitute, instead of
looping 995 times for nothing.)

Fred
 
R

Robert Dober

I was trying to come up with a way to remove x instances of a character
from a string and came up with a problem. If I enter:

a = "a b c d e f"
for i in 1..3
a = a.sub!(' ', '')
end
puts a ==> returns 'abcd e f' which is correct.

But if I enter:

a = "a b c d e f"
for i in 1..10
a = a.sub!(' ', '')
end
puts a ==> returns error.rb:3: private method `sub!' called for
nil:NilClass (NoMethodError, and a is now nil.

What I am looking for is a way to remove the first n instances of a
blank from the string without wiping out the string if it does not
contain at least n blanks. I assume there is a way to do this with
regular expressions, but have not found it yet. This is something an
editor I liked, UCEDIT, on the CDC Cyber had in the 70's.

I was reading this whole thread and I kind of think to be dreaming, I
must have missed something obvious!!!
Anyway maybe this is was OP wants, well I think it is ;)

529/29 > irb
irb(main):001:0> a='a b c d e f'
=> "a b c d e f"
irb(main):002:0> a.gsub!(" ","")
=> "abcdef"
irb(main):003:0> a
=> "abcdef"
irb(main):004:0>

Consider however using the non inplace version, like e.g.

b = a.gsub(" ","")

HTH
Robert
 
M

Martin DeMello

I was reading this whole thread and I kind of think to be dreaming, I
must have missed something obvious!!!
Anyway maybe this is was OP wants, well I think it is ;)

529/29 > irb
irb(main):001:0> a='a b c d e f'
=> "a b c d e f"
irb(main):002:0> a.gsub!(" ","")

Nope, he wants a method that only replaces the first n occurrences of
the pattern. gsub will not do this - you need to run sub in a loop.
(This is where perl's "continue matching where you left off" would
have been a nice optimisation)

martin
 
R

Robert Dober

Nope, he wants a method that only replaces the first n occurrences of
the pattern. gsub will not do this - you need to run sub in a loop.
(This is where perl's "continue matching where you left off" would
have been a nice optimisation)

I knew I missed something but I did not see what, sorry....
 
R

Robert Klemme

2007/7/20 said:
Nope, he wants a method that only replaces the first n occurrences of
the pattern. gsub will not do this - you need to run sub in a loop.

There is a different solution that does not need a loop (see one of my
previous posts).
(This is where perl's "continue matching where you left off" would
have been a nice optimisation)

Well, you can always use the block form with an additional counter

irb(main):012:0> count=0
=> 0
irb(main):013:0> a.gsub(/ /) {count+=1; count<3 ? "" : " "}
=> "abc d e f"

But I guess the other solution is more efficient.

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

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top