Why map! doens't work with a splited string ?
str = "abcd"
str.split('').map! { |x| x + "!" }
str # => "abcd"
I expected this would return ["a!","b!","c!","d!"]
str.split('') returns a new array. Calling .map! against the new string does not change the original str.
Assigning the result of str.split('') back to str:
str = str.split('')
str.map! { |x| x + "!" }
str # => ["a!", "b!", "c!", "d!"]
Or, assigning the whole result back to the str:
str = str.split('').map! { |x| x + "!" }
str # => ["a!", "b!", "c!", "d!"]
NOTE
Using same variable for saving string initially, then saving array could confuse the reader of the code. You'd better to use different variable for array;
arr = str.split('')
arr.map! { |x| x + "!" }
arr # => ["a!", "b!", "c!", "d!"]
Because you are updating in-place yes, but that array object that you waste because you don't save its instance anywhere.
characters = "abcd".split('')
characters.map! { |x| x + "!" }
characters
Note that you can move from split('') to chars
You also can use #chars:
> "abcd".chars.map { |x| x + '!' }
=> ["a!", "b!", "c!", "d!"]
Related
If I have a string like this
str =<<END
7312357006,1.121
3214058234,3456
7312357006,1234
1324958723,232.1
3214058234,43.2
3214173443,234.1
6134513494,23.2
7312357006,11.1
END
If a number in the first value shows up again, I want to add their second values together. So the final string would look like this
7312357006,1246.221
3214058234,3499.2
1324958723,232.1
3214173443,234.1
6134513494,23.2
If the final output is an array that's fine too.
There are lots of ways to do this in Ruby. One particularly terse way is to use String#scan:
str = <<END
7312357006,1.121
3214058234,3456
7312357006,1234
1324958723,232.1
3214058234,43.2
3214173443,234.1
6134513494,23.2
7312357006,11.1
END
data = Hash.new(0)
str.scan(/(\d+),([\d.]+)/) {|k,v| data[k] += v.to_f }
p data
# => { "7312357006" => 1246.221,
# "3214058234" => 3499.2,
# "1324958723" => 232.1,
# "3214173443" => 234.1,
# "6134513494" => 23.2 }
This uses the regular expression /(\d+),([\d.]+)/ to extract the two values from each line. The block is called with each pair as arguments, which are then merged into the hash.
This could also be written as a single expression using each_with_object:
data = str.scan(/(\d+),([\d.]+)/)
.each_with_object(Hash.new(0)) {|(k,v), hsh| hsh[k] += v.to_f }
# => (same as above)
There are likewise many ways to print the result, but here are a couple I like:
puts data.map {|kv| kv.join(",") }.join("\n")
# => 7312357006,1246.221
# 3214058234,3499.2
# 1324958723,232.1
# 3214173443,234.1
# 6134513494,23.2
# or:
puts data.map {|k,v| "#{k},#{v}\n" }.join
# => (same as above)
You can see all of these in action on repl.it.
Edit: Although I don't recommend either of these for the sake of readability, here's more just for kicks (requires Ruby 2.4+):
data = str.lines.group_by {|s| s.slice!(/(\d+),/); $1 }
.transform_values {|a| a.sum(&:to_f) }
...or, to going straight to a string:
puts str.lines.group_by {|s| s.slice!(/(\d+),/); $1 }
.map {|k,vs| "#{k},#{vs.sum(&:to_f)}\n" }.join
Since repl.it is stuck on Ruby 2.3: Try it online!
You could achieve this using each_with_object, as below:
str = "7312357006,1.121
3214058234,3456
7312357006,1234
1324958723,232.1
3214058234,43.2
3214173443,234.1
6134513494,23.2
7312357006,11.1"
# convert the string into nested pairs of floats
# to briefly summarise the steps: split entries by newline, strip whitespace, split by comma, convert to floats
arr = str.split("\n").map(&:strip).map { |el| el.split(",").map(&:to_f) }
result = arr.each_with_object(Hash.new(0)) do |el, hash|
hash[el.first] += el.last
end
# => {7312357006.0=>1246.221, 3214058234.0=>3499.2, 1324958723.0=>232.1, 3214173443.0=>234.1, 6134513494.0=>23.2}
# You can then call `to_a` on result if you want:
result.to_a
# => [[7312357006.0, 1246.221], [3214058234.0, 3499.2], [1324958723.0, 232.1], [3214173443.0, 234.1], [6134513494.0, 23.2]]
each_with_object iterates through each pair of data, providing them with access to an accumulator (in this the hash). By following this approach, we can add each entry to the hash, and add together the totals if they appear more than once.
Hope that helps - let me know if you've any questions.
def combine(str)
str.each_line.with_object(Hash.new(0)) do |s,h|
k,v = s.split(',')
h.update(k=>v.to_f) { |k,o,n| o+n }
end.reduce('') { |s,kv_pair| s << "%s,%g\n" % kv_pair }
end
puts combine str
7312357006,1246.22
3214058234,3499.2
1324958723,232.1
3214173443,234.1
6134513494,23.2
Notes:
using String#each_line is preferable to str.split("\n") as the former returns an enumerator whereas the latter returns a temporary array. Each element generated by the enumerator is line of str that (unlike the elements of str.split("\n")) ends with a newline character, but that is of no concern.
see Hash::new, specifically when a default value (here 0) is used. If a hash has been defined h = Hash.new(0) and h does not have a key k, h[k] returns the default value, zero (h is not changed). When Ruby encounters the expression h[k] += 1, the first thing she does is expand it to h[k] = h[k] + 1. If h has been defined with a default value of zero, and h does not have a key k, h[k] on the right of the equality (syntactic sugar1 for h.[](k)) returns zero.
see Hash#update (aka merge!). h.update(k=>v.to_f) is syntactic sugar for h.update({ k=>v.to_f })
see Kernel#sprint for explanations of the formatting directives %s and %g.
the receiver for the expression reduce('') { |s,kv_pair| s << "%s,%g\n" % kv_pair } (in the penultimate line), is the following hash.
{"7312357006"=>1246.221, "3214058234"=>3499.2, "1324958723"=>232.1,
"3214173443"=>234.1, "6134513494"=>23.2}
1 Syntactic sugar is a shortcut allowed by Ruby.
Implemented this solution as hash was giving me issues:
d = []
s.split("\n").each do |line|
x = 0
q = 0
dup = false
line.split(",").each do |data|
if x == 0 and d.include? data then dup = true ; q = d.index(data) elsif x == 0 then d << data end
if x == 1 and dup == false then d << data end
if x == 1 and dup == true then d[q+1] = "#{'%.2f' % (d[q+1].to_f + data.to_f).to_s}" end
if x == 2 and dup == false then d << data end
x += 1
end
end
x = 0
s = ""
d.each do |val|
if x == 0 then s << "#{val}," end
if x == 1 then s << "#{val}\n ; x = 0" end
x += 1
end
puts(s)
How I could replaces a string like this
I think something like this
inputx.gsub(/variable1/,string1.split(";")[i])
But I dont know How I could do this code
name1;variable1
name;variable1
name3;variable1
by
dog;watch;rock
For obtain this
name1;dog
name;watch
name3;rock
string1 => dog;watch;rock ; this string Im trying to split for replace each string variable1
Please help me
subst = "dog;watch;rock".split ';'
input.gsub(/variable1/) do subst.shift end
#⇒ "name1;dog \n name;watch \n name3;rock"
Given (assuming) this input:
inputx = <<-EOD
name1;variable1
name;variable1
name3;variable1
EOD
#=> "name1;variable1\nname;variable1\nname3;variable1\n"
string1 = 'dog;watch;rock'
#=> "dog;watch;rock"
You can chain gsub and with_index to perform a replacement based on its index:
inputx.gsub('variable1').with_index { |_, i| string1.split(';')[i] }
#=> "name1;dog\nname;watch\nname3;rock\n"
You could also perform the split beforehand:
values = string1.split(';')
#=> ["dog", "watch", "rock"]
inputx.gsub('variable1').with_index { |_, i| values[i] }
#=> "name1;dog\nname;watch\nname3;rock\n"
I'm not sure there's a way to do it using .gsub(). One simple way to achieve what you want to is the following:
str = "dog;watch;rock"
array = str.split(";")
array.each_with_index do |str, i|
array[i] = "name#{i + 1};#{str}"
end
puts array
Output:
name1;dog
name2;watch
name3;rock
file intro2 => dog;watch;rock
file intro
name1;variable1
name;variable1
name3;variable1
ruby code
ruby -e ' n=0; input3= File.read("intro");string1= File.read("intro2") ;input3x=input3.gsub("variable1") { val =string1.split(";")[n].to_s; n+=1; val } ;print input3x' >gggf
Building out a Rot method to solve encryption. I have something that is working but takes out whitespaces and any characters that are included. Was going to use bytes instead of chars then turn it back into a string once I have the byte code but I can't seem to get it working. How would you go about keeping those in place from this code:
code
def rot(x, string, encrypt=true)
alphabet = Array("A".."Z") + Array("a".."z")
results = []
if encrypt == true
key = Hash[alphabet.zip(alphabet.rotate(x))]
string.chars.each do |i|
if ('a'..'z').include? i
results << key.fetch(i).downcase
elsif ('A'..'Z').include? i
results << key.fetch(i).upcase
end
end
return results.join
else
key_false = Hash[alphabet.zip(alphabet.rotate(26 - x))]
string.chars.each do |i|
if ('a'..'z').include? i
results << key_false.fetch(i).downcase
elsif ('A'..'Z').include? i
results << key_false.fetch(i).upcase
end
end
return results.join
end
end
puts rot(10, "Hello, World")
=> RovvyGybvn
puts rot(10, "Rovvy, Gybvn", false)
=> HelloWorld
Thanks for your help in advance!
Just add to both if blocks an else condition like this:
if ('a'..'z').include? i
# ...
elsif ('A'..'Z').include? i
# ...
else
results << i
end
Which will add all non A-z characters untouched to the output.
I've noticed some issues with your code:
Broken replacement hash
This is the biggest problem - your replacement hash is broken. I'm using a smaller alphabet for demonstration purposes, but this applies to 26 characters as well:
uppercase = Array("A".."C")
lowercase = Array("a".."c")
alphabet = uppercase + lowercase
#=> ["A", "B", "C", "a", "b", "c"]
You build the replacement hash via:
x = 1
key = Hash[alphabet.zip(alphabet.rotate(x))]
#=> {"A"=>"B", "B"=>"C", "C"=>"a", "a"=>"b", "b"=>"c", "c"=>"A"}
"C"=>"a" and "c"=>"A" are referring to the wrong character case. This happens because you rotate the entire alphabet at once:
alphabet #=> ["A", "B", "C", "a", "b", "c"]
alphabet.rotate(x) #=> ["B", "C", "a", "b", "c", "A"]
Instead. you have to rotate the uppercase and lowercase letter separately:
uppercase #=> ["A", "B", "C"]
uppercase.rotate(x) #=> ["B", "C", "A"]
lowercase #=> ["a", "b", "c"]
lowercase.rotate(x) #=> ["B", "C", "A"]
and concatenate the rotated parts afterwards. Either:
key = Hash[uppercase.zip(uppercase.rotate(x)) + lowercase.zip(lowercase.rotate(x))]
#=> {"A"=>"B", "B"=>"C", "C"=>"A", "a"=>"b", "b"=>"c", "c"=>"a"}
or:
key = Hash[(uppercase + lowercase).zip(uppercase.rotate(x) + lowercase.rotate(x))]
#=> {"A"=>"B", "B"=>"C", "C"=>"A", "a"=>"b", "b"=>"c", "c"=>"a"}
Replacing the characters
Back to a full alphabet:
uppercase = Array("A".."Z")
lowercase = Array("a".."z")
x = 10
key = Hash[uppercase.zip(uppercase.rotate(x)) + lowercase.zip(lowercase.rotate(x))]
Having a working replacement hash makes replacing the characters almost trivial:
string = "Hello, World!"
result = ""
string.each_char { |char| result << key.fetch(char, char) }
result
#=> "Rovvy, Gybvn!"
I've changed result from an array to a string. It also has a << method and you don't have to join it afterwards.
Hash#fetch works almost like Hash#[], but you can pass a default value that is returned if the key is not found in the hash:
key.fetch("H", "H") #=> "R" (replacement value)
key.fetch("!", "!") #=> "!" (default value)
Handling encryption / decryption
You're duplicating a lot of code to handle the decryption part. But there's a much easier way - just reverse the direction:
rot(10, "Hello") #=> "Rovvy"
rot(10, "Rovvy", false) #=> "Hello"
rot(-10, "Rovvy") #=> "Hello"
So within your code, you can write:
x = -x unless encrypt
Putting it all together
def rot(x, string, encrypt = true)
uppercase = Array("A".."Z")
lowercase = Array("a".."z")
x = -x unless encrypt
key = Hash[uppercase.zip(uppercase.rotate(x)) + lowercase.zip(lowercase.rotate(x))]
result = ""
string.each_char { |char| result << key.fetch(char, char) }
result
end
rot(10, "Hello, World!") #=> "Rovvy, Gybvn!"
rot(10, "Rovvy, Gybvn!", false) #=> "Hello, World!"
Im looking to split an array of strings and creating a hash out of it.
I have an algorithm that splits a string into an array by commas this:1, is:1, a:1, string:1
def split_answer_to_hash(str)
words = str.split(',')
answer = {}
words.each do |w|
a = w.split(':')
h = Hash[ *a.collect { |v| [ v, a[1] ] } ]
answer = h
end
answer
end
What I need to do now is to make the left side of the colon the key to the hash and the right side of the colon the value of the hash. example: {"this" =>1, "is"=>1, "a"=>1, "string"=>1 }
*a.collect is iterating through the array and making the value another key. How can I go about this with out that happening?
The easiest way is:
string = 'this:1, is:1, a:1, string:1'
hash = Hash[*string.split(/:|,/)]
#=> {"this"=>"1", " is"=>"1", " a"=>"1", " string"=>"1"}
Having just one answer to this question just won't do:
str = "this:1, is:1, a:1, string:1"
Hash[str.scan(/(?:([^:]+):(\d+)(?:,\s)?)/)]
.tap { |h| h.keys.each { |k| h[k] = h[k].to_i } }
#=> {"this"=>1, "is"=>1, "a"=>1, "string"=>1}
Object#tap is used merely to convert the values from strings to integers. If you'd prefer:
h = Hash[str.scan(/(?:([^:]+):(\d+)(?:,\s)?)/)]
h.keys.each { |k| h[k] = h[k].to_i }
h
#=> {"this"=>1, "is"=>1, "a"=>1, "string"=>1}
For Ruby 2.1, you can replace Hash[arr] with arr.to_h.
I have a string like this,
str = "uu#p, xx#m, yy#n, zz#m"
I want to know how to convert the given string into a hash. (i.e my actual requirement is, how many values (before the # symbol) have the m, n and p. I don't want the counting, I need an exact value). The output would be better like this,
{"m" => ["xx", "zz"], "n" => ["yy"], "p" => ["uu"]}
Can help me anyone, please?
Direct copy/past of an IRB session:
>> str.split(/, /).inject(Hash.new{|h,k|h[k]=[]}) do |h, s|
.. v,k = s.split(/#/)
.. h[k] << v
.. h
.. end
=> {"p"=>["uu"], "m"=>["xx", "zz"], "n"=>["yy"]}
Simpler code for a newbie :)
str = "uu#p, xx#m, yy#n, zz#m"
h = {}
str.split(",").each do |x|
v,k = x.split('#')
h[k] ||= []
h[k].push(v)
end
p h
FP style:
grouped = str
.split(", ")
.group_by { |s| s.split("#")[1] }
.transform_values { |ss| ss.map { |x| s.split("#")[0] } }
#=> {"m"=>["xx", "zz"], "n"=>["yy"], "p"=>["uu"]}
This is a pretty common pattern. Using Facets.map_by:
require 'facets'
str.split(", ").map_by { |s| s.split("#", 2).reverse }
#=> {"m"=>["xx", "zz"], "n"=>["yy"], "p"=>["uu"]}