How to remove all the elements after a particular index - ruby

Given:
n = 2
arr = %w(10 20 30 40 50)
This is a way to remove all the array elements after index n:
arr.delete_if {|num| arr.index(num) > n }
# => ["10", "20", "30"]
Is there any other way to remove all the elements from an array without iterating over and comparing the condition?

Array#[]= operator comes in handy:
arr[n+1 .. -1] = []
In Ruby 2.6, you can also use endless ranges:
arr[n+1 ..] = []
Note that your code is slow, as it searches the array for every element and is thus O(n^2), but also wrong if the array contains repeating elements. For example, with arr = %w(10 20 30 10 20), your code does not change the array. This would be your code, only faster and correct (O(n); though the #[]= solution above is even faster and more straightforward):
arr.delete_if.with_index { |num, idx| idx > n }

You are deleting elements having index greater than n=2 & get rest of array where you can simply get it as,
arr = arr[0..2]
Above will regenerate new array and arr reference will point to it.
Update: going little deep with point claimed by Cary Swoveland in comment,
Array can be updated without initialising new one as follow,
# This one will have same object id or we can say, do not point to new array
arr.replace(arr[0,3])

You can use Array#slice! to remove elements within a certain index range, e.g.:
arr = %w(10 20 30 40 50)
arr.slice!(3..-1) #=> ["40", "50"]
arr #=> ["10", "20", "30"]
Note that slice! also returns the removed portion.

Related

Is there a function to find the max values index in an array containing ints and strings?

In the example here, is there a way to print out the max int (7654) index (5) properly?
I havent been able to figure out a way to do this with an array containing ints and strings, only ones with strictly ints.
array = ["me", 2345, "you", 345, "him", 7654, "her", 25]
arraybutonlynumbers = [2345, 345, 7654, 25]
puts array.each_with_index.max[1] #comparison of Array with Array failed (ArgumentError)
puts arraybutonlynumbers.each_with_index.max[1] #no error
Using Select, Max, and Index
You can find the intermediate results you need in three conceptual steps using built-in Array methods:
Use Array#select to consider only Integer values.
Capture the largest Integer value with Array#max.
Search the original Array for the captured Integer, and return the index of that element with Array#index.
You would then use some or all of those return values to craft your expected output. To illustrate the general approach from the irb console:
# find largest integer in a mixed array
array.select { |e| e.is_a? Integer }.max
#=> 7654
# find index of last return value
array.index _
#=> 5
However, to get the output you want, you'll need to refactor this into something that keeps the intermediate results so you can return them in the format you expect. For example:
def max_integer_with_index array
max_int = array.select { |e| e.is_a? Integer }.max
max_int_idx = array.index max_int
[max_int, max_int_idx]
end
max_integer_with_index [
"me", 2345, "you", 345, "him", 7654, "her", 25
]
#=> [7654, 5]
You can also reduce finding the index to a single line of code if you don't need the intermediate values. For example:
array.index array.select { |e| e.is_a? Integer }.max
#=> 5
Caveat
Please note that if you want to do something else besides ignore the String objects in your array, you will probably need to implement Array#sort_by (inherited from Enumerable; see also Comparable) to draw your own custom comparisons between Integers and Strings.
array = ["me", 2345, "you", 345, "him", 7654, "her", 25]
element, index = array.each_with_index.max_by{| el, idx| el.to_i}
p element, index
# => 7654
# => 5
Note however that to_i converts strings which do not start with a digit to 0, which may produce unwanted results if there are no positive integers in the array.

sorting programme on Ruby

I've seen a following sorting programme on Ruby but I don't think I fully understand how it actually works:
def sort arr
rec_sort arr, []
end
def rec_sort unsorted, sorted
if unsorted.length <= 0
return sorted
end
smallest = unsorted.pop
still_unsorted = []
unsorted.each do |tested|
if tested < smallest
still_unsorted.push smallest
smallest = tested
else
still_unsorted.push tested
end
end
sorted.push smallest
rec_sort still_unsorted, sorted
end
puts sort ["satoshi", "Bitcoin", "technology", "universe", "smell"]
=> Bitcoin
satoshi
smell
technology
universe
But when I change the first argument of the "rec_sort" method from "still_unsorted" (as indicated above) to "unsorted", the programme gives :
=> Bitcoin
Bitcoin
Bitcoin
Bitcoin
satoshi
I understand that the each loop selects the word "Bitcoin" first (because it would indeed come first when sorted), and "Bitcoin" would be put into the array "sorted". What I dont't quite understand is why there are several "Bitcoin" here, since it should have been excluded from the "unsorted" array in the first iteration of the each loop and, therefore, could not appear in the following iterations, making it impossible for "Bitcoin" to be in the "sorted" array several times.
Could you tell me what makes the two so different?
Any suggestions will be appreciated. Thank you.
The still_unsorted array has the smallest element removed, but the unsorted array only has its last element removed.
as far as I understand its a recursive implementation of bubble sort. and for you confusion unsorted is not being modified except for the statement unsorted.pop but only is being replicated into the still_unsorted except for the smallest element in that array
I am dry running this on [3,1,2] for you
unsorted = [3,1,2]
sm = unsorted.pop # sm = 2 unsorted = [3,1]
still_unsorted = []
#after loop unsorted.each
# still_unsorted = [2,3]
# unsorted = [3,1]
# sm = 1
# sorted = [1]
do the next 2 iterations you'll understand what's happening

Please walk me through this code from ruby monk

def random_select(array, n)
result = []
n.times do
# I do not fully understand how this line below works or why. Thank you
result.push array[rand(array.length)]
end
result
end
You are probably confused by this part:
n.times do
result.push(array[rand(array.length)])
end
n.times says it should loop n times.
result.push says to basically "push" or "put" something in the array. For example:
a = []
a.push(1)
p a #=> [1]
In array[rand(array.length)] , rand(array.length) will produce a random number as an index for the array. Why? rand(n) produces a number from 0 to n-1. rand(5) will produce either 0,1,2,3 or 4, for example.
Arrays use 0-based indexing, so if you have an array, say a = ['x', 'y', 'z'], to access 'x' you do a[0], to access y you do a[1] and so on. If you want to access a random element from a, you do a[rand(array.length)], because a.length in this case is 3, and rand(3) will produce a number that is either 0, 1 or 2. 0 is the smallest index and 2 is the largest index of our example array.
So suppose we call this method:
random_select([6,3,1,4], 2)
Try to see this code from the inside out. When the code reaches this part:
result.push(array[rand(array.length)])
it will first execute array.length which will produce 4. It will then execute rand(array.length) or rand(4) which will get a number between 0 and 3. Then, it will execute array[rand(array.length)] or array(some_random_number_between_0_and_3) which will get you a random element from the array. Finally, result.push(all_of_that_code_inside_that_got_us_a_random_array_element) will put the random element from the array in the method (in our example, it will be either 6, 3, 1 or 4) in the results array. Then it will repeat this same process once again (remember, we told it to go 2 times through the iteration).
The code can be rewritten to be much simpler, using the block-form Array constructor:
def random_select(array, n)
Array.new(n) {array.sample}
end
This creates a new array of size n and fills it with random samples from the array.
Note that the above solution, like your sample code, selects from the entire array each time which allows duplicate selections. If you don't want any duplicate selections, it's even simpler, since it is the default behavior of Array#sample:
def random_select(array, n)
array.sample(n)
end

Efficient way of removing similar arrays in an array of arrays

I am trying to analyze some documents and find similarities in them. After analysis, I have an array, the elements of which are arrays of data from documents considered similar. But sometimes I have two almost similar elements, and naturally I want to leave the biggest of them. For simplification:
data = [[1,2,3,4,5,6], [7,8,9,10], [1,2,3,5,6]...]
How do I efficiently process the data that I get:
data = [[1,2,3,4,5,6], [7,8,9,10]...]
I suppose I could intersect every array, and if the intersected array matches one of the original arrays - I ignore it. Here is a quick code I wrote:
data = [[1,2,3,4,5,6], [7,8,9,10], [1,2,3,5,6], [7,9,10]]
cleaned = []
data.each_index do |i|
similar = false
data.each_index do |j|
if i == j
next
elsif data[i]&data[j] == data[i]
similar = true
break
end
end
unless similar
cleaned << data[i]
end
end
puts cleaned.inspect
Is this an efficient way to go? Also, the current behaviour only allows to leave out arrays that are a few elements short, and I might want to merge similar arrays if they occur:
[[1,2,3,4,5], [1,3,4,5,6]] => [[1,2,3,4,5,6]]
You can delete any element in the list if it is fully contained in another element:
data.delete_if do |arr|
data.any? { |a2| !a2.equal?(arr) && arr - a2 == [] }
end
# => [[1, 2, 3, 4, 5, 6], [7, 8, 9, 10]]
This is a bit more efficient than your suggestion since once you decide that an element should be removed, you don't check against it in the next iterations.

How does negative index work with `Array#[]=`?

I tried to see how Array#[]= works, and played around:
enum[int] = obj → obj
enum[start, length] = obj → obj
enum[range] = obj → obj
Question 1
I have one array b holding nil at its 0 index.
b = []
b[0] # => nil
I tried to replace nil with integer 10 in the code below.
b[-1] = 10 # => IndexError: index -1 too small for array; minimum: 0
Why doesn't the code above work, but the ones below do? In case of an array with size 1, why are the indices 0 and -1 treated differently?
b[0] = 5 # => 5
b[-1] = 10 # => 10
Question 2
I created an array of size 2, and did the following:
a = [1,2]
a[-3] = 3 # => IndexError: index -3 too small for array; minimum: -2
a[-3] = [3] # => IndexError: index -3 too small for array; minimum: -2
a[-3..-4] = [3] # => RangeError: -3..-4 out of range
I believe that negative index never increases the size of an array, but I don't know why. Why did the code below succeed?
a[-2..-3] = [3,4] #=> [3, 4]
I would suggest you to take a look at the first para in Array documentation. It surprisingly says: “A negative index is assumed to be relative to the end of the array—that is, an index of -1 indicates the last element of the array, -2 is the next to last element in the array, and so on.”
That means, that you may set a[-N]th element if and only |N| <= a.size. That’s why a = [1,2] ; a[-3] = 3 fails (3 > 2).
On the other hand, there is [likely] not documented feature for ruby arrays: a[INBOUNDS_IDX..NONSENSE_IDX]=SMTH will insert SMTH before INBOUNDS_IDX index:
a=[1,2]
a[2..0]='a'
a[2..1]='b'
a[2..-100]='c'
# ⇒ [1, 2, "c", "b", "a"]
a[2..-1]='q'
# ⇒ [1, 2, "q"]
Nonsense here means “less than INBOUNDS_IDX, and not treatable as index in an negative notation” (that’s why a[2..-1] in the example above is treated as a[2..(a.size - 1)].)
Q1:
An empty array has 0 elements, so when you try to set its element 0, with negative index -1, it will give an error.
Because negative index cycles through the array from the end.
So
a = []; a[-1] = 3 makes it impossible to
a) get the element at last position, since its null
b) set its value. since it was never captured.
a[0] = 5 will work because you are telling the compiler to
a) grab the first element,
b) create one if not present, and then assign that to the value you requested.
See official api doc specifically mentioning positive index can grow size, negative index past the beginning of the array raises an error.
Q2:
The above explanation almost answers the second question as well.
Given a = [1,2]
a[-3] = 3 causes the first point of break. You are trying to access the 3rd element from the end, which does not exist. By design it breaks down.
While a[-2..-3] is within the capture range of the defined array.
You ask the interpreter to capture the second element from the last (1st from the front in this case), and try to invoke a range which is asking it to increase the array's size, and populate it with whatever you requested.
Happily, all is still well and as desired. Good to know.
Observation #1
The -1 index is related to the last element, if the array has no size, [], you can't use it until you initialize it with one or more elements.
Observation #2:
Yes, you are right, the negative index never increases size of the array, it only references a concrete existing position in the array.
Don't think the array is circular—the 0 index clued to the N-1 index—so you can't use any negative index thinking that it's valid.

Resources