Cut array after key in Ruby - ruby

I need to cut array after key in Ruby, for example:
=> ["Foo", "Bar", "Baz", "ooF", "raB", "zaB"] # Cut after "Baz"
=> ["Baz", "ooF", "raB", "zaB"] # result
It's possible to do? How can I do that?

Yes, just specify the range from index to -1(last element):
arr = ["Foo", "Bar", "Baz", "ooF", "raB", "zaB"]
arr[arr.index("Baz")..-1] # => ["Baz", "ooF", "raB", "zaB"]

Here is one more way to do this:
a = ["Foo", "Bar", "Baz", "ooF", "raB", "zaB"]
a.drop_while {|e| e != "Baz"}
#=> ["Baz", "ooF", "raB", "zaB"]
a.drop_while {|e| e != "Bazzzzzzz"}
#=> []

arr = ["Foo", "Bar", "Baz", "ooF", "raB", "zaB"]
arr.select { |s| s=='Baz'..nil ? 'Baz' : nil }
#=> ["Baz", "ooF", "raB", "zaB"]
Look odd? What's going on here?

Related

Reduce an array of nested hashes to a flattened array of key/value pairs

Given the following data structure:
[
[:created_at, "07/28/2017"],
[:valid_record, "true"],
[:cs_details, { gender: 'm', race: 'w', language: nil } ],
[:co_details, { description: 'possess', extra: { a: 'a', b: 'b', c: 'c'} } ]
]
I want an array of arrays of key/value pairs:
[
[:created_at, "07/28/2017"],
[:valid_record, "true"],
[:gender, 'm'],
[:race, 'w'],
[:description, "process"]
[:a, "a"],
[:b, "b"],
[:c, "c"]
]
Problem is I don't know how to flatten those hashes. flatten doesn't do anything:
arr.map(&:flatten)
=> [[:created_at, "07/28/2017"], [:valid_record, "true"], [:cs_details, {:gender=>"m", :race=>"w", :language=>nil}], [:co_details, {:description=>"possess", :extra=>{:a=>"a", :b=>"b", :c=>"c"}}]]
So I know flat_map won't help either. I cannot even turn those hashes to arrays using to_a:
arr.map(&:to_a)
=> [[:created_at, "07/28/2017"], [:valid_record, "true"], [:cs_details, {:gender=>"m", :race=>"w", :language=>nil}], [:co_details, {:description=>"possess", :extra=>{:a=>"a", :b=>"b", :c=>"c"}}]]
The problem with the above methods is that they work top level index only. And these hashes are nested in arrays. So I try reduce and then invoke flat_map on result:
arr.reduce([]) do |acc, (k,v)|
if v.is_a?(Hash)
acc << v.map(&:to_a)
else
acc << [k,v]
end
acc
end.flat_map(&:to_a)
=> [:created_at, "07/28/2017", :valid_record, "true", [:gender, "m"], [:race, "w"], [:language, nil], [:description, "possess"], [:extra, {:a=>"a", :b=>"b", :c=>"c"}]]
Not quite there, but closer. Any suggestions?
▶ flattener = ->(k, v) do
▷ case v
▷ when Enumerable then v.flat_map(&flattener)
▷ when NilClass then []
▷ else [k, v]
▷ end
▷ end
#⇒ #<Proc:0x000000032169e0#(pry):26 (lambda)>
▶ input.flat_map(&flattener).each_slice(2).to_a
#⇒ [
# [:created_at, "07/28/2017"],
# [:valid_record, "true"],
# [:gender, "m"],
# [:race, "w"],
# [:description, "possess"],
# [:a, "a"],
# [:b, "b"],
# [:c, "c"]
# ]
I think you would benefit from writing a helper function that will be called on each item in the array. So that the results are uniform, we will make sure that this function always returns an array of arrays. In other words, an array containing one or more "entries," depending on whether the thing at index 1 is a hash.
def extract_entries((k,v))
if v.is_a? Hash
v.to_a
else
[[k, v]]
end
end
Trying it out:
require 'pp'
pp data.map {|item| extract_entries(item)}
Output:
[[[:created_at, "07/28/2017"]],
[[:valid_record, "true"]],
[[:gender, "m"], [:race, "w"], [:language, nil]],
[[:description, "possess"], [:extra, {:a=>"a", :b=>"b", :c=>"c"}]]]
Now, we can flatten by one level to reach your desired format:
pp data.map {|item| extract_entries(item)}.flatten(1)
Output:
[[:created_at, "07/28/2017"],
[:valid_record, "true"],
[:gender, "m"],
[:race, "w"],
[:language, nil],
[:description, "possess"],
[:extra, {:a=>"a", :b=>"b", :c=>"c"}]]
def to_flat(arr)
arr.flat_map { |k,v| v.is_a?(Hash) ? to_flat(v.compact) : [[k, v]] }
end
test
arr = [ [:created_at, "07/28/2017"],
[:valid_record, "true"],
[:cs_details, { gender: 'm', race: 'w', language: nil } ],
[:co_details, { description: 'possess', extra: { a: 'a', b: 'b', c: 'c'} } ] ]
to_flat(arr)
#=> [[:created_at, "07/28/2017"], [:valid_record, "true"], [:gender, "m"],
# [:race, "w"], [:description, "possess"], [:a, "a"], [:b, "b"], [:c, "c"]]

