Using a Ruby map on a range - ruby

I'm trying to use .map so I don't need to initialize a products array.
Here's the original code:
products = []
for page in (1..(ShopifyAPI::Product.count.to_f/150.0).ceil)
products += ShopifyAPI::Product.find(:all, :params => {:page => page, :limit => 150})
end
Here's what I've tried:
products = (1..(ShopifyAPI::Product.count.to_f/150.0).ceil).map do |page|
ShopifyAPI::Product.find(:all, :params => {:page => page.to_i, :limit => 150})
end
Which only returns the first product? What am I doing wrong?
The ShopifyAPI::Product returns a list of products based on the sent parameters page, and limit.

I'm not sure why you're finding the second snippet only returns the first product, but in order to make it functionally equivalent to the first, you could use flat_map instead of map here, or alternatively tack on a .flatten at the end (or flatten(1), if you want to be more specific)
Given that the .find call returns an array, you can see the difference in the following examples:
a = []
(0..2).each { |x| a += [x] }
# a == [0,1,2]
(0..2).map { |x| [x] }
# [[0], [1], [2]]
(0..2).flat_map { |x| [x] }
# [0, 1, 2]
That's because array + array combines the two of them.
If your first snippet instead used products.push(<find result>) then you'd see the same nested array result.
See Enumerable#flat_map and Array#flatten

Related

Weird behaviour with hashes in Ruby

I have a method that tries to parse a query into a a hash.
CONTACT_SEARCH_FIELDS = ['LastUpdate','Name','RecAdd','PhoneNumber','Tag_Group','FirstName','LastName','FamilyName','FamilyHead','ClientStatus','ContactType','ClientSource','TaxId']
CONTACT_SEARCH_OPERANDS = ['=','>','<','!=','Like','BeginsWith','IsEmpty']
def search (query, page = 1)
body = [{}]*query.length
query.each_with_index do |expr, i|
body[i]["Field"] = CONTACT_SEARCH_FIELDS.index expr[0]
body[i]["Operand"] = CONTACT_SEARCH_OPERANDS.index expr[1]
body[i]["Value"] = expr[2]
end
return body
end
The method is called like this
search([["FirstName", "=", "John"], ["LastName", "=", "Smith"]])
The problem is that running this gives a very weird output.
search([["FirstName", "=", "John"], ["LastName", "=", "Smith"]])
=> [{"Operand"=>0, "Value"=>"Smith", "Field"=>6}, {"Operand"=>0, "Value"=>"Smith", "Field"=>6}]
I did some debugging and the problem is that all the hashes in the array are get set on every iteration.
I dont understand what is the reason behind this. I would also welcome any shorter or better versions of this code.
Change the line
body = [{}]*query.length
The above means, you are creating an Array, whose elements are same Hash objects.
Example :
a = [{}]*3 # => [{}, {}, {}]
a.map(&:object_id) # => [18499356, 18499356, 18499356]
a[0]["a"] = 2
a # => [{"a"=>2}, {"a"=>2}, {"a"=>2}]
to
body = Array.new(query.length) { {} }
But the above means, you are creating an Array, whose elements are different Hash objects.
Example :
a = Array.new(3) { {} } # => [{}, {}, {}]
a.map(&:object_id) # => [17643864, 17643852, 17643840]
a[0]["a"] = 2
a # => [{"a"=>2}, {}, {}]

delete and return all items in an array

