Get a list of all the prefixes of a string - ruby

is there any inbuilt function in the Ruby String class that can give me all the prefixes of a string in Ruby. Something like:
"ruby".all_prefixes => ["ruby", "rub", "ru", "r"]
Currently I have made a custom function for this:
def all_prefixes search_string
dup_string = search_string.dup
return_list = []
while(dup_string.length != 0)
return_list << dup_string.dup
dup_string.chop!
end
return_list
end
But I am looking for something more rubylike, less code and something magical.
Note: of course it goes without saying original_string should remain as it is.

No, there is no built-in method for this. You could do it like this:
def all_prefixes(string)
string.size.times.collect { |i| string[0..i] }
end
all_prefixes('ruby')
# => ["r", "ru", "rub", "ruby"]

A quick benchmark:
require 'fruity'
string = 'ruby'
compare do
toro2k do
string.size.times.collect { |i| string[0..i] }
end
marek_lipka do
(0...(string.length)).map{ |i| string[0..i] }
end
jorg_w_mittag do
string.chars.inject([[], '']) { |(res, memo), c|
[res << memo += c, memo]
}.first
end
jorg_w_mittag_2 do
acc = ''
string.chars.map {|c| acc += c }
end
stefan do
Array.new(string.size) { |i| string[0..i] }
end
end
And the winner is:
Running each test 512 times. Test will take about 1 second.
jorg_w_mittag_2 is faster than stefan by 19.999999999999996% ± 10.0%
stefan is faster than marek_lipka by 10.000000000000009% ± 10.0%
marek_lipka is faster than jorg_w_mittag by 10.000000000000009% ± 1.0%
jorg_w_mittag is similar to toro2k

def all_prefixes(str)
acc = ''
str.chars.map {|c| acc += c }
end

What about
str = "ruby"
prefixes = Array.new(str.size) { |i| str[0..i] } #=> ["r", "ru", "rub", "ruby"]

This is maybe a long shot, but if you want to find distinct abbreviations for a set of strings, you can use the Abbrev module:
require 'abbrev'
Abbrev.abbrev(['ruby']).keys
=> ["rub", "ru", "r", "ruby"]

A little bit shorter form:
def all_prefixes(search_string)
(0...(search_string.length)).map{ |i| search_string[0..i] }
end
all_prefixes 'ruby'
# => ["r", "ru", "rub", "ruby"]

def all_prefixes(str)
str.chars.inject([[], '']) {|(res, memo), c| [res << memo += c, memo] }.first
end

str = "ruby"
prefixes = str.size.times.map { |i| str[0..i] } #=> ["r", "ru", "rub", "ruby"]

Two not mentioned before and faster than those in the #toro2k's accepted comparison answer.
(1..s.size).map { |i| s[0, i] }
=> ["r", "ru", "rub", "ruby"]
Array.new(s.size) { |i| s[0, i+1] }
=> ["r", "ru", "rub", "ruby"]
Strangely, nobody used String#[start, length] before, only the slower String#[range].
And I think at least my first solution is quite straightforward.
Benchmark results (using Ruby 2.4.2):
user system total real
toro2k 14.594000 0.000000 14.594000 ( 14.724630)
marek_lipka 12.485000 0.000000 12.485000 ( 12.635404)
jorg_w_mittag 16.968000 0.000000 16.968000 ( 17.080315)
jorg_w_mittag_2 11.828000 0.000000 11.828000 ( 11.935078)
stefan 10.766000 0.000000 10.766000 ( 10.831517)
stefanpochmann 9.734000 0.000000 9.734000 ( 9.765227)
stefanpochmann 2 8.219000 0.000000 8.219000 ( 8.240854)
My benchmark code:
require 'benchmark'
string = 'ruby'
#n = 10**7
Benchmark.bm(20) do |x|
#x = x
def report(name, &block)
#x.report(name) {
#n.times(&block)
}
end
report('toro2k') {
string.size.times.collect { |i| string[0..i] }
}
report('marek_lipka') {
(0...(string.length)).map{ |i| string[0..i] }
}
report('jorg_w_mittag') {
string.chars.inject([[], '']) { |(res, memo), c|
[res << memo += c, memo]
}.first
}
report('jorg_w_mittag_2') {
acc = ''
string.chars.map {|c| acc += c }
}
report('stefan') {
Array.new(string.size) { |i| string[0..i] }
}
report('stefanpochmann') {
(1..string.size).map { |i| string[0, i] }
}
report('stefanpochmann 2') {
Array.new(string.size) { |i| string[0, i+1] }
}
end

