Convert range to pattern - ruby

I have several ranges of numbers and I wonder if there are any algorithms to convert these ranges to patterns, like this:
Range: 5710000-5716999
Patterns: 5710*, 5711*, 5712*, 5713*, 5714*, 5715*, 5716*
Range: 5003070-5003089
Patterns: 500307*, 500308*
Range: 7238908-7238909
Patterns: 7238908*, 7238909*
I'm using Ruby, if it matters.
UPDATE 1:
More examples:
Range: 1668659-1668671
Patterns: 1668659*, 166866*, 1668670*, 1668671*
Range: 9505334305-9505334472
Patterns: 9505334305*, 9505334306*, 9505334307*, 9505334308*, 9505334309*, 950533431*, 950533432*, ..., 950533446*, 9505334470*, 9505334471*, 9505334472*

def doit(range)
b, e = range.begin.to_s, range.end.to_s
idx = b.chars.zip(e.chars).index { |a,b| a!=b }
return "#{b}*" if idx.nil?
(b[idx]..e[idx]).map { |c| b[0,idx] + c + '*' }
end
doit(5710000..5716999)
#=> ["5710*", "5711*", "5712*", "5713*", "5714*", "5715*", "5716*"]
doit(5003070..5003089)
#=> ["500307*", "500308*"]
doit(7238908..7238909)
#=> ["7238908*", "7238909*"]
doit(123..123)
#=> "123*"
The steps are as follows.
range = 5003070..5003089
b, e = range.begin.to_s, range.end.to_s
#=> ["5003070", "5003089"]
b #=> "5003070"
e #=> "5003089"
ab = b.chars
#=> ["5", "0", "0", "3", "0", "7", "0"]
ae = e.chars
#=> ["5", "0", "0", "3", "0", "8", "9"]
c = ab.zip(ae)
#=> [["5", "5"], ["0", "0"], ["0", "0"], ["3", "3"],
# ["0", "0"], ["7", "8"], ["0", "9"]]
idx = c.index { |a,b| a!=b }
#=> 5
return "#{b}*" if idx.nil?
#=> return "5003070*" if 5.nil?
r = b[idx]..e[idx]
#=> "7".."8"
r.map { |c| b[0,idx] + c + '*' }
#=> ["500307*", "500308*"]

It seems, I figured out how to make such converting using group_by method on the full range. I suppose, this algorithm kinda slow and inefficient, but it's very straightforward and works fine, so I'll stick to it.
def range_to_pattern(range)
values = range.to_a
while values.group_by{ |x| x.to_s[0...-1] }.any? { |_, v| v.size == 10 }
patterns = []
values.group_by{ |x| x.to_s[0...-1] }.each_pair{ |k, v| v.size == 10 ? patterns << k.to_i : patterns += v }
values = patterns
end
values
end
Results:
irb(main):072:0> range_to_pattern(5710000..5716999)
=> [5710, 5711, 5712, 5713, 5714, 5715, 5716]
irb(main):073:0> range_to_pattern(5003070..5003089)
=> [500307, 500308]
irb(main):074:0> range_to_pattern(7238908..7238909)
=> [7238908, 7238909]
irb(main):075:0> range_to_pattern(1668659..1668671)
=> [1668659, 166866, 1668670, 1668671]
irb(main):076:0> range_to_pattern(9505334305..9505334472)
=> [9505334305, 9505334306, 9505334307, 9505334308, 9505334309, 950533431, 950533432, 950533433, 950533434, 950533435, 950533436, 950533437, 950533438, 950533439, 950533440, 950533441, 950533442, 950533443, 950533444, 950533445, 950533446, 9505334470, 9505334471, 9505334472]

Related

Ruby hash with multiple comma separated values to array of hashes with same keys

