handling data structures (hashes etc) gracefully in ruby - ruby

I recently did a class assignment where I made a really hacky data structure. I ended up using nested hashes, which seems like a good idea, but is really hard to iterate through and manage.
I was doing general stuff, like one tag maps to a hash of items that map to prices and stuff like that. But some of them were getting more complicated.
I know that rails uses a lot of more elegant seeming stuff with symbols and such (which I never use shameful face) and I was wondering how I could optimize this. For example if I had my nested hashes something like this
h["cool"][????][1.2]
is there a graceful way of pulling those values out? Maybe I'm just a total newbie in this regard but I wanted to set things straight before I started doing more things. Maybe I'm even looking for something different like a mix of array/hash or something. Please let me know!

It looks like you need to think about structuring your data more rigorously. Try creating a class for your items, which can contain prices among other things, and perhaps organising them in the way you need to access them. Think about what you want and place the information in structures in a way that makes sense to you. Anything else is a waste of time, both now and three months down the line when you need to extend the system and find you can't.
Yes, it'll be quite a bit of work, and yes, it'll be worth it.

Edit: Revised to provide the rough path to the item. It can't know the name of the variable though.
Try this:
def iterate_nested(array_or_hash, depth = [], &block)
case array_or_hash
when Array:
array_or_hash.each_with_index do |item, key|
if item.class == Array || item.class == Hash
iterate_nested(item, depth + [key], &block)
else
block.call(key, item, depth + [key])
end
end
when Hash:
array_or_hash.each do |key, item|
if item.class == Array || item.class == Hash
iterate_nested(item, depth + [key], &block)
else
block.call(key, item, depth + [key])
end
end
end
end
It should iterate to any depth necessary, limited by memory, etc, and return the key and item and depth of the returned item. Works with both hashes and arrays.
If you test with:
iterate_nested([[[1,2,3], [1,2,3]], [[1,2,3], [1,2,3]], [[1,2,3], [1,2,3]]]) do |key, item, depth|
puts "Element: <#{depth.join('/')}/#{key}> = #{item}"
end
It yields:
Element: <0/0/0/0> = 1
Element: <0/0/1/1> = 2
Element: <0/0/2/2> = 3
Element: <0/1/0/0> = 1
Element: <0/1/1/1> = 2
Element: <0/1/2/2> = 3
Element: <1/0/0/0> = 1
Element: <1/0/1/1> = 2
Element: <1/0/2/2> = 3
Element: <1/1/0/0> = 1
Element: <1/1/1/1> = 2
Element: <1/1/2/2> = 3
Element: <2/0/0/0> = 1
Element: <2/0/1/1> = 2
Element: <2/0/2/2> = 3
Element: <2/1/0/0> = 1
Element: <2/1/1/1> = 2
Element: <2/1/2/2> = 3
Cheerio!

h["cool"].keys
to then iterate the tree would be
h["cool"].keys.each |outer| { h["cool"][outer].each { |inner| puts inner }}

It really depends on what you're trying to do (nowhere near enough information in the question), but if you need to dive in three or more levels into a Hash, you may very well want a recursive tree traversal algorithm:
def hash_traverse(hash)
result = ""
for key, value in hash
result << key.to_s + ":\n"
if !value.kind_of?(Hash)
result << " " + value.to_s + "\n"
else
result << hash_traverse(value).gsub(/^/, " ")
end
end
return result
end
Are you sure a Hash is the best data structure for what you're trying to do?

Related

Ruby randomly shuffle an array so that no element keeps its original place

I have an array that stores some names. Each person solves a task and then I want to assign each solution to a different person for verification. In short this means that I need to perform a shuffle in an array in a way that no element keeps its original place. The solution I thought of is to perform a shuffle in the array until the second condition is met and here is the code for it:
copied = names.dup
loop do
copied.shuffle!
valid = true
(0...copied.size).each do |i|
if copied[i] == names[i]
valid = false
break
end
end
break if valid
end
puts copied
Still I feel there may be a more optimal solution to this problem. Anyone has a better idea?
Looks like what you're trying to do is to create a map of {verifier => task_solver} where verifier != task_solver. A simple way to achieve this is simply have each person's verify the next person's task:
verifiers = {}
task_solvers.each_with_index do |task_solver, index|
verifiers[task_solver] = task_solvers[(index + 1) % task_solvers.size]
end
If you want a little bit more randomisation, you could use this, which uses the same algorithm but just shuffles your list before anything happens:
verifiers = {}
shuffled_task_solvers = task_solvers.shuffle
shuffled_task_solvers.each_with_index do |task_solver, index|
verifiers[task_solver] = shuffled_task_solvers[(index + 1) % shuffled_task_solvers.size]
end
What you are after might be called a derangement. Take a look here: http://rosettacode.org/wiki/Permutations/Derangements (There is an example in Ruby).
I think you need just one shuffle. After that if some element in the copied array is in its original place you can swap it with one of its neighbours (it is clear that new places of the two elements cannot be the same as original places).
Here is what I came up with. I don't know if it's any better though :P
list = [1,2,3,4,5,6,7,8]
def jumble (array)
new = array.shuffle
array.each_with_index do |item, index|
if new[index] == item
new[index], new[index - 1] = new[index - 1], new[index]
end
end
new
end
new = jumble (list)
puts new.inspect
Output:
[4, 1, 8, 3, 7, 2, 6, 5]

