ruby do map and compact together - ruby

I'm new to ruby and this looks wrong but works fine
def get_internal_deps
self.internal_dependencies = self.sources.map do |f|
s = File.open(File.join(self.dir, f)).grep(/\d{8}-\w{5}/)
if s.length > 0
{:file => f, :line => s}
end
end.compact
#how crazy does that look?
end
So how do I do this without having an end.compact?

Some notes
Your method is called get_internal_deps, but it looks like it actually sets an instance variable.
You could define internal_dependencies and use caching.
In this case, you'd need to remove any attr_reader/writer/accessor for #internal_dependencies.
File.open(f) isn't really clean.
You don't need self in self.dir or self.sources
:line is an Array. Shouldn't it be called :lines?
2 separate, short methods might be better than a bigger one.
Refactored code
def internal_dependencies
#internal_dependencies ||= sources.map{|s| parse_dependency(s) }
.reject{|h| h[:line].empty? }
end
private
def parse_dependency(source)
{
file: source,
line: File.readlines(File.join(dir, source)).grep(/\d{8}-\w{5}/)
}
end

To avoid compact one might use reduce (Enumerable#each_with_object in this particular case) instead of map:
def get_internal_deps
self.internal_dependencies = sources.each_with_object do |f, acc|
s = File.open(File.join(self.dir, f)).grep(/\d{8}-\w{5}/)
acc << {:file => f, :line => s} if s.length > 0
end
end
Also, note that an explicit self receiver might make sense in a case of assignment, but it is completely redundant in RHO (sources in this snippet.)

Related

Check if array element data[i][j][k] exists

I need to find out if data[i][j][k] element exists, but I don't know if data[i] or data[i][j] not nil themselves.
If I just data[i][j][k].nil?, it throw undefined method [] for nil:NilClass if data[i] or data[i][j] is nil
So, I am using now
unless data[i].nil? or data[i][j].nil? or data[i][j][k].nil?
# do something with data[i][j][k]
end
But it is somehow cumbersome.
Is there any other way to check if data[i][j][k] exists without data[i].nil? or data[i][j].nil? or data[i][j][k].nil? ?
I usually do:
unless (data[i][j][k] rescue false)
# do something
end
Here are three different alternatives:
Shorten it
You can shorten it slightly by using "!" instead of .nil?:
!data[i] or !data[i][j] or !data[i][j][k]
You could get rid of the repetition by doing this:
((data[i] || [])[j] || [])[k].nil?
Abstract away these details
Both of the code snippets above are nasty enough that I would probably not write them more than once in a code base.
A three-dimensional array seems complicated enough that you shouldn't be accessing it directly in lots of places in your code. You should consider wrapping it inside an object with an appropriate name:
class My3DWorld
def initialize
# set up #data
end
# Gets the point or returns nil if it doesn't exist.
def get_point(i, j, k)
#data[i] && #data[i][j] && #data[i][j][k]
end
end
Use a hash instead
However, ultimately, I wonder whether you really need a 3D array. Another more Ruby-like way to implement this data structure would be to use a hash and use i,j,k coordinate tuples as the keys. Unless this is a huge structure and you need the performance characteristics of a 3D array, I recommend looking at my other answer here:
https://stackoverflow.com/a/20600345/28128
The new feature "refinements" is an option:
module ResponsiveNil
refine NilClass do
def [](obj)
nil
end
end
end
using ResponsiveNil
a = [[1]]
p a[2][3][4] #=> nil
You can shorten slightly to
if data[i] && data[i][j] && data[i][j][k]
# do something with data[i][j][k]
end
You can also you the "andand" gem which allows you to write:
data[i].andand[j].andand[k]
If you are willing to monkey patch Array, you could define a method to enable this, such as:
class Array
def index_series(*args)
result = self
args.each do |key|
result = result[key]
return nil if result.nil?
end
result
end
end
which would let you do:
data.index_series(i, j, k)
The following permits any amount of nesting, and allows for the possibility that an element of the array has a value of nil:
def element_exists?(arr, *indices)
if arr.is_a? Array
return true if indices.empty?
return false if arr.size <= (i = indices.pop)
element_exists?(arr[i], *indices)
else
indices.empty?
end
end
data = [[0,1],[2,nil]]
element_exists?(data) # => true
element_exists?(data, 1) # => true
element_exists?(data, 2) # => false
element_exists?(data, 1, 1) # => true
element_exists?(data, 1, 2) # => false
element_exists?(data, 1, 1, 1) # => false

Ruby next multiple

Is there another way to write 'a'.next.next? I've looked all over and can't seem to find it.
I've tried multiplying the .next but I keep getting errors.
Well, this might not be a good idea in the case here, but if you're looking to chain a method n times in general, you can do something like this:
2.times.inject('a') { |s| s.next }
# => 'c'
20.times.inject('a') { |s| s.next }
# => 'u'
This starts with the value 'a', runs a block that calls next, then each successive result is fed back into the block.
For what it's worth, monkey-patching String can be fine for trivial scripts, but personally I'd try to look for other solutions first, like just adding a utility function to your class/module:
def repeat_next(str, n = 1)
n.times.inject(str) { |s| s.next }
end
A shortcut for your specific problem, (a.ord + 2).chr, potentially exists, although it's not the same thing.
You can just redefine String.next like this:
class String
alias_method :next1, :next
def next(n = 1)
str = self
for i in 1..n
str = str.next1
end
str
end
end
puts 'a'.next
puts 'a'.next(2)
puts 'a'.next(20)
If you're looking for a more succinct way of doing this, you could use: ('a'.ord + 2).chr. This will convert 'a' to a numerical representation (with the "ord" method), increment it by two, then converts it back to the character representation (with "chr").
You can monkey-patch the String class in ruby to add a method to do this for you:
class String
def get_nth_char(n)
current = self
while n > 0 do
current = current.next
n = n - 1
end
current
end
end
So you can do 'a'.get_nth_char(2) # => 'c'

How do I see if a multi-dimensional hash has a value in ruby?

Currently I am doing the following, but I am sure there must be a better way:
def birthday_defined?(map)
map && map[:extra] && map[:extra][:raw_info] && map[:extra][:raw_info][:birthday]
end
There may be cases where only map[:extra] is defined, and then I will end up getting Nil exception errors cause map[:extra][:raw_info] doesn't exist if I dont use my checked code above.
If you're using Rails, then you can use try (and NilClass#try):
value = map.try(:[], :extra).try(:[], :raw_info).try(:[], :birthday)
That looks a bit repetitive: it is just doing the same thing over and over again while feeding the result of one step into the next step. That code pattern means that we have a hidden injection:
value = [:extra, :raw_info, :birthday].inject(map) { |h, k| h.try(:[], k) }
This approach nicely generalizes to any path into map that you have in mind:
path = [ :some, :path, :of, :keys, :we, :care, :about ]
value = path.inject(map) { |h, k| h.try(:[], k) }
Then you can look at value.nil?.
Of course, if you're not using Rails then you'll need a replacement for try but that's not difficult.
I have two ways. Both have the same code but subtly different:
# Method 1
def birthday_defined?(map)
map[:extra][:raw_info][:birthday] rescue nil # rescues current line
end
# Method 2
def birthday_defined?(map)
map[:extra][:raw_info][:birthday]
rescue # rescues whole method
nil
end
Use a begin/rescue block.
begin
map[:extra][:raw_info][:birthday]
rescue Exception => e
'No birthday! =('
end
That's idiomatic why to do it. And yes it can be a little cumbersome.
If you want to extend Hash a bit though, you can do some cool stuff with something like a key path. See Access Ruby Hash Using Dotted Path Key String
def birthday_defined?
map.dig('extra.raw_info.birthday')
end
This is a little hacky but it will work:
def birthday_defined?(map)
map.to_s[":birthday"]
end
If map contains :birthday then it will return the string which will evaluate to true in a conditional statement while if it doesn't contain :birthday, it will return nil.
Note: This assumes the key :birthday does not appear at potentially multiple locations in map.
This should work for you:
def birthday_defined?(map)
map
.tap{|x| (x[:extra] if x)
.tap{|x| (x[:raw_info] if x)
.tap{|x| (x[:birthday] if x)
.tap{|x| return x}}}}
end

Can I reject objects which do not meet my criteria as they are entered into an array?

I know there are a number of ways to create new elements in an existing ruby array.
e.g.
myArray = []
myArray + other_array
myArray << obj
myArray[index] = obj
I'm also pretty sure I could use .collect, .map, .concat, .fill, .replace, .insert, .join, .pack and .push as well to add to or otherwise modify the contents of myArray.
However, I want to ensure that myArray only ever includes valid HTTP/HTTPS URLs.
Can anyone explain how I can enforce that kind of behaviour?
I would create a module that allows you to specify an acceptance block for an array, and then override all the methods you mention (and more, like concat) to pre-filter the argument before calling super. For example:
module LimitedAcceptance
def only_allow(&block)
#only_allow = block
end
def <<( other )
super if #only_allow[ other ]
end
def +( other_array )
super( other_array.select(&#only_allow) )
end
end
require 'uri'
my_array = []
my_array.extend LimitedAcceptance
my_array.only_allow do |item|
uri = item.is_a?(String) && URI.parse(item) rescue nil
uri.class <= URI::HTTP
end
my_array << "http://phrogz.net/"
my_array << "ftp://no.way"
my_array += %w[ ssh://bar http://ruby-lang.org http:// ]
puts my_array
#=> http://phrogz.net/
#=> http://ruby-lang.org
Create a class to encapsulate behavior you want. Then you can create your << method doing the verifications you want.
Put all logic that handle this data in methods in this domain class. Probably you will discover code floating around the use of this data to move to the new class.
My 2 cents.
Use this to insert. (untested).
def insert_to_array(first_array, second_array)
second_array.each do |i| {
if URI.parse(i).class == URI::HTTP
first_array.insert(i)
end
}
first_array
end

Refactoring respond_to? call in if-elsif-else condition

I have the following method and want to make it more readable:
def value_format(value)
if value.respond_to? :to_actor
value.to_actor
elsif value.respond_to? :to_subject
value.to_subject
elsif value.respond_to? :to_json
value.to_json
elsif value.respond_to? :to_hash
value.to_hash
else
value.inspect
end
end
This is my solution. What do you think?
def value_format(value)
methods = [:to_actor, :to_subject, :to_json, :to_hash, :inspect]
value.send(methods.find_all { |m| m if value.respond_to? m }.first)
end
Your solution looks fine, but you might as well use find instead of find_all:
METHODS = [:to_actor, :to_subject, :to_json, :to_hash, :inspect]
def value_format(value)
value.send(METHODS.find { |m| value.respond_to? m })
end
Using a constant has the advantage of not creating a new array every time value_format is ran.
Seems there's a pretty simple optimization to your solution:
def value_format(value)
methods = [:to_actor, :to_subject, :to_json, :to_hash]
value.send(methods.find(:inspect) { |m| value.respond_to? m })
end
The facets gem provides an elegant solution (I think) to this problem. It combines the two steps of checking if an object responds to a method and actually calling that method into a single step.
So your example could be rewritten as this:
require 'facets/kernel/respond'
def value_format(v)
v.respond.to_actor || v.respond.to_subject || v.respond.to_json || v.respond.to_hash || v.respond.inspect
end
Note that this method only works if it is safe to assume that none of these methods are going to return nil or false (because respond returns nil if the object doesn't respond, that is what allows us to chain it together with a bunch of ors).
Since all of the methods you listed should return strings, I believe this approach would work fine in your example.
Documentation:
# Like #respond_to? but returns the result of the call
# if it does indeed respond.
#
# class RespondExample
# def f; "f"; end
# end
#
# x = RespondExample.new
# x.respond(:f) #=> "f"
# x.respond(:g) #=> nil
#
# or
#
# x.respond.f #=> "f"
# x.respond.g #=> nil

Resources