Why do "<<" and "+" behave differently with frozen string objects? - ruby

irb(main):009:0> a = "good"
=> "good"
irb(main):010:0> a = "good" + "morning"
=> "goodmorning"
irb(main):011:0> a = "good"
=> "good"
irb(main):012:0> a << " morning"
=> "good morning"
Till now both the concatenation operators work fine.
irb(main):013:0> a = "good"
=> "good"
irb(main):014:0> a.freeze
=> "good"
irb(main):015:0> a.frozen?
=> true
irb(main):016:0> a << " welcome"
RuntimeError: can't modify frozen String
from (irb):16
from /usr/bin/irb:12:in `<main>'
irb(main):017:0> a = a + " welcome"
=> "good welcome"
But with a frozen string a difference is clearly visible from IRB that << and + are not behaving as they are supposed to. Could anyone tell me the reason for this?

They are doing exactly what they're supposed to. << modifies the string it is called on, while + is closer to str.dup << arg. This behavior is the defined, documented standard.

#Linuxios answer is perfect.
But still here I have tried to show that modification with more transparent way:
#ubuntu:~$ irb --simple-prompt
>> a = "good"
=> "good"
>> a.freeze
=> "good"
>> a.frozen?
=> true
>> a.object_id
=> 10557720 # holds the reference to the "good" string object.
>> a = a + " morning"
=> "good morning"
>> a.object_id
=> 10415700 # holds the reference to the new string object "good morning".
>> a.frozen?
=> false
>> ObjectSpace._id2ref(10415700)
=> "good morning"
>> ObjectSpace._id2ref(10557720)
=> "good"
>> ObjectSpace._id2ref(10557720).frozen?
=> true
We can conclude that - yes , string "good" is still frozen. Only the thing what happened is a referencing the new object "good morning". Only the reference assignment to a has been changed.

<< and + behave differently on ALL string objects, not just frozen ones. One (<<) modifies a string, and the other (+) returns a new string without modifying the original:
With <<:
string1 = "good"
#=> "good"
string2 = string1
#=> "good"
string1 << " morning"
#=> "good morning"
string1
#=> "good morning"
string2
#=> "good morning"
With +:
string1 = "good"
#=> "good"
string2 = string1
#=> "good"
string1 = string1 + " morning"
#=> "good morning"
string1
#=> "good morning"
string2
#=> "good"
Object#freeze is specifically designed to disallow the sort of behavior I demonstrated above with << (it prevents objects from being modified), so calling << on a frozen string results in an error.

Related

How do I apply removing of characters to the string itself?

Using Ruby 2.4. How do I apply an editing of a stirng to the string itself? I have this method
# Removes the word from teh end of the string
def remove_word_from_end_of_str(str, word)
str[0...(-1 * word.length)]
end
I want the parameter to be operated upon, but it isn't working ...
2.4.0 :001 > str = "abc def"
=> "abc def"
2.4.0 :002 > StringHelper.remove_word_from_end_of_str(str, "def")
=> "abc "
2.4.0 :003 > str
=> "abc def"
I want the string that was passed in to be equal to "abc " but that isn't happening. I don't want to set the variable to the result of the function (e.g. "str = StringHelper.remove(...)"
Ruby already has the String#delete! method that does exactly this:
>> str = 'abc def'
=> "abc def"
>> word = 'def'
=> "def"
>> str.delete!(word)
=> "abc "
>> str
=> "abc "
Note that this will remove all instances of word:
>> str = 'def abc def'
=> "def abc def"
>> str.delete!(word)
=> " abc "
To limit the effect to only the last word, you can do:
>> str = 'def abc def'
=> "def abc def"
>> str.slice!(-word.length..-1)
=> "def"
>> str
=> "def abc "
str[range] is just a shorthand for str.slice(range). You just have to use the destructive method, like that :
# Removes the word from the end of the string
def remove_word_from_end_of_str(str, word)
str.slice!((str.length - word.length)...(str.length))
end
For more information, see the documentation.
If you want your function to return the new string as well, you should use :
# Removes the word from the end of the string
def remove_word_from_end_of_str(str, word)
str.slice!((str.length - word.length)...(str.length))
str
end
Try:
def remove_word_from_end_of_str(str, word)
str.slice!((str.length - word.length)..str.length)
end
Also, your explanation is a little confusing. You are calling the remove_word method as a class method but it is an instance method.
chomp! returns a the String with the given record separator removed from the end of string (if present), and nil if nothing was removed.
def remove_word_from_end_of_str(str, word)
str.chomp!( "CO")
end
str = "Aurora CO"
remove_word_from_end_of_str(str, "CO")
p str #=> "Aurora "

Immutable vs mutable

I do not understand what is going on in the line
print buggy_logger << "\n" # <- This insertion is the bug.
Why is that the variable status changes when the above line is called?
I am following this website http://www.reactive.io/tips/2009/01/11/the-difference-between-ruby-symbols-and-strings/
status = "peace"
buggy_logger = status
print "Status: "
print buggy_logger << "\n" # <- This insertion is the bug.
def launch_nukes?(status)
unless status == 'peace'
return true
else
return false
end
end
print "Nukes Launched: #{launch_nukes?(status)}\n"
OUTPUT IS:
=> Status: peace
=> Nukes Launched: true
Your question is "why does the variable change?"
The answer is because buggy_logger holds a reference to status. Easily proven by inspecting the object_id.
irb(main):001:0> a = "hi"
=> "hi"
irb(main):002:0> a.object_id
=> 24088560
irb(main):003:0> b = a
=> "hi"
irb(main):004:0> b.object_id
=> 24088560
irb(main):005:0>
To create a copy use + or any non-mutating operator. Don't use <<.
irb(main):010:0> c = a + " guys"
=> "hi guys"
irb(main):011:0> c.object_id
=> 26523040
irb(main):012:0>
Since status = "peace" is a string, when buggy_logger << "\n" is ran, it's updating the string of buggy_logger (and subsequently, status) to be "peace\n"
Therefore, when the method is ran, it returns true because status != "peace" anymore.
Now, if in the beginning you used a symbol status = :peace, it would not be able to be altered with the "\n" appendage. Therefore, the method would return false because status == :peace
Symbol version:
status = :peace
buggy_logger = status
print "Status: "
#print buggy_logger << "\n" # This is no longer possible. It will cause an error
def launch_nukes?(status)
unless status == :peace
return true
else
return false
end
end
print "Nukes Launched: #{launch_nukes?(status)}\n" # <- Returns FALSE

Replace characters from string without changing its object_id in Ruby

How can I replace characters from string without changing its object_id?
For example:
string = "this is a test"
The first 7 characters need to be replaced with capitalized characters like: "THIS IS a Test" and the object_id needs to be the same. In which way can I sub or replace the characters to make it happen?
You can do it like this:
string = "this is a test"
string[0, 7] = string[0, 7].upcase
With procedural languages, one might write the equivalent of:
string = "this is in jest"
string.object_id
#=> 70309969974760
(1..7).each { |i| string[i] = string[i].upcase }
#=> 1..7
string
#=> "tHIS IS in jest"
string.object_id
#=> 70309969974760
This is not very Ruby-like, but it does offer the advantage over #sawa's solution that it does not create a temporary 7-character string. (Well, it does create a one-character string.) This is unimportant for strings of reasonable length (and for those I'd certainly concur with sawa), but it could be significant for really, really, really long strings.
Another way to do this is as follows:
string.each_char.with_index { |c,i|
string[i] = string[i].upcase if (1..7).cover?(i) }
#=> "tHIS IS in jest"
string.object_id
#=> 70309969974760
This second way might be more efficient if string is not much larger than string[start_index..end_index].
Edit:
In a comment the OP indicates that the string is to be stripped, squeeze and reversed as well as certain characters converted to upper case. That could be done on the string in place, without creating a copy, as follows:
def strip_upcase_squeeze_reverse_whew(string, upcase_range, squeeze_str=nil)
string.strip!
upcase_range.each { |i| string[i] = string[i].upcase }
squeeze_str.nil? ? string.squeeze! : string.squeeze!(squeeze_str)
string.reverse!
end
I have assumed the four operations would be performed in a particular order, but if the order should be different, that's an easy fix.
string = " this may bee inn jest, butt it's alsoo a test "
string.object_id
#=> 70309970103280
strip_upcase_squeeze_reverse_whew(string, (1..7))
#=> "tset a osla s'ti tub ,tsej ni eb YAM SIHt"
string.object_id
#=> 70309970103280
The steps:
string = "this may bee inn jest, butt it's alsoo a test"
#=> "this may bee inn jest, butt it's alsoo a test"
upcase_range = (1..7)
#=> 1..7
string.strip!
#=> nil
string
#=> "this may bee inn jest, butt it's alsoo a test"
upcase_range.each { |i| string[i] = string[i].upcase }
#=> 1..7
string
#=> "tHIS MAY bee inn jest, butt it's alsoo a test"
squeeze_str.nil? ? string.squeeze! : string.squeeze!(squeeze_str)
#=> "tHIS MAY be in jest, but it's also a test"
string
#=> "tHIS MAY be in jest, but it's also a test"
string.reverse!
#=> "tset a osla s'ti tub ,tsej ni eb YAM SIHt"
Notice that in this example, strip! does not remove any characters, and therefore returns nil. Similarly, squeeze! would return nil if there is nothing to squeeze. It is for that reason that strip! and squeeze cannot be chained.
A second example:
string = " thiiiis may beeee in jeeest"
strip_upcase_squeeze_reverse_whew(string, (12..14), "aeiouAEIOU")
Adding onto a string without changing its object id:
foo = "foo"
# => "foo"
foo.object_id
# => 70196045363960
foo << "bar"
# => "foobar"
foo.object_id
# => 70196045363960
Replace an entire string without changing its object id
foo
# => "foo"
foo.object_id
# => 70196045363960
foo.gsub!(/./, '') << 'bar'
# => 'bar'
foo.object_id
# => 70196045363960
Replace part of a string without changing its object id
foo
# => "foo"
foo.object_id
# => 70196045363960
foo.gsub!(/o/, 'z')
# => 'fzz'
foo.object_id
# => 70196045363960

Setting end-of-line character for puts

I have an array of entries I would like to print.
Being arr the array, I used just to write:
puts arr
Then I needed to use the DOS format end-of-line: \r\n, so I wrote:
arr.each { |e| print "#{e}\r\n" }
This works correctly, but I would like to know if there is a way to specify what end-of-line format to use so that I could write something like:
$eol = "\r\n"
puts arr
UPDATE 1
I know that puts will use the correct line-endings depending on the platform it is run on, but I need this because I will write the output to a file.
UPDATE 2
As Mark suggested, setting $\ is useful. Anyway it just works for print.
For example,
irb(main):001:0> a = [1, 2, 3]
=> [1, 2, 3]
irb(main):002:0> $\ = "\r\n"
=> "\r\n"
irb(main):003:0> print a
123
=> nil
irb(main):004:0> puts a
1
2
3
=> nil
print prints all array items on a single line and then add $\, while I would like the behaviour of puts: adding $\ after each item of the array.
Is this possible at all without using Array#each?
The Ruby variable $\ will set the record separator for calls to print and write:
>> $\ = '!!!'
=> "!!!"
>> print 'hi'
hi!!!=> nil
Alternatively you can refer to $\ as $OUTPUT_RECORD_SEPARATOR if you import the English module.
Kernel#puts is equivalent to STDOUT.puts; and IO.puts "writes a newline after every element that does not already end with a newline sequence". So you're out of luck with pure puts for arrays. However, the $, variable is the separator string output between parameters suck as Kernel#print and Array#join. So if you can handle calling print arr.join, this might be the best solution for what you're doing:
>> [1,2,3].join
=> "123"
>> $, = '---'
=> "---"
>> [1,2,3].join
=> "1---2---3"
>> $\ = '!!!'
=> "!!!"
>> print [1,2,3].join
1---2---3!!!=> nil

Binary or "|" in ruby

Why isnt that working:
>> s = "hi"
=> "hi"
>> s == ("hi"|"ho")
NoMethodError: undefined method `|' for "hi":String
from (irb):2
>>
I don't get it.. Is there a solution for this kind of syntax? Because
s == ("hi"|"ho")
#is shorther than
s == "hi" || s == "ho"
Yes, the bitwise operator | is not defined in the String class: http://ruby-doc.org/core/classes/String.html
Consider this for expressiveness:
["hi", "ho"].include? myStr
irb(main):001:0> s = "hi"
=> "hi"
irb(main):002:0> ["hi", "ho"]
=> ["hi", "ho"]
irb(main):003:0> ["hi", "ho"].include? s
=> true
irb(main):004:0> s = "foo"
=> "foo"
irb(main):005:0> ["hi", "ho"].include? s
=> false
In most high level languages that syntax will not work, you have to stick to the longer syntax of:
s == "hi" || s == "ho"
Note that | is a bitwise or, whereas || is a regular or
You could use the include? method on array if you've got several == tests to do:
["hi", "ho"].include?(s)
Not shorter for two checks admittedly but it will be shorter for three or more.
This syntax doesn't exist in any language as far as I know.
What you are saying
s == ("hi"|"ho")
Literally translates to 'bitwise OR the strings "hi" and "ho" together and then compare them with s'. If you can't see why this is not what you are looking for, try writing down the ASCII codes for "hi" and "ho" and then bitwise ORing them together. You are going to get complete gibberish.
You could make it work that way:
irb> class Pair
def initialize(strA,strB)
#strA,#strB = strA,strB
end
def ==(string)
string == #strA || string == #strB
end
def |(other)
Pair.new(self,other)
end
end
#=> nil
irb> class String
def |(other)
Pair.new(self,other)
end
alias old_equals :==
def ==(other)
if other.kind_of? Pair
other == self
else
old_equals other
end
end
end
#=> nil
irb> ("one"|"two") == "one"
#=> true
irb> ("one"|"two") == "two"
#=> true
irb> ("one"|"two") == "three"
#=> false
irb> "one" == ("one"|"two")
#=> true
irb> "three" == ("one"|"two"|"three")
#=> true
But since this involves some monkey-patching of a fairly lowlevel class, I wouldn't advise relying on it. Other people will hate reading your code.
Ruby supports binary 'or' and other binary operations on values of type Fixnum and Bignum, meaning any integer. Bitwise operations aren't supported on strings or any other type, as far as I know.
As other people have mentioned, you probably want something other than binary operations altogether. However, you can easily get integer representations of characters, so you can compare characters like so:
a = "Cake"
b = "Pie"
puts a[0] | b[0] # Prints "83" - C is 67 and P is 80.
You can get an array of the comparisons easily with some conversions.
a = "Cake"
b = "Pie " # Strings of uneven length is trivial but more cluttered.
a_arr = a.split(//)
b_arr = b.split(//)
c_arr = []
a.each_with_index { |char, i| c.push(a[i].to_i | b[i].to_i) }
# If you *really* want an ASCII string back...
c = c_arr.collect(&:chr).join
You could use a regex:
Like so:
regex = /hi|ho/
s = "hi"
t = "foo"
s =~ regex
#=> 0
t =~ regex
#=> nil

Resources