Related

Array with hash, how to merge same keys and add its value

I have an array with hashes in it. If they have the same key I just want to add its value.
#receivers << result
#receivers
=> [{:email=>"user_02#yorlook.com", :amount=>10.00}]
result
=> {:email=>"user_02#yorlook.com", :amount=>7.00}
I want the result of above to look like this
[{:email=>"user_02#yorlook.com", :amount=>17.00}]
Does anyone know how to do this?
Here is the the entire method
def receivers
#receivers = []
orders.each do |order|
product_email = order.product.user.paypal_email
outfit_email = order.outfit_user.paypal_email
if order.user_owns_outfit?
result = { email: product_email, amount: amount(order.total_price) }
else
result = { email: product_email, amount: amount(order.total_price, 0.9),
email: outfit_email, amount: amount(order.total_price, 0.1) }
end
#receivers << result
end
end
Using Enumerable#group_by
#receivers.group_by {|h| h[:email]}.map do |k, v|
{email: k, amount: v.inject(0){|s,h| s + h[:amount] } }
end
# => [{:email=>"user_02#yorlook.com", :amount=>17.0}]
Using Enumerable#each_with_object
#receivers.each_with_object(Hash.new(0)) {|h, nh| nh[h[:email]]+= h[:amount] }.map do |k, v|
{email: k, amount: v}
end
# Output: [{ "em#il.one" => 29.0 }, { "em#il.two" => 39.0 }]
def receivers
return #receivers if #receivers
# Produces: { "em#il.one" => 29.0, "em#il.two" => 39.0 }
partial_result = orders.reduce Hash.new(0.00) do |result, order|
product_email = order.product.user.paypal_email
outfit_email = order.outfit_user.paypal_email
if order.user_owns_outfit?
result[product_email] += amount(order.total_price)
else
result[product_email] += amount(order.total_price, .9)
result[outfit_email] += amount(order.total_price, .1)
end
result
end
#receivers = partial_result.reduce [] do |result, (email, amount)|
result << { email => amount }
end
end
I would just write the code this way:
def add(destination, source)
if destination.nil?
return nil
end
if source.class == Hash
source = [source]
end
for item in source
target = destination.find {|d| d[:email] == item[:email]}
if target.nil?
destination << item
else
target[:amount] += item[:amount]
end
end
destination
end
usage:
#receivers = []
add(#receivers, {:email=>"user_02#yorlook.com", :amount=>10.00})
=> [{:email=>"user_02#yorlook.com", :amount=>10.0}]
add(#receivers, #receivers)
=> [{:email=>"user_02#yorlook.com", :amount=>20.0}]
a = [
{:email=>"user_02#yorlook.com", :amount=>10.0},
{:email=>"user_02#yorlook.com", :amount=>7.0}
]
a.group_by { |v| v.delete :email } # group by emails
.map { |k, v| [k, v.inject(0) { |memo, a| memo + a[:amount] } ] } # sum amounts
.map { |e| %i|email amount|.zip e } # zip to keys
.map &:to_h # convert nested arrays to hashes
From what I understand, you could get away with just .inject:
a = [{:email=>"user_02#yorlook.com", :amount=>10.00}]
b = {:email=>"user_02#yorlook.com", :amount=>7.00}
c = {email: 'user_03#yorlook.com', amount: 10}
[a, b, c].flatten.inject({}) do |a, e|
a[e[:email]] ||= 0
a[e[:email]] += e[:amount]
a
end
=> {
"user_02#yorlook.com" => 17.0,
"user_03#yorlook.com" => 10
}

Building Hash from Array of Hashes

I'm new to Ruby and trying to solve a problem. I have an array of hashes:
list = [{"amount"=>2.25,"rel_id"=>1103, "date"=>"2012-12-21"},
{"amount"=>2.75,"rel_id"=>1103, "date"=>"2012-12-24"},
{"amount"=>2.85,"rel_id"=>666, "date"=>"2012-12-27"},
{"amount"=>3.15,"rel_id"=>666, "date"=>"2012-12-28"}
#and many many more..
]
I need to group them by rel_id, that i could see total amount and dates they were given, in this kind of format:
{1103=>{:total_amount=>5.0, :dates=>["2012-12-21", "2012-12-24"]}, 666=>{:total_amount=>6.0, :dates=>["2012-12-27", "2012-12-28"]}}
I solved this in this way, but i'm pretty sure it's one of the worst approach to do that and i think it's not a ruby way..
results = {}
list.each do |line|
if !(results.has_key?(line["rel_id"]))
results[line["rel_id"]]={:total_amount=>line["amount"],:dates=>[line["date"]]}
else
results[line["rel_id"]][:total_amount] = results[line["rel_id"]][:total_amount]+line["amount"]
results[line["rel_id"]][:dates]<<line["date"]
end
end
Maybe you could give me or explain how to implement a nicer, more beautiful approach in a ruby way?
You can do something like this:
list.each_with_object({}) do |details, rollup|
rollup[details["rel_id"]] ||= { total_amount: 0, dates: [] }
rollup[details["rel_id"]][:total_amount] += details["amount"]
rollup[details["rel_id"]][:dates] << details["date"]
end
Edited for readability/names.
Functional approach (I'll use mash, use Hash[...] if no Facets):
purchases_grouped = list.group_by { |p| p["rel_id"] }
result = purchases_grouped.mash do |rel_id, purchases|
total_amount = purchases.map { |p| p["amount"] }.reduce(:+)
dates = purchases.map { |p| p["date"] }
accumulated = {total_amount: total_amount, dates: dates}
[rel_id, accumulated]
end
#=> {1103=>{:total_amount=>5.0, :dates=>["2012-12-21", "2012-12-24"]},
# 666 =>{:total_amount=>6.0, :dates=>["2012-12-27", "2012-12-28"]}}
h = list.group_by{|h| h["rel_id"]}
h.each{|k, v| h[k] = {
total_amount: v.inject(0){|x, h| x + h["amount"]},
dates: v.map{|h| h["date"]},
}}
h # => ...
Or
h = list.group_by{|h| h["rel_id"]}
h.each{|k, v| h[k] = {
total_amount: v.map{|h| h["amount"]}.inject(:+),
dates: v.map{|h| h["date"]},
}}
h # => ...
list = [
{amount: 2.25, rel_id: 1103, date: "2012-12-21"},
{amount: 2.75, rel_id: 1103, date: "2012-12-24"},
{amount: 2.85, rel_id: 666, date: "2012-12-27"},
{amount: 3.15, rel_id: 666, date: "2012-12-28"},
]
results = Hash.new do |hash, key|
hash[key] = {}
end
list.each do |hash|
totals = results[hash[:rel_id]]
totals[:amount] ||= 0
totals[:amount] += hash[:amount]
totals[:dates] ||= []
totals[:dates] << hash[:date]
end
p results
--output:--
{1103=>{:amount=>5.0, :dates=>["2012-12-21", "2012-12-24"]},
666=>{:amount=>6.0, :dates=>["2012-12-27", "2012-12-28"]}}
Alex Peachey's each_with_object solution modified:
results = list.each_with_object({}) do |h, acc|
record = acc[h["rel_id"]]
record ||= { total_amount: 0, dates: [] }
record[:total_amount] += h["amount"]
record[:dates] << h["date"]
end

Replace keys from the hash by values from the hash

I have a hash (with hundreds of pairs) and I have a string.
I want to replace in this string all occurrences of keys from the hash to according values from the hash.
I understand that I can do something like this
some_hash.each { |key, value| str = str.gsub(key, value) }
However, I am wondering whether there is some better (performance wise) method to do this.
You only need to run gsub once. Since regex (oniguruma) is implemented in C, it should be faster than looping within Ruby.
some_hash = {
"a" => "A",
"b" => "B",
"c" => "C",
}
"abcdefgabcdefg".gsub(Regexp.union(some_hash.keys), some_hash)
# => "ABCdefgABCdefg"
Some benchmarks:
require 'benchmark'
SOME_HASH = Hash[('a'..'z').zip('A'..'Z')]
SOME_REGEX = Regexp.union(SOME_HASH.keys)
SHORT_STRING = ('a'..'z').to_a.join
LONG_STRING = SHORT_STRING * 100
N = 10_000
def sub1(str)
SOME_HASH.each { |key, value|
str = str.gsub(key, value)
}
str
end
def sub2(str)
SOME_HASH.each { |key, value|
str.gsub!(key, value)
}
str
end
def sub_regex(str)
str.gsub(SOME_REGEX, SOME_HASH)
end
puts RUBY_VERSION
puts "#{ N } loops"
puts
puts "sub1: #{ sub1(SHORT_STRING) }"
puts "sub2: #{ sub2(SHORT_STRING) }"
puts "sub_regex: #{ sub_regex(SHORT_STRING) }"
puts
Benchmark.bm(10) do |b|
b.report('gsub') { N.times { sub1(LONG_STRING) } }
b.report('gsub!') { N.times { sub2(LONG_STRING) } }
b.report('regex') { N.times { sub_regex(LONG_STRING) } }
end
Which outputs:
1.9.3
10000 loops
sub1: ABCDEFGHIJKLMNOPQRSTUVWXYZ
sub2: ABCDEFGHIJKLMNOPQRSTUVWXYZ
sub_regex: ABCDEFGHIJKLMNOPQRSTUVWXYZ
user system total real
gsub 14.360000 0.030000 14.390000 ( 14.412178)
gsub! 1.940000 0.010000 1.950000 ( 1.957591)
regex 0.080000 0.000000 0.080000 ( 0.075038)

How to convert deep hash to array of keys

I want to programmatically convert this:
{
"a"=>
{"1"=>
{"A"=>
{"Standard"=>"true"}
}
},
"b"=>
{"1"=>
{"A"=>
{"Standard"=>"true"}
}
}
}
to an array like this:
['a/1/A/Standard', 'b/1/A/Standard']
def extract_keys(hash)
return [] unless hash.is_a?(Hash)
hash.each_pair.map {|key, value| [key, extract_keys(value)].join('/') }
end
extract_keys(hash)
=> ["a/1/A/Standard", "b/1/A/Standard"]
From one of my other answers - adapted for your situation. See the link for a more verbose solution to flat_hash
def flat_hash(hash, k = "")
return {k => hash} unless hash.is_a?(Hash)
hash.inject({}){ |h, v| h.merge! flat_hash(v[-1], k + '/' + v[0]) }
end
example = {...} # your example hash
foo = flat_hash(example).keys
=> ["/a/1/A/Standard", "/b/1/A/Standard"]
Found this flatten lambda definition.
h = {
"a"=>
{"1"=>
{"A"=>
{"Standard"=>"true"}
}
},
"b"=>
{"1"=>
{"A"=>
{"Standard"=>"true"}
}
}
}
a = []
flatten =
lambda {|r|
(recurse = lambda {|v|
if v.is_a?(Hash)
v.to_a.map{|v| recurse.call(v)}.flatten
elsif v.is_a?(Array)
v.flatten.map{|v| recurse.call(v)}
else
v.to_s
end
}).call(r)
}
h.each do |k,v|
a << k + "/" + flatten.call(v).join("/")
end
Output:
["a/1/A/Standard/true", "b/1/A/Standard/true"]

In Ruby, how do I make a hash from an array?

I have a simple array:
arr = ["apples", "bananas", "coconuts", "watermelons"]
I also have a function f that will perform an operation on a single string input and return a value. This operation is very expensive, so I would like to memoize the results in the hash.
I know I can make the desired hash with something like this:
h = {}
arr.each { |a| h[a] = f(a) }
What I'd like to do is not have to initialize h, so that I can just write something like this:
h = arr.(???) { |a| a => f(a) }
Can that be done?
Say you have a function with a funtastic name: "f"
def f(fruit)
fruit + "!"
end
arr = ["apples", "bananas", "coconuts", "watermelons"]
h = Hash[ *arr.collect { |v| [ v, f(v) ] }.flatten ]
will give you:
{"watermelons"=>"watermelons!", "bananas"=>"bananas!", "apples"=>"apples!", "coconuts"=>"coconuts!"}
Updated:
As mentioned in the comments, Ruby 1.8.7 introduces a nicer syntax for this:
h = Hash[arr.collect { |v| [v, f(v)] }]
Did some quick, dirty benchmarks on some of the given answers. (These findings may not be exactly identical with yours based on Ruby version, weird caching, etc. but the general results will be similar.)
arr is a collection of ActiveRecord objects.
Benchmark.measure {
100000.times {
Hash[arr.map{ |a| [a.id, a] }]
}
}
Benchmark #real=0.860651, #cstime=0.0, #cutime=0.0, #stime=0.0, #utime=0.8500000000000005, #total=0.8500000000000005
Benchmark.measure {
100000.times {
h = Hash[arr.collect { |v| [v.id, v] }]
}
}
Benchmark #real=0.74612, #cstime=0.0, #cutime=0.0, #stime=0.010000000000000009, #utime=0.740000000000002, #total=0.750000000000002
Benchmark.measure {
100000.times {
hash = {}
arr.each { |a| hash[a.id] = a }
}
}
Benchmark #real=0.627355, #cstime=0.0, #cutime=0.0, #stime=0.010000000000000009, #utime=0.6199999999999974, #total=0.6299999999999975
Benchmark.measure {
100000.times {
arr.each_with_object({}) { |v, h| h[v.id] = v }
}
}
Benchmark #real=1.650568, #cstime=0.0, #cutime=0.0, #stime=0.12999999999999998, #utime=1.51, #total=1.64
In conclusion
Just because Ruby is expressive and dynamic, doesn't mean you should always go for the prettiest solution. The basic each loop was the fastest in creating a hash.
h = arr.each_with_object({}) { |v,h| h[v] = f(v) }
Ruby 2.6.0 enables a shorter syntax by passing a block to the to_h method:
arr.to_h { |a| [a, f(a)] }
This is what I would probably write:
h = Hash[arr.zip(arr.map(&method(:f)))]
Simple, clear, obvious, declarative. What more could you want?
I'm doing it like described in this great article http://robots.thoughtbot.com/iteration-as-an-anti-pattern#build-a-hash-from-an-array
array = ["apples", "bananas", "coconuts", "watermelons"]
hash = array.inject({}) { |h,fruit| h.merge(fruit => f(fruit)) }
More info about inject method: http://ruby-doc.org/core-2.0.0/Enumerable.html#method-i-inject
Another one, slightly clearer IMHO -
Hash[*array.reduce([]) { |memo, fruit| memo << fruit << f(fruit) }]
Using length as f() -
2.1.5 :026 > array = ["apples", "bananas", "coconuts", "watermelons"]
=> ["apples", "bananas", "coconuts", "watermelons"]
2.1.5 :027 > Hash[*array.reduce([]) { |memo, fruit| memo << fruit << fruit.length }]
=> {"apples"=>6, "bananas"=>7, "coconuts"=>8, "watermelons"=>11}
2.1.5 :028 >
in addition to the answer of Vlado Cingel (I cannot add a comment yet, so I added an answer).
Inject can also be used in this way: the block has to return the accumulator. Only the assignment in the block returns the value of the assignment, and an error is reported.
array = ["apples", "bananas", "coconuts", "watermelons"]
hash = array.inject({}) { |h,fruit| h[fruit]= f(fruit); h }

Resources