Related
guys
I am a beginner in ruby and in my practices I thought of a musical script and there is a point that is making me sleepy: The moment I type Scale.major_by_note ('C') in irb everything is fine, but if I type Scale.major_by_note ('C #'), it doesn't work, for it to work I must put a "C # / Db", help me to make sure with both "C" and "C #" and "C # / Db", thank you! below is the script:
class Scale
NATURAL = %w[C D E F G A B].freeze
ACCIDENT = %w[C# Db D# Eb F# Gb G# Ab A# Bb].freeze
CHROMATIC = %w[C C#/Db D D#/Eb E F F#/Gb G G# A A#/Bb B].freeze
SCALE_MAJOR_PATTERN = [0, 2, 4, 5, 7, 9, 11, 12].freeze # T T st T T T st
SCALE_MINOR_PATTERN = [0, 2, 3, 5, 7, 8, 10, 12].freeze # T st T T st T T
def self.show_all_scales(note)
major = Scale.major_by_note(note)
minor = Scale.minor_by_note(note)
all = { major: major, minor: minor}
end
def self.major_by_note(note)
major_note_index = CHROMATIC.index(note)
SCALE_MAJOR_PATTERN.map do |major_interval| # Interação
major_scale_note_index = major_note_index + major_interval
if major_scale_note_index <= (CHROMATIC.length - 1)
CHROMATIC[major_scale_note_index]
else
reseted_major_scale_note_index = major_scale_note_index - CHROMATIC.length
CHROMATIC[reseted_major_scale_note_index]
end
end
end
def self.minor_by_note(note)
minor_note_index = CHROMATIC.index(note)
SCALE_MINOR_PATTERN.map do |minor_interval|
minor_scale_note_index = minor_note_index + minor_interval
if minor_scale_note_index <= (CHROMATIC.length - 1)
CHROMATIC[minor_scale_note_index]
else
reseted_minor_scale_note_index = minor_scale_note_index - CHROMATIC.length
CHROMATIC[reseted_minor_scale_note_index]
end
end
end
end```
When you type
%w[C C#/Db D D#/Eb E F F#/Gb G G# A A#/Bb B]
Ruby is turning this into an Array of Strings:
["C", "C#/Db", "D", "D#/Eb", "E", "F", "F#/Gb", "G", "G#", "A", "A#/Bb", "B"]
Now while you know C# and Db are the same note, Ruby doesn't. It thinks the note in this case is called C#/Db. When it tries to find CHROMATIC.index("C#") it is returning nil because there is no C# in the Array.
A solution could be to write it like this:
CHROMATIC = %w[C C# D D# E F F# G G# A A# B].freeze
CHROMATIC_PAIR_MAP = {
"Db" => "C#",
"Eb" => "D#",
"Gb" => "F#",
"Ab" => "B#",
}
...
def self.index_of_note(note)
CHROMATIC.index(note) ||
CHROMATIC.index(CHROMATIC_PAIR_MAP[note])
end
def self.major_by_note(note)
major_note_index = index_of_note(note)
Here I am making a new helper method to get the index of the note by either getting it straight from CHROMATIC array, or looking up a the note key in the CHROMATIC_PAR_MAP Hash. It will only perform the lookup in the Hash if CHROMATIC.index(note) returns nil.
This is what I get in the console (irb):
irb(main):191:0> Scale.major_by_note("C#")
=> ["C#", "D#", "F", "F#", "G#", "A#", "C", "C#"]
irb(main):192:0> Scale.major_by_note("Db")
=> ["C#", "D#", "F", "F#", "G#", "A#", "C", "C#"]
irb(main):193:0> Scale.major_by_note("D#")=> ["D#", "F", "G", "G#", "A#", "C", "D", "D#"]
irb(main):194:0> Scale.major_by_note("Eb")=> ["D#", "F", "G", "G#", "A#", "C", "D", "D#"]
The full new class:
class Scale
NATURAL = %w[C D E F G A B].freeze
ACCIDENT = %w[C# Db D# Eb F# Gb G# Ab A# Bb].freeze
CHROMATIC = %w[C C# D D# E F F# G G# A A# B].freeze
SCALE_MAJOR_PATTERN = [0, 2, 4, 5, 7, 9, 11, 12].freeze # T T st T T T st
SCALE_MINOR_PATTERN = [0, 2, 3, 5, 7, 8, 10, 12].freeze # T st T T st T T
CHROMATIC_PAIR_MAP = {
"Db" => "C#",
"Eb" => "D#",
"Gb" => "F#",
"Ab" => "B#",
}
def self.show_all_scales(note)
major = Scale.major_by_note(note)
minor = Scale.minor_by_note(note)
all = { major: major, minor: minor}
end
def self.major_by_note(note)
major_note_index = index_of_note(note)
SCALE_MAJOR_PATTERN.map do |major_interval| # Interação
major_scale_note_index = major_note_index + major_interval
if major_scale_note_index <= (CHROMATIC.length - 1)
CHROMATIC[major_scale_note_index]
else
reseted_major_scale_note_index = major_scale_note_index - CHROMATIC.length
CHROMATIC[reseted_major_scale_note_index]
end
end
end
def self.minor_by_note(note)
minor_note_index = CHROMATIC.index(note)
SCALE_MINOR_PATTERN.map do |minor_interval|
minor_scale_note_index = minor_note_index + minor_interval
if minor_scale_note_index <= (CHROMATIC.length - 1)
CHROMATIC[minor_scale_note_index]
else
reseted_minor_scale_note_index = minor_scale_note_index - CHROMATIC.length
CHROMATIC[reseted_minor_scale_note_index]
end
end
end
def self.index_of_note(note)
CHROMATIC.index(note) ||
CHROMATIC.index(CHROMATIC_PAIR_MAP[note])
end
end
I have an array that contains other arrays of items with prices but when one has a sale a new item is created How do I merge or pull value from one to the other to make 1 array so that the sale price replaces the non sale but contains the original price?
Example:
items=[{"id": 123, "price": 100, "sale": false},{"id":456,"price":25,"sale":false},{"id":678, "price":75, "sale":true, "parent_price_id":123}]
Transform into:
items=[{"id":456,"price":25,"sale":false},{"id":678, "price":75, "sale":true, "parent_price_id":123, "original_price": 100}]
It's not the prettiest solution, but here's one way you can do it. I added a minitest spec to check it against the values you provided and it gives the answer you're hoping for.
require "minitest/autorun"
def merge_prices(prices)
# Create a hash that maps the ID to the values
price_map =
prices
.map do |price|
[price[:id], price]
end
.to_h
# Create a result array which is initially duplicated from the original
result = prices.dup
result.each do |price|
if price.key?(:parent_price)
price[:original_price] = price_map[price[:parent_price]][:price]
# Delete the original
result.delete_if { |x| x[:id] == price[:parent_price] }
end
end
result
end
describe "Merge prices" do
it "should work" do
input = [
{"id":123, "price": 100, "sale": false},
{"id":456,"price":25,"sale": false},
{"id":678, "price":75, "sale": true, "parent_price":123}
].freeze
expected_output = [
{"id":456,"price":25,"sale": false},
{"id":678, "price":75, "sale": true, "parent_price":123, "original_price": 100}
].freeze
assert_equal(merge_prices(input), expected_output)
end
end
Let's being by defining items in an equivalent, but more familiar, way:
items = [
[{:id=>123, :price=>100, :sale=>false}],
[{:id=>456, :price=>25, :sale=>false}],
[{:id=>678, :price=>75, :sale=>true, :parent_price=>123}]
]
with the desired return value being:
[
{:id=>456, :price=>25, :sale=>false},
{:id=>678, :price=>75, :sale=>true, :parent_price=>123,
:original_price=>100}
]
I assume that h[:sale] #=> false for every element of items (a hash) g for which g[:parent_price] = h[:id].
A convenient first step is to create the following hash.
h = items.map { |(h)| [h[:id], h] }.to_h
#=> {123=>{:id=>123, :price=>100, :sale=>false},
# 456=>{:id=>456, :price=>25, :sale=>false},
# 678=>{:id=>678, :price=>75, :sale=>true, :parent_price=>123}}
Then:
h.keys.each { |k| h[k][:original_price] =
h.delete(h[k][:parent_price])[:price] if h[k][:sale] }
#=> [123, 456, 678] (not used)
h #=> {456=>{:id=>456, :price=>25, :sale=>false},
# 678=>{:id=>678, :price=>75, :sale=>true, :parent_price=>123,
# :original_price=>100}}
Notice that Hash#delete returns the value of the deleted key.
The last two steps are to extract the values from this hash and replace items with the resulting array of hashes:
items.replace(h.values)
#=> [{:id=>456, :price=>25, :sale=>false},
# {:id=>678, :price=>75, :sale=>true, :parent_price=>123,
# :original_price=>100}]
See Array#replace.
If desired we could combine these steps as follows.
items.replace(
items.map { |(h)| [h[:id], h] }.to_h.tap do |h|
h.keys.each { |k| h[k][:original_price] =
h.delete(h[k][:parent_price])[:price] if h[k][:sale] }
end.values)
#=> [{:id=>456, :price=>25, :sale=>false},
# {:id=>678, :price=>75, :sale=>true, :parent_price=>123,
# :original_price=>100}]
See Object#tap.
In my quest to understand ruby's enumerable, I have something similar to the following
FileReader.read(very_big_file)
.lazy
.flat_map {|line| get_array_of_similar_words } # array.size is ~10
.each_slice(100) # wait for 100 items
.map{|array| process_100_items}
As much as each flat_map call emits an array of ~10 items, I was expecting the each_slice call to batch the items in 100's but that is not the case. I.e wait until there are 100 items before passing them to the final .map call.
How do I achieve functionality similar to the buffer function in reactive programming?
To see how lazy affects the calculations, let's look at an example. First construct a file:
str =<<~_
Now is the
time for all
good Ruby coders
to come to
the aid of
their bowling
team
_
fname = 't'
File.write(fname, str)
#=> 82
and specify the slice size:
slice_size = 4
Now I will read lines, one-by-one, split the lines into words, remove duplicate words and then append those words to an array. As soon as the array contains at least 4 words I will take the first four and map them into the longest word of the 4. The code to do that follows. To show how the calculations progress I will salt the code with puts statements. Note that IO::foreach without a block returns an enumerator.
IO.foreach(fname).
lazy.
tap { |o| puts "o1 = #{o}" }.
flat_map { |line|
puts "line = #{line}"
puts "line.split.uniq = #{line.split.uniq} "
line.split.uniq }.
tap { |o| puts "o2 = #{o}" }.
each_slice(slice_size).
tap { |o| puts "o3 = #{o}" }.
map { |arr|
puts "arr = #{arr}, arr.max = #{arr.max_by(&:size)}"
arr.max_by(&:size) }.
tap { |o| puts "o3 = #{o}" }.
to_a
#=> ["time", "good", "coders", "bowling", "team"]
The following is displayed:
o1 = #<Enumerator::Lazy:0x00005992b1ab6970>
o2 = #<Enumerator::Lazy:0x00005992b1ab6880>
o3 = #<Enumerator::Lazy:0x00005992b1ab6678>
o3 = #<Enumerator::Lazy:0x00005992b1ab6420>
line = Now is the
line.split.uniq = ["Now", "is", "the"]
line = time for all
line.split.uniq = ["time", "for", "all"]
arr = ["Now", "is", "the", "time"], arr.max = time
line = good Ruby coders
line.split.uniq = ["good", "Ruby", "coders"]
arr = ["for", "all", "good", "Ruby"], arr.max = good
line = to come to
line.split.uniq = ["to", "come"]
line = the aid of
line.split.uniq = ["the", "aid", "of"]
arr = ["coders", "to", "come", "the"], arr.max = coders
line = their bowling
line.split.uniq = ["their", "bowling"]
arr = ["aid", "of", "their", "bowling"], arr.max = bowling
line = team
line.split.uniq = ["team"]
arr = ["team"], arr.max = team
If the line lazy. is removed the return value is the same but the following is displayed (.to_a at the end now being superfluous):
o1 = #<Enumerator:0x00005992b1a438f8>
line = Now is the
line.split.uniq = ["Now", "is", "the"]
line = time for all
line.split.uniq = ["time", "for", "all"]
line = good Ruby coders
line.split.uniq = ["good", "Ruby", "coders"]
line = to come to
line.split.uniq = ["to", "come"]
line = the aid of
line.split.uniq = ["the", "aid", "of"]
line = their bowling
line.split.uniq = ["their", "bowling"]
line = team
line.split.uniq = ["team"]
o2 = ["Now", "is", "the", "time", "for", "all", "good", "Ruby",
"coders", "to", "come", "the", "aid", "of", "their",
"bowling", "team"]
o3 = #<Enumerator:0x00005992b1a41a08>
arr = ["Now", "is", "the", "time"], arr.max = time
arr = ["for", "all", "good", "Ruby"], arr.max = good
arr = ["coders", "to", "come", "the"], arr.max = coders
arr = ["aid", "of", "their", "bowling"], arr.max = bowling
arr = ["team"], arr.max = team
o3 = ["time", "good", "coders", "bowling", "team"]
I would like someone to clarify how can I possibly iterating over an array, find an exact match in an hash[value], and replace the element in the array with the hash[key].
As example, if I have a morse directory morse_dict = {
"a" => ".-","b" => "-...","c" => "-.-.","d" => "-..","e" => ".","f" => "..-.","g" => "--.","h" => "....","i" => "..","j" => ".---","k" => "-.-","l" => ".-..","m" => "--","n" => "-.","o" => "---","p" => ".--.","q" => "--.-","r" => ".-.","s" => "...","t" => "-","u" => "..-","v" => "...-","w" => ".--","x" => "-..-","y" => "-.--","z" => "--.."," " => " ","1" => ".----","2" => "..---","3" => "...--","4" => "....-","5" => ".....","6" => "-....","7" => "--...","8" => "---..","9" => "----.","0" => "-----"
}
and I want a method that for a given string in morse code returns a string in regular alphabet.
This is the codewars kata.
I am not interested in the solution to the challenge itself, I would like to understand the principle of this.
So far I have thought of proceeding this way:
def morse_code(arr)
arr.split(" ").each {|element|
element.each_char {|char|
(morse_dict.include?(char)) ? (print "true") : (print "false")}
}
end
I only print false, which means that I am not actually looking for match into the hash.
Using Hash#key without replacing the array, rather creating a new one (use map! for replacement):
array = [1,2,3,4,5]
hash = {a: 4, b: 7, c: 3}
array.map { |el| hash.key(el) }
# => [nil, nil, :c, :a, nil]
You may want to think about using Hash#invert and simply referencing the elements by keys for performance reasons as Hash#key is O(n) while Hash#[] is O(1).
array = [1,2,3,4,5]
hash = {a: 4, b: 7, c: 3}
inverted_hash = hash.invert
array.map { |el| inverted_hash[el] }
# => [nil, nil, :c, :a, nil]
I understand from the kata that letters are to be separated by one space and words by three spaces.
As a first step I will two changes to the hash morse_dict: remove the key ' '; and add key-value pairs for some punctuation characters. The space character key is not needed; the need for punctuation codes is discussed in the kata.
PUNCTUATION = { "."=>".-.-.-", ","=>"--..--", "?"=>"..--..", "!"=>"-.-.--" }
ALPHA_TO_MORSE = dict.reject { |k,_| k == " " }.merge(PUNCTUATION)
#=> {"a"=>".-", "b"=>"-...", "c"=>"-.-.", "d"=>"-..", "e"=>".", "f"=>"..-.",
# "g"=>"--.", "h"=>"....", "i"=>"..", "j"=>".---", "k"=>"-.-", "l"=>".-..",
# "m"=>"--", "n"=>"-.", "o"=>"---", "p"=>".--.", "q"=>"--.-", "r"=>".-.",
# "s"=>"...", "t"=>"-", "u"=>"..-", "v"=>"...-", "w"=>".--", "x"=>"-..-",
# "y"=>"-.--", "z"=>"--..", "1"=>".----", "2"=>"..---", "3"=>"...--",
# "4"=>"....-", "5"=>".....", "6"=>"-....", "7"=>"--...", "8"=>"---..",
# "9"=>"----.", "0"=>"-----", "."=>".-.-.-", ","=>"--..--", "?"=>"..--..",
# "!"=>"-.-.--"}
I obtained the Morse codes for the punctuation characters from the Morse Code Wiki. Additional punctuation characters could be added if desired.
The hash ALPHA_TO_MORSE is used in encoding text. The inverse of this hash is needed for decoding messages in Morse code. Also needed for decoding is the key value pair "...---..."=>"sos".
MORSE_TO_ALPHA = ALPHA_TO_MORSE.invert.merge("...---..."=>"sos")
#=> {".-"=>"a", "-..."=>"b", "-.-."=>"c", "-.."=>"d", "."=>"e", "..-."=>"f",
# "--."=>"g", "...."=>"h", ".."=>"i", ".---"=>"j", "-.-"=>"k", ".-.."=>"l",
# "--"=>"m", "-."=>"n", "---"=>"o", ".--."=>"p", "--.-"=>"q", ".-."=>"r",
# "..."=>"s", "-"=>"t", "..-"=>"u", "...-"=>"v", ".--"=>"w", "-..-"=>"x",
# "-.--"=>"y", "--.."=>"z", ".----"=>"1", "..---"=>"2", "...--"=>"3",
# "....-"=>"4", "....."=>"5", "-...."=>"6", "--..."=>"7", "---.."=>"8",
# "----."=>"9", "-----"=>"0", ".-.-.-"=>".", "--..--"=>",",
# "..--.."=>"?", "-.-.--"=>"!""...---..."=>"sos"}
One more hash is needed to deal with cases where the message "sos" (or "SOS"--Morse code is case insensitive), or "sos" followed by a punctuation character (e.g., "sos!") is to be encoded.1 See the Wiki.
SOS_WITH_PUNCTUATION = PUNCTUATION.each_with_object({}) { |(k,v),h|
h["sos#{k}"] = "...---... #{v}" }.merge('sos'=>"...---...")
#=> {"sos."=>"...---... .-.-.-", "sos,"=>"...---... --..--",
# "sos?"=>"...---... ..--..", "sos!"=>"...---... -.-.--", "sos"=>"...---..."}
The encoding and decoding methods follow. encode checks to see if each word in the string is a key in the hash SOS_WITH_PUNCTUATION. If it is, the value of key is the Morse code for the word; else, the word is divided into letters and each letter is translated into Morse code.
def encode(str)
str.strip.downcase.split.map do |word|
if SOS_WITH_PUNCTUATION.key?(word)
SOS_WITH_PUNCTUATION[word]
else
word.each_char.map { |c| ALPHA_TO_MORSE[c] }.join(' ')
end
end.join (' ')
end
def decode(morse)
morse.strip.split(/ {3}/).map do |word|
word.split.map { |c| MORSE_TO_ALPHA[c] }.join
end.join(' ')
end
We can now try out these two methods.
str = " Is now the time for you, and 007, to send an SOS?"
morse = encode str
#=> ".. ... -. --- .-- - .... . - .. -- . ..-. --- .-. -.-- --- ..- --..-- .- -. -.. ----- ----- --... --..-- - --- ... . -. -.. .- -. ...---... ..--.."
decode morse
#=> "is now the time for you, and 007, to send an sos?"
1 It would be simpler to have a pre-processing step that would convert, say, "sos." to "sos .", but when the resulting Morse code were decoded there would be a space between "sos" and ".". I suppose that cryptographers could deal with that, but I've chosen to avoid the insertion of the space.
assuming: arr = 'a b c d', which is not an arr, so please make that morse_string
def morse_code(morse_string)
new_elements = []
# iterate over each character in the string,
morse_string.split(" ").each do |element|
if morse_dict[element]
# https://apidock.com/ruby/Array/push
new_elements.push( morse_dict[element] )
else
# whatever you want to do when there is no match
end
end
# re-create the string again, but with braille
# https://apidock.com/ruby/Array/join
new_elements.join(' ')
end
morse_string = 'a b c d'
morse_code(morse_string)
I have an array of floating numbers and a certain cutoff:
myData = [1.3,1.5,1.7,1.7,16.7,18.4,19.2,19.5,19.6,20.2,20.8,58.4,60.7,
61.2,61.2,116.4,121.2,122.7,123.2,123.2,138.5,149.5,149.5]
myBin = 5.3
I'd like to build a hash of arrays so that the difference by subtraction between the last element and the first element of the array is less or equal to myBin (5.3)
myHash = {
'hap_1' => [1.3,1.5,1.7],
'hap_2' => [16.8, 18.4,19.2,19.5,19.6,20.2,20.8],
'hap_3' => [58.4,60.7,61.2,61.2],
'hap_4' => [116.4,121.2],
'hap_5' => [122.7,123.2,123.2],
'hap_6' => [138.5],
'hap_7' => [149.5,149.5]}
Thank you so much in advance for your time and helpful assistance.
Cheers
Enumerable#slice_before can solve your problem:
first = myData[0]
myData.slice_before { |e| first = e if e - first > myBin }.to_a
#=> [[1.3, 1.5, 1.7, 1.7],
# [16.7, 18.4, 19.2, 19.5, 19.6, 20.2, 20.8],
# [58.4, 60.7, 61.2, 61.2],
# [116.4, 121.2],
# [122.7, 123.2, 123.2],
# [138.5],
# [149.5, 149.5]]
myData.drop(1).each_with_object([[myData.first]]) { |n,a|
n - a.last.first <= myBin ? (a.last << n) : a << [n] }.
each.with_index(1).with_object({}) { |(a,i),h| h["hap_#{i}"] = a }
#=> {"hap_1"=>[1.3, 1.5, 1.7, 1.7],
# "hap_2"=>[16.7, 18.4, 19.2, 19.5, 19.6, 20.2, 20.8],
# "hap_3"=>[58.4, 60.7, 61.2, 61.2],
# "hap_4"=>[116.4, 121.2],
# "hap_5"=>[122.7, 123.2, 123.2],
# "hap_6"=>[138.5],
# "hap_7"=>[149.5, 149.5]}
You could build a customer enumerator that works like chunk_while, but compares each chunk's first element to the current one, i.e. 1.3 to 1.5, then 1.3 to 1.7 and so on:
module Enumerable
def chunk_while1
return enum_for(__method__) unless block_given?
each_with_object([]) do |elt, result|
if result.last && yield(result.last.first, elt)
result.last << elt
else
result << [elt]
end
end
end
end
Usage:
data = [
1.3, 1.5, 1.7, 1.7, 16.7, 18.4, 19.2, 19.5, 19.6, 20.2, 20.8, 58.4, 60.7,
61.2, 61.2, 116.4, 121.2, 122.7, 123.2, 123.2, 138.5, 149.5, 149.5
]
result = data.chunk_while1 { |i, j| j - i <= 5.3 }
#=> [
# [1.3, 1.5, 1.7, 1.7],
# [16.7, 18.4, 19.2, 19.5, 19.6, 20.2, 20.8],
# [58.4, 60.7, 61.2, 61.2],
# [116.4, 121.2],
# [122.7, 123.2, 123.2],
# [138.5],
# [149.5, 149.5]
# ]
The result can then be converted to a hash, e.g. via:
result.map.with_index(1) { |a, i| ["hap_#{i}", a] }.to_h
#=> {
# "hap_1"=>[1.3, 1.5, 1.7, 1.7],
# "hap_2"=>[16.7, 18.4, 19.2, 19.5, 19.6, 20.2, 20.8],
# "hap_3"=>[58.4, 60.7, 61.2, 61.2],
# "hap_4"=>[116.4, 121.2],
# "hap_5"=>[122.7, 123.2, 123.2],
# "hap_6"=>[138.5],
# "hap_7"=>[149.5, 149.5]
# }