Working on an array method - ruby

In my homework, I had to create a digit_sum method for the Integer class. Outcome:
class Integer
def digit_sum
to_s.split("").inject(0) { |sum, n| sum + n.to_i }
end
end
Now I have to create something similar for an Array:
[1, 2, 3, "words"].sum #=> 6
[12, 13, 14].sum_up_digit_sums #=> 12
But I don't know how to work with the Array Class. A simple start should look like that.
class Array
def sum
#Code
end
end
An Array has variables and I don't know how to include those variables into my code. I hope someone can explain how I should work with this.

Implementation of sum_up_digit_sums
class Array
def sum_up_digit_sums
join.chars.map(&:to_i).reduce(:+)
end
end
puts [12, 13, 14].sum_up_digit_sums # => 12
Your sum requirements also satisfied with it.
Explanation for [12, 13, 14]:
.join will create a string from array given: [12, 13, 14] => "121314"
.chars will create an array of characters from this string "121314" => ["1", "2", "1", "3", "1", "4"]
.map(&:to_i) will call to_i on each character ["1", "2", "1", "3", "1", "4"] => [1, 2, 1, 3, 1, 4](on this point, if you had any word characters, they'll become zeros)
Then simply sum everything with .reduce(:+)(reduce is an alias of inject)

Let's start with the second one, a Array#sum method. This is a commonly desired method and as such a practical homework question.
First, take a look at your existing implementation of Integer#digit_sum:
# Here you're taking a number, turning it into a string, and splitting it
# on an empty string, resulting in an array of characters (same effect as
# `String#chars`)
to_s.split("").
# The result of this? An array, to which you're then sending `inject` to
# calculate the sum. As you see you've already written a rudimentary
# `Array#sum` (for ints) right here.
inject(0) { |sum, n| sum + n.to_i }
Simply move that to an instance method of Array
class Array
# whereas in your other method you send `inject` to an array you created,
# here you're sending it to *this* array instance `self`, which is the
# implicit target of instance methods. `self.inject` would be equivalent.
def sum
inject(0) {|sum, n| sum + n.to_i }
end
end
You could then use this to refactor your existing Integer#digit_sum
class Integer
def digit_sum
to_s.split("").sum
end
end
Now take what you learned here to write the 2nd method.
class Array
# There are plenty of ways to accomplish the task here, but for the sake
# of pedagogy let's use the method you already wrote yourself.
# This (rather fragile) method would assume you have an array of objects
# that respond to `digit_sum`
def sum_up_digit_sums
# Exactly like plain `sum` but now we're using the other method you wrote,
# `digit_sum` to determine the value instead of `to_i`
inject(0) {|sum, n| sum + n.digit_sum }
end
end

Related

Reassign entire array to the same reference

I've searched extensively but sadly couldn't find a solution to this surely often-asked question.
In Perl I can reassign an entire array within a function and have my changes reflected outside the function:
#!/usr/bin/perl -w
use v5.20;
use Data::Dumper;
sub foo {
my ($ref) = #_;
#$ref = (3, 4, 5);
}
my $ref = [1, 2];
foo($ref);
say Dumper $ref; # prints [3, 4, 5]
Now I'm trying to learn Ruby and have written a function where I'd like to change an array items in-place by filtering out elements matching a condition and returning the removed items:
def filterItems(items)
removed, items = items.partition { ... }
After running the function, items returns to its state before calling the function. How should I approach this please?
I'd like to change an array items in-place by filtering out elements matching a condition and returning the removed items [...] How should I approach this please?
You could replace the array content within your method:
def filter_items(items)
removed, kept = items.partition { |i| i.odd? }
items.replace(kept)
removed
end
ary = [1, 2, 3, 4, 5]
filter_items(ary)
#=> [1, 3, 5]
ary
#=> [2, 4]
I would search for pass by value/reference in ruby. Here is one I found first https://mixandgo.com/learn/is-ruby-pass-by-reference-or-pass-by-value.
You pass reference value of items to the function, not the reference to items. Variable items is defined out of method scope and always refers to same value, unless you reassign it in the variable scope.
Also filterItems is not ruby style, see https://rubystyle.guide/
TL;DR
To access or modify an outer variable within a block, declare the variable outside the block. To access a variable outside of a method, store it in an instance or class variable. There's a lot more to it than that, but this covers the use case in your original post.
Explanation and Examples
In Ruby, you have scope gates and closures. In particular, methods and blocks represent scope gates, but there are certainly ways (both routine and meta) for accessing variables outside of your local scope.
In a class, this is usually handled by instance variables. So, as a simple example of String#parition (because it's easier to explain than Enumerable#partition on an Array):
def filter items, separator
head, sep, tail = items.partition separator
#items = tail
end
filter "foobarbaz", "bar"
#=> "baz"
#items
#=> "baz"
Inside a class or within irb, this will modify whatever's passed and then assign it to the instance variable outside the method.
Partitioning Arrays Instead of Strings
If you really don't want to pass things as arguments, or if #items should be an Array, then you can certainly do that too. However, Arrays behave differently, so I'm not sure what you really expect Array#partition (which is inherited from Enumerable) to yield. This works, using Enumerable#slice_after:
class Filter
def initialize
#items = []
end
def filter_array items, separator
#items = [3,4,5].slice_after { |i| i == separator }.to_a.pop
end
end
f = Filter.new
f.filter_array [3, 4, 5], 4
#=> [5]
Look into the Array class for any method which mutates the object, for example all the method with a bang or methods that insert elements.
Here is an Array#push:
ary = [1,2,3,4,5]
def foo(ary)
ary.push *[6, 7]
end
foo(ary)
ary
#=> [1, 2, 3, 4, 5, 6, 7]
Here is an Array#insert:
ary = [1,2,3,4,5]
def baz(ary)
ary.insert(2, 10, 20)
end
baz(ary)
ary
#=> [1, 2, 10, 20, 3, 4, 5]
Here is an example with a bang Array#reject!:
ary = [1,2,3,4,5]
def zoo(ary)
ary.reject!(&:even?)
end
zoo(ary)
ary
#=> [1, 3, 5]
Another with a bang Array#map!:
ary = [1,2,3,4,5]
def bar(ary)
ary.map! { |e| e**2 }
end
bar(ary)
ary
#=> [1, 4, 9, 16, 25]

How to get reference to object you're calling in method?

I am trying to create a method for objects that I create. In this case it's an extension of the Array class. The method below, my_uniq, works. When I call puts [1, 1, 2, 6, 8, 8, 9].my_uniq.to_s it outputs to [1, 2, 6, 8].
In a similar manner, in median, I'm trying to get the reference of the object itself and then manipulate that data. So far I can only think of using the map function to assign a variable arr as an array to manipulate that data from.
Is there any method that you can call that gets the reference to what you're trying to manipulate? Example pseudo-code that I could replace arr = map {|n| n} with something like: arr = self.
class Array
def my_uniq
hash = {}
each do |num|
hash[num] = 0;
end
hash.keys
end
end
class Array
def median
arr = map {|n| n}
puts arr.to_s
end
end
Thanks in advance!
dup
class Array
def new_self
dup
end
def plus_one
arr = dup
arr.map! { |i| i + 1 }
end
def plus_one!
arr = self
arr.map! { |i| i + 1 }
end
end
array = [1, 3, 5]
array.new_self # => [1, 3, 5]
array.plus_one # => [2, 4, 6]
array # => [1, 3, 5]
array.plus_one! # => [2, 4, 6]
array # => [2, 4, 6]
dup makes a copy of the object, making it a safer choice if you need to manipulate data without mutating the original object. You could use self i.e. arr = self, but anything you do that changes arr will also change the value of self. It's a good idea to just use dup.
If you do want to manipulate and change the original object, then you can use self instead of dup, but you should make it a "bang" ! method. It is a convention in ruby to put a bang ! at the end of a method name if it mutates the receiving object. This is particularly important if other developers might use your code. Most Ruby developers would be very surprised if a non-bang method mutated the receiving object.
class Array
def median
arr = self
puts arr.to_s
end
end
[1,2,3].median # => [1,2,3]

Understanding flattening an array in Ruby

I'm a confused with what .each_with_object does to an extent.
For example:
("a".."c").each_with_object("") {|i,str| str << i} # => "abc"
Also:
(1..3).each_with_object(0) {|i,sum| sum += i} #=> 0
(since integers are immutable).
After reading the example in the Ruby documentation, I'm
confused as to what the parameter inside object() actually does.
Regarding the flattify code below: I was confused with the usage of *; and why is the else statement just element? What is element intended to do?
def flattify(array)
array.each_with_object([]) do |element, flattened|
flattened.push *(element.is_a?(Array) ? flattify(element) : element)
end
end
confused with what #each_with_object does
You may have a better time understanding #each_with_object if you look at #inject first. #each_with_object is similar to #inject. Examples from http://blog.krishnaswamy.in/blog/2012/02/04/ruby-inject-vs-each-with-object/, included below:
#using inject
[[:tom,25],[:jerry,15]].inject({}) do |result, name_and_age|
name, age = name_and_age
result[name] = age
result
end
=> {:tom=>25, :jerry=>15}
#using each_with_object
[[:tom,25],[:jerry,15]].each_with_object({}) do |name_and_age, result|
name, age = name_and_age
result[name] = age
end
=> {:tom=>25, :jerry=>15}
See this Gist for example tests: https://gist.github.com/cupakromer/3371003
In depth article: http://engineering-blog.alphasights.com/tap-inject-and-each_with_object/
UPDATE
would #inject as opposed to #each_with_object work in this flattening code?
Yes, see below. I've illustratively refactored your flattening code to use #inject. Additionally, I removed the dependency on the "splat" operator (http://ruby-doc.org/core-2.3.1/doc/syntax/calling_methods_rdoc.html#label-Array+to+Arguments+Conversion)
# Flattens nested array; uses `Enumerable#inject`
# #see http://ruby-doc.org/core-2.3.1/Enumerable.html#method-i-inject
# #param arg [Array] contains objects of any type including any amount of nested arrays.
# #raise [StandardError] if arg is not Array class
# #return [Array] flat array comprised of elements from arg.
# #example
# flattify([nil, [1, [:two, [3.0], {4=>5}], "6"]]) #=> [nil, 1, :two, 3.0, {4=>5}, "6"]
def flattify(arg)
raise "arg is not Array" unless arg.is_a?(Array)
# variable ret_var used here to illustrate method's return in verbose fasion
# supplied [] used as initial value for flattened_array
ret_var = arg.inject([]) do |flattened_array, element|
# check if element class is Array
if element.is_a?(Array)
# Array#concat because flattify returns Array
# same as: a = a + b
# same as: a += b
flattened_array.concat(
# recursively call flattify with element as arg
# element is an Array
flattify(element)
)
else
# Array#push because element is not an Array
# same as: a << b
flattened_array.push(element)
end
# used in next iteration as value for first arg above in: "|flattened_array, element|"
# OR returned on last iteration, becoming value of ret_var above
flattened_array
end
# explicit return for illustrative purposes
return ret_var
end
UPDATE 2
may [I] ask why the splat operator is used here? I am still a bit
confused on that. It seems the code is [looping] each time and pushing
it in the flattened array, whats the point of the *?
flattened.push *(element.is_a?(Array) ? flattify(element) : element)
The above block is a "ternary operation" (see: https://en.wikipedia.org/wiki/Ternary_operation), which is explained here: https://stackoverflow.com/a/4252945/1076207 like so:
if_this_is_a_true_value ? then_the_result_is_this : else_it_is_this
Compare the flattify examples with each other:
# each_with_object
flattened.push *(flattify(element))
# inject
flattened_array.concat(flattify(element))
Here the * splat operator (see: https://stackoverflow.com/search?q=%5Bruby%5D+splat) is doing the same thing as Array#concat. However, the splat allows flattened.push to accept either of the two possible types the ternary operation returns: 1) an Array; or 2) whatever element is. For illustration, notice how the splat operator prevents nesting:
# each_with_object with splat
flattened = [1,2,3]
flattened.push *([4,5,6]) # => [1, 2, 3, 4, 5, 6]
flattened.push *(7) # => [1, 2, 3, 4, 5, 6, 7]
# each_with_object without splat
flattened = [1,2,3]
flattened.push ([4,5,6]) # => [1, 2, 3, [4, 5, 6]]
flattened.push (7) # => [1, 2, 3, [4, 5, 6], 7]
Conversely, Array#concat will only accept an array. If the same ternary operation was used and returned an element, it would cause an error:
# inject
flattened_array = [1,2,3]
flattened_array.concat([4,5,6]) # => [1, 2, 3, 4, 5, 6]
flattened_array.concat(7) # => TypeError: no implicit conversion of Fixnum into Array
In summary, both versions of flattify achieve the same result. However, #each_with_object uses #push, a ternary operation and a splat operator; while #inject uses an if/else statement, #concat and #push.
UPDATE 3
When we did each with object([]), the last parameter became an
array.
Yes. It becomes an array and continues to be that same array throughout the iterations until it's passed back.
So with inject the first one becomes an array?
Yes. The first one becomes the passed in array, but only for the first iteration, then it's replaced by the result of the code block for each subsequent iteration.
how does our code know if element is defined as an int and
flattened_Array is an array?
element.is_a?(Array) # => true or false
When element is Array class this method returns true, and if not returns false. false means that it's anything but an array including int.
For more info, see: http://ruby-doc.org/core-2.3.1/Object.html#method-i-is_a-3F
The parameter you pass to object() acts as accumulator for intermediate values between iterations. On entry to each iteration it is passed as flattened argument.
* is a splat operator. It converts an array to a list of arguments being passed to the push method.
Here element will take value of each array element consequently.
All this piece of code does is just recursively flat all nested arrays inside initial array.
But Ruby has built in flatten method which does the same thing.
For example
ar = [1, 2, [3, 4, [5, 6]]]
ar.flatten
#=> [1, 2, 3, 4, 5, 6]
Just to compare with your flattify
flattify ar
#=> [1, 2, 3, 4, 5, 6]
# flattened.push *(element.is_a?(Array) ? flattify(element) : element)
# flattened is the array ...object([])
# element.is_a?(Array) ...is the element in this iteration an array?
# if true flattify(element) again... meaning recursively apply method again
# if false push element onto the object([]) aka flattened
# the () around the ternary allow the recursion to complete
# the * operator can then pass the elements "passing the array condition"
# cont'd... onto flattened.push(4, 5, 6) as list of args instead of an array
# array object with range of string elements
("a".."c").each_with_object([]) do |element, the_object|
p the_object.class # returns Array
p element.class # returns String
end
# hash object with range of fixnum elements
(1..3).each_with_object({}) do |element, the_object|
p the_object.class # returns Hash
p element.class # returns Fixnum
end

How to display dynamic case statement in Ruby

How would I write a case statement that would list all elements in an array, allow the user to pick one, and do processing on that element?
I have an array:
array = [ 'a', 'b', 'c', 'd' ]
Ultimately I'd like it to behave like this:
Choices:
1) a
2) b
3) c
4) d
Choice =>
After the user picks 3, I would then do processing based off the choice of the user. I can do it in bash pretty easily.
Ruby has no built-in menu stuff like shell scripting languages do. When doing menus, I favor constructing a hash of possible options and operating on that:
def array_to_menu_hash arr
Hash[arr.each_with_index.map { |e, i| [i+1, e] }]
end
def print_menu menu_hash
puts 'Choices:'
menu_hash.each { |k,v| puts "#{k}) #{v}" }
puts
end
def get_user_menu_choice menu_hash
print 'Choice => '
number = STDIN.gets.strip.to_i
menu_hash.fetch(number, nil)
end
def show_menu menu_hash
print_menu menu_hash
get_user_menu_choice menu_hash
end
def user_menu_choice choice_array
until choice = show_menu(array_to_menu_hash(choice_array)); end
choice
end
array = %w{a b c d}
choice = user_menu_choice(array)
puts "User choice was #{choice}"
The magic happens in array_to_menu_hash:
The [] method of Hash converts an array with the form [ [1, 2], [3, 4] ] to a hash {1 => 2, 3 => 4}. To get this array, we first call each_with_index on the original menu choice array. This returns an Enumerator that emits [element, index_number] when iterated. There are two problems with this Enumerator: the first is that Hash[] needs an array, not an Enumerator. The second is that the arrays emitted by the Enumerator have the elements in the wrong order (we need [index_number, element]). Both of these problems are solved with #map. This converts the Enumerator from each_with_index into an array of arrays, and the block given to it allows us to alter the result. In this case, we are adding one to the zero-based index and reversing the order of the sub-arrays.