Is there a helper method to delete all items from an array and return those items in ruby?
For example,
array = [{:a=>1, :b=>2},{:a=>3,:b=>4},{:a=>5,:b=>6}]
and I want to delete all the array elements and return them so I can do some processing on those elements?
array = [{:a=>1, :b=>2},{:a=>3,:b=>4},{:a=>5,:b=>6}]
while element = array.pop do
# process element however you like...
end
array # => []
or use shift rather than pop if order matters to you.
Do as below using Array#shift to delete the array content in one shot and return its elements:
array = [{:a=>1, :b=>2},{:a=>3,:b=>4},{:a=>5,:b=>6}]
array.shift(array.size)
# => [{:a=>1, :b=>2}, {:a=>3, :b=>4}, {:a=>5, :b=>6}]
array
# => []
If you want to delete one by one,you can do as below using Array#delete_if:
array = [{:a=>1, :b=>2},{:a=>3,:b=>4},{:a=>5,:b=>6}]
array.delete_if do |e|
#do your work with e
true
end
array # => []
Another approach is do your work first with the array and then delete all the elements from the array:
array = [{:a=>1, :b=>2},{:a=>3,:b=>4},{:a=>5,:b=>6}]
array.each do |e|
#do your work with e
end
array.clear
array # => []

How to display dynamic case statement in Ruby

How would I write a case statement that would list all elements in an array, allow the user to pick one, and do processing on that element?
I have an array:
array = [ 'a', 'b', 'c', 'd' ]
Ultimately I'd like it to behave like this:
Choices:
1) a
2) b
3) c
4) d
Choice =>
After the user picks 3, I would then do processing based off the choice of the user. I can do it in bash pretty easily.
Ruby has no built-in menu stuff like shell scripting languages do. When doing menus, I favor constructing a hash of possible options and operating on that:
def array_to_menu_hash arr
Hash[arr.each_with_index.map { |e, i| [i+1, e] }]
end
def print_menu menu_hash
puts 'Choices:'
menu_hash.each { |k,v| puts "#{k}) #{v}" }
puts
end
def get_user_menu_choice menu_hash
print 'Choice => '
number = STDIN.gets.strip.to_i
menu_hash.fetch(number, nil)
end
def show_menu menu_hash
print_menu menu_hash
get_user_menu_choice menu_hash
end
def user_menu_choice choice_array
until choice = show_menu(array_to_menu_hash(choice_array)); end
choice
end
array = %w{a b c d}
choice = user_menu_choice(array)
puts "User choice was #{choice}"
The magic happens in array_to_menu_hash:
The [] method of Hash converts an array with the form [ [1, 2], [3, 4] ] to a hash {1 => 2, 3 => 4}. To get this array, we first call each_with_index on the original menu choice array. This returns an Enumerator that emits [element, index_number] when iterated. There are two problems with this Enumerator: the first is that Hash[] needs an array, not an Enumerator. The second is that the arrays emitted by the Enumerator have the elements in the wrong order (we need [index_number, element]). Both of these problems are solved with #map. This converts the Enumerator from each_with_index into an array of arrays, and the block given to it allows us to alter the result. In this case, we are adding one to the zero-based index and reversing the order of the sub-arrays.

Finding N keys with highest value in hash, keeping order

