I'm currently learning ruby and I wrote this piece of code :
def multi_gen
s = []
for i in (3..10)
if i%3 == 0 || i%5 == 0
s<<i
end
end
return s
end
puts multi_gen
def rec_sum(num_arr)
if num_arr == []
return 0
else
num_arr.first + rec_sum(num_arr.shift)
end
end
puts rec_sum(multi_gen)
That should return the sum of all 3 and 5 multiples up to 1000.
But I get an error :
myrbfile.rb:17:in `rec_sum': undefined method `first' for 3:Fixnum (NoMethodError)
from villani.rb:17:in `rec_sum'
from villani.rb:21:in `<main>'
But when I re-write it like this :
def multi_gen
s = []
for i in (3..10)
if i%3 == 0 || i%5 == 0
s<<i
end
end
return s
end
puts multi_gen
def rec_sum(num_arr)
if num_arr == []
return 0
else
num_arr[0] + rec_sum(num_arr[1..num_arr.last])
end
end
puts rec_sum(multi_gen)
I don't get the error.
So why is my first rec_sum functions interpretting my Array as a Fixnum in the first case?
The issue is in the recursive call:
rec_sum(num_arr.shift)
Array#shift returns the shifted element, not the remaining array. You should explicitly pass the array as an argument to recursive call:
rec_sum(num_arr[1..-1])
or
rec_sum(num_arr.tap(&:shift))
The latter would [likely] be looking too cumbersome for the beginner, but it’s a very common rubyish approach: Object#tap yields the receiver to the block, returning the receiver. Inside a block (num_arr.tap(&:shift) is a shorthand for num_arr.tap { |a| a.shift } we mutate the array by shifting the element out, and it’s being returned as a result.
mudasobwa already explained why using shift doesn't give the expected result. Apart from that, your code is somehow unidiomatic.
In multi_gen you are creating an empty array and append elements to it using a for loop. You rarely have to populate an array manually. Instead, you can usually use one of Ruby's Array or Enumerable methods to generate the array. select is a very common one – it returns an array containing the elements for which the given block returns true:
(1..1000).select { |i| i % 3 == 0 || i % 5 == 0 }
#=> [3, 5, 6, 9, 10, 12, ...]
In rec_sum, you check if num_arr == []. Although this works, you are creating an empty throw-away array. To determine whether an array is empty, you should call its empty?:
if num_arr.empty?
# ...
end
To get the remaining elements from the array, you use:
num_arr[1..num_arr.last]
which can be abbreviated by passing a negative index to []:
num_arr[1..-1]
There's also drop which might look a little nicer:
num_arr[0] + rec_sum(num_arr[1..-1])
# vs
num_arr.first + rec_sum(num_arr.drop(1))
Another option to get first and remaining elements from an array is Ruby's array decomposition feature (note the *):
def rec_sum(num_arr)
if num_arr.empty?
0
else
first, *remaining = num_arr
first + rec_sum(remaining)
end
end
You could also consider using a guard clause to return from the method early:
def rec_sum(num_arr)
return 0 if num_arr.empty?
first, *remaining = num_arr
first + rec_sum(remaining)
end
Writing recursive methods is great for learning purposed, but Ruby also has a built-in sum method:
multi_gen.sum #=> 234168
or – since you are using an older Ruby version – inject:
multi_gen.inject(0, :+) #=> 234168
Related
I'm working on a method that takes an array of words as a param and returns an array of arrays, where each subarray contains words that are anagrams of each other. The line while sort_array[i][1]==temp do is throwing undefined method '[]' for nil:NilClass (NoMethodError) and I have no idea why.
def combine_anagrams(words)
sort_array = Hash.new
words.each do |w|
sort_array[w] = w.split("").sort
end
sort_array = sort_array.sort_by { |w, s| s }
return_array = []
i = 0
while i<sort_array.length do
temp_array = []
temp = sort_array[i][1]
while sort_array[i][1]==temp do
temp_array += [sort_array[i][0]]
i+=1
end
return_array += [temp_array]
end
return temp_array
end
p combine_anagrams( ['cars', 'for', 'potatoes', 'racs', 'four','scar', 'creams','scream'] )
It looks like this is because you are incrementing your i variable without checking to make sure you're still in bounds of the sort_array. To see the problem, put an puts statement in your code inside the inner most while loop:
while sort_array[i][1]==temp do
temp_array += [sort_array[i][0]]
i+=1
puts "i is #{i} and the length is #{sort_array.length}"
end
and then run your code and you'll see:
i is 1 and the length is 8
i is 2 and the length is 8
i is 3 and the length is 8
i is 4 and the length is 8
i is 5 and the length is 8
i is 6 and the length is 8
i is 7 and the length is 8
i is 8 and the length is 8
example.rb:15:in `combine_anagrams': undefined method `[]' for nil:NilClass (NoMethodError)
You need to make sure on both while loops that you stay within the bounds of your array, for instance:
while i < sort_array.length && sort_array[i][1]==temp do
end
As a side note, your code is currently only going to return the last temp_array, since you're also resetting that at the beginning of your outer while loop. And, if I understand what problem you're trying to solve you might want to look at group_by available on Array thanks to the Enumerable module:
words = ['cars', 'for', 'potatoes', 'racs', 'four','scar', 'creams','scream']
words.group_by { |word| word.chars.sort }.values
# => [["cars", "racs", "scar"], ["for"], ["potatoes"], ["four"], ["creams", "scream"]]
If I use an each do loop to fill an array, it will leave the array as it is (in this case it will be a nil array of size 4)
array = Array.new(4)
array.each do |i|
i = 5
end
I understand that I can initialize an array with my desired value using array = Array.new(4) {desired value} but there are situations in which I'm choosing between different values and I am trying to understand how the each do loop work exactly.
The current way I'm doing it is the following which fills in the array with my desired value
array = Array.new(4)
array.each_with_index do |val, i|
array[i] = 5
end
Solution
You need :
array = Array.new(4) do |i|
5
# or some logic depending on i (the index between 0 and 3)
end
Your code
array = Array.new(4)
array is now an Array with 4 elements (nil each time).
array.each iterates over those 4 elements (still nil), and sets i as block-local variable equal to nil.
Inside this block, you override i with 5, but you don't do anything with it. During the next iteration, i is set back to nil, and to 5, and so on...
You don't change the original array, you only change local variables that have been set equal to the array elements.
The difference is that
i = 5
is an assignment. It assigns the value 5 to the variable i.
In Ruby, assignments only affect the local scope, they don't change the variable in the caller's scope:
def change(i)
i = 5 # <- doesn't work as you might expect
end
x = nil
change(x)
x #=> nil
It is therefore impossible to replace an array element with another object by assigning to a variable.
On the other hand,
array[i] = 5
is not an assignment, but a disguised method invocation. It's equivalent to:
array.[]=(i, 5)
or
array.public_send(:[]=, i, 5)
It asks the array to set the element at index i to 5.
I am trying to write a method that takes an array and returns trueif there is an element that occurs three times in a row or false if it doesn't. I can't think of the syntax. Would you use count? See the example below.
def got_three?(array)
end
got_three?([1,2,2,3,4,4,4,5,6]) would return true as 4 shows up three times in a row.
Toying with the new Ruby 2.3.0 method chunk_while:
def got_three?(array)
array.chunk_while(&:==).any?{|g| g.size >= 3}
end
With Enumerable#chunk:
def got_three?(xs)
xs.chunk(&:itself).any? { |y, ys| ys.size >= 3 }
end
Not so smart but a naive one (using a instead of array since it is long):
a.each_index.any?{|i| a[i] == a[i + 1] and a[i + 1] == a[i + 2]}
I assume you don't have any nil in the array.
An alternative which may be more performant (as per #sawa's comments)...
def got_three?(array)
(0..(array.count-2)).any?{|i|array[i] == array[1+1] && array[i] == array[i+2]}
end
Look, ma, no indices!
def ducks_in_a_row?(arr, n)
cnt = 0
last = arr.first
arr.each do |d|
if d==last
cnt += 1
return true if cnt==n
else
last = d
cnt = 1
end
end
false
end
ducks_in_a_row?([1,2,3,4,5,6,6,7,7,7], 3)
#=> true
def got_three?(array)
array.each_cons(3).map{|g|g.uniq.length == 1}.any?
end
or as #wandmaker suggests...
def got_three?(array)
array.each_cons(3).any?{|g|g.uniq.length == 1}
end
Here is my take on this problem - I tried to make it generic, hopefully efficient so that the loop terminates as soon as n-consecutive elements are found.
def got_consecutive?(array, n = 3)
case array.size
when 0...n
return false
when n
return array.uniq.size == n
else
array[n..-1].each_with_object(array[0...n]) do |i, t|
(t.uniq.size == 1 ? (break t) : (t << i).shift)
end.uniq.size == 1
end
end
p got_consecutive?([])
#=> false
p got_consecutive?([1,2])
#=> false
p got_consecutive?([1,2,2,3,2,3,3,3], 3)
#=> true
p got_consecutive?([1,2,2,3,2,3,3,3], 4)
#=> false
p got_consecutive?([1,2,2,3,2,3,3,3,3,3,4,4,4,4], 5)
#=> true
The code takes care of border cases first such as when array did not have n elements in which case answer is obviously false, and another one being when the array had only n elements - in which case just a uniqueness check would suffice.
For cases where array size is greater than n, the code uses Enumerable#each_with_object with the initial object being an array of n elements from the array - this array is used also as temporary work area to track n consecutive elements and perform a check whether all those elements are same or not.
I have this code here
string.split(/(\w{1,}=)/).each_slice(1).map { |i| items << i }
items.map! do |i|
i = i << str if i.to_s =~ /\w{1,}=/
end
puts items*''
And I want to modify certain items in the array based on regex, then return the full array with the modified items in it. This only returns the modified items. How do I achieve what I'm looking for?
EDIT: Ok, so say I'm trying to split a link using this regex:
page.php?site=blah&id=1
The link is split and added to the array which now contains
page.php?
site=
blah&
id=
1
What I want to do is append some value to the end of the elements ending with a =. This way, when I return the modified array as a string it would output like this:
page.php?site=(newval)&id=(newval)
You have several undefined variables in your example, which is very sloppy.
each_slice(1) is equivalent to each(), so it's not clear why you are using each_slice(1). In any case, both each() and map() step through the items in an Array one by one, but each() returns the original Array unchanged. On the other hand, you use map() when you want to create a new Array that contains changes to the items.
In the regex /\w{1,}/, there is a shortcut for the quantifier {1, }, and it's: +, so most people would write the regex as /\w+/, where + means 1 or more.
I want to modify certain items in the array based on regex, then
return the full array with the modified items in it.
Here is an example:
results = [1, 2, 3].map do |num|
if num == 2
num + 4
else
num - 1
end
end
p results
--output:--
[0, 6, 2]
Your current attempt with map() doesn't return anything if the conditional fails. Note how the example above returns something both when the condition fails AND when the condition succeeds. map() replaces an item with whatever is returned for that item.
Now look at this example:
results = [1, 2, 3].map do |num|
if num == 2
num + 4
end
end
p results
--output:--
[nil, 6, nil]
If you don't return something for an item, then map() will use nil for that item. In the example, if the condition num == 2 is true then num+4 is returned--but if num == 2 is false, nothing is returned.
Edit:
words = %w[
page.php?
site=
blah&
id=
1
] #=> words = ["page.php?", "site=", "blah&", "id=", "1"]
suffix = 'hello'
results = words.map do |word|
if word.end_with?('=')
"#{word}#{suffix}"
else
word
end
end
p results
--output:--
["page.php?", "site=hello", "blah&", "id=hello", "1"]
Instead of parsing a URL with a regex, have you considered using the addressable gem?
require 'addressable/uri'
uri = Addressable::URI.parse('page.php?site=blah&id=1&bar')
uri.query_values = uri.query_values.map do |k, v|
[k, v.is_a?(String) ? v << 'foo' : v]
end
puts uri.to_s # => page.php?site=blahfoo&id=1foo&bar
This won't handle very complex query parameters (it will just pass them through).
You can use respond_to? :sub! and v.sub! /$/, 'foo' instead of checking types if that makes you uneasy. (I wouldn't use :<< or :concat because those are valid methods for Arrays.)
I am trying to find the duplicate values in an array of strings between 1 to 1000000.
However, with the code I have, I get the output as all the entries that are doubled.
So for instance, if I have [1,2,3,4,3,4], it gives me the output of 3 4 3 4 instead of 3 4.
Here is my code:
array = [gets]
if array.uniq.length == array.length
puts "array does not contain duplicates"
else
puts "array does contain duplicates"
print array.select{ |x| array.count(x) > 1}
end
Also, every time I test my code, I have to define the array as array = [1,2,3,4,5,3,5]. The puts works but it does not print when I use array [gets].
Can someone help me how to fix these two problems?
How I wish we had a built-in method Array#difference:
class Array
def difference(other)
h = other.tally
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
though #user123's answer is more straightforward. (Array#difference is probably the more efficient of the two, as it avoids the repeated invocations of count.) See my answer here for a description of the method and links to its use.
In a nutshell, it differs from Array#- as illustrated in the following example:
a = [1,2,3,4,3,2,4,2]
b = [2,3,4,4,4]
a - b #=> [1]
a.difference b #=> [1, 3, 2, 2]
For the present problem, if:
arr = [1,2,3,4,3,4]
the duplicate elements are given by:
arr.difference(arr.uniq).uniq
#=> [3, 4]
For your first problem, you need to uniq function like
array.select{ |x| array.count(x) > 1}.uniq
For your second problem, when you receive a value using array = [gets] it would receive your entire sequence of array numbers as a single string, so everything would be stored in a[0] like ["1, 2 3 4\n"].
puts "Enter array"
array = gets.chomp.split(",").map(&:to_i)
if array.uniq.length == array.length
puts "array does not contain duplicates"
else
puts "array does contain duplicates"
print array.select{ |x| array.count(x) > 1}.uniq
end
copy this code in ruby file and try to run using
ruby file_name.rb
Coming to your 'gets' problem,
When you are doing a gets, your are basically getting a string as an input but not an array.
2.2.0 :001 > array = [gets]
1,2,1,4,1,2,3
=> ["1,2,1,4,1,2,3\n"]
See the above example, how the ruby interpreter took all your elements as a single string and put it in an array as a single array element. So you need to explicitly convert the input to an array with comma as a delimiter. The below will address both your questions.
array = gets.chomp
array = array.split(',').map(&:to_i)
if array.uniq.length == array.length
puts "array does not contain duplicates"
else
puts "array does contain duplicates"
print array.select{ |x| array.count(x) > 1}.uniq!
end