how can I build this method in a more concise way? - ruby

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

Related

Group numbers by numerical order in ruby

I need to group numbers that are in numerical order from an array.
(using ruby 1.9.2, rails 3.2)
Example1:
[1,2,4,5,6]
Example2:
[1,3,4,6,7]
Example3:
[1,2,3,5,6]
Example4:
[1,2,4,5,7]
After grouping
Example1:
[[1,2],[4,5,6]]
Example2:
[[1],[3,4],[6,7]]
Example3:
[[1,2,3],[5,6]]
Example4:
[[1,2],[4,5],[7]]
You get the idea.
(What I'm actually doing is grouping days, not relevant though)
Thanks in advance!
I'm not sure what you'd call this operation, but it's a sort of grouping method based on the last element processed. Something like:
def groupulate(list)
list.inject([ ]) do |result, n|
if (result[-1] and result[-1][-1] == n - 1)
result[-1] << n
else
result << [ n ]
end
result
end
end
The Enumerable module provides a large number of utility methods for processing lists, but inject is the most flexible by far.
Perfect problem to use inject (aka reduce) with:
def group_consecutive(arr)
arr.inject([[]]) do |memo, num|
if memo.last.count == 0 or memo.last.last == num - 1
memo.last << num
else
memo << [ num ]
end
memo
end
end
See it run here: http://rubyfiddle.com/riddles/0d0a5
a = [1,2,4,5,7]
out = []
a.each_index do |i|
if out.last and out.last.last == a[i]-1
out.last << a[i]
else
out << [a[i]]
end
end
puts out.inspect

Sorting array with multiple variables

I am trying to create a program where by the user can enter multiple names. those names are then displayed under each other in alphabetical order, and print(display) every second name backwards. i have gone through several tutorials this is my second day using ruby.. here is what i have so far.
name_list = {}
puts 'please enter names seperated by a space:'
name_list = gets.chomp
names = name_list.split(" ")
to grab names...
names.sort do |a,b| a.upcase <=> b.upcase end
display = "#{names}"
for ss in 0...display.length
print ss, ": ", display[ss], "\n"
end
to arrange them alphabetically and under each other.
i am really struggling to mesh it all together i think i have got at least half a dozen errors in here...if i am on the wrong path could someone guide me to some info so i can start again??
EDIT
i also had this idea of using a class.
but i would have to program the names in i wanted the user to be able to add info via the consol.
class A
def initialize(name)
#name = name
end
def to_s
#name.reverse
end
end
>> a = [A.new("greg"),A.new("pete"),A.new("paul")]
>> puts a
Problems in your code:
name_list defined as an empty hash at the top but not used.
split(" ") -> split
sort { |a, b| a.method <=> b.method } -> sort_by { |x| x.method } -> sort_by(&:method)
sort is not an in-place operation, assign the result (or directly use it).
display = "#{names}" -> display = names
for ss in 0...display.length -> enumerable.each_with_index { |item, index| ... }
don't write do/end in one-liners, use { ... }
I'd write:
puts 'Please enter names separated by spaces'
gets.split.sort_by(&:upcase).each_with_index do |name, index|
puts "%s: %s" % [index, (index % 2).zero? ? name : name.reverse]
end
A few pointers then:
names.sort do |a,b| a.upcase <=> b.upcase end # Will not modify the "names" array, but will return a sorted array.
names.sort! do |a,b| a.upcase <=> b.upcase end # Will modify the "names" array.
To display your names:
names.each_with_index do |name, index|
if index % 2 == 0
puts name
else
puts name.reverse
end
end
puts 'please enter names seperated by a space`enter code here` :'
names = gets.chomp.split(" ")
names.sort! {|a,b| a.upcase <=> b.upcase } # For a single line use {..} instead of do..end
names.each_with_index do |n,i|
if i % 2 == 0
p n
else
p n.reverse
end
end
You can also use a ternary operator, I used the full if else block for readability in this case.
names.each_with_index do |n,i|
p (i % 2 == 0) ? n : n.reverse
end
EDIT
command = ""
names = []
while command != "exit"
puts 'please enter names seperated by a space`enter code here` :'
command = gets.chomp!
if command == "display"
names.sort! {|a,b| a.upcase <=> b.upcase } # For a single line use {..} instead of do..end
names.each_with_index do |n,i|
if i % 2 == 0
p n
else
p n.reverse
end
end
else
names << command
end
end

Ruby Script to Create an Array of Arrays

I have written a simple screen scraping script and at the end of the script I am attempting to create an array of arrays in preparation for an activerecord insert. The structure I am trying to achieve is as follows:
Array b holds a series of 10 element arrays
b = [[0,1,2,3,4,5,6,7,8,9],[0,1,2,3,4,5,6,7,8,9],[0,1,2,3,4,5,6,7,8,9]]
Currently when I try to print out Array b the array is empty. I'm still fairly new to ruby and programming for that matter and would appreciate any feedback on how to get values in array b and to improve the overall script. Script follows:
require "rubygems"
require "celerity"
t = 0
r = 0
c = 0
a = Array.new(10)
b = Array.new
#initialize Browser
browser = Celerity::IE.new
#goto Login Page
browser.goto('http://www1.drf.com/drfLogin.do?type=membership')
#input UserId and Password
browser.text_field(:name, 'p_full_name').value = 'username'
browser.text_field(:name, 'p_password').value = 'password'
browser.button(:index, 2).click
#goto DRF Frontpage
browser.goto('http://www.drf.com/frontpage')
#goto DRF Entries
browser.goto('http://www1.drf.com/static/indexMenus/eindex.html')
#click the link to access the entries
browser.link(:text, '09').click
browser.tables.each do |table|
t = t + 1
browser.table(:index, t).rows.each do |row|
r = r + 1
browser.table(:index, t).row(:index, r).cells.each do |cell|
a << cell.text
end
b << a
a.clear
end
r = 0
end
puts b
browser.close
This a minor rewrite of your main loop to a more Ruby-like way.
b = Array.new
browser.tables.each_with_index do |table, t|
browser.table(:index, 1 + t).rows.each_with_index do |row, r|
a = Array.new(10)
browser.table(:index, 1 + t).row(:index, 1 + r).cells.each do |cell|
a << cell.text
end
b << a
end
end
puts b
I moved the array initializations to immediately above where they'll be needed. That's a programmer-choice thing of course.
Rather than create two counter variables up above, I switched to using each_with_index which adds an index variable, starting at 0. To get your 1-offsets I add 1.
They're not big changes but they add up to a more cohesive app.
Back to the original code: One issue I see with it is that you create your a array outside the loops then reuse it when you assign to b. That means that each time the same array gets used, but cleared and values stored to it. That will cause the previous array values to be overwritten, but resulting in duplicated arrays in b.
require 'pp'
a = []
b = []
puts a.object_id
a[0] = 1
b << a
a.clear
a[0] = 2
b << a
puts
pp b
b.each { |ary| puts ary.object_id }
# >> 2151839900
# >>
# >> [[2], [2]]
# >> 2151839900
# >> 2151839900
Notice that the a array gets reused repeatedly.
If I change a to a second array there are two values for b and a is two separate objects:
require 'pp'
a = []
b = []
puts a.object_id
a[0] = 1
b << a
a = []
a[0] = 2
b << a
puts
pp b
b.each { |ary| puts ary.object_id }
# >> 2151839920
# >>
# >> [[1], [2]]
# >> 2151839920
# >> 2151839780
Hopefully that'll help you avoid the problem in the future.
Your problem is there at the end:
b << a # push a *reference to* a onto b
a.clear # clear a; the reference in b now points to an empty array!
If you remove the reference to a.clear and start that loop with:
browser.tables.each do |table|
t = t + 1
a = []
...you'll be golden (at least as far as your array-building goes)
I can't tell from your question whether you have multiple tables or not. Maybe just one? In which case:
b = browser.tables.first.rows.map {|row| row.cells.map(&:text)}
If you have multiple tables, and really want an array (tables) of arrays (rows) of arrays (cells), that would be
b = browser.tables.map {|t| t.rows.map {|row| row.cells.map(&:text)}}
And if the tables all have the same structure and you just want all the rows as if they were in one big table, you can do:
b = browser.tables.map {|t| t.rows.map {|row| row.cells.map(&:text)}}.flatten(1)

How does Ruby's Array.| compare elements for equality?

Here's some example code:
class Obj
attr :c, true
def == that
p '=='
that.c == self.c
end
def <=> that
p '<=>'
that.c <=> self.c
end
def equal? that
p 'equal?'
that.c.equal? self.c
end
def eql? that
p 'eql?'
that.c.eql? self.c
end
end
a = Obj.new
b = Obj.new
a.c = 1
b.c = 1
p [a] | [b]
It prints 2 objects but it should print 1 object. None of the comparison methods get called. How is Array.| comparing for equality?
Array#| is implemented using hashs. So in order for your type to work well with it (as well as with hashmaps and hashsets), you'll have to implement eql? (which you did) and hash (which you did not). The most straight forward way to define hash meaningfully would be to just return c.hash.
Ruby's Array class is implemented in C, and from what I can tell, uses a custom hash table to check for equality when comparing objects in |. If you wanted to modify this behavior, you'd have to write your own version that uses an equality check of your choice.
To see the full implementation of Ruby's Array#|: click here and search for "rb_ary_or(VALUE ary1, VALUE ary2)"
Ruby is calling the hash functions and they are returning different values, because they are still just returning the default object_id. You will need to def hash and return something reflecting your idea of what makes an Obj significant.
>> class Obj2 < Obj
>> def hash; t = super; p ['hash: ', t]; t; end
>> end
=> nil
>> x, y, x.c, y.c = Obj2.new, Obj2.new, 1, 1
=> [#<Obj2:0x100302568 #c=1>, #<Obj2:0x100302540 #c=1>, 1, 1]
>> p [x] | [y]
["hash: ", 2149061300]
["hash: ", 2149061280]
["hash: ", 2149061300]
["hash: ", 2149061280]
[#<Obj2:0x100302568 #c=1>, #<Obj2:0x100302540 #c=1>]

conditional chaining in ruby

Is there a good way to chain methods conditionally in Ruby?
What I want to do functionally is
if a && b && c
my_object.some_method_because_of_a.some_method_because_of_b.some_method_because_of_c
elsif a && b && !c
my_object.some_method_because_of_a.some_method_because_of_b
elsif a && !b && c
my_object.some_method_because_of_a.some_method_because_of_c
etc...
So depending on a number of conditions I want to work out what methods to call in the method chain.
So far my best attempt to do this in a "good way" is to conditionally build the string of methods, and use eval, but surely there is a better, more ruby, way?
You could put your methods into an array and then execute everything in this array
l= []
l << :method_a if a
l << :method_b if b
l << :method_c if c
l.inject(object) { |obj, method| obj.send(method) }
Object#send executes the method with the given name. Enumerable#inject iterates over the array, while giving the block the last returned value and the current array item.
If you want your method to take arguments you could also do it this way
l= []
l << [:method_a, arg_a1, arg_a2] if a
l << [:method_b, arg_b1] if b
l << [:method_c, arg_c1, arg_c2, arg_c3] if c
l.inject(object) { |obj, method_and_args| obj.send(*method_and_args) }
You can use tap:
my_object.tap{|o|o.method_a if a}.tap{|o|o.method_b if b}.tap{|o|o.method_c if c}
Sample class to demonstrate chaining methods that return a copied instance without modifying the caller.
This might be a lib required by your app.
class Foo
attr_accessor :field
def initialize
#field=[]
end
def dup
# Note: objects in #field aren't dup'ed!
super.tap{|e| e.field=e.field.dup }
end
def a
dup.tap{|e| e.field << :a }
end
def b
dup.tap{|e| e.field << :b }
end
def c
dup.tap{|e| e.field << :c }
end
end
monkeypatch: this is what you want to add to your app to enable conditional chaining
class Object
# passes self to block and returns result of block.
# More cumbersome to call than #chain_if, but useful if you want to put
# complex conditions in the block, or call a different method when your cond is false.
def chain_block(&block)
yield self
end
# passes self to block
# bool:
# if false, returns caller without executing block.
# if true, return result of block.
# Useful if your condition is simple, and you want to merely pass along the previous caller in the chain if false.
def chain_if(bool, &block)
bool ? yield(self) : self
end
end
Sample usage
# sample usage: chain_block
>> cond_a, cond_b, cond_c = true, false, true
>> f.chain_block{|e| cond_a ? e.a : e }.chain_block{|e| cond_b ? e.b : e }.chain_block{|e| cond_c ? e.c : e }
=> #<Foo:0x007fe71027ab60 #field=[:a, :c]>
# sample usage: chain_if
>> cond_a, cond_b, cond_c = false, true, false
>> f.chain_if(cond_a, &:a).chain_if(cond_b, &:b).chain_if(cond_c, &:c)
=> #<Foo:0x007fe7106a7e90 #field=[:b]>
# The chain_if call can also allow args
>> obj.chain_if(cond) {|e| e.argified_method(args) }
Although the inject method is perfectly valid, that kind of Enumerable use does confuse people and suffers from the limitation of not being able to pass arbitrary parameters.
A pattern like this may be better for this application:
object = my_object
if (a)
object = object.method_a(:arg_a)
end
if (b)
object = object.method_b
end
if (c)
object = object.method_c('arg_c1', 'arg_c2')
end
I've found this to be useful when using named scopes. For instance:
scope = Person
if (params[:filter_by_age])
scope = scope.in_age_group(params[:filter_by_age])
end
if (params[:country])
scope = scope.in_country(params[:country])
end
# Usually a will_paginate-type call is made here, too
#people = scope.all
Use #yield_self or, since Ruby 2.6, #then!
my_object.
then{ |o| a ? o.some_method_because_of_a : o }.
then{ |o| b ? o.some_method_because_of_b : o }.
then{ |o| c ? o.some_method_because_of_c : o }
Here's a more functional programming way.
Use break in order to get tap() to return the result. (tap is in only in rails as is mentioned in the other answer)
'hey'.tap{ |x| x + " what's" if true }
.tap{ |x| x + "noooooo" if false }
.tap{ |x| x + ' up' if true }
# => "hey"
'hey'.tap{ |x| break x + " what's" if true }
.tap{ |x| break x + "noooooo" if false }
.tap{ |x| break x + ' up' if true }
# => "hey what's up"
Maybe your situation is more complicated than this, but why not:
my_object.method_a if a
my_object.method_b if b
my_object.method_c if c
I use this pattern:
class A
def some_method_because_of_a
...
return self
end
def some_method_because_of_b
...
return self
end
end
a = A.new
a.some_method_because_of_a().some_method_because_of_b()
If you're using Rails, you can use #try. Instead of
foo ? (foo.bar ? foo.bar.baz : nil) : nil
write:
foo.try(:bar).try(:baz)
or, with arguments:
foo.try(:bar, arg: 3).try(:baz)
Not defined in vanilla ruby, but it isn't a lot of code.
What I wouldn't give for CoffeeScript's ?. operator.
I ended up writing the following:
class Object
# A naïve Either implementation.
# Allows for chainable conditions.
# (a -> Bool), Symbol, Symbol, ...Any -> Any
def either(pred, left, right, *args)
cond = case pred
when Symbol
self.send(pred)
when Proc
pred.call
else
pred
end
if cond
self.send right, *args
else
self.send left
end
end
# The up-coming identity method...
def itself
self
end
end
a = []
# => []
a.either(:empty?, :itself, :push, 1)
# => [1]
a.either(:empty?, :itself, :push, 1)
# => [1]
a.either(true, :itself, :push, 2)
# => [1, 2]

Resources