Ruby: split string in hash - ruby-on-rails

I have a string
str = "race_1: 650m, 215m, 265m, 315m\r\nrace_2: 165m, 215m, 265m, 315m."
Expected result: I wanted to split this in a hash like this:
hash = {
race_1 => [650, 215, 265, 315],
race_2 => [165, 215, 265, 315]
}
Can someone please guide me in a direction to create the matching hash?

When the input always follows the same pattern, then I would use String#scan with a Regexp to extract the significant values.
string = "race_1: 650m, 215m, 265m, 315m\r\nrace_2: 165m, 215m, 265m, 315m."
regexp = /(race_\d+).*?(\d+(?=m)).*?(\d+(?=m)).*?(\d+(?=m)).*?(\d+(?=m))/
string.scan(regexp)
#=> [["race_1", "650", "215", "265", "315"], ["race_2", "165", "215", "265", "315"]]
These nested array of values can then be transformed into an hash like this:
string.scan(regexp).to_h { |values| [values[0], values[1..-1]] }
#=> {"race_1"=>["650", "215", "265", "315"], "race_2"=>["165", "215", "265", "315"]}
And because you want the numbers in the array to be integers:
string.scan(regexp).to_h { |values| [values[0], values[1..-1].map(&:to_i)] }
#=> {"race_1"=>[650, 215, 265, 315], "race_2"=>[165, 215, 265, 315]}

You can write this code
Input
str = "race_1: 650m, 215m, 265m, 315m\r\nrace_2: 165m, 215m, 265m, 315m."
Code
Split the code with colon : and replace the m at the end
hash = str.scan(/(race_\d+): (.*)/).each_with_object({}) do |(race, distances), hash|
hash["#{race}"] = distances.split(', ').map { |d| d.sub(/m$/, '').to_i }
end
p hash
Output
{"race_1"=>[650, 215, 265, 315], "race_2"=>[165, 215, 265, 315]}

The following allows any number of races and for each race to have any number of associated distances (in str below there are four).
str = "race_1: 650m, 215m, 265m, 315m\r\nrace_2: 165m, 215m, 265m, 315m"
str.gsub(/(\w+): ((?:\d+m, *)*\d+)/).with_object({}) do |_s,h|
h[$1] = $2.split(',').map(&:to_i)
end
#=> {"race_1"=>[650, 215, 265, 315],
# "race_2"=>[165, 215, 265, 315]}
This employs a little-used (and greatly undervalued) form of String#gsub that takes a single argument but no block, and returns an enumerator. The enumerator merely generates matches of gsub's argument and therefore has nothing to do with string replacement. This form of gsub is sometimes a convenient replacement for String#scan when scan's argument is a regular expression that contains one or more capture groups.
The regular expression that is gsub's argument can be expressed in free-spacing mode to make it self-documenting.
/
( # begin capture group 1
\w+ # match >= 1 word characters
) # end capture group 1
: # match a colon
[ ] # match a space
( # begin capture group 2
(?: # begin non-capture group
\d+ # match >= 1 digits
m,[ ]* # match "m," followed by >= 0 spaces
) # end non-capture group
* # execute preceding non-capture group >= 0 times
\d+ # match >= 1 digits
) # end capture group 2
/x # invoke free-spacing regex definition mode
Note that in free-spacing mode spaces that are part of the expression must be protected. There are various ways of doing that. I have enclosed each space in a character class ([ ]).
In the example above we compute the following enumerator.
enum = str.gsub(/(\w+): ((?:\d+m, *)*\d+)/)
#=> #<Enumerator: "race_1: 650m, 215m, 265m, 315m\r\n
# race_2: 165m, 215m, 265m, 315m":
# gsub(/(\w+): ((?:\d+m, *)*\d+)/)>
The elements it will generate are as follows.
enum.next
#=> "race_1: 650m, 215m, 265m, 315"
enum.next
#=> "race_2: 165m, 215m, 265m, 315"
enum.next
#=> StopIteration: iteration reached an end
Note also that
arr = "650m, 215m, 265m, 315".split(',')
#=> ["650m", " 215m", " 265m", " 315"]
arr.map(&:to_i)
#=> [650, 215, 265, 315]
A variant of this is to write
rgx = /\w+: (?:\d+m, *)*\d+/
str.gsub(rgx).with_object({}) do |s,h|
key, value = s.split(':')
h[key] = value.split(',').map(&:to_i)
end
#=> {"race_1"=>[650, 215, 265, 315],
# "race_2"=>[165, 215, 265, 315]}
As the regular expression now has no capture groups we get the same result when the first line is replaced with
str.scan(rgx).each_with_object({}) do |s,h|

