What does array * string mean in Ruby? - ruby-on-rails

I was looking through some Rails source code and came across
# File vendor/rails/activesupport/lib/active_support/vendor/builder-2.1.2/builder/css.rb, line 129
129: def target!
130: #target * ''
131: end
What does the * '' do? Is that multiplication by an empty string...? And why would you do that.

This is a bizarre syntax. These are equivalent:
>> [1, 2, 3] * 'joiner'
=> "1joiner2joiner3"
>> [1, 2, 3].join 'joiner'
=> "1joiner2joiner3"
so in this case it joins all the entries of #target into one string, with nothing between the entries.
Note: if you do something like [1, 2, 3] * 3 (using an int instead of a str), you'll get three concatenated copies of the array instead.

It does the same thing as:
["foo","bar","baz"].join
http://ruby-doc.org/core/classes/Array.html#M002210
Per Z.E.D.'s suggestion, you would use it if you want to confuse people and make your code more error prone.

Really cryptic code indeed.
After checking the source code, I realized that #target is actually an Array instance, I know you can do stuff like this
[5] * 5 # => [5,5,5,5,5]
I don't know where Array#* is defined (maybe in ActiveSupport), but what I can tell you is that, this is the behaviour when it gets multiplied by a String
[1,2,3] * 'comma' # => "1comma2comma3"
[1,2,3] * '' # => '123'
So I can infer it is concatanating all the elements of the array without any separators.

Array#* with a String argument is equivalent to Array#join.

Two comments:
Having a ! end a method name implies that it's a mutating operation, which this example doesn't seem to be.
As others have stated it's indeed cryptic. I would go for #target.to_s or #target.join

The only reason I'd see someone wanting to use * to join an array of strings is if they want to avoid the word join, which is also used on threads.
With the following:
strings = ["John", "Smith"]
threads = [Thread.new{puts "hai"}, Thread.new{ puts "world!"}]
strings.join(" ") # Legit
threads.each {|thread| thread.join} # Legit
you could make the mistake of doing
threads.join # Not legit, but won't raise an error
If you replaced strings.join with strings.*, then you'd have fewer joins in your code. Now you could do a grep for them, and check that each one is being done to a thread, not to an array. In fact, you could choose to make Array#join throw an exception.

Related

How do I naturally sort an array of multi-dot numbers in ruby [duplicate]

How do I sort a list of versions in Ruby? I've seen stuff about natural sort, but this is a step beyond that.
Input is a bunch of strings like this:
input = ['10.0.0b12', '10.0.0b3', '10.0.0a2', '9.0.10', '9.0.3']
I can almost do it with the naturally gem:
require 'naturally'
Naturally.sort(input)
=> ["9.0.3", "9.0.10", "10.0.0a2", "10.0.0b12", "10.0.0b3"]
Problem: 10.0.0b3 is sorted after 10.0.0b12; 10.0.0b3 should be first.
Anyone have a way that works? Other languages are helpful too!
Ruby ships with the Gem class, which knows about versions:
ar = ['10.0.0b12', '10.0.0b3', '10.0.0a2', '9.0.10', '9.0.3']
p ar.sort_by { |v| Gem::Version.new(v) }
# => ["9.0.3", "9.0.10", "10.0.0a2", "10.0.0b3", "10.0.0b12"]
If you interpret this as "sort by each segment of digits", then you the following will handle your example input above:
input.map{ |ver| ver.split(%r{[^\d]+}).map(&:to_i) }.zip(input).sort.map(&:last)
=> ["9_0", "9_1", "10_0b3", "10_0b12"]
That is,
for each value, eg 10_0b3
split on any length of non-digit characters, eg ["10","0","3"]
cast each digit segment to integer, eg [10,0,3]
zip with original input, yields [[[10, 0, 12], "10_0b12"], [[10, 0, 3], "10_0b3"], [[9, 0], "9_0"], [[9, 1], "9_1"]]
sort, by virtue of [10,0,3] < [10,0,12]
get last value of each element, which is the original input value which corresponds to each processed sortable value
Now granted, this is still quite custom -- version numbers as simple as "9_0a" vs "9_0b" won't be handled, both will appear to be [9,0] -- so you may need to tweak it further, but hopefully this starts you down a viable path.
EDIT: Example input above changed, so I changed the regex to make sure the digit-matching is greedy, and with that it still holds up:
irb(main):018:0> input = ['10.0.0b12', '10.0.0b3', '9.0.10', '9.0.3']
=> ["10.0.0b12", "10.0.0b3", "9.0.10", "9.0.3"]
irb(main):025:0> input.map{ |ver| ver.split(%r{[^\d]+}).map(&:to_i) }.zip(input).sort.map(&:last)
=> ["9.0.3", "9.0.10", "10.0.0b3", "10.0.0b12"]
In the specific case that you are working with NuGet and want to parse, compare or sort by NuGet's peculiar own versioning scheme from Ruby code, there is now this:
https://rubygems.org/gems/nuget_versions
I created it specifically to solve this problem. NuGet's version numbers are a bit weird, they are a superset of SemVer that also permits the use of 4 components instead of 3.

