I am pretty sure that it can be done in one line using things like map, sum etc. I cannot figure out how exactly, because I just started learning ruby. Could someone help? Thanks
class Something < ApplicationRecord
def function
res = items.count
items.each do |i|
res += i.function
end
res
end
I'm not sure why you need to do it recursively and in one line, but you can try something like this:
edit:
def add(arr)
return 0 if arr.length == 0
# if the arr argument is an empty array, return 0.
arr[0] + add(arr[1..-1])
# add the first element of the array to the result of calling add
# on the array minus the first element.
end
If you just want to sum an array as concisely as possible, all you need to do is [1, 2, 3].sum or [1,2,3,4].reduce(&:+). No recursion needed.
The straightforward oneliner equivalent to yours:
def function
items.count + items.sum(&:function)
end
Demo (testing it alongside your original):
class Something
attr_accessor :items
def initialize(items = [])
self.items = items
end
def function
res = items.count
items.each do |i|
res += i.function
end
res
end
def function2
items.count + items.sum(&:function2)
end
end
root = Something.new([
Something.new,
Something.new([
Something.new,
Something.new([
Something.new,
Something.new([
Something.new
])
])
])
])
puts root.function
puts root.function2
Prints:
7
7
Another way:
def function
items.sum { |i| 1 + i.function }
end
By the way, you count all items except for the root item. Is that intentional?
You could count all including the root with this:
def function
1 + items.sum(&:function)
end
Not in one line but this is how you can do this recursively.
def add_array(arr)
return arr.first if arr.length == 1
return nil if arr.length < 1
arr.pop + add_arr(arr)
end
Related
I was trying to implement a bubble sort method that takes a block and returns the array sorted in ascending order.
For some reason that I can't understand I get the right result when I use { } but I get the error 'no block given' when I use do...end.
Here's the code:
def bubble_sort_by(arr)
return arr if arr.size == 1
swapped = true
while swapped
swapped = false
(0...arr.size - 1).each do |index|
block_result = yield(arr[index], arr[index + 1])
# binding.pry
if block_result >= 1
arr[index], arr[index + 1] = arr[index + 1], arr[index]
swapped = true
# binding.pry
end
end
end
arr
end
p bubble_sort_by(["hi","hello","heys"]) do |left,right|
left.length - right.length
end
#the code returns ["hi", "heys", "hello"] when the block is passed with { }
Any help will be most appreciated.
Precedence matters.
{} has nearly the topmost precedence and is executed before function application (before p() call.)
do end OTOH has nearly the lowest precedence and is executed after function application (after p() call.)
Put parentheses to avoid ambiguity:
p(bubble_sort_by(["hi","hello","heys"]) do |left,right|
left.length - right.length
end)
In your original example, the order of execution was as following:
p(bubble_sort_by(["hi","hello","heys"])) do ... end
Basically, you have been calling p with a parameter and a block.
I am trying to implement a merge sort algorithm. I have the following code:
def merge_sort(array)
if array.length < 2
return array
else
length = array.length
i = array[0..array.length/2-1]
j = array[array.length/2 .. -1]
first = merge_sort(i)
second = merge_sort(j)
sorted_array = []
until first.empty? || second.empty? do
if first[0] >= second[0]
sorted_array << second.shift
else
sorted_array << first.shift
end
end
end
end
I get a NoMethodError for NilClass with it.
From my understanding, the unless block should check for empty array, and stop execution before a Nil class ever occurs.
Why do I get this error?
If array.length < 2 then your merge_sort will return array. Otherwise, merge_sort will return whatever until some_condition do ... end evaluates to. It so happens that until evaluates to nil so your method behaves like this:
def merge_sort(array)
if array.length < 2
return array
else
# Do a bunch of stuff...
return nil
end
end
That means that first and second will be nil most of the time and there's your NoMethodError. Perhaps you want to return sorted_array after your until:
def merge_sort(array)
if array.length < 2
array
else
#...
sorted_array = []
until first.empty? || second.empty? do
#...
end
sorted_array # <------------------- sort of important
end
end
[1,2,3,3] - [1,2,3] produces the empty array []. Is it possible to retain duplicates so it returns [3]?
I am so glad you asked. I would like to see such a method added to the class Array in some future version of Ruby, as I have found many uses for it:
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
A description of the method and links to some of its applications are given here.
By way of example:
a = [1,2,3,4,3,2,4,2]
b = [2,3,4,4,4]
a - b #=> [1]
a.difference b #=> [1,2,3,2]
Ruby v2.7 gave us the method Enumerable#tally, allowing us to replace the first line of the method with
h = other.tally
As far as I know, you can't do this with a built-in operation. Can't see anything in the ruby docs either. Simplest way to do this would be to extend the array class like this:
class Array
def difference(array2)
final_array = []
self.each do |item|
if array2.include?(item)
array2.delete_at(array2.find_index(item))
else
final_array << item
end
end
end
end
For all I know there's a more efficient way to do this, also
EDIT:
As suggested by user2864740 in question comments, using Array#slice! is a much more elegant solution
def arr_sub(a,b)
a = a.dup #if you want to preserve the original array
b.each {|del| a.slice!(a.index(del)) if a.include?(del) }
return a
end
Credit:
My original answer
def arr_sub(a,b)
b = b.each_with_object(Hash.new(0)){ |v,h| h[v] += 1 }
a = a.each_with_object([]) do |v, arr|
arr << v if b[v] < 1
b[v] -= 1
end
end
arr_sub([1,2,3,3],[1,2,3]) # a => [3]
arr_sub([1,2,3,3,4,4,4],[1,2,3,4,4]) # => [3, 4]
arr_sub([4,4,4,5,5,5,5],[4,4,5,5,5,5,6,6]) # => [4]
Would love to refactor this into just one line:
def sum_something
sum = 0
self.each { |a| sum += a }
return sum
end
There must be a way to define 'sum' within the block and I could drop the 'return'.
def sum_something
inject(0, :+)
end
Excuse the newbie question. I'm trying to create a two dimensional array in ruby, and initialise all its values to 1. My code is creating the two dimensional array just fine, but fails to modify any of its values.
Can anyone explain what I'm doing wrong?
def mda(width,height)
#make a two dimensional array
a = Array.new(width)
a.map! { Array.new(height) }
#init all its values to 1
a.each do |row|
row.each do |column|
column = 1
end
end
return a
end
It the line row.each do |column| the variable column is the copy of the value in row. You can't edit its value in such way. You must do:
def mda(width,height)
a = Array.new(width)
a.map! { Array.new(height) }
a.each do |row|
row.map!{1}
end
return a
end
Or better:
def mda(width,height)
a = Array.new(width)
a.map! { Array.new(height) }
a.map do |row|
row.map!{1}
end
end
Or better:
def mda(width,height)
a = Array.new(width){ Array.new(height) }
a.map do |row|
row.map!{1}
end
end
Or better:
def mda(width,height)
Array.new(width) { Array.new(height){1} }
end
each passes into the block parameter the value of each element, not the element itself, so column = 1 doesn't actually modify the array.
You can do this in one step, though - see the API docs for details on the various forms of Array#new. Try a = Array.new(width) {|i| Array.new(height) {|j| 1 } }
you can create it like this?
a=Array.new(width) { Array.new(height,1) }
column in your nested each loop is a copy of the value at that place in the array, not a pointer/reference to it, so when you change its value you're only changing the value of the copy (which ceases to exist outside the block).
If you just want a two-dimensional array populated with 1s something as simple as this will work:
def mda(width,height)
[ [1] * width ] * height
end
Pretty simple.
By the way, if you want to know how to modify the elements of a two-dimensional array as you're iterating over it, here's one way (starting from line 6 in your code):
#init all its values to 1
a.length.times do |i|
a[i].length.times do |j|
a[i][j] = 1
end
end