remove last character from looping Hash - ruby

can you help me to solve this problem with this code?
total_user_input = []
while true
print "Field Name: "
user_input = gets.chomp
break if user_input.empty?
total_user_input << user_input
end
total_user_input.each do |input|
aa = input.split(":").reduce {|first, second| " \t'#{first}': '#{second}',\r\n".gsub("'",'"') }
puts aa.chomp(',')
end
and the result I get
"name": "string",
"desc": "string",
"price": "integer",
but what I want is just remove the last comma only
"name": "string",
"desc": "string",
"price": "integer"
thank you for helping me

I don't know actualy what your scenario But I base on your expected and You can try it.
total_user_input = [
"name:test",
"age:30"
]
input_length = total_user_input.size
total_user_input.each_with_index do |input, index|
aa = input.split(":").reduce {|first, second| " \t'#{first}': '#{second}',\r\n".gsub("'",'"') }
aa = aa.gsub(',', '') if index == input_length - 1 # or aa.slice(-1)
puts aa
end
=> My result
"name": "test",
"age": "30"

TL;DR
I'm happy to answer the question you actually asked, but it needs to be pointed out that what you're doing is inherently an anti-pattern and not idiomatic Ruby.
I'll first show you a better way to do what you're doing, using stages of transformation rather than a single idiomatic method chain. Next I'll cover some alternatives that will help you with the String data you've already constructed.
At the end, I also provide a few caveats that you should generally be aware of, but aren't essential to answering your question. They're still useful, and will definitely help with addressing these sorts of problems when you're trying to debug something.
The Right Way
First of all, don't what you're doing if you can avoid it. Solving the problem in that way leads to very brittle solutions. Instead, just take your pairs of inputs and convert them to a Hash or JSON directly. For example:
require "json"
pp total_user_input
#=> ["name", "string", "desc", "string", "price", "integer"]
aa = total_user_input.each_slice(2).to_h
#=> {"name"=>"string", "desc"=>"string", "price"=>"integer"}
puts JSON.pretty_generate aa
{
"name": "string",
"desc": "string",
"price": "integer"
}
This will transform your input in stages, and print a string to STDOUT suitable for writing to a file or otherwise pretty-printing as JSON.
Your Original Question Answered
If you insist on trying to modify the given String, we'll start by representing it as a here-document.
your_pseudo_json = <<~STRING
"name": "string",
"desc": "string",
"price": "integer",
STRING
You have limited options with this given String. You're also complicating your life by using Windows-style newlines instead of Unix-style ones, but you can handle this one of two ways:
Use the $/ record separator for your system, possibly defined by default, or set it yourself. Then you can just use String#chomp. For example:
# If you have to set it explicitly, do so by assigning to $/
# NB: You may not. I don't have a Windows system to check.
$/ = "\r\n"
your_pseudo_json.chomp!(",#{$/}")
Use an anchored regular expression to remove just the items at the end. This will work regardless of your platform’s line endings. For example:
your_pseudo_json.sub! /,(?:\r?\n)\z/, ""
If you're running a newer Ruby that supports String#delete_suffix! then you can do this almost like #chomp, but with a simple String without interpolation. For example:
# for Windows line endings
your_pseudo_json.delete_suffix! ",\r\n"
# for *nix-style line endings
your_pseudo_json.delete_suffix! ",\n"
Caveats
Use Non-Bang Methods for Testing in Irb or Pry
As a testing note, use the non-bang methods of #chomp, #sub, and #delete_suffix in your REPL for testing, so that you don't have to keep recreating your string while you experiment. The bang methods will modify your string in-place, unless you're running with frozen strings enabled by default.
Frozen Strings
Also, note that if you're using the frozen strings pragma in your program, e.g.:
# frozen_string_literal: true at the top of your .rb file
RUBYOPT="--enable-frozen-string-literal" in your environment when calling your REPL
then use the non-bang methods and assign the results back to a different (or even the same) variable. Duplicating unfrozen copies of frozen strings is outside the scope of your current question, so I wouldn't even mention it at all except to prevent general bike-shedding by other readers on the subject. :)

Related

How do I make these string substitutions using hash keys in Ruby?

