I am newbie to ruby. Below is my code where I am trying to find a value which has value true and put it into values. But getting error
possible_values =
[
'ABC',
'DEF',
'GHI',
'JKL',
'MNO',
'PQR'
]
options = {
"environment"=>"dev",
"status"=>"valid",
"abc"=>true,
"def"=>true,
"ghi"=>false,
"jkl"=>false
}
options['values'] = []
possible_values.each do |val|
options['values'] << val if options[val.downcase]
options.delete("val")
end
puts "values: #{options['values'}"
Error: can't convert String into Integer (TypeError)
Desired Output:
options = [
"environment"=>"dev",
"status"=>"valid",
"values"=>["ABC", "DEF"]
]
Three bugs:
Your puts line is missing a ]:
puts "values: #{options['values'}"
# ^ should be here
You want to remove the options key after processing it, but you're always removing "val" instead:
options.delete("val")
# ^ ^ extra quotes making this a "val" String
Again, you want to remove the options key after processing. The possible_values are all uppercase, and won't match the corresponding keys in options for deletion without a downcase. Taking #2 and #3 together, what you want is: options.delete(val.downcase)
After fixing all three of these, this is the contents of options:
{"environment" => "dev", "status" => "valid", "values" => ["ABC", "DEF"]}
which seems to be what you wanted
I see you wish to mutate (modify) options, as opposed to returning a separate hash, leaving options unchanged.
I would be inclined to write the code as follows, in part to facilitate the testing of true_keys and options (below), after true_keys has been computed.
true_keys = options.keys.each_with_object([]) do |k,arr|
case options[k]
when true
arr << k.upcase
options.delete(k)
when false
options.delete(k)
end
end
#=> ["ABC", "DEF"]
We now have:
options
#=> {"environment"=>"dev", "status"=>"valid"}
Notice that we cannot write
true_keys = options.each_key.each_with_object([]) do |k,arr|...
or
true_keys = options.each_with_object([]) do |(k,v),arr|...
because that would remove elements of options whilst interating over it, a no-no. That is not a problem when iterating over options.keys.
One more step:
options.tap { |h| h['values'] = true_keys & possible_values }
#=> {"environment"=>"dev", "status"=>"valid", "values"=>["ABC", "DEF"]}
See Object#tap.
Just checking:
options
#=> {"environment"=>"dev", "status"=>"valid", "values"=>["ABC", "DEF"]}
One could alternatively write
options.update('values'=>true_keys & possible_values)
#=> {"environment"=>"dev", "status"=>"valid", "values"=>["ABC", "DEF"]}
See Hash#update (a.k.a. merge!).
If options and possible_values were large it would be sensible to convert possible_values to a set as an initial step, as set lookups are much faster than array lookups, the latter requiring a linear search. This only effects the tap (or update) operation.
require 'set'
possible_values_set = possible_values.to_set
#=> #<Set: {"ABC", "DEF", "GHI", "JKL", "MNO", "PQR"}>
Then with
true_keys
#=> ["ABC", "DEF"]
the tap expression would become:
options.tap do |h|
h['values'] = true_keys.select { |k| possible_values_set.include?(k) }
end
#=> {"environment"=>"dev", "status"=>"valid", "values"=>["ABC", "DEF"]}
Related
I have a string "wwwggfffw" and want to break it up into an array as follows:
["www", "gg", "fff", "w"]
Is there a way to do this with regex?
"wwwggfffw".scan(/((.)\2*)/).map(&:first)
scan is a little funny, as it will return either the match or the subgroups depending on whether there are subgroups; we need to use subgroups to ensure repetition of the same character ((.)\1), but we'd prefer it if it returned the whole match and not just the repeated letter. So we need to make the whole match into a subgroup so it will be captured, and in the end we need to extract just the match (without the other subgroup), which we do with .map(&:first).
EDIT to explain the regexp ((.)\2*) itself:
( start group #1, consisting of
( start group #2, consisting of
. any one character
) and nothing else
\2 followed by the content of the group #2
* repeated any number of times (including zero)
) and nothing else.
So in wwwggfffw, (.) captures w into group #2; then \2* captures any additional number of w. This makes group #1 capture www.
You can use back references, something like
'wwwggfffw'.scan(/((.)\2*)/).map{ |s| s[0] }
will work
Here's one that's not using regex but works well:
def chunk(str)
chars = str.chars
chars.inject([chars.shift]) do |arr, char|
if arr[-1].include?(char)
arr[-1] << char
else
arr << char
end
arr
end
end
In my benchmarks it's faster than the regex answers here (with the example string you gave, at least).
Another non-regex solution, this one using Enumerable#slice_when, which made its debut in Ruby v.2.2:
str.each_char.slice_when { |a,b| a!=b }.map(&:join)
#=> ["www", "gg", "fff", "w"]
Another option is:
str.scan(Regexp.new(str.squeeze.each_char.map { |c| "(#{c}+)" }.join)).first
#=> ["www", "gg", "fff", "w"]
Here the steps are as follows
s = str.squeeze
#=> "wgfw"
a = s.each_char
#=> #<Enumerator: "wgfw":each_char>
This enumerator generates the following elements:
a.to_a
#=> ["w", "g", "f", "w"]
Continuing
b = a.map { |c| "(#{c}+)" }
#=> ["(w+)", "(g+)", "(f+)", "(w+)"]
c = b.join
#=> "(w+)(g+)(f+)(w+)"
r = Regexp.new(c)
#=> /(w+)(g+)(f+)(w+)/
d = str.scan(r)
#=> [["www", "gg", "fff", "w"]]
d.first
#=> ["www", "gg", "fff", "w"]
Here's one more way of doing it without a regex:
'wwwggfffw'.chars.chunk(&:itself).map{ |s| s[1].join }
# => ["www", "gg", "fff", "w"]
I need the Ruby idiom for sorting on two fields. In Python if you sort a list of two-element tuples, it sorts based on the first element, and if two elements are equal then the sort is based on the second element.
One example is the following sorting code in Python (word sort from longest to shortest and consider second element to break ties) from http://www.pythonlearn.com/html-008/cfbook011.html
txt = 'but soft what light in yonder window breaks'
words = txt.split()
t = list()
for word in words:
t.append((len(word), word))
t.sort(reverse=True)
res = list()
for length, word in t:
res.append(word)
print res
What I came up in Ruby is the following code that uses structs
txt = 'but soft what light in yonder window breaks'
words = txt.split()
t = []
tuple = Struct.new(:len, :word)
for word in words
tpl = tuple.new
tpl.len = word.length
tpl.word = word
t << tpl
end
t = t.sort {|a, b| a[:len] == b[:len] ?
b[:word] <=> a[:word] : b[:len] <=> a[:len]
}
res = []
for x in t
res << x.word
end
puts res
I would like to know if there are better ways (less code) to achieve this stable sort.
I think you're overthinking this.
txt = 'but soft what light in yonder window breaks'
lengths_words = txt.split.map {|word| [ word.size, word ] }
# => [ [ 3, "but" ], [ 4, "soft" ], [ 4, "what" ], [ 5, "light" ], ... ]
sorted = lengths_words.sort
# => [ [ 2, "in" ], [ 3, "but" ], [ 4, "soft" ], [ 4, "what" ], ... ]
If you really want to use Struct, you can:
tuple = Struct.new(:length, :word)
tuples = txt.split.map {|word| tuple.new(word.size, word) }
# => [ #<struct length=3, word="but">, #<struct length=4, word="soft">, ... ]
sorted = tuples.sort_by {|tuple| [ tuple.length, tuple.word ] }
# => [ #<struct length=2, word="in">, #<struct length=3, word="but">, ... ]
This is equivalent:
sorted = tuples.sort {|tuple, other| tuple.length == other.length ?
tuple.word <=> other.word : tuple.length <=> other.length }
(Note that it's sort this time, not sort_by.)
...but since we're using a Struct we can make this nicer by defining our own comparison operator (<=>), which sort will invoke (the same works in any Ruby class):
tuple = Struct.new(:length, :word) do
def <=>(other)
[ length, word ] <=> [ other.length, other.word ]
end
end
tuples = txt.split.map {|word| tuple.new(word.size, word) }
tuples.sort
# => [ #<struct length=2, word="in">, #<struct length=3, word="but">, ... ]
There are other options for more complex sorting. If you wanted to get longest words first, for example:
lengths_words = txt.split.map {|word| [ word.size, word ] }
sorted = lengths_words.sort_by {|length, word| [ -length, word ] }
# => [ [ 6, "breaks" ], [ 6, "window" ], [ 6, "yonder" ], [ 5, "light" ], ... ]
Or:
tuple = Struct.new(:length, :word) do
def <=>(other)
[ -length, word ] <=> [ -other.length, other.word ]
end
end
txt.split.map {|word| tuple.new(word.size, word) }.sort
# => [ #<struct length=6, word="breaks">, #<struct length=6, word="window">, #<struct length=6, word="yonder">, ... ]
As you can see, I'm relying a lot on Ruby's built-in ability to sort arrays based on their contents, but you can also "roll your own" if you prefer, which might perform better with many, many items. Here's a comparison method that's equivalent to your t.sort {|a, b| a[:len] == b[:len] ? ... } code (plus a bonus to_s method):
tuple = Struct.new(:length, :word) do
def <=>(other)
return word <=> other.word if length == other.length
length <=> other.length
end
def to_s
"#{word} (#{length})"
end
end
sorted = txt.split.map {|word| tuple.new(word.size, word) }.sort
puts sorted.join(", ")
# => in (2), but (3), soft (4), what (4), light (5), breaks (6), window (6), yonder (6)
Finally, a couple comments on your Ruby style:
You pretty much never see for in idiomatic Ruby code. each is the idiomatic way to do almost all iteration in Ruby, and "functional" methods like map, reduce and select are also common. Never for.
A great advantage of Struct is that you get accessor methods for each property, so you can do tuple.word instead of tuple[:word].
Methods with no arguments are called without parentheses: txt.split.map, not txt.split().map
Ruby makes this easy, using Enumerable#sort_by will and Array#<=> for sorting.
def sort_on_two(arr, &proc)
arr.map.sort_by { |e| [proc[e], e] }.reverse
end
txt = 'but soft what light in yonder window breaks'
sort_on_two(txt.split) { |e| e.size }
#=> ["yonder", "window", "breaks", "light", "what", "soft", "but", "in"]
sort_on_two(txt.split) { |e| e.count('aeiou') }
#=> ["yonder", "window", "breaks", "what", "soft", "light", "in", "but"]
sort_on_two(txt.split) { |e| [e.count('aeiou'), e.size] }
#=> ["yonder", "window", "breaks", "light", "what", "soft", "but", "in"]
Note that in recent versions of Ruby, proc.call(e) can be written proc[e], proc.yield(e) or proc.(e).
UPDATE: my first answer was wrong (this time!), thanks to #mu is too short comment
Your code is ok to sort on two criteria, but if you just want to achieve the same result, the best is to do the following:
txt.split.sort_by{|a| [a.size,a] }.reverse
=> ["breaks", "window", "yonder", "light", "soft", "what", "but", "in"]
The first check will use the size operator, and if the result is zero, it will use the second one....
If you really want to keep your data structure, it's same principle:
t.sort_by{ |a| [a[:len],a[:word]] }.reverse
The following code:
str = "1, hello,2"
puts str
arr = str.split(",")
puts arr.inspect
arr.collect { |x| x.strip! }
puts arr.inspect
produces the following result:
1, hello,2
["1", " hello", "2"]
["1", "hello", "2"]
This is as expected. The following code:
str = "1, hello,2"
puts str
arr = (str.split(",")).collect { |x| x.strip! }
puts arr.inspect
Does however produce the following output:
1, hello,2
[nil, "hello", nil]
Why do I get these "nil"? Why can't I do the .collect immediately on the splitted-array?
Thanks for the help!
The #collect method will return an array of the values returned by each block's call. In your first example, you're modifying the actual array contents with #strip! and use those, while you neglect the return value of #collect.
In the second case, you use the #collect result. Your problem is that #strip! will either return a string or nil, depending on its result – especially, it'll return nil if the string wasn't modified.
Therefore, use #strip (without the exclamation mark):
1.9.3-p194 :005 > (str.split(",")).collect { |x| x.strip }
=> ["1", "hello", "2"]
Because #strip! returns nil if the string was not altered.
In your early examples you were not using the result of #collect, just modifying the strings with #strip!. Using #each in that case would have made the non-functional imperative loop a bit more clear. One normally uses #map / #collect only when using the resulting new array.
You last approach looks good, you wrote a functional map but you left the #strip! in ... just take out the !.
I would like to display the following:
anu1 cau1 doh1 bef1
To do that I need to complete the following Ruby code by adding only ONE statement.
a = ["ant", "cat", "dog", "bee"]
It sounds like you need to perform a succ function each of the words, which will give you the next value for each of them, then you would just need to append "1" to each.
Example: -Forgive my syntax, Haven't used Ruby in a while-
a.map {|word| word.succ << "1"}
should output:
["anu1", "cau1", "doh1", "bef1"]
a = ["ant", "cat", "dog", "bee"]
# => ["ant", "cat", "dog", "bee"]
a.map {|str| str.succ << "1" }
# => ["anu1", "cau1", "doh1", "bef1"]
#search_results = Array.new
duplicates = Set.new
results.each { |result| #search_results.push(result) unless duplicates.add?(result[:url]) }
This piece of code is garbling the order of elements in the array #search_results. Why would inserting the same element in a set and an array change the insertion order for Array? Seems like some issue with element references. Can someone explain?
Edit 1:
I am using an Array. Sorry for the earlier typo. I double checked by code and it uses Array too (there is no push method for Hash anyways)
The order of elements in a Hash is not guaranteed. You'll have to sort the keys if you want a guaranteed order.
This is supposedly fixed in Ruby 1.9 I believe.
Edit: I'm assuming your results in an Array, if its a Hash then order isn't guaranteed and you'll have to sort the keys, here's what my test looks like:
#!/usr/bin/ruby -W
require 'pp'
require 'set'
results = Array.new
results << {:url => 'http://lifehacker.com'}
results << {:url => 'http://stackoverflow.com'}
results << {:url => 'http://43folders.com'}
results << {:url => 'http://lolindrath.com'}
results << {:url => 'http://stackoverflow.com'}
results << {:url => 'http://lifehacker.com'}
#search_results = Array.new
duplicates = Set.new
results.each { |result| #search_results.push(result) unless duplicates.add?(result[:url])}
puts "## #search_results"
pp #search_results
If I run that, here's the result:
## #search_results
[{:url=>"http://stackoverflow.com"}, {:url=>"http://lifehacker.com"}]
I found that odd, so just to be sure, I put a .nil? add the end of .add? and here was my result:
## #search_results
[{:url=>"http://lifehacker.com"},
{:url=>"http://stackoverflow.com"},
{:url=>"http://43folders.com"},
{:url=>"http://lolindrath.com"}]
Now that was what I was expecting: is this what you mean by "garbled"?
Edit 2: Upon further investigation, I think this is because of Ruby's super strict rules when converting non-Boolean data to Booleans (see Ruby Gotchas on Wikipedia and Stack Overflow, of course) so that basically anything that only false is really false and everything else is true. so the .nil? is converting it explicitly to true/false.
irb(main):007:0> puts "zero is true" if 0
zero is true
=> nil
irb(main):008:0> puts "zero is false" unless 0
=> nil
Garbled how? What kind of object is results? If results is a Set or a Hash, then you're not guaranteed that any two traversals of results will be in the same order.
Also, you could do
#search_results = results.uniq
if results is an Array to get all the unique results.
------------------------------------------------------------- Array#uniq
array.uniq -> an_array
------------------------------------------------------------------------
Returns a new array by removing duplicate values in self.
a = [ "a", "a", "b", "b", "c" ]
a.uniq #=> ["a", "b", "c"]