without iterating an array, How to check a value exisitng inside the loop or not?

i have an array.
key_values =
["loc=june 1 2 jubli's, Captain tim, BI",
"locSlug=june-1-",
"-2-jubli's_Captain-tim_BI",
"lat=29.404153823852539",
"long=-54.88862609863281",
"status=7",
"pg=10",
"pgsz=15",
"sprefix=/kings_search",
"city=Captain tim",
"neighborhood=june 1 ",
" 2 jubli's",
"state_id=BI",
"county_fips=15045"]
This is my array. I am iterating this till the end , when the current value includes "locSlug" or "neighborhood" then i am checking for next value whether it has "=" or not . if it has "=" then i am not doing anything, otherwise i am adding next value and my current value with "&". But i dont want to iterate this for whole values. How can i do it without iterating the whole loop.I have written code like as shown below.
def check_for_special_character(key_values)
key_values.each_with_index do |val,index|
unless index+1 >= key_values.length
if (val.include?("locSlug=") || val.include?("neighborhood=")) && !key_values[index+1].include?("=")
key_values[index] = [val, key_values[index+1]].join("&")
key_values.delete_at(index+1)
end
end
end
end
The above code is working fine but i dont want to do in this way. need your suggestions.
If u need any further clarifications please ask.
Convert Array to Hash, Then Access By Key
There may be a more efficient way of doing this if you have access to the original data object (e.g. a cookie, JSON string, or database query). However, assuming that you can't access the source data directly for some reason, or that you're just working with an array that you can't otherwise control, you can convert your data into a Ruby Hash object and then access the values you want by key. For example:
# Split your array elements on "=", and then convert the resulting array of
# arrays to a hash so you can return values by key. You need to have some
# sort of guard in place for sub-arrays that don't have exactly two elements.
hash = key_values.map { |e| e.split ?= }.select { |a| a.size.eql? 2 }.to_h
hash.fetch 'locSlug'
#=> "june-1-"
hash.fetch 'neighborhood'
#=> "june 1 "
The string you are retrieving from the cookie looks like this:
str = "loc=june 1 2 jubli's, Captain tim, BI&locSlug=june-1-&-2-jubli's_Capta"\
"in-tim_BI&lat=29.404153823852539&long=-54.88862609863281&status=7&pg=10"\
"&pgsz=15&sprefix=/kings_search&city=Captain tim&neighborhood=june 1 & 2"\
" jubli's&state_id=BI&county_fips=15045"
As you can see, & is used as both, a delimiter and a regular character:
"loc=june 1 2 jubli's, Captain tim, BI&locSlug=june-1-&-2-jubli's_Captain-..."
# ^ ^
# delimiter character
In other words: your string is broken. You should fix this by writing a properly escaped string to the cookie in the first place. For a query string, you'd use percent encoding. It should look like this:
str = "loc=june+1++2+jubli%27s%2C+Captain+tim%2C+BI&locSlug=june-1-%26-2-jubli"\
"%27s_Captain-tim_BI&lat=29.404153823852539&long=-54.88862609863281&stat"\
"us=7&pg=10&pgsz=15&sprefix=%2Fkings_search&city=Captain+tim&neighborhoo"\
"d=june+1+%26+2+jubli%27s&state_id=BI&county_fips=15045"
This can be parsed via:
Rack::Utils.parse_query(str)
#=> {
# "loc"=>"june 1 2 jubli's, Captain tim, BI",
# "locSlug"=>"june-1-&-2-jubli's_Captain-tim_BI",
# "lat"=>"29.404153823852539",
# "long"=>"-54.88862609863281",
# "status"=>"7",
# "pg"=>"10",
# "pgsz"=>"15",
# "sprefix"=>"/kings_search",
# "city"=>"Captain tim",
# "neighborhood"=>"june 1 & 2 jubli's",
# "state_id"=>"BI",
# "county_fips"=>"15045"
# }

flatten an array of arbitrarily nested arrays of integers into a flat array of integers in ruby

