I wrote this method:
def stringify_query_params(query_parameters)
stringified_query_params = ''
query_parameters.each_with_index do |kv, i|
k, v = kv
index = i
if index == 0
stringified_query_params += "?#{k}=#{v}"
else
stringified_query_params += "&#{k}=#{v}"
end
end
return stringified_query_params
end
RubyCop is complaining in my running instance of RubyMine saying that I should instead be capturing the output of the conditional branching logic like this.
I was able to make it slightly better, using some methods in the Enumerable module
def stringify_query_parameters(query_parameters)
query_parameters.each_with_object([]).with_index do |((k, v), acc), index|
acc.push((index.positive? ? '&' : '?') + "#{k}=#{v}")
end.join('')
end
Can anyone think of a way to make it even terser?
It can be as follows:
def stringify_query_parameters(query_parameters)
'?' + query_parameters.map{ |k, v| "#{k}=#{v}" }.join('&')
end
def self.sort(_sort_items, _collection)
return _collection unless _sort_items
_collection.sort! do |first_item, second_item|
side_one = []
side_two = []
_sort_items.each do |sort_item|
a = first_item.send(sort_item.item_name)
b = second_item.send(sort_item.item_name)
if sort_item.descending
side_one << b
side_two << a
else
side_one << a
side_two << b
end
end
side_one <=> side_two
end
end
I want to write this method in a more concise/stylish way
Sort method explanation:
The #sort method take a collection and order by multiple attributes.
If I have a #collection like this:
and #sort_items:
#sort_items.add_item(:gender, true)
#sort_items.add_item(:age, false)
If I perform:
MyClass.sort(#sort_items, #collection)
I will get:
The same collection ordered by gender desc first and then by age asc.
I'm using pure ruby but including Active Support
As for concision and style, your code actually looks pretty good. It's well-organized and readable.
I do see a way to optimize for performance: If I'm understanding the method correctly, you should only have to go to the next level of _sort_items if you have a tie (i.e. both items being compared are the same: "F" vs "F"). So you could have an early return as soon as the 2 items are different:
_sort_items.each do |sort_item|
a = first_item.send(sort_item.item_name)
b = second_item.send(sort_item.item_name)
if sort_item.descending
side_one << b
side_two << a
else
side_one << a
side_two << b
end
return side_one <=> side_two if a != b
end
My only other suggestion would be to watch out for methods getting too long. This one is over 20 lines, and you should think about separating some logic into a separate method. Maybe something like this:
_sort_items.each do |sort_item|
a = first_item.send(sort_item.item_name)
b = second_item.send(sort_item.item_name)
add_to_sides(a, b, side_one, side_two)
return side_one <=> side_two if a != b
end
.
.
.
def add_to_sides(a, b, side_one, side_two)
.
.
.
end
I have two two-dimensional arrays,
a = [[17360, "Z51.89"],
[17361, "S93.601A"],
[17362, "H66.91"],
[17363, "H25.12"],
[17364, "Z01.01"],
[17365, "Z00.121"],
[17366, "Z00.129"],
[17367, "K57.90"],
[17368, "I63.9"]]
and
b = [[17360, "I87.2"],
[17361, "s93.601"],
[17362, "h66.91"],
[17363, "h25.12"],
[17364, "Z51.89"],
[17365, "z00.121"],
[17366, "z00.129"],
[17367, "k55.9"],
[17368, "I63.9"]]
I would like to count similar rows in both the arrays irrespective of the character case, i.e., "h25.12" would be equal to "H25.12".
I tried,
count = a.count - (a - b).count
But (a - b) returns
[[17360, "Z51.89"],
[17361, "S93.601A"],
[17362, "H66.91"],
[17363, "H25.12"],
[17364, "Z01.01"],
[17365, "Z00.121"],
[17366, "Z00.129"],
[17367, "K57.90"]]
I need the count as 5 since there are five similar rows when we do not consider the character case.
Instead of a - b you should do this:
a.map{|k,v| [k,v.downcase]} - b.map{|k,v| [k,v.downcase]} # case-insensitive
You can convert Arrays to Hash, and use Enumerable#count with a block.
b_hash = b.to_h
a.to_h.count {|k, v| b_hash[k] && b_hash[k].downcase == v.downcase }
# => 5
It will convert second element of inner array to upcase for both array then you can perform subtraction, then It will return exact result that you want
a.map{|first,second| [first,second.upcase]} - b.map{|first,second| [first,second.upcase]}
You can zip them and then use the block form of count:
a.zip(b).count{|e| e[0][1].downcase == e[1][1].downcase}
a.count - (a.map{|e| [e[0],e[1].downcase] } - b.map{|e| [e[0],e[1].downcase] }).count
The above maps a and b to new arrays where the second sub-array element is downcase.
You want to count similar, so &(AND) operation is more suitable.
(a.map { |k, v| [k, v.upcase] } & b.map { |k, v| [k, v.upcase] }).count
Using Proc and '&':
procedure = Proc.new { |i, j| [i, j.upcase] }
(a.map(&procedure) & b.map(&procedure)).count
#=> 5
For better understanding, let's simplify it:
new_a = a.map {|i, j| [i, j.upcase]}
new_b = b.map {|i, j| [i, j.upcase]}
# Set intersection using '&'
(new_a & new_b).count
#=> 5
I have assumed that the ith element of a is to be compared with the ith element of b. (Edit: a subsequent comment by the OP confirmed this interpretation.)
I would be inclined to use indices to avoid the construction of relatively large temporary arrays. Here are two ways that might be done.
#1 Use indices
[a.size,b.size].min.size.times.count do |i|
af,al=a[i]
bf,bl=b[i];
af==bf && al.downcase==bl.downcase
end
#=> 5
#2 Use Refinements
My purpose in giving this solution is to illustrate the use of Refinements. I would not argue for its use for the problem at hand, but this problem provides a good vehicle for showing how the technique can be applied.
I could not figure out how best to do this, so I posted this question on SO. I've applied #ZackAnderson's answer below.
module M
refine String do
alias :dbl_eql :==
def ==(other)
downcase.dbl_eql(other.downcase)
end
end
refine Array do
def ==(other)
zip(other).all? {|x, y| x == y}
end
end
end
'a' == 'A' #=> false (as expected)
[1,'a'] == [1,'A'] #=> false (as expected)
using M
'a' == 'A' #=> true
[1,'a'] == [1,'A'] #=> true
I could use Enumerable#zip, but for variety I'll use Object#to_enum and Kernel#loop in conjunction with Enumerator#next:
ea, eb = a.to_enum, b.to_enum
cnt = 0
loop do
cnt += 1 if ea.next == eb.next
end
cnt #=> 5
When writing iterative code with mutation in ruby, I often find myself following this pattern:
def build_x some_data
x = [] # or x = {}
some_data.each do |data|
x.some_in_place_update! (... data ...)
end
x
end
(x often does not have the same shape as some_data, so a simple map will not do.)
Is there a more idiomatic or better way to write code that follows this pattern?
[edit] A real example:
def to_hierarchy stuff
h = {}
stuff.each do |thing|
path = thing.uri.split("/").drop(4)
sub_h = h
path.each do |segment|
sub_h[segment] ||= {}
sub_h = sub_h[segment]
end
sub_h.merge!(
data: thing.data,
)
end
h
end
This begins with a flat list of things, which have related but distinct uris. It transforms this flat list into a hierarchy, grouping related things that share the same segments of a uri. This follows the pattern I described: initialize h, loop over some data and mutate h along the way, and then spit out h at the end.
[edit2] Another related example
def count_data obj
i = if obj[:data] then 1 else 0
obj.each do |k, v|
i += count_statements v unless :data == k
end
i
end
Your to_hierarchy example could be done with each_with_object:
def to_hierarchy stuff
stuff.each_with_object({}) do |thing, h|
#...
end
end
each_with_object passes the extra object to the block and returns that object when the iteration is done.
If you're more of a traditionalist, you could use inject:
def to_hierarchy stuff
stuff.inject({}) do |h, thing|
#...
h
end
end
Note the block argument order change and that the block has to return h so that inject can feed it back into the next block invocation.
Your general example could be written as:
def build_x some_data
some_data.each_with_object([]) do |data, x|
x.some_in_place_update! (... data ...)
end
end
or:
def build_x some_data
some_data.inject({}) do |x, data|
x.some_in_place_update! (... data ...)
x
end
end
Ah! You want each_with_object. Like this
def to_hierarchy stuff
stuff.each_with_object({}) do |thing, h|
path = thing.uri.split("/").drop(4)
sub_h = h
path.each do |segment|
sub_h[segment] ||= {}
sub_h = sub_h[segment]
end
sub_h.merge!(
data: thing.data,
)
end
end
I'm looking for a functional way to perform this messy procedural logic:
values = [a, b, c, d, e, f]
last_value = nil
string = ""
values.each do |v|
string << if last_value && last_value.special?
"/x/" + v.name.to_s
else
"/" + v.name.to_s
end
last_value = v
end
I basically have an array of objects (all the same type) and need to join their #name attributes, but following an object that has a particular characteristic, I need a different separator.
This is an easily solved problem, but I'm looking for the cleanest, most functional approach. I first dived into #inject, but you lose the previous value at each iteration, so I couldn't make that work. Any ideas?
I'd love to post the real code instead of pseudo code, but it's really dense and complex DataMapper Relationship stuff, so you probably couldn't just run it anyway, sorry :(
If I understand correctly what you want, this should work:
output = values.map do |v|
["/" + v.name.to_s, v.special? ? "/x" : ""]
end.flatten[0...-1].join
Alternative phrasing (Ruby 1.9):
output = "/" + values.flat_map do |v|
[v.name.to_s, ("x" if v.special?)]
end.take(2*values.size - 1).join("/")
Without analyzing the algorithm, just making it functional:
output = ([nil] + values).each_cons(2).map do |last_value, v|
if last_value && last_value.special?
"/x/" + v.name.to_s
else
"/" + v.name.to_s
end
end.join
try values.collect{|v| v.special? ? v + "/x/" : v + "/"}.join("")
EDIT, solution using inject:
values.inject(["", ""]) {|path_and_sep, item| [path_and_sep[0] + path_and_sep[1] + item, item.special? "/x/" : "/"]} [0]
Do the join at the end to get rid of the leading and trailing /:
values.collect{|v| v.special? && v != values.last ? [v.name.to_s, "x"] : v.name.to_s}.flatten.join("/")
I'm not particularly proud of that one, but it's kind of functional ;)
values.clone.unshift(nil).each_cons(2).map { |last_value, v|
last_value.special? ? "/x/" + v.to_s : "/" + v.to_s
}.join()
The clone is needed because each_cons destroys he original array.
values.map{|x| x.special? ? [x, SEPARATOR_2] : [x, SEPARATOR_1]}.flatten[0..-2].join('')
values.inject('') { |m, e| m << e.to_s; m << (e.special? ? '/x/' : '/' }
To make it completely functional you could recreate m each time instead of appending to it.
And to avoid the last iteration, perhaps something more complex like:
delay = ''
values.inject('') do |m, e|
m << delay << e.to_s
delay = e.special? ? '/x/' : '/'
m
end