I have a bunch of JSON files, processed in both Python and Ruby, that look something like this:
{
"KEY1": "foo",
"KEY2": "bar",
"URL": "https://{KEY2}.com/{KEY1}",
"IMPORTANT_THING": "repos/{KEY1}",
"NOTE": "This thing is {KEY1}{KEY2}ed",
"PYTHON_ONLY_THING": "{}/test/{}.py"
}
Note that the order that the keys will show up is not consistent, and I'd rather not change the JSON.
Here's my test code showing what I've tried so far:
my_config = {"KEY1"=>"foo",
"KEY2"=>"bar",
"URL"=>"https://{KEY2}.com/{KEY1}",
"IMPORTANT_THING"=>"repos/{KEY1}",
"NOTE"=>"This thing is {KEY1}{KEY2}ed",
"PYTHON_ONLY_THING"=>"{}/test/{}.py"}
my_config.each_key do |key|
# Braindead, hard-coded solution that works:
# my_config[key].gsub!("{KEY1}", my_config["KEY1"])
# my_config[key].gsub!("{KEY2}", my_config["KEY2"])
# More flexible (if it would work):
# my_config[key].gsub!(/{.*}/, my_config['\0'.slice(1,-2)])
my_config[key].gsub!(/{.*}/) {|s| my_config[s.slice(1,-2)]}
end
puts my_config
I'm using the braindead solution for now, which produces the expected output:
{"KEY1"=>"foo", "KEY2"=>"bar", "URL"=>"https://bar.com/foo", "IMPORTANT_THING"=>"repos/foo", "NOTE"=>"This thing is foobared", "PYTHON_ONLY_THING"=>"{}/test/{}.py"}
But I want to make it more flexible and maintainable. The first "better" solution throws an error apparently because slice operates on '\0' itself and not the match, plus I'm not sure it would match more than once. The currently uncommented solution doesn't work because the second part seems to operate on one letter at a time rather than each match like I expected, so it just removes the stuff in curly braces. Worse, it removes everything between the outer braces in the PYTHON_ONLY_THING, which is no good.
I figure I need to change both my regex and Ruby code if this is going to work, but I'm not sure where to look for more help. Or perhaps gsub isn't the right tool for this job. Any ideas?
I am using Ruby 2.3.7 on Linux x86_64.
Use String#gsub with an initial hash for replacements:
my_config.map do |k, v|
[
k,
v.gsub(/(?<={)[^}]+(?=})/, my_config).gsub(/{(?!})|(?<!{)}/, '')
]
end.to_h
#⇒ {"KEY1"=>"foo",
# "KEY2"=>"bar",
# "URL"=>"https://bar.com/foo",
# "IMPORTANT_THING"=>"repos/foo",
# "NOTE"=>"This thing is foobared",
# "PYTHON_ONLY_THING"=>"{}/test/{}.py"}
Starting with Ruby 2.4 (or using Rails) it might be done simpler using Hash#transform_values.
If you dislike the second gsubbing, transform the hash upfront:
my_substs = my_config.map { |k, v| ["{#{k}}", v] }.to_h
my_config.map do |k, v|
[k, v.gsub(/{[^}]+}/, my_substs)]
end.to_h
Here's a possible solution:
my_config = {"KEY1"=>"foo",
"KEY2"=>"bar",
"URL"=>"https://{KEY2}.com/{KEY1}",
"IMPORTANT_THING"=>"repos/{KEY1}",
"NOTE"=>"This thing is {KEY1}{KEY2}ed",
"PYTHON_ONLY_THING"=>"{}/test/{}.py"}
my_config.each_key do |key|
placeholders = my_config[key].scan(/{([^}]+)}/).flatten
placeholders.each do |placeholder|
my_config[key].gsub!("{#{placeholder}}", my_config[placeholder]) if my_config.keys.include?(placeholder)
end
end
puts my_config
By using scan, this will substitute all matches, not just the first match.
Using [[^}]+ in the regex, rather than .*, means you won't "swallow" too much in this part of the match. For example, if the input contains "{FOO} bar {BAZ}", then you want that pattern to only capture FOO and BAZ, not FOO} bar {BAZ.
Grouping the scan result, then calling flatten, is an easy way to reject what's outside the capture group, i.e. in this case the { and } characters. (This just makes the code a little less cryptic than using indexes like slice(1,-2)!
my_config.keys.include?(placeholder) checks whether this is actually . a known value, so you don't replace things with nil.

Ruby one liner lazy string evaluation