How to act differently on the first iteration in a Ruby loop?

I always use a counter to check for the first item (i==0) in a loop:
i = 0
my_array.each do |item|
if i==0
# do something with the first item
end
# common stuff
i += 1
end
Is there a more elegant way to do this (perhaps a method)?
You can do this:
my_array.each_with_index do |item, index|
if index == 0
# do something with the first item
end
# common stuff
end
Try it on ideone.
Using each_with_index, as others have described, would work fine, but for the sake of variety here is another approach.
If you want to do something specific for the first element only and something general for all elements including the first, you could do:
# do something with my_array[0] or my_array.first
my_array.each do |e|
# do the same general thing to all elements
end
But if you want to not do the general thing with the first element you could do:
# do something with my_array[0] or my_array.first
my_array.drop(1).each do |e|
# do the same general thing to all elements except the first
end
Arrays have an "each_with_index" method which is handy for this situation:
my_array.each_with_index do |item, i|
item.do_something if i==0
#common stuff
end
What fits best is depending on the situation.
Another option (if you know your array is not empty):
# treat the first element (my_array.first)
my_array.each do | item |
# do the common_stuff
end
each_with_index from Enumerable (Enumerable is already mixed in with Array, so you can call it on an array without any trouble):
irb(main):001:0> nums = (1..10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
irb(main):003:0> nums.each_with_index do |num, idx|
irb(main):004:1* if idx == 0
irb(main):005:2> puts "At index #{idx}, the number is #{num}."
irb(main):006:2> end
irb(main):007:1> end
At index 0, the number is 1.
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
If you don't need the array afterwards:
ar = %w(reversed hello world)
puts ar.shift.upcase
ar.each{|item| puts item.reverse}
#=>REVERSED
#=>olleh
#=>dlrow
Ruby's Enumerable#inject provides an argument that can be used for doing something differently on the first iteration of a loop:
> l=[1,2,3,4]
=> [1, 2, 3, 4]
> l.inject(0) {|sum, elem| sum+elem}
=> 10
The argument is not strictly necessary for common things like sums and products:
> l.inject {|sum, elem| sum+elem}
=> 10
But when you want to do something different on the first iteration, that argument might be useful to you:
> puts fruits.inject("I like to eat: ") {|acc, elem| acc << elem << " "}
I like to eat: apples pears peaches plums oranges
=> nil
Here's a solution that doesn't need to be in an immediately enclosing loop and avoids the redundancy of specifying a status placeholder more than once unless you really need to.
do_this if ($first_time_only ||= [true]).shift
Its scope matches the holder: $first_time_only will be globally once; #first_time_only will be once for the instance, and first_time_only will be once for the current scope.
If you want the first several times, etc, you can easily put [1,2,3] if you need to distinguish which of the first iterations you're in, or even something fancy [1, false, 3, 4] if you need something weird.

Resources