How to find a bigger version number? - ruby

When compare two version strings, to_f does not work well:
> "1.5.8".to_f > "1.5.7".to_f
=> false
string comparison is better, but not always correct:
> "1.5.8" > "1.5.7"
=> true
> "1.5.8" > "1.5.9"
=> false
> "1.5.8" > "1.5.10" # oops!
=> true
How to compare version strings properly?

An idea: create a Object#compare_by method that behaves like compare (aka the spaceship operator Object#<=>) but takes a custom block:
class Object
def compare_by(other)
yield(self) <=> yield(other)
end
end
>> "1.5.2".compare_by("1.5.7") { |s| s.split(".").map(&:to_i) }
#=> -1
You can also take a more specific approach still based on the compare method:
class String
def compare_by_fields(other, fieldsep = ".")
cmp = proc { |s| s.split(fieldsep).map(&:to_i) }
cmp.call(self) <=> cmp.call(other)
end
end
>> "1.5.8".compare_by_fields("1.5.8")
#=> 0

Personally I'd probably just use the Versionomy gem, no need to reinvent this specific wheel IMHO.
Example:
require 'versionomy'
v1 = Versionomy.parse("1.5.8")
v2 = Versionomy.parse("1.5.10")
v2 > v1
#=> true

First start off by splitting the different parts of the versions:
v1 = "1.5.8"
v2 = "1.5.7"
v1_arr = v1.split(".")
=> ["1", "5", "8"]
v2_arr = v2.split(".")
=> ["1", "5", "7"]
v1_arr.size.times do |index|
if v1_arr[index] != v2_arr[index]
# compare the values at the given index. Don't forget to use to_i!
break
end
end

Related

Ruby merge duplicates in string

If I have a string like this
str =<<END
7312357006,1.121
3214058234,3456
7312357006,1234
1324958723,232.1
3214058234,43.2
3214173443,234.1
6134513494,23.2
7312357006,11.1
END
If a number in the first value shows up again, I want to add their second values together. So the final string would look like this
7312357006,1246.221
3214058234,3499.2
1324958723,232.1
3214173443,234.1
6134513494,23.2
If the final output is an array that's fine too.
There are lots of ways to do this in Ruby. One particularly terse way is to use String#scan:
str = <<END
7312357006,1.121
3214058234,3456
7312357006,1234
1324958723,232.1
3214058234,43.2
3214173443,234.1
6134513494,23.2
7312357006,11.1
END
data = Hash.new(0)
str.scan(/(\d+),([\d.]+)/) {|k,v| data[k] += v.to_f }
p data
# => { "7312357006" => 1246.221,
# "3214058234" => 3499.2,
# "1324958723" => 232.1,
# "3214173443" => 234.1,
# "6134513494" => 23.2 }
This uses the regular expression /(\d+),([\d.]+)/ to extract the two values from each line. The block is called with each pair as arguments, which are then merged into the hash.
This could also be written as a single expression using each_with_object:
data = str.scan(/(\d+),([\d.]+)/)
.each_with_object(Hash.new(0)) {|(k,v), hsh| hsh[k] += v.to_f }
# => (same as above)
There are likewise many ways to print the result, but here are a couple I like:
puts data.map {|kv| kv.join(",") }.join("\n")
# => 7312357006,1246.221
# 3214058234,3499.2
# 1324958723,232.1
# 3214173443,234.1
# 6134513494,23.2
# or:
puts data.map {|k,v| "#{k},#{v}\n" }.join
# => (same as above)
You can see all of these in action on repl.it.
Edit: Although I don't recommend either of these for the sake of readability, here's more just for kicks (requires Ruby 2.4+):
data = str.lines.group_by {|s| s.slice!(/(\d+),/); $1 }
.transform_values {|a| a.sum(&:to_f) }
...or, to going straight to a string:
puts str.lines.group_by {|s| s.slice!(/(\d+),/); $1 }
.map {|k,vs| "#{k},#{vs.sum(&:to_f)}\n" }.join
Since repl.it is stuck on Ruby 2.3: Try it online!
You could achieve this using each_with_object, as below:
str = "7312357006,1.121
3214058234,3456
7312357006,1234
1324958723,232.1
3214058234,43.2
3214173443,234.1
6134513494,23.2
7312357006,11.1"
# convert the string into nested pairs of floats
# to briefly summarise the steps: split entries by newline, strip whitespace, split by comma, convert to floats
arr = str.split("\n").map(&:strip).map { |el| el.split(",").map(&:to_f) }
result = arr.each_with_object(Hash.new(0)) do |el, hash|
hash[el.first] += el.last
end
# => {7312357006.0=>1246.221, 3214058234.0=>3499.2, 1324958723.0=>232.1, 3214173443.0=>234.1, 6134513494.0=>23.2}
# You can then call `to_a` on result if you want:
result.to_a
# => [[7312357006.0, 1246.221], [3214058234.0, 3499.2], [1324958723.0, 232.1], [3214173443.0, 234.1], [6134513494.0, 23.2]]
each_with_object iterates through each pair of data, providing them with access to an accumulator (in this the hash). By following this approach, we can add each entry to the hash, and add together the totals if they appear more than once.
Hope that helps - let me know if you've any questions.
def combine(str)
str.each_line.with_object(Hash.new(0)) do |s,h|
k,v = s.split(',')
h.update(k=>v.to_f) { |k,o,n| o+n }
end.reduce('') { |s,kv_pair| s << "%s,%g\n" % kv_pair }
end
puts combine str
7312357006,1246.22
3214058234,3499.2
1324958723,232.1
3214173443,234.1
6134513494,23.2
Notes:
using String#each_line is preferable to str.split("\n") as the former returns an enumerator whereas the latter returns a temporary array. Each element generated by the enumerator is line of str that (unlike the elements of str.split("\n")) ends with a newline character, but that is of no concern.
see Hash::new, specifically when a default value (here 0) is used. If a hash has been defined h = Hash.new(0) and h does not have a key k, h[k] returns the default value, zero (h is not changed). When Ruby encounters the expression h[k] += 1, the first thing she does is expand it to h[k] = h[k] + 1. If h has been defined with a default value of zero, and h does not have a key k, h[k] on the right of the equality (syntactic sugar1 for h.[](k)) returns zero.
see Hash#update (aka merge!). h.update(k=>v.to_f) is syntactic sugar for h.update({ k=>v.to_f })
see Kernel#sprint for explanations of the formatting directives %s and %g.
the receiver for the expression reduce('') { |s,kv_pair| s << "%s,%g\n" % kv_pair } (in the penultimate line), is the following hash.
{"7312357006"=>1246.221, "3214058234"=>3499.2, "1324958723"=>232.1,
"3214173443"=>234.1, "6134513494"=>23.2}
1 Syntactic sugar is a shortcut allowed by Ruby.
Implemented this solution as hash was giving me issues:
d = []
s.split("\n").each do |line|
x = 0
q = 0
dup = false
line.split(",").each do |data|
if x == 0 and d.include? data then dup = true ; q = d.index(data) elsif x == 0 then d << data end
if x == 1 and dup == false then d << data end
if x == 1 and dup == true then d[q+1] = "#{'%.2f' % (d[q+1].to_f + data.to_f).to_s}" end
if x == 2 and dup == false then d << data end
x += 1
end
end
x = 0
s = ""
d.each do |val|
if x == 0 then s << "#{val}," end
if x == 1 then s << "#{val}\n ; x = 0" end
x += 1
end
puts(s)