Could you try the code below?
str = "race_1: 650m, 215m, 265m, 315m\r\nrace_2: 165m, 215m, 265m, 315m."
rows = str.delete('.').split("\r\n") # => ["race_1: 650m, 215m, 265m, 315m", "race_2: 165m, 215m, 265m, 315m"]
hash_result = {}
rows.each do |row|
key = row.split(':').first # => race_1
value = row.split(':').last.split('m, ').map(&:to_i) # => [650, 215, 265, 315]
hash_result[key.to_sym] = value
end
# hash_result = {:race_1=>[650, 215, 265, 315], :race_2=>[165, 215, 265, 315]}
p/s: I think you should do it yourself to improve yourself

Is this the expected output?
require 'yaml'
str = "race_1: 650m, 215m, 265m, 315m\r\nrace_2: 165m, 215m, 265m, 315m."
races = YAML.load(str)
races.each { |k, v| races[k] = v.scan(/\d+/).map(&:to_i) }
pp races
produces
{
"race_1" => [650, 215, 265, 315],
"race_2" => [165, 215, 265, 315]
}

Related

Slice into chunks from arranged hash in ruby

I have hash which keys are in sorted order and hash size is more than 1000. How can I divide hash in chunks based on range.
Example :-
h_main = {"1" => "a", "2" => "b", "9" => "c", .............. "880" => "xx", "996" => "xyz", "998" => "lll", "1050" => "mnx"}
I have to divide above hash into sorter hash chunks based on range :-
h_result = {"1-100" => {"1" => "a", "2" => "b", "9" => "c" ..... "99" => "re"},
"101-200" => {}
....
....
"900-1000" => {"996" => "xyz", "998" => "lll"},
"1000-1100" => {"1050" => "mnx"}
}
I can do by applying each loop and then can add condition to merge key-value pair in respective hash but that's lengthy process.
Please help to provide optimize solution thanks in advance.
def doit(h, group_size)
h.keys.
slice_when { |k1,k2| k2.to_i/group_size > k1.to_i/group_size }.
each_with_object({}) do |key_group,g|
start_range = group_size * (key_group.first.to_i/group_size)
g["%d-%d" % [start_range, start_range+group_size-1]] = h.slice(*key_group)
end
end
h = {"11"=>"a", "12"=>"b", "19"=>"c", "28"=>"xx", "29"=> "xyz",
"42"=>"lll", "47"=>"mnx"}
doit(h, 10)
#=> {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"},
# "20-29"=>{"28"=>"xx", "29"=>"xyz"},
# "40-49"=>{"42"=>"lll", "47"=>"mnx"}}
doit(h, 15)
#=> {"0-14"=>{"11"=>"a", "12"=>"b"},
# "15-29"=>{"19"=>"c", "28"=>"xx", "29"=>"xyz"},
# "30-44"=>{"42"=>"lll"}, "45-59"=>{"47"=>"mnx"}}
doit(h, 20)
#=> {"0-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"},
# "20-39"=>{"28"=>"xx", "29"=>"xyz"},
# "40-59"=>{"42"=>"lll", "47"=>"mnx"}}
See Enumerable#slice_when and Hash#slice.
The steps are as follows.
group_size = 10
a = h.keys
#=> ["11", "12", "19", "28", "29", "42", "47", "74", "76"]
b = a.slice_when { |k1,k2| k2.to_i/group_size > k1.to_i/group_size }
#=> #<Enumerator: #<Enumerator::Generator:0x000056fa312199b8>:each>
We can see the elements that will be generated by this enumerator and passed to the block by converting it to an array.
b.to_a
#=> [["11", "12", "19"], ["28", "29"], ["42", "47"]]
Lastly,
b.each_with_object({}) do |key_group,g|
start_range = group_size * (key_group.first.to_i/group_size)
g["%d-%d" % [start_range, start_range+group_size-1]] =
h.slice(*key_group)
end
#=> {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"},
# "20-29"=>{"28"=>"xx", "29"=>"xyz"},
# "40-49"=>{"42"=>"lll", "47"=>"mnx"}}
Note that:
e = b.each_with_object({})
#=> #<Enumerator: #<Enumerator:
# #<Enumerator::Generator:0x0000560a0fc12658>:each>:
# each_with_object({})>
e.to_a
#=> [[["11", "12", "19"], {}], [["28", "29"], {}], [["42", "47"], {}]]
The last step begins by the enumerator e generating a value and passing it to the block, after which the block variables are assigned values using array decomposition.
key_group,g = e.next
#=> [["11", "12", "19"], {}]
key_group
#=> ["11", "12", "19"]
g #=> {}
The block calculations are then performed.
start_range = group_size * (key_group.first.to_i/group_size)
#=> 10 * (11/10) => 10
g["%d-%d" % [start_range, start_range+group_size-1]] =
h.slice(*key_group)
#=> g["%d-%d" % [10, 10+10-1]] = h.slice("11", "12", "19")
#=> g["10-19"] = {"11"=>"a", "12"=>"b", "19"=>"c"}
#=> {"11"=>"a", "12"=>"b", "19"=>"c"}
Now,
g #=> {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"}}
The enumerator e then generates another element, passes it to the block and the block variables are assigned.
key_group,g = e.next
#=> [["28", "29"], {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"}}]
key_group
#=> ["28", "29"]
g #=> {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"}}
Notice that the value of g has been updated. The block calculations now proceed as before, after which:
g #=> {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"},
# "20-29"=>{"28"=>"xx", "29"=>"xyz"}}
Then
key_group,g = e.next
#=> [["42", "47"], {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"},
# "20-29"=>{"28"=>"xx", "29"=>"xyz"}}]
key_group
#=> ["42", "47"]
g #=> {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"},
# "20-29"=>{"28"=>"xx", "29"=>"xyz"}}
After the the block calculations are performed:
g #=> {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"},
# "20-29"=>{"28"=>"xx", "29"=>"xyz"},
# "40-49"=>{"42"=>"lll", "47"=>"mnx"}}
Then an exception is raised:
key_group,g = e.next
#=> StopIteration (iteration reached an end)
causing the enumerator to return g.
Since your Hash is already sorted by the keys things like slice_when as proposed by #CarySwoveland would probably have an efficiency benefit; however were the Hash to be, or become, unsorted the following solutions would be unaffected as far as grouping goes.
Using a lambda to group the keys:
def group_numeric_range(h, group_size)
groups = ->(n) do
g = n.to_i / group_size
"#{g * group_size + 1}-#{g * group_size + group_size}"
end
h.group_by do |k,_|
groups.(k)
end.transform_values(&:to_h)
end
Example:
h = {"11"=>"a", "12"=>"b", "19"=>"c", "28"=>"xx", "29"=> "xyz",
"42"=>"lll", "47"=>"mnx"}
group_numeric_range(h,10)
#=> {"11-20"=>{"11"=>"a", "12"=>"b", "19"=>"c"}, "21-30"=>{"28"=>"xx", "29"=>"xyz"}, "41-50"=>{"42"=>"lll", "47"=>"mnx"}}
Alternative:
def group_numeric_range(h, group_size)
groups = ->(n) do
g = n.to_i / group_size
"#{g * group_size + 1}-#{g * group_size + group_size}"
end
h.each_with_object(Hash.new{|h,k| h[k] = {}}) do |(k,v),obj|
obj[groups.(k)].merge!(k=>v)
end
end
Update
Another option would be to build an Array of the groups and then select the index for grouping (I added outputting empty ranges too) e.g.
def group_numeric_range(h, group_size)
groups = ((h.keys.max.to_i / group_size) + 1).times.map do |g|
["#{g * group_size + 1}-#{g * group_size + group_size}",{}]
end
h.each_with_object(groups) do |(k,v),obj|
obj[k.to_i / group_size].last.merge!(k=>v)
end.to_h
end
h = {"11"=>"a", "12"=>"b", "19"=>"c", "28"=>"xx", "29"=> "xyz",
"42"=>"lll", "47"=>"mnx"}
group_numeric_range(h,10)
#=> {"1-10"=>{}, "11-20"=>{"11"=>"a", "12"=>"b", "19"=>"c"}, "21-30"=>{"28"=>"xx", "29"=>"xyz"}, "31-40"=>{}, "41-50"=>{"42"=>"lll", "47"=>"mnx"}}
This is how I would do it, but unsure what you have done already.
Creating a large hash:
hash = {}
1000.times do |x|
hash[x] = "hi!"
end
slicing by range:
hash.slice(*(1 .. 100))
=> # keys from 1 .. 100
producing desired hash:
def split_hash(range, hash)
end_result = {}
(hash.count / range).times do |x|
range_start = (range * x) + 1
range_end = range_start + range
end_result["#{range_start}-#{range_end}"] = hash.slice(*(range_start .. range_end)) # slice returns a hash which was desired. If you can convert to an array you gain range access as slice still iterates but is performative. if you are OK with an array: hash.to_a[range_start .. range_end]
end
end_result
end