Subtraction of two arrays with incremental indexes of the other array to a maximum limit

I have lots of math to do on lots of data but it's all based on a few base templates. So instead of say, when doing math between 2 arrays I do this:
results = [a[0]-b[1],a[1]-b[2],a[2]-b[3]]
I want to instead just put the base template: a[0]-b[1] and make it automatically fill say 50 places in the results array. So I don't always have to manually type it.
What would be the ways to do that? And would a good way be to create 1 method that does this automatically. And I just tell it the math and it fills out an array?
I have no clue, I'm really new to programming.
a = [2,3,4]
b = [1,2,3,4]
results = a.zip(b.drop(1)).take(50).map { |v,w| v - w }
Custom
a = [2,3,4..............,1000]
b = [1,2,3,4,.............900]
class Array
def self.calculate_difference(arr1,arr2,limit)
begin
result ||= Array.new
limit.send(:times) {|index| result << arr1[index]-arr2[index+=1]}
result
rescue
raise "Index/Limit Error"
end
end
end
Call by:
Array.calculate_difference(a,b,50)

Ruby find in array with offset

I'm looking for a way to do the following in Ruby in a cleaner way:
class Array
def find_index_with_offset(offset, &block)
[offset..-1].find &block
end
end
offset = array.find_index {|element| element.meets_some_criterion?}
the_object_I_want =
array.find_index_with_offset(offset+1) {|element| element.meets_another_criterion?}
So I'm searching a Ruby array for the index of some object and then I do a follow-up search to find the first object that matches some other criterion and has a higher index in the array. Is there a better way to do this?
What do I mean by cleaner: something that doesn't involve explicitly slicing the array. When you do this a couple of times, calculating the slicing indices gets messy fast. I'd like to keep operating on the original array. It's easier to understand and less error-prone.
NB. In my actual code I haven't monkey-patched Array, but I want to draw attention to the fact that I expect I'm duplicating existing functionality of Array/Enumerable
Edits
Fixed location of offset + 1 as per Mladen Jablanović's comment; rewrite error
Added explanation of 'cleaner' as per Mladen Jablanović's comment
Cleaner is here obviously subjective matter. If you aim for short, I don't think you could do better than that. If you want to be able to chain multiple such finds, or you are bothered by slicing, you can do something like this:
module Enumerable
def find_multi *procs
return nil if procs.empty?
find do |e|
if procs.first.call(e)
procs.shift
next true if procs.empty?
end
false
end
end
end
a = (1..10).to_a
p a.find_multi(lambda{|e| e % 5 == 0}, lambda{|e| e % 3 == 0}, lambda{|e| e % 4 == 0})
#=> 8
Edit: And if you're not concerned with the performance you could do something like:
array.drop_while{|element|
!element.meets_some_criterion?
}.drop(1).find{|element|
element.meets_another_criterion?
}

getting dimension of multidimensional array in ruby

I just started learning ruby.
Now I need to figure out the dimension of a multidimensional array. I had a look at ruby-docs for the all the array methods, but I could not find a method that returns the dimension.
Here is an example:
For [[1, 2],[3,4],[5,6]] the dimension should be 2.
For [[[1,2],[2,3]],[[3,4],[5]]], the dimension should be 3.
Simple, object-oriented solution.
class Array
def depth
map {|element| element.depth + 1 }.max
end
end
class Object
def depth
0
end
end
There is not a built-in function for that as there may be multiple definition as to what you mean by "dimension" for an array. Ruby's arrays may contain anything including hashes or other arrays. That's why I believe you need to implement your own function for that.
Asuming that by dimension you mean "the deepest nested level of arrays" this should do the trick:
def get_dimension a
return 0 if a.class != Array
result = 1
a.each do |sub_a|
if sub_a.class == Array
dim = get_dimension(sub_a)
result = dim + 1 if dim + 1 > result
end
end
return result
end
EDIT: and as ruby is a great language and allows you to do some fancy stuff you can also make get_dimension a method of Array:
class Array
def get_dimension
... # code from above slightly modified
end
end
in the simplest case
depth = Proc.new do |array|
depth = 1
while Array === array.first do
array = array.first
depth += 1
end
depth
end
array = [[[1,2],[2,3]],[[3,4],[5]]]
depth.call(array)
#=> 3
Or this tiny recursive method
def depth(array, depth=1)
array = array.send(:first)
Array === array ? depth(array, depth+1) : depth
end
array = [[[1,2],[2,3]],[[3,4],[5]]]
depth(array)
#=> 3
How about:
class Object
def dimension
self.class == Array ? 1 + self[0].dimension : 0
end
end
[[[1,2],[2,3]],[[3,4],[5]]].dimension
#=> 3
As a modification of Tass's approach:
class Array
def depth
map{ |element| element.is_a?( Vector ) ? element.depth + 1 : 1 }.max
end
end
Keeps depth as a method of Array, and doesn't require adding a method to Object.
Of course, that might be what you want if you are going to call my_object.depth, where you don't know in advance that my_object.class == Array
I was not satisfied with the other solutions so I wrote a one-liner I'd actually use:
def depth(array)
array.to_a == array.flatten(1) ? 1 : depth(array.flatten(1)) + 1
end
It will flatten the array 1 dimension at the time until it can't flatten anymore, while counting the dimensions.
Why is this better?
doesn't require modification to native classes (avoid that if possible)
doesn't use metaprogramming (is_a?, send, respond_to?, etc.)
fairly easy to read
works with hashes as well (notice array.to_a)
actually works (unlike only checking the first branch, and other silly stuff)

