Find various ways of allocating fruit to people - ruby-on-rails

# Example 1
People = ["Terry", "Merry"]
Fruit = ["Apple","Grape","Peach"]
# Possible solutions:
[
{"Terry"=>"Apple","Merry"=>"Grape"},
{"Terry"=>"Apple","Merry"=>"Peach"},
{"Terry"=>"Grape","Merry"=>"Apple"},
{"Terry"=>"Grape","Merry"=>"Peach"},
{"Terry"=>"Peach","Merry"=>"Apple"},
{"Terry"=>"Peach","Merry"=>"Grape"},
]
# Example 2
People = ["Terry", "Merry", "Perry"]
Fruit = ["Apple","Grape"]
# Possible solutions:
[
{"Terry"=>"Apple","Merry"=>"Grape","Perry"=>nil},
{"Terry"=>"Apple","Merry"=>nil,"Perry"=>"Grape"},
{"Terry"=>"Grape","Merry"=>"Apple","Perry"=>nil},
{"Terry"=>"Grape","Merry"=>nil,"Perry"=>"Apple"},
{"Terry"=>nil,"Merry"=>"Apple","Perry"=>"Grape"},
{"Terry"=>nil,"Merry"=>"Grape","Perry"=>"Apple"},
]
Stuck trying to solve this recursively (necessary for this exercise, though let me know if you don't think recursion is possible).
I feel like basically I start by assigning a random person a fruit, and then add that to all possible solutions that arise from the smaller subset of assigning remaining people remaining fruit.
E.g., for Example 1, I assign Terry an Apple, and then aggregate that with the remaining possible options of what Merry can get (either Grape or Peach).
Then just repeat changing up the fruit assigned to the first random person (e.g., with Terry getting Grape then Peach, in Example 1).
I feel like this sounds so straightforward but I'm struggling.

It can be done recursively as follows.
def hmmm(people, fruit)
adj_fruit = fruit + [nil]*([people.size-fruit.size, 0].max)
recurse(adj_fruit).map { |a| people.zip(a).to_h }
end
def recurse(fruit_left, fruit_selected = [])
return [fruit_selected + fruit_left] if fruit_left.size == 1
fruit_left.each_with_object([]) do |f,a|
recurse(fruit_left - [f], fruit_selected + [f]).each { |e| a << e }
end
end
hmmm(["Terry", "Merry"], ["Apple", "Grape", "Peach"])
#=> [{"Terry"=>"Apple", "Merry"=>"Grape"}, {"Terry"=>"Apple", "Merry"=>"Peach"},
# {"Terry"=>"Grape", "Merry"=>"Apple"}, {"Terry"=>"Grape", "Merry"=>"Peach"},
# {"Terry"=>"Peach", "Merry"=>"Apple"}, {"Terry"=>"Peach", "Merry"=>"Grape"}]
Here adj_fruit #=> ["Apple", "Grape", "Peach"]
hmmm(["Terry", "Merry", "Perry"], ["Apple", "Grape"])
#=> [{"Terry"=>"Apple", "Merry"=>"Grape", "Perry"=>nil},
# {"Terry"=>"Apple", "Merry"=>nil, "Perry"=>"Grape"},
# {"Terry"=>"Grape", "Merry"=>"Apple", "Perry"=>nil},
# {"Terry"=>"Grape", "Merry"=>nil, "Perry"=>"Apple"},
# {"Terry"=>nil, "Merry"=>"Apple", "Perry"=>"Grape"},
# {"Terry"=>nil, "Merry"=>"Grape", "Perry"=>"Apple"}]
Here adj_fruit #=> ["Apple", "Grape", nil].
We can see map's receiver in hmmm by removing .map { |a| people.zip(a).to_h } from its last line.
def hmmm(people, fruit)
adj_fruit = fruit + [nil]*([people.size-fruit.size, 0].max)
recurse(adj_fruit)
end
hmmm(["Terry", "Merry"], ["Apple","Grape","Peach"])
#=> [["Apple", "Grape", "Peach"], ["Apple", "Peach", "Grape"],
# ["Grape", "Apple", "Peach"], ["Grape", "Peach", "Apple"],
# ["Peach", "Apple", "Grape"], ["Peach", "Grape", "Apple"]]
A more conventional solution, such as the one following, would not employ recursion.
def hmmm(people, fruit)
(fruit + [nil]*[people.size - fruit.size, 0].max).
permutation(people.size).
map { |a| people.zip(a).to_h }
end
This produces the same return values as those shown above for the recursive solution.
See Array#permutation and Enumerable#zip.

If len(people) <= len(fruit), then you can use
for pieces in itertools.permutations(fruit, len(people)):
assign the pieces of fruit to the people in order
If len(people) > len(fruit), then use
for eaters in itertools.permutations(people, len(fruit))
assign the eaters to the fruit in order, and the others get nothing
I don't know how to combine the two separate cases into a single case
I now see that this was supposed to be solve recursively. Misread the original.
Let's look the possibilities for
assignment(people, fruit):
If len(people) == 0, then you're done, with the empty solution. (Not to be confused with no solution.)
If len(fruit) == 0, then no one gets any fruit. Again, this is an actual solution.
If len(people) <= len(fruit), then the first person gets some piece of fruit, appended onto all possible results of the remainder of the people getting the remainder of the fruit.
If len(people) > len(fruit), then either the first person does or doesn't get a piece of fruit, and recursively the rest of the people get whatever's left.
It's left as an exercise to you how to code this.

For anyone's future reference, this was my answer using recursion.
NOTE that "nil" overcounts; since "nil" is treated as a unique entry, the code reads {"Terry"=>"apple","Merry"=>"nil","Perry"=>"nil"} and {"Terry"=>"apple","Perry"=>"nil","Merry"=>"nil"} as 2 distinct solutions. I did not investigate further because this isn't super realistic for the exercise that this is a part of.
I also didn't investigate further for same reason, but using string "nil" versus nil yielded different results
def pure5(people, fruit, solution = [])
people_count = people.size
fruit_count = fruit.size
diff = people_count - fruit_count
diff.times { fruit << "nil" } if diff > 0
people.each do |p|
fruit.each do |f|
if people.size == 1
obj = {}
obj[p] = f
solution << obj
else
partial_solution = pure5(people - [p], fruit - [f])
partial_solution.each do |s|
s[p] = f
end
solution = solution + partial_solution
end
end
return solution
end
end

Related

How to sort a hierarchical array of hashes

I'm working with an array like the below:
arr = [{
item: "Subject",
id: "16",
parent_id: ""
},
{
item: "Math",
id: "17",
parent_id: "16"
},
{
item: "Geology",
id: "988",
parent_id: "208"
},
{
item: "Biology",
id: "844",
parent_id: "208"
},
{
item: "Botany",
id: "594",
parent_id: "844"
},
{
item: "Science",
id: "208",
parent_id: "16"
}
]
I'm wanting to sort them so and print them out so that they display like this, grouping them and showing their parentage within the hierarchy as indentations:
Subject
Math
Science
Geology
Biology
Botany
I'm fairly stumped on how to accomplish this. I am ultimately wanting to iterate through the array only once, but I get stuck when I realize that a parent item may come after its child. Any help is greatly appreciated.
Edit: eliminated the duplicate item
To sort an array by an attribute of its elements...
arr = arr.sort_by{ |e| e[:parent_id] }
Then a little recursion to walk the tree...
def display_hierarchy(arr, parent_id="", level = 0)
current_indent = 2 * level
arr.select{ |e| e[:parent_id] == parent_id }.each do |elem|
name = elem[:item]
puts name.rjust(name.length + current_indent)
display_hierarchy(arr, elem[:id], level + 1)
end
end
Putting it all together...
arr = arr.sort_by{ |e| e[:parent_id] }
display_hierarchy(arr)
Note, if you have duplicate parents, you will get duplicate branches (hint: your example array has a duplicate "Science" node).
Also note, sorting the array ahead of time doesn't really matter for the tree, since it is a hierarchy, I just added it to show how to do it. You'd probably just sort each set of children like so...
def display_hierarchy(arr, parent_id="", level = 0)
current_indent = 2 * level
arr.select{ |e| e[:parent_id] == parent_id }.sort_by{ |e| e[:item]}.each do |elem|
name = elem[:item]
puts name.rjust(name.length + current_indent)
display_hierarchy(arr, elem[:id], level + 1)
end
end
And you get this...
Subject
Math
Science
Biology
Botany
Geology
Science
Biology
Botany
Geology
I am ultimately wanting to iterate through the array only once
In a way, you can do that: by transforming the array into a different structure that's more fit for the task. You'd still have to traverse the resulting collection again though.
Say, you could partition the array into groups by their :parent_id:
children = arr.group_by { |subject| subject[:parent_id] }
Then it's your typical recursive walk, where indentation is added on every level:
hierarchical_print = proc do |node, indent = ""|
puts(indent + node[:item]) # base
new_indent = indent + " "
children[node[:id]]&.each do |child| # recursion
hierarchical_print.(child, new_indent)
end
end
Notice the use of &. (the "lonely operator") in there that only calls the method if the callee isn't nil. Because getting a value from a hash with a key that isn't there returns nil.
In your case the root is a node with an empty parent id, so it's readily available from the children hash as well:
hierarchical_print.(children[""].first)
Subject
Math
Science
Geology
Biology
Botany
Science
Geology
Biology
Botany
...well, yes, you do have two Sciences in there.

Fastest way to remove a key from array and make it last

I have to remove the 'Other' Category from the array, which is originally sorted alphabetically, and just make it the last index. I created this little helper but believe there could be a faster way of accomplishing this.
The array is something like this [#<Category id: 17, title: "Books">, #<Category id: 18, title: "Children's Clothing">,
Here is what I've done. It works. Although, I was wonder if theres a more efficient way.
<%
#options = []
#other_option = []
#free_item_options.each do |category|
if category.title.downcase == "other"
#other_option << category
else
#options << category
end
end
#options << #other_option[0]
%>
In cases like this, I usually reach for multi-parameter sorting.
#free_item_options.sort_by do |option|
[
option.title.casecmp?('other') ? 1 : 0,
option.title,
]
end
"Other" category will have 1 and will sort last. Everything else will have 0 and will sort between themselves by ascending title.
Another approach is to just use SQL.
#free_item_options = Category.select("categories.*, (LOWER(title) = 'other') as is_other").order('is_other', :title).to_a
There is Enumerable#partition which is designed to split a collection up in two partitions.
#other_option, #options = #free_item_options.partition { |category| category.title.casecmp?('other') }
#options.concat(#other_options)
If you are certain there is a maximum of one "other" category (which seems to be the case based upon #options << #other_option[0]). You could also use find_index in combination with delete_at and <<. find_index stops iterating upon the first match.
index = #free_item_options.find_index { |category| category.title.casecmp?('other') }
#free_item_options << #free_item_options.delete_at(index) if index
Keep in mind the above does mutate #free_item_options.

Assign each array element attribute with a value from another array respectively

I am trying to assign each person an age value from a list with same size.
class Person
attr_accessor :age
end
a = [person1, person2, person3, person4, person5]
b = [1,2,3,4,5]
How can I do the assignment below using a neat way(without using index i)?
i = 0
a.each do |p|
p.age = b[i]
i += 1
end
If they are guaranteed to be the same length, then you can use zip:
a.zip(b).each do |p, age|
p.age = age
end
As #ardavis pointed out, zip takes a block so you can remove the .each.
I know you asked for a solution without an index, but note that your code can be made neater even with an index. In Ruby, you don't need to define and increment your own index. Instead, you can use with_index like so:
a.each.with_index do |p, i|
p.age = b[i]
end
You can use index (as each Person instance is going to be unique):
a.each { |ai| ai.age = b[a.index(ai)] }
Demonstration
P.S. I would go with the approach introduced by #ardavis, using just zip:
a.zip(b) { |a, b| a.age = b }

ruby looping question

I want to make a loop on a variable that can be altered inside of the loop.
first_var.sort.each do |first_id, first_value|
second_var.sort.each do |second_id, second_value_value|
difference = first_value - second_value
if difference >= 0
second_var.delete(second_id)
else
second_var[second_id] += first_value
if second_var[second_id] == 0
second_var.delete(second_id)
end
first_var.delete(first_id)
end
end
end
The idea behind this code is that I want to use it for calculating how much money a certain user is going to give some other user. Both of the variables contain hashes. The first_var is containing the users that will get money, and the second_var is containing the users that are going to pay. The loop is supposed to "fill up" a user that should get money, and when a user gets full, or a user is out of money, to just take it out of the loop, and continue filling up the rest of the users.
How do I do this, because this doesn't work?
Okay. What it looks like you have is two hashes, hence the "id, value" split.
If you are looping through arrays and you want to use the index of the array, you would want to use Array.each_index.
If you are looping through an Array of objects, and 'id' and 'value' are attributes, you only need to call some arbitrary block variable, not two.
Lets assume these are two hashes, H1 and H2, of equal length, with common keys. You want to do the following: if H1[key]value is > than H2[key]:value, remove key from H2, else, sum H1:value to H2:value and put the result in H2[key].
H1.each_key do |k|
if H1[k] > H2[k] then
H2.delete(k)
else
H2[k] = H2[k]+H1[k]
end
end
Assume you are looping through two arrays, and you want to sort them by value, and then if the value in A1[x] is greater than the value in A2[x], remove A2[x]. Else, sum A1[x] with A2[x].
b = a2.sort
a1.sort.each_index do |k|
if a1[k] > b[k]
b[k] = nil
else
b[k] = a1[k] + b[k]
end
end
a2 = b.compact
Based on the new info: you have a hash for payees and a hash for payers. Lets call them ees and ers just for convenience. The difficult part of this is that as you modify the ers hash, you might confuse the loop. One way to do this--poorly--is as follows.
e_keys = ees.keys
r_keys = ers.keys
e = 0
r = 0
until e == e_keys.length or r == r_keys.length
ees[e_keys[e]] = ees[e_keys[e]] + ers[r_keys[r]]
x = max_value - ees[e_keys[e]]
ers[r_keys[r]] = x >= 0 ? 0 : x.abs
ees[e_keys[e]] = [ees[e_keys[e]], max_value].min
if ers[r_keys[r]] == 0 then r+= 1 end
if ees[e_keys[e]] == max_value then e+=1 end
end
The reason I say that this is not a great solution is that I think there is a more "ruby" way to do this, but I'm not sure what it is. This does avoid any problems that modifying the hash you are iterating through might cause, however.
Do you mean?
some_value = 5
arrarr = [[],[1,2,5],[5,3],[2,5,7],[5,6,2,5]]
arrarr.each do |a|
a.delete(some_value)
end
arrarr now has the value [[], [1, 2], [3], [2, 7], [6, 2]]
I think you can sort of alter a variable inside such a loop but I would highly recommend against it. I'm guessing it's undefined behaviour.
here is what happened when I tried it
a.each do |x|
p x
a = []
end
prints
1
2
3
4
5
and a is [] at the end
while
a.each do |x|
p x
a = []
end
prints nothing
and a is [] at the end
If you can I'd try using
each/map/filter/select.ect. otherwise make a new array and looping through list a normally.
Or loop over numbers from x to y
1.upto(5).each do |n|
do_stuff_with(arr[n])
end
Assuming:
some_var = [1,2,3,4]
delete_if sounds like a viable candidate for this:
some_var.delete_if { |a| a == 1 }
p some_var
=> [2,3,4]

Best way to analyse data using ruby

I would like to analyse data in my database to find out how many times certain words appear.
Ideally I would like a list of the top 20 words used in a particular column.
What would be the easiest way of going about this.
Create an autovivified hash and then loop through the rows populating the hash and incrementing the value each time you get the same key (word). Then sort the hash by value.
A word counter...
I wasn't sure if you were asking how to get rails to work on this or how to count words, but I went ahead and did a column-oriented ruby wordcounter anyway.
(BTW, at first I did try the autovivified hash, what a cool trick.)
# col: a column name or number
# strings: a String, Array of Strings, Array of Array of Strings, etc.
def count(col, *strings)
(#h ||= {})[col = col.to_s] ||= {}
[*strings].flatten.each { |s|
s.split.each { |s|
#h[col][s] ||= 0
#h[col][s] += 1
}
}
end
def formatOneCol a
limit = 2
a.sort { |e1,e2| e2[1]<=>e1[1] }.each { |results|
printf("%9d %s\n", results[1], results[0])
return unless (limit -= 1) > 0
}
end
def formatAllCols
#h.sort.each { |a|
printf("\n%9s\n", "Col " + a[0])
formatOneCol a[1]
}
end
count(1,"how now")
count(1,["how", "now", "brown"])
count(1,[["how", "now"], ["brown", "cow"]])
count(2,["you see", "see you",["how", "now"], ["brown", "cow"]])
count(2,["see", ["see", ["see"]]])
count("A_Name Instead","how now alpha alpha alpha")
formatAllCols
$ ruby count.rb
Col 1
3 how
3 now
Col 2
5 see
2 you
Col A_Name Instead
3 alpha
1 how
$
digitalross answer looks too verbose to me, also, as you tag ruby-on-rails and said you use DB.. i'm assuming you need an activerecord model so i'm giving you a full solution
in your model:
def self.top_strs(column_symbol, top_num)
h = Hash.new(0)
find(:all, :select => column_symbol).each do |obj|
obj.send(column_symbol).split.each do |word|
h[word] += 1
end
end
h.map.sort_by(&:second).reverse[0..top_num]
end
for example, model Comment, column body:
Comment.top_strs(:body, 20)

Resources