convert a string into a map in Elixir

I have a string something like this:
### image_date: 23/01/2019 ###
pool2 wxcs 2211
pool3 wacs 1231
### line_count: 1 ###
I want to convert this to a map, something like:
%{
image_data: "23/01/2019",
content: "pool2 wxcs 2211\npool3 wacs 1231",
line_count: 1
}
Can anyone help me with this?
One might use Regex.scan/3:
for [capture] <- Regex.scan(~r/(?<=###).*?(?=###)/mus, str), into: %{} do
case capture |> String.split(":") |> Enum.map(&String.trim/1) do
[name, value] -> {name, value}
[content] -> {"content", content}
end
end
resulting in:
#⇒ %{
# "content" => "pool2 wxcs 2211\n pool3 wacs 1231",
# "image_date" => "23/01/2019",
# "line_count" => "1"
# }
It ain't pretty but it does the job.
defmodule UglyParser do
def main do
str = """
### image_date: 23/01/2019 ###
pool2 wxcs 2211
pool3 wacs 1231
### line_count: 1 ###
"""
[header, content, footer] = String.split(str, ~r/(?:#\s*\n)|(?:\n\s*#)/, trim: true)
header = to_pair(header)
footer = to_pair(footer)
content = {:content, String.trim(content) |> String.replace(~r/\n\s*/, "\n")}
Enum.into([header, footer, content], %{})
end
defp to_pair(str) do
String.replace(str, "#", "")
|> String.trim()
|> String.split(": ")
|> (fn [key, value] -> {String.to_atom(key), value} end).()
end
end

How to not allowed taking in a number into a variable in Ruby

Given a string S of length N that is indexed from 0 to N-1 , print its even-indexed and odd-indexed characters as space-separated strings on a single line (see the Sample below for more detail).
Sample Input:
2
Hacker
Rank
Sample output:
Hce akr
Rn ak
explanation:
S="Hacker" S[0]="H", S[1]="a", S[2]= "c", S[3]="k", S[4]="e", S[5]="r"
However, with the following code I haven't been able to complete the challenge. How do I constraint taken input as an integer?
S = gets.chomp.chars.to_a
for i in 0..S.length
if i%2 == 0
s1 = S[i]
else
s2 = S[i]
end
end
puts s1.to_s + " " + s2.to_s
Code
def doit(str)
str.each_char.each_slice(2).with_object(['','']) do |(c_even, c_odd), (s_even, s_odd)|
s_even << c_even
s_odd << c_odd unless c_odd.nil?
end.join(' ')
end
Examples
doit "abracadabra"
#=> "arcdba baaar"
doit "Jack be nimble, Jack be quick"
#=> "Jc enml,Jc eqik akb ibe akb uc"
Explanation
For
str = "abracadabra"
enum0 = str.each_char
#=> #<Enumerator: "abracadabra":each_char>
We can convert the enumerator enum0 to an array to see what values it will generate:
enum0.to_a
#=> ["a", "b", "r", "a", "c", "a", "d", "a", "b", "r", "a"]
enum1 = enum0.each_slice(2)
#=> #<Enumerator: #<Enumerator: "abracadabra":each_char>:each_slice(2)>
enum1.to_a
#=> [["a", "b"], ["r", "a"], ["c", "a"], ["d", "a"], ["b", "r"], ["a"]]
enum2 = enum1.with_object(['',''])
#=> #<Enumerator: #<Enumerator: #<Enumerator: "abracadabra":each_char>:each_slice(2)>
# :with_object(["", ""])>
enum2.to_a
#=> [[["a", "b"], ["", ""]], [["r", "a"], ["", ""]], [["c", "a"], ["", ""]],
# [["d", "a"], ["", ""]], [["b", "r"], ["", ""]], [["a"], ["", ""]]]
If you examine the return values obtained when constructing enum1 and enum2, you will see that they can be thought of as "compound" enunerators.
The first element of enum2 is generated and passed to the block, assigning values to the four block variables1:
(c_even, c_odd), (s_even, s_odd) = enum2.next
#=> [["a", "b"], ["", ""]]
c_even #=> "a"
c_odd #=> "b"
s_even #=> ""
s_odd #=> ""
The block calculation is now performed.
s_even << c_even
#=> "a"
s_odd << c_odd unless c_odd.nil?
# s_odd << c_odd unless false
# s_odd << c_odd
#=> "b"
The return values "a" and "b" are the new values of s_even and s_odd, respectively.
Now the next element of enum_2 is generated, passed to the block and the block calculations are performed:
(c_even, c_odd), (s_even, s_odd) = enum2.next
#=> [["r", "a"], ["a", "b"]]
s_even << c_even
# "a" << "r"
#=> "ar"
s_odd << c_odd unless c_odd.nil?
# s_odd << c_odd unless "a".nil?
# s_odd << c_odd
#=> "ba"
Calculations continue in this way until the last value of enum2 is generated: ["a"]. This has the effect of assigning nil to c_odd, so the second line of the block is not executed2. Lastly, the array of two strings is joined with a separating space.
Another way
def doit(str)
str.each_char.with_index.with_object(' ') { |(c,i),s|
s.insert(i.even? ? s.index(' ') : s.size, c) }
end
doit "abracadabra"
#=> "arcdba baaar"
1 The following expression employs parallel assignment (sometimes called multiple assignment) and disambiguation (sometimes referred to as decomposition) to assign values to the variables.
2 The second line could alternatively be written s_odd << c_odd.to_s or s_odd << c_odd || ''.
First input should be treated as an integer (namely, the amount of following strings to come):
amount = gets.to_i
Now we are to get amount strings and do our job (using Enumerable#partition):
amount.times do
input = gets.chomp
puts (input.split('').partition.with_index do |_, i|
i.even?
end.map(&:join).join(' '))
end
Note that instead of inspecting each character's index, you could also use scan:
'Hacker'.scan(/(.)(.?)/) #=> [["H", "a"], ["c", "k"], ["e", "r"]]
.transpose #=> [["H", "c", "e"], ["a", "k", "r"]]
.map(&:join) #=> ["Hce", "akr"]
.join(' ') #=> "Hce akr"
Or, using temporary variables:
s1 = ''
s2 = ''
'Hacker'.scan(/(.)(.?)/) { |a, b| s1 << a ; s2 << b }
puts "#{s1} #{s2}"
Here is the basic algorithm that would take O(n).
tests = gets.to_i
# run the loop for number of tests given
tests.times do
string = gets.chomp # sanitize string from input, i.e. removes \n \r
s_length = string.length # String length N
new_string = " " * s_length # create of string of length N
even_index = 0 # because evens are going to come first
odd_index = s_length - (s_length/2) + 1 # odds are going to start where even numbers end + 1
0.upto(s_length-1) do |i|
if i%2 == 0
new_string[even_index] = string[i]
even_index += 1
elsif
new_string[odd_index] = string[i]
odd_index += 1
end
end
puts new_string
end
Benchmark:
require 'benchmark'
def using_ugly_algo(tests, string)
# tests = gets.to_i
tests.times do
string = string
s_length = string.length # String length N
new_string = " " * s_length # creat of string of length N
even_index = 0
odd_index = s_length - (s_length/2) + 1
0.upto(s_length-1) do |i|
if i%2 == 0
new_string[even_index] = string[i]
even_index += 1
elsif
new_string[odd_index] = string[i]
odd_index += 1
end
end
# new_string
end
end
def with_partition(amount, string)
amount.times do
input = string
(input.split('').partition.with_index do |_, i|
i.even?
end.map(&:join).join(' '))
end
end
n = 10_000
string = (0...500).map { ('a'..'z').to_a[rand(26)] }.join
Benchmark.bmbm(100) do |x|
x.report("using_ugly_algo "){ n.times { using_ugly_algo(5, string) } }
x.report("with_partition "){ n.times { with_partition(5, string) } }
end
Report:
Rehearsal ----------------------------------------------------------------------------------------------------------------------------------------
using_ugly_algo 13.790000 0.030000 13.820000 ( 13.843560)
with_partition 16.790000 0.030000 16.820000 ( 16.830992)
------------------------------------------------------------------------------------------------------------------------------ total: 30.640000sec
user system total real
using_ugly_algo 13.930000 0.060000 13.990000 ( 14.070378)
with_partition 18.640000 0.210000 18.850000 ( 19.392816)
Well, the problem you are having is, if I am using the right term, a usage error. Your code is setting s1 and s2 to whatever the last checked letter is instead of concatenating. Modifying you code, I suppose what you are looking for is something like this:
S = gets.chomp.chars.to_a
s1 = ""
s2 = ""
for i in 0...S.length
if i%2 == 0
s1.concat(S[i])
else
s2.concat(S[i])
end
end
puts s1.to_s + " " + s2.to_s

Using Regex and ruby regular expressions to find values

So I'm currently trying to sort values from a file. I'm stuck on the finding the first attribute, and am not sure why. I'm new to regex and ruby so I'm not sure how to go about the problem. I'm trying to find values of a,b,c,d,e where they are all positive numbers.
Here's what the line will look like
length=<a> begin=(<b>,<c>) end=(<d>,<e>)
Here's what I'm using to find the values
current_line = file.gets
if current_line == nil then return end
while current_line = file.gets do
if line =~ /length=<(\d+)> begin=((\d+),(\d+)) end=((\d+),(\d+))/
length, begin_x, begin_y, end_x, end_y = $1, $2, $3, $4, $5
puts("length:" + length.to_s + " begin:" + begin_x.to_s + "," + begin_y.to_s + " end:" + end_x.to_s + "," + end_y.to_s)
end
end
for some reason it never prints anything out, so I'm assuming it never finds a match
Sample input
length=4 begin=(0,0) end=(3,0)
A line with 0-4 decimals after 2 integers seperated by commas.
So it could be any of these:
2 4 1.3434324,3.543243,4.525324
1 2
18 3.3213,9.3233,1.12231,2.5435
7 9 2.2,1.899990
0 3 2.323
Here is your regex:
r = /length=<(\d+)> begin=((\d+),(\d+)) end=((\d+),(\d+))/
str.scan(r)
#=> nil
First, we need to escape the parenthesis:
r = /length=<(\d+)> begin=\((\d+),(\d+)\) end=\((\d+),(\d+)\)/
Next, add the missing < and > after "begin" and "end".
r = /length=<(\d+)> begin=\(<(\d+)>,<(\d+)>\) end=\(<(\d+)>,<(\d+)>\)/
Now let's try it:
str = "length=<4779> begin=(<21>,<47>) end=(<356>,<17>)"
but first, let's set the mood
str.scan(r)
#=> [["4779", "21", "47", "356", "17"]]
Success!
Lastly (though probably not necessary), we might replace the single spaces with \s+, which permits one or more spaces:
r = /length=<(\d+)>\s+begin=\(<(\d+)>,<(\d+)>\)\send=\(<(\d+)>,<(\d+)>\)/
Addendum
The OP has asked how this would be modified if some of the numeric values were floats. I do not understand precisely what has been requested, but the following could be modified as required. I've assumed all the numbers are non-negative. I've also illustrated one way to "build" a regex, using Regexp#new.
s1 = '<(\d+(?:\.\d+)?)>' # note single parens
#=> "<(\\d+(?:\\.\\d+)?)>"
s2 = "=\\(#{s1},#{s1}\\)"
#=> "=\\(<(\\d+(?:\\.\\d+)?)>,<(\\d+(?:\\.\\d+)?)>\\)"
r = Regexp.new("length=#{s1} begin#{s2} end#{s2}")
#=> /length=<(\d+(?:\.\d+)?)> begin=\(<(\d+(?:\.\d+)?)>,<(\d+(?:\.\d+)?)>\) end=\(<(\d+(?:\.\d+)?)>,<(\d+(?:\.\d+)?)>\)/
str = "length=<47.79> begin=(<21>,<4.7>) end=(<0.356>,<17.999>)"
str.scan(r)
#=> [["47.79", "21", "4.7", "0.356", "17.999"]]
Sample input:
length=4 begin=(0,0) end=(3,0)
data.txt:
length=3 begin=(0,0) end=(3,0)
length=4 begin=(0,1) end=(0,5)
length=2 begin=(1,3) end=(1,5)
Try this:
require 'pp'
Line = Struct.new(
:length,
:begin_x,
:begin_y,
:end_x,
:end_y,
)
lines = []
IO.foreach('data.txt') do |line|
numbers = []
line.scan(/\d+/) do |match|
numbers << match.to_i
end
lines << Line.new(*numbers)
end
pp lines
puts lines[-1].begin_x
--output:--
[#<struct Line length=3, begin_x=0, begin_y=0, end_x=3, end_y=0>,
#<struct Line length=4, begin_x=0, begin_y=1, end_x=0, end_y=5>,
#<struct Line length=2, begin_x=1, begin_y=3, end_x=1, end_y=5>]
1
With this data.txt:
2 4 1.3434324,3.543243,4.525324
1 2
18 3.3213,9.3233,1.12231,2.5435
7 9 2.2,1.899990
0 3 2.323
Try this:
require 'pp'
data = []
IO.foreach('data.txt') do |line|
pieces = line.split
csv_numbers = pieces[-1]
next if not csv_numbers.index('.') #skip the case where there are no floats on a line
floats = csv_numbers.split(',')
data << floats.map(&:to_f)
end
pp data
--output:--
[[1.3434324, 3.543243, 4.525324],
[3.3213, 9.3233, 1.12231, 2.5435],
[2.2, 1.89999],
[2.323]]

Can I iterate through an array during a comparison?

s = Array.new
s << 19
while (s.last + 19) < 100000 do
s << s.last + 19
end
This^ works. s is an array of all factors of 19 below 100,000.
I'm trying to, in a succinct statement, find all numbers in s where the reverse of that number is also in the array. Ex: 176 and 671.
reflections= s.select { |num| num.to_s.reverse == s.each.to_s }
I know this is wrong, but how can I check each reversed item against the entire array?
This should work:
reflections = s.select { |num| s.include?(num.to_s.reverse.to_i) }
Although it produces results that you probably didn't anticipate
s = [176, 234, 671, 111]
reflections = s.select { |num| s.include?(num.to_s.reverse.to_i) }
reflections # => [176, 671, 111]
These are all valid results according to your logic.
Excluding self-match is pretty straighforward:
s = [176, 234, 671, 111]
reflections = s.select do |x|
x = x.to_s
r = x.reverse
(x != r) && s.include?(r.to_i)
end
reflections # => [176, 671]
reflections = s & s.map{|num| num.to_s.reverse.to_i}
Try:
reverse_array = s.select {|num| num.to_s == num.to_s.reverse }
UPDATE:
After checking I found this will work:
myarr = ""
s = (1..1000)
s.select{ |num|
unless s.include?(num.to_s.reverse.to_i)
myarr << num.to_s
end
}
Finally, the myarr will contain all the numbers whose reverse is present in array s.

Resources