Ruby - Convert a string concatanated by & into a hash

I have a string of the form
str="a=b&c=d&e=f&...."
Question is how do I convert the above str in below form
{ "a" => "b" , "c" => "d" , "e" => "f" .... }
You can use this method URI::decode_www_form.
require 'uri'
URI.decode_www_form "a=b&c=d&e=f"
# => [["a", "b"], ["c", "d"], ["e", "f"]]
URI.decode_www_form("a=b&c=d&e=f").to_h
# => {"a"=>"b", "c"=>"d", "e"=>"f"}
Just out of curiosity:
▶ q = "a=b&c=d&e=f"
▶ require 'json'
#⇒ true
▶ JSON.parse "{\"#{q}\"}".gsub /[=&]/, Hash('=' => '":"', '&' => '","')
#⇒ {
# "a" => "b",
# "c" => "d",
# "e" => "f"
#}
The straight way with splits:
q.split('&').map { |e| e.split('=') }.to_h
Most simple answer is:
hash = Rack::Utils.parse_query("a=b&c=d&e=f")
=> {"a"=>"b", "c"=>"d", "e"=>"f"} #output
and if you want to revert again then:
hash.to_query
str.split("&").inject({}) do |sum, e|
k, v = e.split("=")
sum.merge(k => v)
end
=> {"a"=>"b", "c"=>"d", "e"=>"f"}

Take any hash keys and flatten into mixed array