how to write a code snippet Ruby that will flatten an array of arbitrarily nested arrays of integers into a flat array of integers. e.g. [[1,2,[3]],4] -> [1,2,3,4]. Please don't use any built-in flatten functions in either language.
Here's one solution without using the built-in flatten method. It involves recursion
def flattify(array)
array.each_with_object([]) do |element, flattened|
flattened.push *(element.is_a?(Array) ? flattify(element) : element)
end
end
I tested this out in irb.
flattify([[1,2,[3],4])
=> [1,2,3,4]
arr = [[1,2,[3]],4]
If, as in you example, arr contains only numbers, you could (as opposed to "should") do this:
eval "[#{arr.to_s.delete('[]')}]"
=> [1, 2, 3, 4]

Ruby method returns hash values in binary

I wrote a method that takes six names then generates an array of seven random numbers using four 6-sided dice. The lowest value of the four 6-sided dice is dropped, then the remainder is summed to create the value. The value is then added to an array.
Once seven numbers have been generated, the array is then ordered from highest to lowest and the lowest value is dropped. Then the array of names and the array of values are zipped together to create a hash.
This method ensures that the first name in the array of names receives the highest value, and the last name receives the lowest.
This is the result of calling the method:
{:strength=>1, :dexterity=>1, :constitution=>0, :intelligence=>0, :wisdom=>0, :charisma=>1}
As you can see, all the values I receive are either "1" or "0". I have no idea how this is happening.
Here is the code:
module PriorityStatGenerator
def self.roll_stats(first_stat, second_stat, third_stat, fourth_stat, fifth_stat, sixth_stat)
stats_priority = [first_stat, second_stat, third_stat, fourth_stat, fifth_stat, sixth_stat].map(&:to_sym)
roll_array = self.roll
return Hash[stats_priority.zip(roll_array)]
end
private
def self.roll
roll_array = []
7.times {
roll_array << Array.new(4).map{ 1 + rand(6) }.sort.drop(1).sum
}
roll_array.reverse.delete_at(6)
end
end
This is how I'm calling the method while I'm testing:
render plain: PriorityStatGenerator.roll_stats(params[:prioritize][:first_stat], params[:prioritize][:second_stat], params[:prioritize][:third_stat], params[:prioritize][:fourth_stat], params[:prioritize][:fifth_stat], params[:prioritize][:sixth_stat])
I added require 'priority_stat_generator' where I'm calling the method, so it is properly calling it.
Can someone help me make it return proper values between 1 and 18?
Here's a refactoring to simplify things and use an actually random number generator, as rand is notoriously terrible:
require 'securerandom'
module PriorityStatGenerator
def self.roll_stats(*stats)
Hash[
stats.map(&:to_sym).zip(self.roll(stats.length).reverse)
]
end
private
def self.roll(n = 7)
(n + 1).times.map do
4.times.map { 1 + SecureRandom.random_number(6) }.sort.drop(1).inject(:+)
end.sort.last(n)
end
end
This makes use of inject(:+) so it works in plain Ruby, no ActiveSupport required.
The use of *stats makes the roll_stats function way more flexible. Your version has a very rigid number of parameters, which is confusing and often obnoxious to use. Treating the arguments as an array avoids a lot of the binding on the expectation that there's six of them.
As a note it's not clear why you're making N+1 roles and then discarding the last. That's the same as generating N and discarding none. Maybe you meant to sort them and take the N best?
Update: Added sort and reverse to properly map in terms of priority.
You need to learn to use IRB or PRY to test snippets of your code, or better, learn to use a debugger. They give you insight into what your code is doing.
In IRB:
[7,6,5,4,3,2,1].delete_at(6)
1
In other words, delete_at(6) is doing what it's supposed to, but that's not what you want. Instead, perhaps slicing the array will behave more like you expect:
>> [7,6,5,4,3,2,1][0..-2]
[
[0] 7,
[1] 6,
[2] 5,
[3] 4,
[4] 3,
[5] 2
]
Also, in your code, it's not necessary to return a value when that operation is the last logical step in a method. Ruby will return the last value seen:
Hash[stats_priority.zip(roll_array)]
As amadan said, I can't see how you are getting the results you are, but their is a definite bug in your code.
The last line in self.roll is the return value.
roll_array.reverse.delete_at(6)
Which is going to return the value that was deleted. You need to add a new lines to return the roll_array instead of the delete_at value. You are also not sorting your array prior to removing that last item which will give you the wrong values as well.
def self.roll
roll_array = []
7.times {
roll_array << Array.new(4).map{ 1 + rand(6) }.sort.drop(1).sum
}
roll_array.sort.drop(1)
roll_array
end

are there any additional inject shorthand

I recently ran into this issue:
I've always used inject like so (i knew that (0) part is optional and can be omitted)
array = [13,23,13]
#=> [13, 23, 13]
array.inject(0) { |sum,i| sum+i }
#=> 49
By chance i found out that you can use:
array.inject(:+)
#=> 49
array.inject(:-)
#=> -23
array.inject(:*)
#=> 3887
array.inject(:/)
#=> 0
Googling on the issue i found a nice article on inject, but no mentioning there about what i've tried....
Can anyone explain to me or give some info about these inject commands that I've just used?
From the doc on Enumerable#inject:
... If you specify a symbol instead, then each element in the collection will be passed to the named method of memo. In either case, the result becomes the new value for memo. At the end of the iteration, the final value of memo is the return value for the method.
If you do not explicitly specify an initial value for memo, then uses the first element of collection is used as the initial value of memo.
So, if you specify a symbol, it treats it as a method name and invokes this method on every element of the enumerable, replacing memo as stated above. Now, the math operators (+-*/) are just methods, nothing else. These lines produce identical result:
13 + 23 # => 36
13.+(23) # => 36
13.send(:+, 23) # => 36
When you pass a symbol to inject or reduce it uses the third form to dynamically apply that operator to elements:
[1,2,3].inject(:+) # => 6
This shorthand can be used with methods other than operators as well:
[{"a"=>1}, {"b"=>2}].inject(:merge) # => {"a"=>1, "b"=>2}

Resources