Deleting a portion from a string in ruby

I just want to delete a portion from a string.
My string: "&product=Software"
Need output: "Software"
Tried delete, split , slice but doesn't work. Can someone help me on this? I am very very new to Ruby.
It's slightly surprising, but Ruby lets you use [] and assignment to "overwrite" the substring you want to replace:
x = "&product=Software"
x['&product='] = ''
x # "Software"
str = "&product=Software"
str['&product='] = '' # method 1
str.sub!('&product=', '') # method 2
But if you wanna be smarter about it...
str = '&product=Software&price=19.99'
h = {}
str.split('&').each do |s|
next if s.length == 0
key, val = s.split '='
h[key] = val
end
puts h # {"product"=>"Software", "price"=>"19.99"}
Another two ways of achieving this:
Using split:
2.3.0 :014 > "&product=software".split('=')[1]
=> "software"
Using sub:
2.3.0 :015 > "&product=software".sub(/^.*?=/,'')
=> "software"

Ruby: how to split a string on a regex while keeping the delimiters? [duplicate]

This question already has answers here:
How do I keep the delimiters when splitting a Ruby string?
(5 answers)
Closed 7 years ago.
This has been asked multiple times around here, but never got a generic answer, so here we go:
Say you have a string, any string, but let's go with "oruh43451rohcs56oweuex59869rsr", and you want to split it with a regular expression. Any regular expression, but let's go with a sequence of digits: /\d+/. Then you'd use split:
"oruh43451rohcs56oweuex59869rsr".split(/\d+/)
# => ["oruh", "rohcs", "oweuex", "rsr"]
That's lovely and all, but I want the digits. So for that we have scan:
"oruh43451rohcs56oweuex59869rsr".scan(/\d+/)
# => ["43451", "56", "59869"]
But I want it all! Is there, say, a split_and_scan? Nope.
How about I split and scan then zip them? Let me stop you right there.
Ok, so how?
If split's pattern contains a capture group, the group will be included in the resulting array.
str = "oruh43451rohcs56oweuex59869rsr"
str.split(/(\d+)/)
# => ["oruh", "43451", "rohcs", "56", "oweuex", "59869", "rsr"]
If you want it zipped,
str.split(/(\d+)/).each_slice(2).to_a
# => [["oruh", "43451"], ["rohcs", "56"], ["oweuex", "59869"], ["rsr"]]
I'm glad you asked… well, there's String#shatter from Facets. I don't love it because it's implemented using trickery (look at the source, it's cute clever trickery, but what if your string actually contains a "\1"?).
So I rolled my own. Here's what you get:
"oruh43451rohcs56oweuex59869rsr".unjoin(/\d+/)
# => ["oruh", "43451", "rohcs", "56", "oweuex", "59869", "rsr"]
And here's the implementation:
class Object
def unfold(&f)
(m, n = f[self]).nil? ? [] : n.unfold(&f).unshift(m)
end
end
class String
def unjoin(rx)
unfold do |s|
next if s.empty?
ix = s =~ rx
case
when ix.nil?; [s , ""]
when ix == 0; [$&, $']
when ix > 0; [$`, $& + $']
end
end
end
end
(verbosier version at the bottom)
And here are some examples of corner cases being handled:
"".unjoin(/\d+/) # => []
"w".unjoin(/\d+/) # => ["w"]
"1".unjoin(/\d+/) # => ["1"]
"w1".unjoin(/\d+/) # => ["w", "1"]
"1w".unjoin(/\d+/) # => ["1", "w"]
"1w1".unjoin(/\d+/) # => ["1", "w", "1"]
"w1w".unjoin(/\d+/) # => ["w", "1", "w"]
And that's it, but here's more…
Or, if you don't like mucking with the built-in classes… well, you could use Refinements… but if you really don't like it, here it is as functions:
def unfold(x, &f)
(m, n = f[x]).nil? ? [] : unfold(n, &f).unshift(m)
end
def unjoin(s, rx)
unfold(s) do |s|
next if s.empty?
ix = s =~ rx
case
when ix.nil?; [s , ""]
when ix == 0; [$&, $']
when ix > 0; [$`, $& + $']
end
end
end
It also occurs to me that it may not always be clear which are the separators and which are the separated bits, so here's a little addition that lets you query a string with #joint? to know what role it played before the split:
class String
def joint?
false
end
class Joint < String
def joint?
true
end
end
def unjoin(rx)
unfold do |s|
next if s.empty?
ix = s =~ rx
case
when ix.nil?; [s, ""]
when ix == 0; [Joint.new($&), $']
when ix > 0; [$`, $& + $']
end
end
end
end
and here it is in use:
"oruh43451rohcs56oweuex59869rsr".unjoin(/\d+/)\
.map { |s| s.joint? ? "(#{s})" : s }.join(" ")
# => "oruh (43451) rohcs (56) oweuex (59869) rsr"
You can now easily reimplement split and scan:
class String
def split2(rx)
unjoin(rx).reject(&:joint?)
end
def scan2(rx)
unjoin(rx).select(&:joint?)
end
end
"oruh43451rohcs56oweuex59869rsr".split2(/\d+/)
# => ["oruh", "rohcs", "oweuex", "rsr"]
"oruh43451rohcs56oweuex59869rsr".scan2(/\d+/)
# => ["43451", "56", "59869"]
And if you hate match globals and general brevity…
class Object
def unfold(&map_and_next)
result = map_and_next.call(self)
return [] if result.nil?
mapped_value, next_value = result
[mapped_value] + next_value.unfold(&map_and_next)
end
end
class String
def unjoin(regex)
unfold do |tail_string|
next if tail_string.empty?
match = tail_string.match(regex)
index = match.begin(0)
case
when index.nil?; [tail_string, ""]
when index == 0; [match.to_s, match.post_match]
when index > 0; [match.pre_match, match.to_s + match.post_match]
end
end
end
end

Match a pattern in an array

There is an array with 2 elements
test = ["i am a boy", "i am a girl"]
I want to test if a string is found inside the array elements, say:
test.include("boy") ==> true
test.include("frog") ==> false
Can i do it like that?
Using Regex.
test = ["i am a boy" , "i am a girl"]
test.find { |e| /boy/ =~ e } #=> "i am a boy"
test.find { |e| /frog/ =~ e } #=> nil
Well you can grep (regex) like this:
test.grep /boy/
or even better
test.grep(/boy/).any?
Also you can do
test = ["i am a boy" , "i am a girl"]
msg = 'boy'
test.select{|x| x.match(msg) }.length > 0
=> true
msg = 'frog'
test.select{|x| x.match(msg) }.length > 0
=> false
I took Peters snippet and modified it a bit to match on the string instead of the array value
ary = ["Home:Products:Glass", "Home:Products:Crystal"]
string = "Home:Products:Glass:Glasswear:Drinking Glasses"
USE:
ary.partial_include? string
The first item in the array will return true, it does not need to match the entire string.
class Array
def partial_include? search
self.each do |e|
return true if search.include?(e.to_s)
end
return false
end
end
If you don't mind to monkeypatch the the Array class you could do it like this
test = ["i am a boy" , "i am a girl"]
class Array
def partial_include? search
self.each do |e|
return true if e[search]
end
return false
end
end
p test.include?("boy") #==>false
p test.include?("frog") #==>false
p test.partial_include?("boy") #==>true
p test.partial_include?("frog") #==>false
If you want to test if a word included into the array elements, you can use method like this:
def included? array, word
array.inject([]) { |sum, e| sum + e.split }.include? word
end

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