I have two arrays. The first one will be an array of string with a name and the amount. The second is an array of letters.
a1 = ["ASLK 50", "BSKD 150", "ZZZZ 100", "BSDF 50"]
a2 = ["B", "Z"]
I want to create a third array to sort the contents from a1 based off a2 and return the number based on the information from first array. Since a2 has "B" and "Z", I need to scan first array for all entry starting with letter B and Z and add up the numbers.
My ultimate goal is to return the sum on third array, something like this:
a3 = ["B = 200", "Z = 100"]
Since "A" was not on a2, it is not counted.
I was able to extract the information from a1:
arr = a1.map{|el| el[0] + " : " + el.gsub(/\D/, '\1')}
#=> ["A : 50", "B : 150", "Z : 100", "B : 50"]
I am having trouble comparing a1with a2. I have tried different methods, such as:
a1.find_all{|i| i[0] == a2[0]} #=> only returns the first element of a2. How can I iterate through a2?
alternatively,
i = 0
arr_result = []
while i < (arr.length + 1)
#(append to arr_result the number from a1 if a1[i][0] matches a2[i])
I think either would solve it, but I can't put neither idea down to working code. How can I implement either method? Is there a more efficient way to do it?
Here is how I would do this:
a1 = ["ASLK 50", "BSKD 150", "ZZZZ 100", "BSDF 50"]
a2 = ["B", "Z"]
a3 = a1.each_with_object(Hash.new(0)) do |a,obj|
obj[a[0]] += a.split.last.to_i if a2.include?(a[0])
end.map {|a| a.join(" = ")}
#=>["B = 200", "Z = 100"]
First step adds them all into a Hash
by summing the values by each first letter that is contained in the second Array.
Second step provides the desired output
If you want a Hash instead just take off the last call to map and you'll have.
{"B" => 200, "Z" => 100}
Running with your requirements, that you want to turn this:
a1 = ["ASLK 50", "BSKD 150", "ZZZZ 100", "BSDF 50"]
a2 = ["B", "Z"]
into this: a3 = ["B = 200", "Z = 100"]
a3 = a2.map do |char|
sum = a1.reduce(0) do |sum, item|
name, price = item.split(" ")
sum += price.to_i if name[0].eql?(char)
sum
end
"#{char} = #{sum}"
end
Rather than defining an Array
like ["B = 200", "Z = 100"]
, it would be more sensible to define this as a mapping - ie the following Hash
object:
{"B" => 200, "Z" => 100}
As for the implementation, there are many ways to do it. Here is just one approach:
a1 = ["ASLK 50", "BSKD 150", "ZZZZ 100", "BSDF 50"]
a2 = ["B", "Z"]
result = a2.map do |letter|
[
letter,
a1.select {|str| str[0] == letter}
.inject(0) {|sum, str| sum += str[/\d+/].to_i}
]
end.to_h
puts result # => {"B"=>200, "Z"=>100}
Explanation:
Array#to_h
to convert the array of pairs: [["B", 200], ["Z", 100]]
into a Hash
: {"B" => 200, "Z" => 100}
. a1.select {|str| str[0] == letter}
a1.select {|str| str[0] == letter}
selects only the elements from a1
whose first letter is that of the hash key. inject(0) {|sum, str| sum += str[/\\d+/].to_i}
inject(0) {|sum, str| sum += str[/\\d+/].to_i}
adds up all the numbers, with safe-guards to default to zero (rather than having nil
thrown around unexpectedly). This is quite similar to @engineersmnky's answer.
r = /
\A[A-Z] # match an upper case letter at the beginning of the string
| # or
\d+ # match one or more digits
/x # free-spacing regex definition mode
a1.each_with_object(Hash.new(0)) do |s,h|
start_letter, value = s.scan(r)
h[start_letter] += value.to_i if a2.include?(start_letter)
end.map { |k,v| "#{k} = #{v}" }
#=> ["B = 200", "Z = 100"]
Hash.new(0)
is often referred to as a "counting hash". See the doc for the class method Hash::new for an explanation.
The regex matches the first letter of the string or one or more digits. For example,
"ASLK 50".scan(r)
#=> ["A", "50"]
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.