I have a string in ruby like this:
str = "AABBCCDDEEFFGGHHIIJJ01020304050607080910"
# 20 letters and 20 numbers in this case
I want to split this in half, which I can do like this:
str[0, str.length/2]
or
str.split(0, str.length/2)
After that, I need to make arrays with the chars but with length 2 for each element like this:
["AA", "BB", "CC", "DD", "EE", "FF", "GG", "HH", "II", "JJ"],
[01, 02, 03, 04, 05, 06, 07, 08, 09, 10]
The problem is, I can't find a concise way to convert this string. I can do something like this
arr = []
while str.length > 0 do
arr << str[0, 1]
str[0, 1] = ""
end
but I rather want something like str.split(2), and the length of the string may change anytime.
How about this?
str.chars.each_slice(2).map(&:join)
You could use the scan method:
1.9.3p194 :004 > a = 'AABBCCDDEEC'
=> "AABBCCDDEEC"
1.9.3p194 :005 > a.scan(/.{1,2}/)
=> ["AA", "BB", "CC", "DD", "EE", "C"]
2.1.0 :642 > "d852".scan(/../)
=> ["d8", "52"]
Related
I have two arrays generated from :
#dividends_values = #dividends.historical.map(&:dividend).reverse.last(50)
#dividends_dates = #dividends.historical.map(&:date).reverse.last(50)
The first array is an array of float values and occasional there can be a few nil entries in that. I want to remove those nil entries(which is pretty easy with a compact or something like that), but I also want to move the corresponding entries from the #dividends_dates array.
That is because they the dates array is a 1-1 reference to the values array, so index 0 of array with dates correspondings to index 0 of array with values.
What is a good way to do that?
First, filter by nil. Then break that up into two arrays.
#last_dividends = #dividends.historical.select { |d| d.dividend }
#dividends_values = #last_dividends.map(&:dividend)
#dividends_dates = #last_dividends.map(&:date)
Better yet, turn them into a single array of [[dividend, date], [...]]
#last_dividends = #dividends
.historical
.select { |d| d.dividend }
.map { |d| [d.dividend, d.date] }
First let's create a class-like object for illustration.
Dividend = Struct.new(:value, :date)
historical = [
Dividend.new(nil, "Jan 1"),
Dividend.new(10, "Mar 22"),
Dividend.new(13, "Apr 21"),
Dividend.new(nil, "Aug 7"),
Dividend.new(8, "Oct 11")
]
#=> [#<struct Dividend value=nil, dade="Jan 1">,
# #<struct Dividend value=10, date="Mar 22">,
# #<struct Dividend value=13, date="Apr 21">,
# #<struct Dividend value=nil, date="Aug 7">,
# #<struct Dividend value=8, date="Oct 11">]
Then, for example,
inst = historical[3]
#=> #<struct Dividend value=nil, date="Aug 7">
inst.value
#=> nil
inst.date
#=> "Aug 7"
We may write
historical.filter_map do |inst|
[inst.value, inst.date] unless inst.value.nil?
end.transpose
#=> [[10, 13, 8], ["Mar 22", "Apr 21", "Oct 11"]]
Note that
historical.filter_map do |inst|
[inst.value, inst.date] unless inst.value.nil?
end
#=> [[10, "Mar 22"], [13, "Apr 21"], [8, "Oct 11"]]
See Enumerable#filter_map.
If you have two arrays with corresponding elements, e.g.:
values = [1, nil, 3]
dates = [Date.new(2022, 5, 1), Date.new(2022, 5, 2), Date.new(2022, 5, 3)]
You can turn them into one combined array of [dividend, date] pairs by using transpose:
values_with_dates = [values, dates].transpose
#=> [[1, #<Date: 2022-05-01>], [nil, #<Date: 2022-05-02>], [3, #<Date: 2022-05-03>]]
You can then remove the elements with value of nil via reject!:
values_with_dates.reject! { |value, date| value.nil? }
#=> [[1, #<Date: 2022-05-01>], [3, #<Date: 2022-05-03>]]
And transpose again to separate the pairs:
values_with_dates.transpose
#=> [[1, 3], [#<Date: 2022-05-01>, #<Date: 2022-05-03>]]
The inner arrays can be assigned back to separate variables using Ruby's multiple assignment.
As a one-liner:
values, dates = [values, dates].transpose.reject { |v, _| v.nil? }.transpose
I am looking to extract keys and values from a hash. I manage to retrieve the data but in the wrong format. I am doing the following:
#message_count_series = #messages.collect { |p| "[#{p["created_at"]}, #{p["total_cnt"]}]" }
=> ["[2021-12-02 13:21:19.837233, 3]", "[2021-11-20 13:54:54.846048, 3]"]
What I would like to obtain is:
=> [[2021-12-02 13:21:19.837233, 3], [2021-11-20 13:54:54.846048, 3]]
Just without the quote (not a string).
I tried the following :
#message_opened_series = #messages.collect { |p| ["#{p["created_at"]}, #{p["opened_cnt"]}"] }
=> [["2021-12-02 13:21:19.837233, 1"], ["2021-11-20 13:54:54.846048, 0"]]
Which takes me closer, but now my data are considered a string inside the array.
The following appear to work, but might not be very robust
#message_opened_series = #messages.collect { |p| [DateTime.parse("#{p["created_at"]}"), ("#{p["opened_cnt"]}").to_i] }
=> [[Thu, 02 Dec 2021 13:21:19 +0000, 1], [Sat, 20 Nov 2021 13:54:54 +0000, 0]]
Is there a better way to do this please ?
#message_opened_series =
#messages.collect { |p| [p["created_at"].to_s, p["opened_cnt"]] }
=> [["2021-12-02 13:21:19.837233", 3], ["2021-11-20 13:54:54.846048", 3]]
Hello I have been doing some research for sometime on this particular project I have been working on and I am at a loss. What I am looking to do is use information from a file and convert that to a hash using some of those components for my key. Within the file I have:1,Foo,20,Smith,40,John,55
An example of what I am looking for I am looking for an output like so {1 =>[Foo,20], 2 =>[Smith,40] 3 => [John,55]}
Here is what I got.
h = {}
people_file = File.open("people.txt") # I am only looking to read here.
until people_file.eof?
i = products_file.gets.chomp.split(",")
end
people_file.close
FName = 'test'
str = "1,Foo,20,Smith, 40,John,55"
File.write(FName, str)
#=> 26
base, *arr = File.read(FName).
split(/\s*,\s*/)
enum = (base.to_i).step
arr.each_slice(2).
with_object({}) {|pair,h| h[enum.next]=pair}
#=> {1=>["Foo", "20"], 2=>["Smith", "40"],
# 3=>["John", "55"]}
The steps are as follows.
s = File.read(FName)
#=> "1,Foo,20,Smith, 40,John,55"
base, *arr = s.split(/\s*,\s*/)
#=> ["1", "Foo", "20", "Smith", "40", "John", "55"]
base
#=> "1"
arr
#=> ["Foo", "20", "Smith", "40", "John", "55"]
a = base.to_i
#=> 1
I assume the keys are to be sequential integers beginning with a #=> 1.
enum = a.step
#=> (1.step)
enum.next
#=> 1
enum.next
#=> 2
enum.next
#=> 3
Continuing,
enum = a.step
b = arr.each_slice(2)
#=> #<Enumerator: ["Foo", "20", "Smith", "40", "John", "55"]:each_slice(2)>
Note I needed to redefine enum (or execute enum.rewind) to reinitialize it. We can see the elements that will be generated by this enumerator by converting it to an array.
b.to_a
#=> [["Foo", "20"], ["Smith", "40"], ["John", "55"]]
Continuing,
c = b.with_object({})
#=> #<Enumerator: #<Enumerator: ["Foo", "20", "Smith", "40", "John", "55"]
# :each_slice(2)>:with_object({})>
c.to_a
#=> [[["Foo", "20"], {}], [["Smith", "40"], {}], [["John", "55"], {}]]
The now-empty hashes will be constructed as calculations progress.
c.each {|pair,h| h[enum.next]=pair}
#=> {1=>["Foo", "20"], 2=>["Smith", "40"], 3=>["John", "55"]}
To see how the last step is performed, each initially directs the enumerator c to generate the first value, which it passes to the block. The block variables are assigned to that value, and the block calculation is performed.
enum = a.step
b = arr.each_slice(2)
c = b.with_object({})
pair, h = c.next
#=> [["Foo", "20"], {}]
pair
#=> ["Foo", "20"]
h #=> {}
h[enum.next]=pair
#=> ["Foo", "20"]
Now,
h#=> {1=>["Foo", "20"]}
The calculations are similar for the remaining two elements generated by the enumerator c.
See IO::write, IO::read, Numeric#step, Enumerable#each_slice, Enumerator#with_object, Enumerator#next and Enumerator#rewind. write and read respond to File because File is a subclass of IO (File.superclass #=> IO). split's argument, the regular expression, /\s*,\s*/, causes the string to be split on commas together with any spaces that surround the commas. Converting [["Foo", "20"], {}] to pair and h is a product of Array Decompostion.
I have an array that I am looping through and pushing specific values to a separate array. EX:
first_array = ["Promoter: 8", "Passive: 7"]
I want to push every value that is an integer to a separate array, that would look like this in the end:
final_array = [8,7]
It would be nice for the values in the new array to be integers. I can't think of a way to push all numeric values within a string to a new array, but what would be the best option to do what I am wanting?
first_array.map{|s| s[/\d+/].to_i}
# => [8, 7]
first_array.map{|a| a.match(/\d+/)}.compact.map{|a| a[0].to_i }
Use a regex to grab the integers,
compact the blank spaces from the strings with no integers, and
convert them all to ints
And I have to add this super short but complicated one-liner solution:
a = ["Promoter: 8", "Passive: 7"]
p a.grep(/(\d+)/){$&.to_i} #=> [8,7]
Your question, as formulated, has an easy practical answer, already provided by others. But it seems to me, that your array of strings
a = ["Promoter: 8", "Passive: 7"]
envies being a Hash. So, from broader perspective, I would take freedom of converting it to a Hash first:
require 'pyper' # (type "gem install pyper" in your command line to install it)
hsh = Hash[ a.τBmm2dτ &/(\w+): *(\d+)/.method( :match ) ]
#=> {"Promoter"=>"8", "Passive"=>"7"}
# (The construction of #τBmm2dτ Pyper method will be explained in the appendix.)
Now, having your input data in a hash, you can do things with them more easily, eg.
hsh.τmbtiτ
#=> [8, 7]
APPENDIX: Explanation of the Pyper methods.
Pyper methods are similar to Lisp #car/#cdr methods in that, that a combination of
letters controls the method behavior. In the first method, #τBmm2dτ:
τ - opening and ending character
m - means #map
B - means take a block
2 - means first 3 elements of an array
d - means all elements except the first one (same meaning as in #cdr, btw.)
So, in #τBmm2dτ, Bm applies the block as follows:
x = ["Promoter: 8", "Passive: 7"].map &/(\w+): *(\d+)/.method( :match )
#=> [#<MatchData "Promoter: 8" 1:"Promoter" 2:"8">, #<MatchData "Passive: 7" 1:"Passive" 2:"7">]
# Which results in an array of 2 MatchData objects.
Then, m2d chars map (m) the MatchData objects using 2 and d chars. Character 2 gives
x = x.map { |e| e.to_a.take 3 }
#=> [["Promoter: 8", "Promoter", "8"], ["Passive: 7", "Passive", "7"]]
and d removes the first element from each:
x = x.map { |e| e.drop 1 }
#=> [["Promoter", "8"], ["Passive", "7"]]
In the secon method, #τmbtiτ, m means again #map, b means take the second element, and ti means convert it to Integer:
{"Promoter"=>"8", "Passive"=>"7"}.to_a.map { |e| Integer e[1] }
#=> [8, 7]
If the integer part of each string in (which look like members of a hash) is always preceded by at least one space, and there is no other whitespace (other than possibly at the beginning of the string), you could do this:
first_array = ["Promoter: 8", "Passive: 7"]
Hash[*first_array.map(&:split).flatten].values.map(&:to_i) # => [8,7]
map first_array => [["Promoter:", "8"], ["Passive:", "7"]]
flatten => ["Promoter:", "8", "Passive:", "7"]
convert to hash => {"Promoter:" => "8", "Passive:" => "7"}
get hash values => ["8", "7"]
convert to ints => [8, 7]
Note the need for the splat:
Hash[*["Promoter:", "8", "Passive:", "7"]]
=> Hash["Promoter:", "8", "Passive:", "7"]
=> {"Promoter:" => "8", "Passive:" => "7"}
I have a string (e.g. "AABBCCDDEEFF") and want to split this into an array with each element containing two characters - ["AA", "BB", "CC", "DD", "EE", "FF"].
Try the String object's scan method:
>> foo = "AABBCCDDEEFF"
=> "AABBCCDDEEFF"
>> foo.scan(/../)
=> ["AA", "BB", "CC", "DD", "EE", "FF"]
Depending on your needs, this may work better:
> foo = "AAABBCDEEFF"
=> "AAABBCDEEFF"
> foo.scan(/.{1,2}/)
=> ["AA", "AB", "BC", "DE", "EF", "F"]
Not sure what your input looks like. The above answer will drop any characters that do not have a pair, this one will work on odd length strings.