In a Ruby script,
I have a hash that has sentences as keys and relevance scores as values.
I want to retrieve an array containing the N most relevant sentences (highest scores).
I want to retain the order in which these sentences are extracted.
Given:
hash = {
'This is the first sentence.' => 5,
'This is the second sentence.' => 1,
'This is the last sentence.' => 6
}
Then:
choose_best(hash, 2)
Should return:
['This is the first sentence.', 'This is the last sentence.']
All the methods I can think of involve reordering the hash, thus losing the order of the sentences. What would be the best way to tackle this?
Try the following monster:
hash.map(&:reverse).each_with_index
.sort_by(&:first).reverse
.take(2)
.sort_by(&:last)
.map { |(_,s),_| s }
Another functional one:
hash.to_a.values_at(*hash.values.each_with_index
.sort.reverse
.map(&:last)
.sort.take(2))
.map(&:first)
Note however, that as an unordered data structure, a hash table is not really suitable for this use case (although the order is remembered in Ruby 1.9). You should use an array instead (the sorting code remains the same):
sentences = [
['This is the first sentence.', 5],
['This is the second sentence.', 1],
['This is the last sentence.', 6],
]
def extract hash, n
min = hash.values.sort[-n]
a = []
i = 0
hash.each{|k, v| (a.push(k) and i += 1) if i < n and v >= min}
a
end
hash = {
'This is the first sentence.' => 5,
'This is the second sentence.' => 1,
'This is the last sentence.' => 6
}
cutoff_val = hash.values.sort[-2] #cf. sawa
p hash.select{|k,v| v >= cutoff_val }
# =>{"This is the first sentence."=>5, "This is the last sentence."=>6}
Starting in Ruby 2.2.0, Enumerable#max_by takes an optional integer argument that makes it return an array instead of just a single element. Therefore, we can do:
hash = {
'This is the first sentence.' => 6,
'This is the second sentence.' => 1,
'This is the last sentence.' => 5
}
p hash.max_by(2, &:last).map(&:first).sort_by { |k| hash.keys.index k }
# => ["This is the first sentence.", "This is the last sentence."]
The call to sort_by at the end guarantees the sentences are in the right order, as you requested.
a = hash.sort_by { |sentence, score| score }.reverse
The array a now contains pairs of values of your top scoring sentences. You can select the first N of them.
hash = {"foo" => 7, "bar" => 2, "blah" => 3 }
a = hash.sort_by { |sentence, score| score }.reverse
=> [["foo", 7], ["blah", 3], ["bar", 2]]

hash assignment when (key => value) are stored in an array? (ruby)

I have hash (#post) of hashes where I want to keep the order of the hash's keys in the array (#post_csv_order) and also want to keep the relationship key => value in the array.
I don't know the final number of both #post hashes and key => value elements in the array.
I don't know how to assign the hash in a loop for all elements in the array. One by one #post_csv_order[0][0] => #post_csv_order[0][1] works nicely.
# require 'rubygems'
require 'pp'
#post = {}
forum_id = 123 #only sample values.... to make this sample script work
post_title = "Test post"
#post_csv_order = [
["ForumID" , forum_id],
["Post title", post_title]
]
if #post[forum_id] == nil
#post[forum_id] = {
#post_csv_order[0][0] => #post_csv_order[0][1],
#post_csv_order[1][0] => #post_csv_order[1][1]
##post_csv_order.map {|element| element[0] => element[1]}
##post_csv_order.each_index {|index| #post_csv_order[index][0] => #post_csv_order[index][1] }
}
end
pp #post
desired hash assignment should be like that
{123=>{"Post title"=>"Test post", "ForumID"=>123}}
The best way is to use to_h:
[ [:foo,1],[:bar,2],[:baz,3] ].to_h #=> {:foo => 1, :bar => 2, :baz => 3}
Note: This was introduced in Ruby 2.1.0. For older Ruby, you can use my backports gem and require 'backports/2.1.0/array/to_h', or else use Hash[]:
array = [[:foo,1],[:bar,2],[:baz,3]]
# then
Hash[ array ] #= > {:foo => 1, :bar => 2, :baz => 3}
This is available in Ruby 1.8.7 and later. If you are still using Ruby 1.8.6 you could require "backports/1.8.7/hash/constructor", but you might as well use the to_h backport.
I am not sure I fully understand your question but I guess you want to convert a 2d array in a hash.
So suppose you have an array such as:
array = [[:foo,1],[:bar,2],[:baz,3]]
You can build an hash with:
hash = array.inject({}) {|h,e| h[e[0]] = e[1]; h}
# => {:foo=>1, :bar=>2, :baz=>3}
And you can retrieve the keys in correct order with:
keys = array.inject([]) {|a,e| a << e[0] }
=> [:foo, :bar, :baz]
Is it what you were looking for ?
Answers summary
working code #1
#post[forum_id] = #post_csv_order.inject({}) {|h,e| h[e[0]] = e[1]; h}
working code #2
#post[forum_id] = Hash[*#post_csv_order.flatten]
working code #3
#post[forum_id] ||= Hash[ #post_csv_order ] #requires 'require "backports"'

Resources