I'd like to create ruby one liner that prints some information to stdout and gets data from stdin. I've got some code:
["This should be shown first", "This second: #{gets.chomp}"].each{|i| puts "#{i}"}
...but apparently, get.chomp is evaluated in the same time when whole array is evaluated, before iteration of each element.
In result, I'm first prompted for input, and then each element is printed.
Can I somehow evaluate it lazily, print array in order and still have whole thing in one line?
One way to achieve lazy evaluation is to use procs. Something like this (multiple lines for readability):
[
-> { puts "This should be shown first" },
-> { print "This second: "; puts gets.chomp },
].each(&:call)
I don't really see the advantage of making this a one-liner since it becomes pretty unreadable, but nevertheless:
[ ->{ "This should be shown first" },
->{ "This second: #{gets.chomp}" }
].each {|line| puts line.call }
P.S. Never do "#{foo}". Use string interpolation (#{...}) when you want to, well, interpolate strings, as on the second line above. If you want to turn a non-string into a string, do foo.to_s. If you know it's already a string (or don't care if it is) just use it directly: foo. But puts automatically calls to_s on its arguments, so just do puts foo.
If you dont mind the repetiton of puts:
['puts "This should be shown first"', 'puts "This second: #{gets.chomp}"'].each{|i| eval i}
This is just to show you could use a method rather than a proc.
def line2
"#{["cat","dog"].sample}"
end
["Line 1", :line2, "line 3"].each { |l| puts (l.is_a? Symbol) ? method(l).call : l }
#=> dog

Ruby creating title case method, can't handle words like McDuff or McComb

The method is supposed to take in a name of a book and return it in proper title case. All of my specs pass ( )handles non-letter characters, handles upper and mixed cases) except the last one which is to return special words like McDuff or McComb with a capital 3rd letter. Anyone see what I'm doing wrong? And, is there a way to simplify this, using the tools at hand and not some higher level shortcut?
class String
define_method(:title_case) do
sentence_array = self.downcase.split
no_caps = ["a", "an", "the", "at", "by", "and", "as", "but", "or", "for", "in", "nor", "on", "at", "up", "to", "on", "of", "from", "by"]
sentence_array.each do |word|
if no_caps.include?(word)
word
else
word.capitalize!
end
sentence_array.first.capitalize!
# Manage special words
if (word.include?("mc"))
letter_array = word.split!("") # word with mc changed to an array of letters
if (letter_array[0] == "m") && (letter_array[1] == "c") # 1st & 2nd letters
letter_array[2].capitalize!
word = letter_array.join
end
end
end
sentence_array.join(" ")
end
end
There are several issues with your "Mc" code:
if (word.include?("mc"))
This will always return false, because you have already capitalized word. It has to be:
if word.include?('Mc')
This line doesn't work either:
letter_array = word.split!("")
because there is no split! method, just split. There is however no reason to use a character array at all. String#[] allows you to access a string's characters (or sub-strings), so the next line becomes:
if (word[0] == 'M') && (word[1] == 'c')
or just:
if word[0, 2] == 'Mc'
or even better using start_with?:
if word.start_with?('Mc')
In fact, we can replace the first if with this one.
The next line is a bit tricky:
letter_array[2].capitalize!
Using String#[] this becomes:
word[2].capitalize!
But unfortunately, both don't work as expected. This is because [] returns a new object, so the bang method doesn't change the original object. Instead you have to call the element assignment method []=:
word[2] = word[2].upcase
Everything put together:
if word.start_with?('Mc')
word[2] = word[2].upcase
end
Or in a single line:
word[2] = word[2].upcase if word.start_with?('Mc')
First of all, please, don't monkey patch. This is bad design, just make a helper function that takes an argument you need (string in your case).
def title_case(string)
no_caps = %w(a an the at by and as but or for in nor on at up to on of from by)
no_caps_regex = /\b(#{no_caps.join('|')})\b/i # match separate words from above, case-insensitive
# you will need ActiveSupport (or Rails) for +String#titleize+ support
titleized = string.titleize
handle_special = titleized.gsub(/\b(mc)(.+?)\b/i) do |match|
[$1, $2].map(&:capitalize).join
end
no_capsed = handle_special.gsub(no_caps_regex) { |match| match.downcase }
end
title_case('mcdonalds is fast food, but mrmcduff is not')
# => "McDonalds Is Fast Food, but Mrmcduff Is Not"
UPDATE: I am sorry about that, it was really bad reading, but I still want to elaborate on the confused terms you noted:
Monkey patching is a technique, available for some dynamic languages (Ruby or Javascript, for example) where you can change (add or remove methods/properties) to already existing classes, such as String, Fixnum, DateTime and others. Often this technique is used for "enhancing" core types (exactly like you did in your code, adding method title_case to String).
The problem here is that if any other library developer chooses the same name and adds it to String class, and you eventually want to try his library in your project, your implementations will clash together and which one is added later wins (depending on the code loading time, usually yours). This will either brake your code or brake the library which is no good also.
Another similar problem, is when you try to "fix" some bug in third party library this way. You monkey patch it, everything works and you forget about it. Then 6 months later you decide to upgrade the library to a new version and suddenly everything blows up, because library code clashed with your changes and you may even not to remember about your monkey patch (or it may even be another developer, that doesn't even know about your monkey patch existence).
Helper function - is just some function that you can add a) to a separate file, called helper b) or just to the current controller/model (the place you need it).
\b is a mark in regex that tells regex engine to treat the following text as a separate word, i.e. /as/ regex can match for word as and also for word fast since it contains as. If you instead use /\bas\b/, only as will be matched.
Regexes are very powerful, please, find some time to learn them, you'll boost your text processing skills to a next level. Combined with some console tools knowledge (I mean commands in UNIX terminals, such as ls, ps, find, grep and etc.), they can be very powerful in day-to-day routines such as "whether yesterday logs contain some ip?", "what is the process name that eats all memory on my machine right now?" or "what are all files in my project that contain this function call?".
The classic book on this subject is J. Friedl's "Mastering regular expressions", highly recommended.
Have a nice day.
#Stefan, you were right!
str = "Lay on, Macduff, and damn'd be him that first cries, 'Hold, enough!'"
no_caps = ["a", "an", "the", "at", "by", "and", "as", "but", "or",
"for", "in", "nor", "on", "at", "up", "to", "on", "of",
"from", "by", "that", "lay"]
str.gsub(/\w+/) do |s|
(no_caps.include?(s.downcase) && $~.begin(0) > 0) ? s.downcase! : s.capitalize!
case s
when /^Mc./ then s[2] = s[2].upcase
when /^Mac./ then s[3] = s[3].upcase
end
s
end
# => "Lay on, MacDuff, and Damn'D Be Him that First Cries, 'Hold, Enough!'"

