How to drop the end of an array in Ruby - ruby

Array#drop removes the first n elements of an array. What is a good way to remove the last m elements of an array? Alternately, what is a good way to keep the middle elements of an array (greater than n, less than m)?

This is exactly what Array#pop is for:
x = [1,2,3]
x.pop(2) # => [2,3]
x # => [1]

You can also use Array#slice method, e.g.:
[1,2,3,4,5,6].slice(1..4) # => [2, 3, 4, 5]
or
a = [1,2,3,4,5,6]
a.take 3 # => [1, 2, 3]
a.first 3 # => [1, 2, 3]
a.first a.size - 1 # to get rid of the last one

The most direct opposite of drop (drop the first n elements) would be take, which keeps the first n elements (there's also take_while which is analogous to drop_while).
Slice allows you to return a subset of the array either by specifying a range or an offset and a length. Array#[] behaves the same when passed a range as an argument or when passed 2 numbers

this will get rid of last n elements:
a = [1,2,3,4,5,6]
n = 4
p a[0, (a.size-n)]
#=> [1, 2]
n = 2
p a[0, (a.size-n)]
#=> [1, 2, 3, 4]
regard "middle" elements:
min, max = 2, 5
p a.select {|v| (min..max).include? v }
#=> [2, 3, 4, 5]

I wanted the return value to be the array without the dropped elements. I found a couple solutions here to be okay:
count = 2
[1, 2, 3, 4, 5].slice 0..-(count + 1) # => [1, 2, 3]
[1, 2, 3, 4, 5].tap { |a| a.pop count } # => [1, 2, 3]
But I found another solution to be more readable if the order of the array isn't important (in my case I was deleting files):
count = 2
[1, 2, 3, 4, 5].reverse.drop count # => [3, 2, 1]
You could tack another .reverse on there if you need to preserve order but I think I prefer the tap solution at that point.

You can achieve the same as Array#pop in a non destructive way, and without needing to know the lenght of the array:
a = [1, 2, 3, 4, 5, 6]
b = a[0..-2]
# => [1, 2, 3, 4, 5]
n = 3 # if we want drop the last n elements
c = a[0..-(n+1)]
# => [1, 2, 3]

Array#delete_at() is the simplest way to delete the last element of an array, as so
arr = [1,2,3,4,5,6]
arr.delete_at(-1)
p arr # => [1,2,3,4,5]
For deleting a segment, or segments, of an array use methods in the other answers.

You can also add some methods
class Array
# Using slice
def cut(n)
slice(0..-n-1)
end
# Using pop
def cut2(n)
dup.tap{|x| x.pop(n)}
end
# Using take
def cut3(n)
length - n >=0 ? take(length - n) : []
end
end
[1,2,3,4,5].cut(2)
=> [1, 2, 3]

Related

Ruby Second Smallest Number and index

Trying to use Ruby to find the second smallest number and the index from an input. I have the logic working from a hard coded array but can't seem to get the input from a user to work successfully. Thoughts?
print "Enter a list of numbers: "
nums = [gets]
#nums = [3,1,7,5]
lists = nums.sort
A = lists[1]
B = nums.find_index(A)
print "The second smallest number is: #{A} and the index is #{B}"
Suppose the user entered
str = " 2, -3 , 4, 1\n"
Then
arr = str.split(/ *, */).map(&:to_i)
#=> [2, -3, 4, 1]
The regular expression matches zero or more spaces followed by a comma followed by zero or more spaces.
The second smallest element, together with its index, can be obtained as follows.
n, i = arr.each_with_index.min(2).last
#=> [1, 3]
n #=> 1
i #=> 3
See Enumerable#min.
The steps are as follows.
enum = arr.each_with_index
#=> #<Enumerator: [2, -3, 4, 1]:each_with_index>
We can see the elements that will be generated by this enumerator by converting it to an array.
enum.to_a
#=> [[2, 0], [-3, 1], [4, 2], [1, 3]]
Continuing,
a = enum.min(2)
#=> [[-3, 1], [1, 3]]
min(2) returns the two smallest elements generated by enum. min compares each pair of elements with the method Array#<=>. (See especially the third paragraph of the doc.) For example,
[2, 0] <=> [-3, 1]
#=> 1
[4, 2] <=> [1, 3]
#=> 1
[1, 3] <=> [1, 4]
#=> -1
min would therefore order these pairs as follows.
[2, 0] > [-3, 1]
[4, 2] > [1, 3]
[1, 3] < [1, 4]
I've included the last example (though enum.to_a does not contain a second 1 at index 4) to illustrate that the second element of each two-element array serves as a tie-breaker.
As we want the second-smallest element of arr there is one final step.
n, i = a.last
#=> [1, 3]
n #=> 1
i #=> 3
Note that
n, i = [3, 2, 4, 2].each_with_index.min(2).last
#=> [2, 3]
n #=> 2
If we wanted n to equal 3 in this case we could write
n, i = [3, 2, 4, 2].uniq.each_with_index.min(2).last
#=> [3, 0]
n #=> 3
If the entries were floats or a mix of floats and integers we need only replace to_i with to_f.
str = "6.3, -1, 2.4, 3\n"
arr = str.split(/ *, */).map(&:to_f)
#=> [6.3, -1.0, 2.4, 3.0]
n, i = arr.each_with_index.min(2).last
#=> [2.4, 2]
n #=> 2.4
i #=> 2
Scan Input Strings and Map to Integers
The Kernel#gets method returns a String, so you have to parse and sanitize the user input to convert it to an Array. For example:
nums = gets.scan(/\d+/).map &:to_i
This uses String#scan to parse the input string, and Array#map to feed each element of the resulting array to String#to_i. The return value of this method chain will be an Array, which is then assigned to your nums variable.
Results of Example Data
Given input with inconsistent spacing or numbers of digits like:
1,2, 3, 4, 5, 10, 201
the method chain will nevertheless assign sensible values to nums. For example, the input above yields:
#=> [1, 2, 3, 4, 5, 10, 201]

How to handle negative iterator passed in function?

I am working on a manual rotate function in Ruby. But I ran into issue there are negative offsets passed in some examples. Is it possible to iterate from a negative number up to a specified index(not sure what that index would be)?
def my_rotate(arr, offset=1)
if offset < 1
for i in offset
arr.push(arr.shift)
end
else
for i in 1..offset
arr.push(arr.shift)
end
end
arr
end
Following with your code, you can use Array#pop and Array#unshift (which are the opposites of Array#push and Array#shift):
def my_rotate(array, offset=1)
arr = array.dup
if offset < 1
for i in 1..offset.abs
arr.unshift(arr.pop)
end
else
for i in 1..offset
arr.push(arr.shift)
end
end
arr
end
Notice the change in line 5 for i in 1..offset.abs to be able to loop the array, and the addition of line 2 arr = array.dup to prevent the original array from being mutated.
This is pretty much how Array#rotate does it (in C).
Code
class Array
def my_rotate(n=1)
n %= self.size
self[n..-1].concat(self[0,n])
end
end
Examples
arr = [1,2,3,4]
arr.my_rotate 0 #=> [1,2,3,4]
arr.my_rotate #=> [2, 3, 4, 1]
arr.my_rotate 1 #=> [2, 3, 4, 1]
arr.my_rotate 4 #=> [1, 2, 3, 4]
arr.my_rotate 5 #=> [2, 3, 4, 1]
arr.my_rotate 9 #=> [2, 3, 4, 1]
arr.my_rotate -1 #=> [4, 1, 2, 3]
arr.my_rotate -4 #=> [1, 2, 3, 4]
arr.my_rotate -5 #=> [4, 1, 2, 3]
arr.my_rotate -9 #=> [4, 1, 2, 3]
Explanation
The line
n %= self.size
which Ruby's parser expands to
n = n % self.size
converts n to an integer between 0 and self.size - 1. Moreover, it does so for both positive and negative values of n.
The line
self[n..-1].concat(self[0,n])
appends the first n elements of arr to an array comprised of the last arr.size - n elements of arr. The resulting array is then returned by the method.
If you do not wish to add this method to the class Array you could of course define it def my_rotate(arr, n)....

How to take one element out of an array and put in front?

a = [1,2,3]
b = [2,1,3]
What is the best way to get b from a?
My inelegant solution:
x = 2
y = a - [x]
b = y.unshift(x)
a.unshift a.delete(2)
This appends the recently deleted object (here 2).
Beware that, if the object in question appears more than once in the array, all occurences will be deleted.
In case you want only the first occurrence of an object to be moved, try this:
a = [1,2,3,2]
a.unshift a.delete_at(a.index(2))
# => [2, 1, 3, 2]
a.unshift a.slice!(a.index(2)||0)
# => [2, 1, 3]
If there are multiple instances, only the first instance is moved to the front.
If the element doesn't exist, then a is unchanged.
If you wanted to move elements of an array arbitrarily, you could do something like this:
Code
# Return a copy of the receiver array, with the receiver's element at
# offset i moved before the element at offset j, unless j == self.size,
# in which case the element at offset i is moved to the end of the array.
class Array
def move(i,j)
a = dup
case
when i < 0 || i >= size
raise ArgumentError, "From index is out-of-range"
when j < 0 || j > size
raise ArgumentError, "To index is out-of-range"
when j < i
a.insert(j, a.delete_at(i))
when j == size
a << a.delete_at(i)
when j > i+1
a.insert(j-1, a.delete_at(i))
else
a
end
end
end
With Ruby v2.1, you could optionally replace class Array with refine Array. (Module#refine was introduced experimentally in v2.0, but was changed substantially in v2.1.)
Demo
arr = [1,2,3,4,5] #=> [1, 2, 3, 4, 5]
arr.move(2,1) #=> [1, 3, 2, 4, 5]
arr.move(2,2) #=> [1, 2, 3, 4, 5]
arr.move(2,3) #=> [1, 2, 3, 4, 5]
arr.move(2,4) #=> [1, 2, 4, 3, 5]
arr.move(2,5) #=> [1, 2, 4, 5, 3]
arr.move(2,6) #=> ArgumentError: To index is out-of-range

How do I pick a fractional sample from an array?

I know that ruby has myarray.sample(i) to sample i elements from an array. My problem is that the number of elements are not integers.
i.e I would like a method mysample such that if I call myarray.mysample(1.5) 10 times, the number of elements I get should be close to 15.
With sample, I will get either 10 or 20 depending on the int conversion. Similarly, if I call myarray.mysample(.25) I want it to return an element with 0.25 probability (that is, it should return one element one times out of four, and three times out of four, it should return an empty array/nil).
How do I do this?
My attempt so far:
def mysample(array,s)
ints = array.sample(s.floor)
if (Random.rand > s - s.floor)
ints << array.sample
end
return ints
end
Is there a better way?
Basing my answer off of this:
if I call myarray.mysample(1.5) 10 times, the number of elements I get should be close to 15.
Extending Array yields the following:
class Array
def mysample(num)
self.sample( ( num + rand() ).floor )
end
end
> [1, 2, 3, 4, 5].mysample(2.5)
=> [1, 3]
> [1, 2, 3, 4, 5].mysample(2.5)
=> [4, 2, 5]
> [1, 2, 3, 4, 5].mysample(0.5)
=> []
> [1, 2, 3, 4, 5].mysample(0.5)
=> [3]
etc.
To optimal argument is there to decide the spread of randomness for numbers above 1.
class Array
def my_sample(number, deviation=0.3)
if number < 1
return sample rand(100) < number * 100 ? 1 : 0
end
speard = (number*deviation).to_i
randomness = rand(-speard..speard)
sample(number+randomness)
end
end
p [1,2,3,4,5,6,7,8,9,10].my_sample(0.5) #=> []
p [1,2,3,4,5,6,7,8,9,10].my_sample(0.5) #=> [3]
p [1,2,3,4,5,6,7,8,9,10].my_sample(5) #=> [9, 2, 1, 4, 10, 7, 3]
p [1,2,3,4,5,6,7,8,9,10].my_sample(5) #=> [7, 2, 3, 8]

Flip array in chunks

I need to reverse an array in chunks of a specified size. Here is an example of what needs to happen:
chunk = 2
arr = [1,2,3,4,5]
How can I build an array in which chunks are reversed like this:
[2, 1, 4, 3, 5]
My code:
arr.each_slice(chunk) { |a| p a }
outputs:
[1,2]
[3,4]
[5]
Each of the chunks above needs to be reversed and appended to a final array as seen above.
arr = [1,2,3,4,5]
arr.each_slice(2).flat_map(&:reverse)
# => [2, 1, 4, 3, 5]

Resources