I have a mixed array like: ["foo", "bar", {test: "stuff"}, "foobar", {just: "examples}]
I want to take any hashes, remove the key, and push the value into the array. Taking my example above, I want to return:
["foo", "bar", "stuff", "foobar", "examples"]
Simplest way to do that?
a = ["foo", "bar", {test: "stuff"}, "foobar", {just: "examples"}]
a.flat_map { |x| x.is_a?(Hash) ? x.values : x }
# => ["foo", "bar", "stuff", "foobar", "examples"]
Another way, without if (which, as #Matt pointed out, only works with the hashes have a single key, as in the example):
a = ["foo", "bar", {test: "stuff"}, "foobar", {just: "examples"}]
a.map { |e| [*e].flatten.last }
#=> ["foo", "bar", "stuff", "foobar", "examples"]

Dynamic menu with Mongoid in Sinatra

I'm building Sinatra/Mongoid app and I want to create a dynamic menu of array values ​​in documents from MongoDB. I guess the following algorithm:
Supose in Mongo stored some documents
{ "name": "doc1", "array": ["foo", "bar", "baz", "quux"] }
{ "name": "doc2", "array": ["foo", "baz"] }
{ "name": "doc3", "array": ["bar", "baz", "quux"] }
{ "name": "doc4", "array": ["quux"] }
{ "name": "doc5", "array": ["foo", "quux"] }
Now I guess it should
ask all docs for "array" field values,
then sort values in order of number of mentions,
delete duplicate values
and give me this new array to build %ul in my view, like so:
%ul
%li foo
%li bar
%li baz
%li quux
And I've got no idea how to implement this.
Thank you in advance for any help.
Suppose you already have an arrays variable with the "array" values of your documents:
arrays = [
["foo", "bar", "baz", "quux"],
["foo", "baz"],
["bar", "baz", "quux"],
["quux"],
["foo", "quux"]
]
In this case, I'd use a Hash to store the array value as key, and a counter a value. This allows to
eliminate double entries, and
create a handle for sorting.
The interesting bit is the inject method, which iterates over an enumerable and injects an accumolator (memo):
list = arrays.flatten.inject({}) do |memo, e|
memo[e] = (memo[e] || 0) + 1
memo
end
#=> [["foo", 3], ["bar", 2], ["baz", 3], ["quux", 4]]
This creates this list which then can be sorted:
list = list.sort{|a,b| a[1] <=> b[1] }
#=> [["bar", 2], ["foo", 3], ["baz", 3], ["quux", 4]]
and filtered:
list = list.map(&:first)
#=> ["bar", "foo", "baz", "quux"]
You then can iterate over that array in your view.
N.B. I usually try to compress the inject call a little more, like so
list = arrays.flatten.inject({}) do |memo, e|
(memo[e] ||= 0) += 1
memo
end
or
list = arrays.flatten.inject(Hash.new {|h,k| h[k]=0 }) do |memo, e|
memo[e] += 1
memo
end
… but for some reason, my Ruby currently thinks that's an syntax error. Maybe it's just a lack of coffee ;-)

Ruby: How to turn a hash into HTTP parameters?

That is pretty easy with a plain hash like
{:a => "a", :b => "b"}
which would translate into
"a=a&b=b"
But what do you do with something more complex like
{:a => "a", :b => ["c", "d", "e"]}
which should translate into
"a=a&b[0]=c&b[1]=d&b[2]=e"
Or even worse, (what to do) with something like:
{:a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}]
Thanks for the much appreciated help with that!
For basic, non-nested hashes, Rails/ActiveSupport has Object#to_query.
>> {:a => "a", :b => ["c", "d", "e"]}.to_query
=> "a=a&b%5B%5D=c&b%5B%5D=d&b%5B%5D=e"
>> CGI.unescape({:a => "a", :b => ["c", "d", "e"]}.to_query)
=> "a=a&b[]=c&b[]=d&b[]=e"
http://api.rubyonrails.org/classes/Object.html#method-i-to_query
If you are using Ruby 1.9.2 or later, you can use URI.encode_www_form if you don't need arrays.
E.g. (from the Ruby docs in 1.9.3):
URI.encode_www_form([["q", "ruby"], ["lang", "en"]])
#=> "q=ruby&lang=en"
URI.encode_www_form("q" => "ruby", "lang" => "en")
#=> "q=ruby&lang=en"
URI.encode_www_form("q" => ["ruby", "perl"], "lang" => "en")
#=> "q=ruby&q=perl&lang=en"
URI.encode_www_form([["q", "ruby"], ["q", "perl"], ["lang", "en"]])
#=> "q=ruby&q=perl&lang=en"
You'll notice that array values are not set with key names containing [] like we've all become used to in query strings. The spec that encode_www_form uses is in accordance with the HTML5 definition of application/x-www-form-urlencoded data.
Update: This functionality was removed from the gem.
Julien, your self-answer is a good one, and I've shameless borrowed from it, but it doesn't properly escape reserved characters, and there are a few other edge cases where it breaks down.
require "addressable/uri"
uri = Addressable::URI.new
uri.query_values = {:a => "a", :b => ["c", "d", "e"]}
uri.query
# => "a=a&b[0]=c&b[1]=d&b[2]=e"
uri.query_values = {:a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}]}
uri.query
# => "a=a&b[0][c]=c&b[0][d]=d&b[1][e]=e&b[1][f]=f"
uri.query_values = {:a => "a", :b => {:c => "c", :d => "d"}}
uri.query
# => "a=a&b[c]=c&b[d]=d"
uri.query_values = {:a => "a", :b => {:c => "c", :d => true}}
uri.query
# => "a=a&b[c]=c&b[d]"
uri.query_values = {:a => "a", :b => {:c => "c", :d => true}, :e => []}
uri.query
# => "a=a&b[c]=c&b[d]"
The gem is 'addressable'
gem install addressable
No need to load up the bloated ActiveSupport or roll your own, you can use Rack::Utils.build_query and Rack::Utils.build_nested_query. Here's a blog post that gives a good example:
require 'rack'
Rack::Utils.build_query(
authorization_token: "foo",
access_level: "moderator",
previous: "index"
)
# => "authorization_token=foo&access_level=moderator&previous=index"
It even handles arrays:
Rack::Utils.build_query( {:a => "a", :b => ["c", "d", "e"]} )
# => "a=a&b=c&b=d&b=e"
Rack::Utils.parse_query _
# => {"a"=>"a", "b"=>["c", "d", "e"]}
Or the more difficult nested stuff:
Rack::Utils.build_nested_query( {:a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}] } )
# => "a=a&b[][c]=c&b[][d]=d&b[][e]=e&b[][f]=f"
Rack::Utils.parse_nested_query _
# => {"a"=>"a", "b"=>[{"c"=>"c", "d"=>"d", "e"=>"e", "f"=>"f"}]}
Here's a short and sweet one liner if you only need to support simple ASCII key/value query strings:
hash = {"foo" => "bar", "fooz" => 123}
# => {"foo"=>"bar", "fooz"=>123}
query_string = hash.to_a.map { |x| "#{x[0]}=#{x[1]}" }.join("&")
# => "foo=bar&fooz=123"
Steal from Merb:
# File merb/core_ext/hash.rb, line 87
def to_params
params = ''
stack = []
each do |k, v|
if v.is_a?(Hash)
stack << [k,v]
else
params << "#{k}=#{v}&"
end
end
stack.each do |parent, hash|
hash.each do |k, v|
if v.is_a?(Hash)
stack << ["#{parent}[#{k}]", v]
else
params << "#{parent}[#{k}]=#{v}&"
end
end
end
params.chop! # trailing &
params
end
See http://noobkit.com/show/ruby/gems/development/merb/hash/to_params.html
class Hash
def to_params
params = ''
stack = []
each do |k, v|
if v.is_a?(Hash)
stack << [k,v]
elsif v.is_a?(Array)
stack << [k,Hash.from_array(v)]
else
params << "#{k}=#{v}&"
end
end
stack.each do |parent, hash|
hash.each do |k, v|
if v.is_a?(Hash)
stack << ["#{parent}[#{k}]", v]
else
params << "#{parent}[#{k}]=#{v}&"
end
end
end
params.chop!
params
end
def self.from_array(array = [])
h = Hash.new
array.size.times do |t|
h[t] = array[t]
end
h
end
end
I know this is an old question, but I just wanted to post this bit of code as I could not find a simple gem to do just this task for me.
module QueryParams
def self.encode(value, key = nil)
case value
when Hash then value.map { |k,v| encode(v, append_key(key,k)) }.join('&')
when Array then value.map { |v| encode(v, "#{key}[]") }.join('&')
when nil then ''
else
"#{key}=#{CGI.escape(value.to_s)}"
end
end
private
def self.append_key(root_key, key)
root_key.nil? ? key : "#{root_key}[#{key.to_s}]"
end
end
Rolled up as gem here: https://github.com/simen/queryparams
{:a=>"a", :b=>"b", :c=>"c"}.map{ |x,v| "#{x}=#{v}" }.reduce{|x,v| "#{x}&#{v}" }
"a=a&b=b&c=c"
Here's another way. For simple queries.
The best approach it is to use Hash.to_params which is the one working fine with arrays.
{a: 1, b: [1,2,3]}.to_param
"a=1&b[]=1&b[]=2&b[]=3"
require 'uri'
class Hash
def to_query_hash(key)
reduce({}) do |h, (k, v)|
new_key = key.nil? ? k : "#{key}[#{k}]"
v = Hash[v.each_with_index.to_a.map(&:reverse)] if v.is_a?(Array)
if v.is_a?(Hash)
h.merge!(v.to_query_hash(new_key))
else
h[new_key] = v
end
h
end
end
def to_query(key = nil)
URI.encode_www_form(to_query_hash(key))
end
end
2.4.2 :019 > {:a => "a", :b => "b"}.to_query_hash(nil)
=> {:a=>"a", :b=>"b"}
2.4.2 :020 > {:a => "a", :b => "b"}.to_query
=> "a=a&b=b"
2.4.2 :021 > {:a => "a", :b => ["c", "d", "e"]}.to_query_hash(nil)
=> {:a=>"a", "b[0]"=>"c", "b[1]"=>"d", "b[2]"=>"e"}
2.4.2 :022 > {:a => "a", :b => ["c", "d", "e"]}.to_query
=> "a=a&b%5B0%5D=c&b%5B1%5D=d&b%5B2%5D=e"
If you are in the context of a Faraday request, you can also just pass the params hash as the second argument and faraday takes care of making proper param URL part out of it:
faraday_instance.get(url, params_hsh)
I like using this gem:
https://rubygems.org/gems/php_http_build_query
Sample usage:
puts PHP.http_build_query({"a"=>"b","c"=>"d","e"=>[{"hello"=>"world","bah"=>"black"},{"hello"=>"world","bah"=>"black"}]})
# a=b&c=d&e%5B0%5D%5Bbah%5D=black&e%5B0%5D%5Bhello%5D=world&e%5B1%5D%5Bbah%5D=black&e%5B1%5D%5Bhello%5D=world
2.6.3 :001 > hash = {:a => "a", :b => ["c", "d", "e"]}
=> {:a=>"a", :b=>["c", "d", "e"]}
2.6.3 :002 > hash.to_a.map { |x| "#{x[0]}=#{x[1].class == Array ? x[1].join(",") : x[1]}"
}.join("&")
=> "a=a&b=c,d,e"

Resources