String splitting with unknown punctuation in Ruby

I am building an application that downloads sentences and parses them for a word game. I don't know in advance what punctuation the text will contain.
I'd like to be able to split up the sentence/s, examine them for part of speech tag, and if the correct tag is found, replace it with " ", and rejoin them back in order.
text = "some string, with punctuation- for example: things I don't know about, that may or may not have whitespaces and random characters % !!"
How can I split it into an array so that I can pass the parser over each word, and rejoin them in order, bearing in mind that string.split(//) seems to need to know what punctuation I'm looking for?
split is useful when you can more easily describe the delimiters than the parts to be extracted. In your case, you can more easily describe the parts to be extracted rather than the delimiters, in which case scan is more suited. It is a wrong decision to use split. You should you scan.
text.scan(/[\w']+/)
# => ["some", "string", "with", "punctuation", "for", "example", "things", "I", "don't", "know", "about", "that", "may", "or", "may", "not", "have", "whitespaces", "and", "random", "characters"]
If you want to replace the matches, there is even more reason to not use split. In that case, you should use gsub.
text.gsub(/[\w']+/) do |word|
if word.is_of_certain_part_of_speech?
"___" # Replace it with `"___"`.
else
word # Put back the original word.
end
end

Ruby count number of found keywords in string

I have an array with keywords and I have a string, which may contain those keywords. I now need to know how many keywords are in the given string:
keywords = [ 'text' ,'keywords' ,'contains' ,'blue', '42']
text = 'This text is not long but it contains 3 keywords'
How can I now find out with a ruby command how many of the strings in my array are in the text (three in this case)? I could of course use a for each loop but I am almost sure that there is a more concise way to achieve this.
Thanks for your help
Update: Preferably the solution should not rely on the spaces. So the spaces could be replaced by arbitrary characters.
Update 2: The command should look for unique occurrences.
Here's one approach:
text.scan(/#{keywords.join('|')}/).length
Note that this is safe only if the keywords array contains only alphanumeric characters.
Not exactly what you wanted but
irb(main):012:0> text.split(' ')
=> ["This", "text", "is", "not", "long", "but", "it", "contains", "3", "keywords"]
irb(main):013:0> text.split(' ') & keywords
=> ["text", "contains", "keywords"]
will give you an array with matches

Resources