What is the most efficient and pretty way to map this:
{name:"cheese,test", uid:"1,2"}
to this:
[ {name:"cheese", uid:"1"}, {name:"test", uid:"2"} ]
should work dinamically for example with: { name:"cheese,test,third", uid:"1,2,3" } or {name:"cheese,test,third,fourth", uid:"1,2,3,4", age:"9,8,7,6" }
Finally I made this:
hash = {name:"cheese,test", uid:"1,2"}
results = []
length = hash.values.first.split(',').length
length.times do |i|
results << hash.map {|k,v| [k, v.split(',')[i]]}
end
results.map{|e| e.to_h}
It is working, but i am not pleased with it, has to be a cleaner and more 'rubyst' way to do this
def splithash(h)
# Transform each element in the Hash...
h.map do |k, v|
# ...by splitting the values on commas...
v.split(',').map do |vv|
# ...and turning these into individual { k => v } entries.
{ k => vv }
end
end.inject do |a,b|
# Then combine these by "zip" combining each list A to each list B...
a.zip(b)
# ...which will require a subsequent .flatten to eliminate nesting
# [ [ 1, 2 ], 3 ] -> [ 1, 2, 3 ]
end.map(&:flatten).map do |s|
# Then combine all of these { k => v } hashes into one containing
# all the keys with associated values.
s.inject(&:merge)
end
end
Which can be used like this:
splithash(name:"cheese,test", uid:"1,2", example:"a,b")
# => [{:name=>"cheese", :uid=>"1", :example=>"a"}, {:name=>"test", :uid=>"2", :example=>"b"}]
It looks a lot more convoluted at first glance, but this handles any number of keys.
I would likely use transpose and zip like so:
hash = {name:"cheese,test,third,fourth", uid:"1,2,3,4", age:"9,8,7,6" }
hash.values.map{|x| x.split(",")}.transpose.map{|v| hash.keys.zip(v).to_h}
#=> [{:name=>"cheese", :uid=>"1", :age=>"9"}, {:name=>"test", :uid=>"2", :age=>"8"}, {:name=>"third", :uid=>"3", :age=>"7"}, {:name=>"fourth", :uid=>"4", :age=>"6"}]
To break it down a bit (code slightly modified for operational clarity):
hash.values
#=> ["cheese,test,third,fourth", "1,2,3,4", "9,8,7,6"]
.map{|x| x.split(",")}
#=> [["cheese", "test", "third", "fourth"], ["1", "2", "3", "4"], ["9", "8", "7", "6"]]
.transpose
#=> [["cheese", "1", "9"], ["test", "2", "8"], ["third", "3", "7"], ["fourth", "4", "6"]]
.map do |v|
hash.keys #=> [[:name, :uid, :age], [:name, :uid, :age], [:name, :uid, :age], [:name, :uid, :age]]
.zip(v) #=> [[[:name, "cheese"], [:uid, "1"], [:age, "9"]], [[:name, "test"], [:uid, "2"], [:age, "8"]], [[:name, "third"], [:uid, "3"], [:age, "7"]], [[:name, "fourth"], [:uid, "4"], [:age, "6"]]]
.to_h #=> [{:name=>"cheese", :uid=>"1", :age=>"9"}, {:name=>"test", :uid=>"2", :age=>"8"}, {:name=>"third", :uid=>"3", :age=>"7"}, {:name=>"fourth", :uid=>"4", :age=>"6"}]
end
Input
hash={name:"cheese,test,third,fourth", uid:"1,2,3,4", age:"9,8,7,6" }
Code
p hash
.transform_values { |v| v.split(',') }
.map { |k, v_arr| v_arr.map { |v| [k, v] }
}
.transpose
.map { |array| array.to_h }
Output
[{:name=>"cheese", :uid=>"1", :age=>"9"}, {:name=>"test", :uid=>"2", :age=>"8"}, {:name=>"third", :uid=>"3", :age=>"7"}, {:name=>"fourth", :uid=>"4", :age=>"6"}]
We are given
h = { name: "cheese,test", uid: "1,2" }
Here are two ways to create the desired array. Neither construct arrays that are then converted to hashes.
#1
First compute
g = h.transform_values { |s| s.split(',') }
#=> {:name=>["cheese", "test"], :uid=>["1", "2"]}
then compute
g.first.last.size.times.map { |i| g.transform_values { |v| v[i] } }
#=> [{:name=>"cheese", :uid=>"1"}, {:name=>"test", :uid=>"2"}]
Note
a = g.first
#=> [:name, ["cheese", "test"]]
b = a.last
#=> ["cheese", "test"]
b.size
#=> 2
#2
This approach does not convert the values of the hash to arrays.
(h.first.last.count(',')+1).times.map do |i|
h.transform_values { |s| s[/(?:\w+,){#{i}}\K\w+/] }
end
#=> [{:name=>"cheese", :uid=>"1"}, {:name=>"test", :uid=>"2"}]
We have
a = h.first
#=> [:name, "cheese,test"]
s = a.last
#=> "cheese,test"
s.count(',')+1
#=> 2
We can express the regular expression in free-spacing mode to make it self-documenting.
/
(?: # begin a non-capture group
\w+, # match one or more word characters followed by a comma
) # end the non-capture group
{#{i}} # execute the preceding non-capture group i times
\K # discard all matches so far and reset the start of the match
\w+ # match one or more word characters
/x # invoke free-spacing regex definition mode

how to convert special string data to hash in ruby?

Hi i have some data that system send to me alternative like this:
"Screw:1,Bound:5,Hing:3"
"Bound:5,Screw:3,Hing:1"
"Bound:2,Screw:2"
how can i make this Hash?
{"Screw"=>6 ,"Bound"=>12, "Hing"=>4}
its probably add other key and value later , i hop solve this for me.
arr = [
"Screw:1,Bound:5,Hing:3",
"Bound:5,Screw:3,Hing:1",
"Bound:2,Screw:2"
]
arr.flat_map { |s| s.split(',') }
.each_with_object(Hash.new(0)) do |s,h|
k, v = s.split(':')
h[k] += v.to_i
end
#=> {"Screw"=>6, "Bound"=>12, "Hing"=>4}
Step 1
arr.flat_map { |s| s.split(',') }
#=>["Screw:1", "Bound:5", "Hing:3", "Bound:5", "Screw:3", "Hing:1",
# "Bound:2", "Screw:2"]
See the form of Hash::new that takes an argument and no block. The argument is called the default value, which is here zero. If h has been defined h = Hash.new(0), and h does not have a key k, h[k] returns the default value (and does not modify the hash). h[k] += v.to_i expands to
h[k] = h[k] + v.to_i
so if h does not have a key k this becomes
h[k] = 0 + v.to_i
Alternatively, one could write the following.
arr.flat_map { |s| s.split(/:|,/) }
.each_slice(2)
.with_object(Hash.new(0)) { |(k,v),h| h[k] += v.to_i }
#=> {"Screw"=>6, "Bound"=>12, "Hing"=>4}
Steps 1 and 2
a = arr.flat_map { |s| s.split(/:|,/) }
#=> ["Screw", "1", "Bound", "5", "Hing", "3", "Bound", "5",
# "Screw", "3", "Hing", "1", "Bound", "2", "Screw", "2"]
e = a.each_slice(2)
#=> #<Enumerator: ["Screw", "1", "Bound", "5", "Hing", "3",
# "Bound", "5", "Screw", "3", "Hing", "1",
# "Bound", "2", "Screw", "2"]:each_slice(2)>
The elements generated by the enumerator e can be seen as follows:
e.entries
#=> [["Screw", "1"], ["Bound", "5"], ["Hing", "3"], ["Bound", "5"],
# ["Screw", "3"], ["Hing", "1"], ["Bound", "2"], ["Screw", "2"]]
A good way would be too loop through all of the entries and update the hash depending on the entries that get found.
The following will do it for you.
str = "Screw:1,Bound:5,Hing:3"
output = Hash.new(0)
str.split(",").each do |entry|
key = entry.split(":")
output[key[0]] += key[1].to_i
end
Just modify it so that it handles multiple strings correctly, depending on how they are fed to you in the system.
Looks like the data is CSV so I'd opt to use a CSV parser to avoid possible encoding issues
require 'csv'
def parse input
Hash[CSV.parse_line(input).map { |pair| pair.split(":") }]
end

Multiple sub-hashes out of one hash

I have a hash:
hash = {"a_1_a" => "1", "a_1_b" => "2", "a_1_c" => "3", "a_2_a" => "3",
"a_2_b" => "4", "a_2_c" => "4"}
What's the best way to get the following sub-hashes:
[{"a_1_a" => "1", "a_1_b" => "2", "a_1_c" => "3"},
{"a_2_a" => "3", "a_2_b" => "4", "a_2_c" => "4"}]
I want them grouped by the key, based on the regexp /^a_(\d+)/. I'll have 50+ key/value pairs in the original hash, so something dynamic would work best, if anyone has any suggestions.
If you're only concerned about the middle component you can use group_by to get you most of the way there:
hash.group_by do |k,v|
k.split('_')[1]
end.values.map do |list|
Hash[list]
end
# => [{"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3"}, {"a_2_a"=>"3", "a_2_b"=>"4", "a_2_c"=>"4"}]
The final step is extracting the grouped lists and combining those back into the required hashes.
Code
def partition_hash(hash)
hash.each_with_object({}) do |(k,v), h|
key = k[/(?<=_).+(?=_)/]
h[key] = (h[key] || {}).merge(k=>v)
end.values
end
Example
hash = {"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3", "a_2_a"=>"3", "a_2_b"=>"4", "a_2_c"=>"4"}
partition_hash(hash)
#=> [{"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3"},
# {"a_2_a"=>"3", "a_2_b"=>"4", "a_2_c"=>"4"}]
Explanation
The steps are as follows.
enum = hash.each_with_object({})
#=> #<Enumerator: {"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3", "a_2_a"=>"3",
# "a_2_b"=>"4", "a_2_c"=>"4"}:each_with_object({})>
The first element of this enumerator is generated and passed to the block, and the block variables are computed using parallel assignment.
(k,v), h = enum.next
#=> [["a_1_a", "1"], {}]
k #=> "a_1_a"
v #=> "1"
h #=> {}
and the block calculation is performed.
key = k[/(?<=_).+(?=_)/]
#=> "1"
h[key] = (h[key] || {}).merge(k=>v)
#=> h["1"] = (h["1"] || {}).merge("a_1_a"=>"1")
#=> h["1"] = (nil || {}).merge("a_1_a"=>"1")
#=> h["1"] = {}.merge("a_1_a"=>"1")
#=> h["1"] = {"a_1_a"=>"1"}
so now
h #=> {"1"=>{"a_1_a"=>"1"}}
The next value of enum is now generated and passed to the block, and the following calculations are performed.
(k,v), h = enum.next
#=> [["a_1_b", "2"], {"1"=>{"a_1_a"=>"1"}}]
k #=> "a_1_b"
v #=> "2"
h #=> {"1"=>{"a_1_a"=>"1"}}
key = k[/(?<=_).+(?=_)/]
#=> "1"
h[key] = (h[key] || {}).merge(k=>v)
#=> h["1"] = (h["1"] || {}).merge("a_1_b"=>"2")
#=> h["1"] = ({"a_1_a"=>"1"}} || {}).merge("a_1_b"=>"2")
#=> h["1"] = {"a_1_a"=>"1"}}.merge("a_1_b"=>"2")
#=> h["1"] = {"a_1_a"=>"1", "a_1_b"=>"2"}
After the remaining four elements of enum have been passed to the block the following has is returned.
h #=> {"1"=>{"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3"},
# "2"=>{"a_2_a"=>"3", "a_2_b"=>"4", "a_2_c"=>"4"}}
The final step is simply to extract the values.
h.values
#=> [{"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3"},
# {"a_2_a"=>"3", "a_2_b"=>"4", "a_2_c"=>"4"}]

How to find union between array of first value in ruby

I have a set of array like this:
[["1","2"],["1","3"],["2","3"],["2","5"]]
I want to find the union of first values like
["1","2"],["1","3"] matches so i need to create new array like ["1","2,3"]
so the resulting array will be like
[["1","2,3"],["2","3,5"]]
Like most problems in Ruby, the Enumerable module does the job:
input = [["1","2"],["1","3"],["2","3"],["2","5"]]
result = input.group_by do |item|
# Group by first element
item[0]
end.collect do |key, items|
# Compose into new format
[
key,
items.collect do |item|
item[1]
end.join(',')
]
end
puts result.inspect
# => [["1", "2,3"], ["2", "3,5"]]
The group_by method comes in very handy when aggregating things like this, and collect is great for rewriting how the elements appear.
What you are asking for is not a true union for a true union of each 2 it would be:
data = [["1","2"],["1","3"],["2","3"],["2","5"]]
data.each_slice(2).map{|a,b| a | b.to_a }
#=> [["1","2","3"],["2","3","5"]]
Here is a very simple solution that modifies this concept to fit your needs:
data = [["1","2"],["1","3"],["2","3"],["2","5"]]
data.each_slice(2).map do |a,b|
unified = (a | b.to_a)
[unified.shift,unified.join(',')]
end
#=> [["1", "2,3"], ["2", "3,5"]]
Added to_a to piped variable b in the event that there are an uneven number of arrays. eg.
data = [["1","2"],["1","3"],["2","3"],["2","5"],["4","7"]]
data.each_slice(2).map do |a,b|
unified = (a | b.to_a)
[unified.shift,unified.join(',')]
end
#=> [["1", "2,3"], ["2", "3,5"], ["4","7"]]
If you meant that you want this to happen regardless of order then this will work but will destroy the data object
data.group_by(&:shift).map{|k,v| [k,v.flatten.join(',')]}
#=> [["1", "2,3"], ["2", "3,5"], ["4","7"]]
Non destructively you could call
data.map(&:dup).group_by(&:shift).map{|k,v| [k,v.flatten.join(',')]}
#=> [["1", "2,3"], ["2", "3,5"], ["4","7"]]
Here's another way.
Code
def doit(arr)
arr.each_with_object({}) { |(i,*rest),h| (h[i] ||= []).concat(rest) }
.map { |i,rest| [i, rest.join(',')] }
end
Examples
arr1 = [["1","2"],["1","3"],["2","3"],["2","5"]]
doit(arr1)
#=> [["1", "2,3"], ["2", "3,5"]]
arr2 = [["1","2","6"],["2","7"],["1","3"],["2","3","9","4","cat"]]
doit(arr2)
# => [["1", "2,6,3"], ["2", "7,3,9,4,cat"]]
Explanation
For arr1 above, we obtain:
enum = arr1.each_with_object({})
#=> #<Enumerator: [["1", "2"], ["1", "3"], ["2", "3"],
# ["2", "5"]]:each_with_object({})>
We can convert enum to an array see its elements:
enum.to_a
#=> [[["1", "2"], {}], [["1", "3"], {}],
# [["2", "3"], {}], [["2", "5"], {}]]
These elements will be passed into the block, and assigned to the block variables, by Enumerator#each, which will invoke Array#each. The first of these elements ([["1", "2"], {}]) can be obtained by invoking Enumerator#next on enum:
(i,*rest),h = enum.next
#=> [["1", "2"], {}]
i #=> "1"
rest #=> ["2"]
h #=> {}
We then execute:
(h[i] ||= []).concat(rest)
#=> (h["1"] ||= []).concat(["2"])
#=> (nil ||= []).concat(["2"])
#=> [].concat(["2"])
#=> ["2"]
each then passes the next element of enum to the block:
(i,*rest),h = enum.next
#=> [["1", "3"], {"1"=>["2"]}]
i #=> "1"
rest #=> ["3"]
h #=> {"1"=>["2"]}
(h[i] ||= []).concat(rest)
#=> (h["1"] ||= []).concat(["3"])
#=> (["2"] ||= []).concat(["3"])
#=> ["2"].concat(["3"])
#=> ["2", "3"]
After passing the last two elements of enum into the block, we obtain:
h=> {"1"=>["2", "3"], "2"=>["3", "5"]}
map creates an enumerator:
enum_h = h.each
#=> > #<Enumerator: {"1"=>["2", "3"]}:each>
and calls Enumerator#each (which calls Hash#each) to pass each element of enum_h into the block:
i, rest = enum_h.next
#=> ["1", ["2", "3"]]
then computes:
[i, rest.join(',')]
#=> ["1", ["2", "3"].join(',')]
#=> ["1", "2,3"]
The other element of enum_h is processed similarly.

Array multiplication - what is the best algorithm?

I have to make a multiplication of n arrays.
Example :
input = ["a", "b", "c"] * ["1", "2"] * ["&", "(", "$"]
output = ["a1&", "a1(", "a1$", "a2&", "a2(", "a2$", "b1&", "b1(", "b1$", "b2&", "b2(", "b2$, "c1&, "c1(, "c1$, "c2&", "c2(", "c2$"]
I have created an algorithm to do that, it works good.
# input
entries = ["$var1$", "$var2$", "$var3$"]
data = [["a", "b", "c"], ["1", "2"], ["&", "(", "$"]]
num_combinaison = 1
data.each { |item| num_combinaison = num_combinaison * item.length }
result = []
for i in 1..num_combinaison do
result.push entries.join()
end
num_repetition = num_combinaison
data.each_index do |index|
item = Array.new(data[index])
num_repetition = num_repetition / item.length
for i in 1..num_combinaison do
result[i-1].gsub!(entries[index], item[0])
if i % num_repetition == 0
item.shift
item = Array.new(data[index]) if item.length == 0
end
end
end
I'm sure there is a best way to do that, but I don't find it. I have tried to use product or flatten function without success.
Somebody see a best solution ?
Thanks for your help.
Eric
class Array
def * other; product(other).map(&:join) end
end
["a", "b", "c"] * ["1", "2"] * ["&", "(", "$"]
# =>
# ["a1&", "a1(", "a1$", "a2&", "a2(", "a2$", "b1&", "b1(", "b1$", "b2&",
# "b2(", "b2$", "c1&", "c1(", "c1$", "c2&", "c2(", "c2$"]
The best algorithm you can use is implemented by the Array#product method:
data = [["a", "b", "c"], ["1", "2"], ["&", "(", "$"]]
data.first.product(*entries.drop(1)).map(&:join)
# => ["a1&", "a1(", "a1$", "a2&", "a2(", "a2$", ...
Update
A safer alternative, my first solution raises a NoMethodError if data is emtpy:
data.reduce { |result, ary| result.product(ary).map(&:join) }
# => ["a1&", "a1(", "a1$", "a2&", "a2(", "a2$", ...
[].reduce { |r, a| r.product(a).map(&:join) }
# => nil

Resources