How to rewrite this Ruby loop in a cleaner fashion

I'm implementing a loop in Ruby, but it looks ugly and I wonder if there's a neater, more Ruby-like way of writing it:
def get_all_items
items = []; page = 1; page_items = nil
while page_items != [] # Loop runs until no more items are received
items += (page_items = get_page_items(page))
page += 1
end
items
end
Note that the get_page_items method runs a HTTP request to get the items for the page, and there is no way of knowing the number of pages, or the total number of items, or the number of items for any page before actually executing the requests in order until one of them returns an empty item set.
Imagine leafing through a catalog and writing down all the products, without knowing in advance how many pages it has, or how many products there are.
I think that this particular problem is compounded because A) there's no API for getting the total number of items and B) the response from get_page_items is always truthy. Further, it doesn't make sense for you to iteratively call a method that is surely making individual requests to your DB with an arbitrary limit, only to concatenate them together. You should, at the risk of repeating yourself, implement this method to prompt a DB query (i.e. model.all).
Normally when you are defining an empty collection, iterating and transforming a set, and then returning a result, you should be using reduce (a.k.a inject):
array.reduce(0) { |result, item| result + item } # a quick sum
Your need to do a form of streaming in this same process makes this difficult without tapping into Enumerable. I find this to be a good compromise that is much more readable, even if a bit distasteful in fondling this items variable too much:
items = []
begin
items << page_items = get_page_items(page ||= 1)
page += 1
end until page_items.empty?
items.flatten
Here's how I'd have written it. You'll see it's actually more lines, but it's easier to read and more Rubyish.
def get_all_items
items = []
page = 1
page_items = get_page_items page
until page_items.empty? # Loop runs until no more items are received
items += page_items
page += 1
page_items = get_page_items page
end
items
end
You could also implement get_page_items as an Enumerator which would eliminate the awkward page += 1 pattern but that might be overkill.
I don't know that this is any better, but it does have a couple of Ruby-isms in it:
def get_all_items
items = []; n = 0; page = 1
while items.push(*get_page_items(page)).length > n
page += 1
n = items.length
end
end
I would use this solution, which is a good compromise between readability and length:
def get_all_items
[].tap do |items|
page = 0
until (page_items = get_page_items(page)).empty?
items << page_items
page += 1
end
end
end
The short version, just for fun ;-)
i=[]; p=0; loop { i+=get_page_items(p+=1).tap { |r| return i if r.empty? } }
I wanted to write a functional solution which would closely resemble the task you want to achieve.
I'd say that your solution comes down to this:
For all page numbers from 1 on, you get the corresponding list of
items; Take lists while they are not empty, and join them into a
single array.
Sounds ok?
Now let's try to translate this, almost literally, to Ruby:
(1..Float::INFINITY). # For all page numbers from 1 on
map{|page| get_page_items page}. # get the corresponding list of items
take_while{|items| !items.empty?}. # Take lists while they are not empty
inject(&:+) # and join them into a single array.
Unfortunately, the above code won't work right away, as Ruby's map is not lazy, i.e. it would try to evaluate on all members of the infinite range first, before our take_while had the chance to peek at the values.
However, implementing a lazy map is not that hard at all, and it could be useful for other stuff. Here's one straightforward implementation, along with nice examples in the blog post.
module Enumerable
def lazy_map
Enumerator.new do |yielder|
self.each do |value|
yielder.yield(yield value)
end
end
end
end
Along with a mockup of your actual HTTP call, which returns arrays of random length between 0 and 4:
# This simulates actual HTTP call, sometimes returning an empty array
def get_page_items page
(1..rand(5)).to_a
end
Now we have all the needed parts to solve our problem easily:
(1..Float::INFINITY). # For all page numbers from 1 on
lazy_map{|page| get_page_items page}. # get the corresponding list of items
take_while{|items| !items.empty?}. # Take lists while they are not empty
inject(&:+) # and join them into a single array.
#=> [1, 1, 2, 3, 1]
It's a small (and almost entirely cosmetic) tweak, but one option would be to replace while page_items != [] with until page_items.empty?. It's a little more "Ruby-ish," in my opinion, which is what you're asking about.
def get_all_items
items = []; page = 0
items << page_items while (page_items = get_page_items(page += 1))
items
end

Resources