Why does comparing frozen Gem versions fail? - ruby

I am writing something in Ruby that needs to compare versions in order to determine whether or not something needs to be updated.
But when I run current_version <=> desired_version and at least one of the versions is frozen, I get:
4: from .../ruby/2.6.0/rubygems/version.rb:344:in `<=>'
3: from .../ruby/2.6.0/rubygems/version.rb:371:in `canonical_segments'
2: from .../ruby/2.6.0/rubygems/version.rb:393:in `_split_segments'
1: from .../ruby/2.6.0/rubygems/version.rb:387:in `_segments'
FrozenError (can't modify frozen Gem::Version)
According to the docs, the source code is this:
def <=>(other)
return unless Gem::Version === other
return 0 if #version == other._version || canonical_segments == other.canonical_segments
lhsegments = _segments
rhsegments = other._segments
lhsize = lhsegments.size
rhsize = rhsegments.size
limit = (lhsize > rhsize ? lhsize : rhsize) - 1
i = 0
while i <= limit
lhs, rhs = lhsegments[i] || 0, rhsegments[i] || 0
i += 1
next if lhs == rhs
return -1 if String === lhs && Numeric === rhs
return 1 if Numeric === lhs && String === rhs
return lhs <=> rhs
end
return 0
end
I don't see why this code would be mutating the state of the Gem. Is there something that I'm missing?

The error tells you where: The <=> method calls canonical_segments, which calls _split_segments, which calls _segments. So that's where the mutation must be happening; not directly in the method you've copied in the post.
More specifically, here's the offending source code:
def canonical_segments
#canonical_segments ||=
_split_segments.map! do |segments|
segments.reverse_each.drop_while {|s| s == 0 }.reverse
end.reduce(&:concat)
end
protected
def _version
#version
end
def _segments
# segments is lazy so it can pick up version values that come from
# old marshaled versions, which don't go through marshal_load.
# since this version object is cached in ##all, its #segments should be frozen
#segments ||= #version.scan(/[0-9]+|[a-z]+/i).map do |s|
/^\d+$/ =~ s ? s.to_i : s
end.freeze
end
def _split_segments
string_start = _segments.index {|s| s.is_a?(String) }
string_segments = segments
numeric_segments = string_segments.slice!(0, string_start || string_segments.size)
return numeric_segments, string_segments
end

Related

Scripting speed test for formatting a string

I am formatting a phone number, and there can be no single digit after a dash, i.e. 123-555-5555 or 12-34, but not 123-4. There can also be any alpha characters in the answer.
Here is my answer.
class CodeTestException < Exception; end
# driver method
def phone_format(s)
string = s.to_s.gsub(/[^0-9]/, '') # force input to string
string_length = string.length
# ensure that the return type stays consistent and make sure that there isn't
# one digit by itself as specified by the API
return string unless string_length > 2
format_phone_string(string, string_length)
end
private
def format_phone_string(string, string_length)
formatted_string = ""
early_dash = string_length % 3 == 1
skip_dash = false
# start index at 1 for easier comprehension
i = 0
string.each_char do |char|
formatted_string << char
break if i == string_length
i += 1
formatted_string << '-' && skip_dash = true if early_dash && (i == string_length - 2)
formatted_string << '-' if i % 3 == 0 && !skip_dash
end
formatted_string
end
raise CodeTestException unless phone_format("(+1) 888 33x19") == "188-833-19"
raise CodeTestException unless phone_format("555 123 1234") == "555-123-12-34"
raise CodeTestException unless phone_format("(+1)") == '1'
raise CodeTestException unless phone_format(nil) == ""
raise CodeTestException unless phone_format("") == ""
I was told that the answer was not recursion, or scan. I believe O(n) is the best time that I can make. Some other solutions that I found was a higher multiple of linear time. Can anyone beat my solution?

How to refactor this code from two methods down to one method

I have the following two methods that are very similar:
def space_before_element?(start_element)
element = start_element.previous_element
until element.nil? ||
(element.name == start_element.name || "r" && !element.text.empty?)
element = element.previous_element
end
character = element
.text
.split(/(\W)/)
.compact
.reject(&:empty?)
.last
.last_character \
unless element.nil?
element.nil? ||
(character.punctuation? && !character.hyphen? && !character.apostrophe?) ||
character.spaces?
end
def space_after_element?(start_element)
element = start_element.next_element
until element.nil? ||
(element.name == start_element.name || "r" && !element.text.empty?)
element = element.next_element
end
character = element
.text
.split(/(\W)/)
.compact
.reject(&:empty?)
.last
.first_character \
unless element.nil?
element.nil? ||
(character.punctuation? && !character.hyphen? && !character.apostrophe?) ||
character.spaces?
end
I can't seem to figure out how to make the necessary changes that would allow me to reduce this down to one method.
Still getting my Ruby skills going.
Any help appreciated.
Ruby 2.2.3
I counted three differences. So, these differences should be explicitly declared:
METHODS = {
prev: [:previous_element, %i|last last_character|],
next: [:next_element, %i|first first_character|]
}.freeze
Now we just pass a parameter to the method:
def space_around_element?(start_element, prev_or_next = :prev)
element = start_element.public_send(METHODS[prev_or_next].first)
# same code
character = element
.text
.split(/(\W)/)
.reject(&:empty?)
.public_send(METHODS[prev_or_next].last.first)
.public_send(METHODS[prev_or_next].last.last) \
unless element.nil?
# same code
end

Why does `String#delete` return an error in my ruby code?

The goal is to take a string and return the most common letter along with it's count. For string 'hello', it would return ['l', 2].
I've written the following:
def most_common_letter(string)
list = []
bigcount = 0
while 0 < string.length
count = 0
for i in 0..string.length
if string[0] == string[i]
count += 1
end
end
if count > bigcount
bigcount = count
list = (string[0])
string.delete[string[0]]
end
end
return [list,bigcount]
end
I get the following error:
wrong number of arguments (0 for 1+)
(repl):14:in `delete'
(repl):14:in `most_common_letter'
(repl):5:in `initialize'
Please help me understand what I'm doing wrong with the delete statement, or what else is causing this to return an error.
I have a solution done another way, but I thought this would work just fine.
you are using the delete function wrong
Use string.delete(string[0]) instead of string.delete[string[0]]
EDIT
As for the infinite loop you mentioned.
Your condition for while is 0 < string.length
And you expect the string.delete[string[0]] statement to actually delete a character at a time.
But what exactly it does is, it deletes a character and returns the new string, but it never actually mutates/changes the actual string.
So try changing it to string = string.delete[string[0]]
Apart from using delete() instead of delete[] which has already been answered...
Most of what you need is implemented in Ruby's String class natively. each_char and count.
def most_common_letter(string)
max = [ nil, 0 ]
string.each_char {|char|
char_count = string.count(char)
max = [ char, char_count ] if char_count > max[1]
}
return max
end
You may do this in a much easier way, if you allow me to say.
def most_common_letter(string)
h = Hash.new
string.chars.sort.map { |c|
h[c] = 0 if (h[c].nil?)
h[c] = h[c] + 1
}
maxk = nil
maxv = -1
mk = h.keys
mk.each do |k|
if (h[k] > maxv) then
maxk = k
maxv = h[k]
end
end
[ maxk , maxv ]
end
If you test this with
puts most_common_letter("alcachofra")
the result will be
[ 'a', 3 ]
Finally, remember you don't need a return in the end of a Ruby method. The last value assigned is automatically returned.
Do Ruby in a Ruby way!

Find the first differing character between two Strings in Ruby

I have two strings.
str_a = "the_quick_brown_fox"
str_b = "the_quick_red_fox"
I want to find the first index at which the two strings differ (i.e. str_a[i] != str_b[i]).
I know I could solve this with something like the following:
def diff_char_index(str_a, str_b)
arr_a, arr_b = str_a.split(""), str_b.split("")
return -1 unless valid_string?(str_a) && valid_string?(str_b)
arr_a.each_index do |i|
return i unless arr_a[i] == arr_b[i]
end
end
def valid_string?(str)
return false unless str.is_a?(String)
return false unless str.size > 0
true
end
diff_char_index(str_a, str_b) # => 10
Is there a better way to do this?
Something like this ought to work:
str_a.each_char.with_index
.find_index {|char, idx| char != str_b[idx] } || str_a.size
Edit: It works: http://ideone.com/Ttwu1x
Edit 2: My original code returned nil if str_a was shorter than str_b. I've updated it to work correctly (it will return str_a.size, so if e.g. the last index in str_a is 3, it will return 4).
Here's another method that may strike some as slightly simpler:
(0...str_a.size).find {|i| str_a[i] != str_b[i] } || str_a.size
http://ideone.com/275cEU
i = 0
i += 1 while str_a[i] and str_a[i] == str_b[i]
i
str_a = "the_quick_brown_dog"
str_b = "the_quick_red_dog"
(0..(1.0)/0).find { |i| (str_a[i] != str_b[i]) || str_a[i].nil? }
#=> 10
str_a = "the_quick_brown_dog"
str_b = "the_quick_brown_dog"
(0..(1.0)/0).find { |i| (str_a[i] != str_b[i]) || str_a[i].nil? }
#=> 19
str_a.size
#=> 19
This uses a binary search to find the index where a slice of str_a no longer occurs at the beginning of str_b:
(0..str_a.length).bsearch { |i| str_b.rindex(str_a[0..i]) != 0 }

Nil when calling a method on the variable, but not nil when calling just the variable

Don't understand why #nums.pop won't work in the value method. It seems to tell me that it can't do that for nil, but if I just say #nums, it shows that there is indeed something in the array. So then why can't I pop it out?
class RPNCalculator
def initialize
#value = value
nums ||= []
#nums = nums
end
def push(num)
#nums << num
end
def plus
if #nums[-2] == nil || #nums[-1] == nil
raise "calculator is empty"
else
#value = #nums.pop + #nums.pop
#nums.push(#value)
end
end
def minus
if #nums[-2] == nil || #nums[-1] == nil
raise "calculator is empty"
else
#value = #nums[-2] - #nums[-1]
#nums.pop(2)
#nums.push(#value)
end
end
def divide
if #nums[-2] == nil || #nums[-1] == nil
raise "calculator is empty"
else
#value = #nums[-2].to_f / #nums[-1].to_f
#nums.pop(2)
#nums.push(#value)
end
end
def times
if #nums[-2] == nil || #nums[-1] == nil
raise "calculator is empty"
else
#value = #nums.pop.to_f * #nums.pop.to_f
#nums.push(#value)
end
end
def value
#nums #Don't understand why #nums.pop won't work here
end
def tokens(str)
str.split(" ").map { |char| (char.match(/\d/) ? char.to_i : char.to_sym)}
end
def evaluate(str)
tokens(str).each do |x|
if x == ":-"
minus
elsif x == ":+"
plus
elsif x == ":/"
divide
elsif x ==":*"
times
else
push(x)
end
end
value
end
end
Error relates to the following part of a spec:
it "adds two numbers" do
calculator.push(2)
calculator.push(3)
calculator.plus
calculator.value.should == 5
end
Error says either:
Failure/Error: calculator.value.should == 5
expected: 5
got: [5] <using ==>
OR if .pop is used
Failure/Error: #calculator = RPNCalculator.new
NoMethodError:
undefined method 'pop' for nil:NilClass
Your initialize method assigning #value = value calls the function at def value which returns #nums which has not yet been created in initialize since #nums is created afterwards with nums ||= []; #nums = nums therefore it's nil. This is why .pop won't work.
You've created #nums as an array with nums ||= [] and you're using it with push and pop so why are you checking for the value with value.should == 5 (Integer) when calling value returns an (Array). You would need to write it like value.first.should == 5 or value[0].should == 5 ... otherwise you should change value to return just the element you want
def value
#nums.pop # or #nums[0], or #nums.first or #nums.last however you plan on using it
end
The problem is #value = value in your initialize method. Fix that then you can add the .pop in value.
EDIT
Also your evaluation is calling methods before you've populated #nums with the values. Then the methods "raise" errors. You can't call minus after only one value has been pushed to #nums.
Here's how I would do the flow for splitting the string
# Multiplication and Division need to happen before addition and subtraction
mylist = "1+3*7".split(/([+|-])/)
=> ["1", "+", "3*7"]
# Compute multiplication and division
mylist = mylist.map {|x| !!(x =~ /[*|\/]/) ? eval(x) : x }
=> ["1", "+", 21]
# Do the rest of the addition
eval mylist.join
=> 22
I realize this isn't exactly how you're going about solving this... but I think splitting by order of mathematical sequence will be the right way to go. So first evaluate everything between (), then only multiplication and division, then all addition and subtraction.
EDIT I just looked into what a RPN Calculator is. So don't mind my splitting recommendation as it doesn't apply.

Resources