Operate on array elements without changing index - ruby

I'm trying to operate on certain elements of an array while referencing their index in the block. Operating on the whole array is easy
arr = [1, 2, 3, 4, 5, 6, 7, 8]
arr.each_with_index { |num, index| puts "#{num}, "#{index}" }
But what if I want to work just with elements 4, 6 to return
4, 3
6, 5
I can create a new array composed of certain elements of the original and run the block on that, but then the index changes.
How can I select the elements and their index?

Just put a condition on it:
indice = [3, 5]
arr.each_with_index do
|num, index| puts "#{num}, #{index}" if indice.include?(index)
end
This is another style:
indice = [3, 5]
arr.each_with_index do
|num, index|
next unless indice.include?(index)
puts "#{num}, #{index}"
end

I cannot tell from the question whether you are given values in the array and want to obtain their indices, or vice-versa. I therefore will suggest one method for each task. I will use this array for examples:
arr = [1, 2, 3, 4, 5, 6, 7, 8]
Values to Indices
If you are given values:
vals = [4, 6]
you can retrieve the number-index pairs like this:
vals.map { |num| [num, arr.index(num)] }
#=> [[4, 3], [6, 5]]
or print them directly:
vals.each { |num| puts "#{num}, #{arr.index(num)}" }
# 4, 3
# 6, 5
#=> [4, 6]
If an element of vals is not present in arr:
vals = [4, 99]
vals.map { |num| [num, arr.index(num)] }
#=> [[4, 3], [99, nil]]
Indices to Values
If you are given indices:
indices = [3, 5]
you can retrieve the index-value pairs like this:
indices.zip(arr.values_at(*indices))
#=> [[3, 4], [5, 6]]
and then print in whatever format you like.
If an index is out-of-range, nil will be returned:
indices.zip(arr.values_at(*[3, 99]))
#=> [[3, 4], [5, nil]]

Related

Inserting elements into new array and then deleting from old array, some elements getting ignored

I'm trying to remove pairs of the smallest and largest elements from an Array and store them in a second one. Is there a better way to do this or a Ruby method I don't know about that could accomplish something like this?
Here's my code:
nums = [1, 2, 3, 4, 5, 6]
pairs = []; for n in nums
pairs << [n, nums.last]
nums.delete nums.last
nums.delete n
end
Current result:
nums
#=> [2, 4]
pairs
#=> [[1, 6], [3, 5]]
Expected result:
nums
#=> []
pairs
#=> [[1, 6], [2, 5], [3, 4]]
Assuming nums is sorted and can be modified, I like this way because it has a mechanical feel about it:
pairs = (nums.size/2).times.map { [nums.shift, nums.pop] }
#=> [[1, 6], [2, 5], [3, 4]]
nums
#=> []
I see #Drenmi has the same idea of using shift and pop.
If you don't want to modify nums, you could of course operate on a copy.
Enumerating over an Array while deleting it's content is generally not advisible. Here's an alternative solution:
nums = *(1..6)
#=> [1, 2, 3, 4, 5, 6]
pairs = []
#=> []
until nums.size < 2 do
pairs << [nums.shift, nums.pop]
end
pairs
#=> [[1, 6], [2, 5], [3, 4]]

Assigning values to a 2D array using "each" method

I'm trying to transpose [[0, 1, 2], [3, 4, 5], [6, 7, 8]]. I get [[2, 5, 8], [2, 5, 8], [2, 5, 8]].
I can see what is happening with the line p transposed_arr but do not understand why this is happening. At every iteration it changes every row instead of only one.
def my_transpose(arr)
# number of rows
m = arr.count
#number of columns
n = arr[0].count
transposed_arr = Array.new(n, Array.new(m))
# loop through the rows
arr.each_with_index do |row, index1|
# loop through the colons of one row
row.each_with_index do |num, index2|
# swap indexes to transpose the initial array
transposed_arr[index2][index1] = num
p transposed_arr
end
end
transposed_arr
end
You need to make only one wee change and your method will work fine. Replace:
transposed_arr = Array.new(n, Array.new(m))
with:
transposed_arr = Array.new(n) { Array.new(m) }
The former makes transposed_arr[i] the same object (an array of size m) for all i. The latter creates a separate array of size m for each i
Case 1:
transposed_arr = Array.new(2, Array.new(2))
transposed_arr[0].object_id
#=> 70235487747860
transposed_arr[1].object_id
#=> 70235487747860
Case 2:
transposed_arr = Array.new(2) { Array.new(2) }
transposed_arr[0].object_id
#=> 70235478805680
transposed_arr[1].object_id
#=> 70235478805660
With that change your method returns:
[[0, 1, 2],
[3, 4, 5],
[6, 7, 8]]

Finding the mode of a Ruby Array (simplified_

I'm trying to find the mode of an Array. Mode = the element(s) that appear with the most frequency.
I know there are lots of tricks with #enumerable, but I'm not there yet in my learning. The exercise I'm doing assumes I can solve this problem without understanding enumerable.
I've written out my game plan, but I'm stuck on the 2nd part. I'm not sure if it's possible to compare a hash key against an array, and if found, increment the value.
def mode(array)
# Push array elements to hash. Hash should overwrite dup keys.
myhash = {}
array.each do |x|
myhash[x] = 0
end
# compare Hash keys to Array. When found, push +=1 to hash's value.
if myhash[k] == array[x]
myhash[k] += 1
end
# Sort hash by value
# Grab the highest hash value
# Return key(s) per the highest hash value
# rejoice!
end
test = [1, 2, 3, 3, 3, 4, 5, 6, 6, 6]
mode(test) # => 3, 6 (because they each appear 3 times)
You can create a hash with a default initial value:
myhash = Hash.new(0)
Then increment specific occurrences:
myhash["foo"] += 1
myhash["bar"] += 7
myhash["bar"] += 3
p myhash # {"foo"=>1, "bar"=>10}
With that understanding, if you replace your initial hash declaration and then do the incrementing in your array.each iterator, you're practically done.
myhash.sort_by{|key,value| value}[-1]
gives the last entry in the sorted set of hash values, which should be your mode. Note that there may be multiple modes, so you can iterate backwards while the value portion remains constant to determine them all.
There are many, many ways you could do this. Here are a few.
#1
array = [3,1,4,5,4,3]
a = array.uniq #=> [3, 1, 4, 5]
.map {|e| [e, array.count(e)]}
#=> [[3, 2], [1, 1], [4, 2], [5, 1]]
.sort_by {|_,cnt| -cnt} #=> [[3, 2], [4, 2], [1, 1], [5, 1]]
a.take_while {|_,cnt| cnt == a.first.last}
#=> [[3, 2], [4, 2]]
.map(&:first) #=> [3, 4]
#2
array.sort #=> [1, 3, 3, 4, 4, 5]
.chunk {|e| e}
#<Enumerator: #<Enumerator::Generator:0x000001021820b0>:each>
.map { |e,a| [e, a.size] } #=> [[1, 1], [3, 2], [4, 2], [5, 1]]
.sort_by { |_,cnt| -cnt } #=> [[4, 2], [3, 2], [1, 1], [5, 1]]
.chunk(&:last)
#<Enumerator: #<Enumerator::Generator:0x00000103037e70>:each>
.first #=> [2, [[4, 2], [3, 2]]]
.last #=> [[4, 2], [3, 2]]
.map(&:first) #=> [4, 3]
#3
h = array.each_with_object({}) { |e,h|
(h[e] || 0) += 1 } #=> {3=>2, 1=>1, 4=>2, 5=>1}
max_cnt = h.values.max #=> 2
h.select { |_,cnt| cnt == max_cnt }.keys
#=> [3, 4]
#4
a = array.group_by { |e| e } #=> {3=>[3, 3], 1=>[1], 4=>[4, 4], 5=>[5]}
.map {|e,ees| [e,ees.size]}
#=> [[3, 2], [1, 1], [4, 2], [5, 1]]
max = a.max_by(&:last) #=> [3, 2]
.last #=> 2
a.select {|_,cnt| cnt == max}.map(&:first)
#=> [3, 4]
In your approach, you have first initialized a hash containing keys taken from the unique values of the array, with the associated values all set to zero. For example, the array [1,2,2,3] would create the hash {1: 0, 2: 0, 3: 0}.
After this, you plan to count the instances of each of the values in the array by incrementing the value for the associated key in the hash by one for each instance. So, after finding the number 1 in the array, the hash would look like so: {1: 1, 2: 0, 3: 0}. You clearly need to do this for each value in the array, so given your approach and current level of understanding, I would suggest looping through the array again:
array.each do |x|
myhash[x] += 1
end
As you can see, we don't need to check that myhash[k] == array[x] since we have already created a key:value pair for each number in the array.
However, while this approach will work, it's not very efficient: we're having to loop through the array twice. The first time to initialize all the key:value pairs to some default (zero, in this case), and the second to count the frequencies of each number.
Since the default value for each key will be zero, we can remove the need to initialize the defaults by using a different hash constructor. myhash = {} will return nil if we access a key that doesn't exist, but myhash = Hash.new(0) will return 0 if we access a non-existent key (note that you could provide any other value or variable, if required).
By providing a default value of zero, we can get rid of the first loop entirely. When the second loop finds a key that doesn't exist, it will use the default provided and automatically initialize it.
def mode(array)
array.group_by{ |e| e }.group_by{ |k, v| v.size }.max.pop.map{ |e| e.shift }
end
Using the simple_stats gem:
test = [1, 2, 3, 3, 3, 4, 5, 6, 6, 6]
test.modes #=> [3, 6]
If it is an unsorted array, we can sort the array in descending order
array = array.sort!
Then use the sorted array to create a hash default 0 and with each element of the array as a key and number of occurrence as the value
hash = Hash.new(0)
array.each {|i| hash[i] +=1 }
Then mode will be the first element if the hash is sorted in descending order of value(number of occurrences)
mode = hash.sort_by{|key, value| -value}.first[0]

Merging inner arrays of indices if they contain the same content

I'm generating an array of grouped indexes. The indexes are points within an array that meet my grouping requirements. For example I'm grouping indexes from a grid where things are horizontally "close" to each other. This is kind of what I'll be working with.
[[0,1,2],[3],[4,5],[5,6],[7,8],[8,9]]
I would like to merge by common indexes. So the result should look like.
[[0,1,2],[3],[4,5,6],[7,8,9]]
It feels like it should be an inject :+ on pairs if any inner items match. But I don't see the Ruby way to do this.
x.sort.inject([]) do |y, new|
(((y.last || []) & new).length > 0) ? y[0..-2].push(y.last | new) : y.push(new)
end.map(&:sort)
Knowing Ruby, there's probably a more concise way to do this, but this should give you what you want:
foo = [[0,1,2],[3],[4,5],[5,6],[7,8],[8,9]]
foo.inject([]) {|result,element|
if (result and existing = result.find_index{|a| !(element & [*a]).empty?})
tmp = result[existing]
result.delete_at(existing)
result << (tmp | element).sort
else
result << element
end
}.sort
Output:
=> [[0, 1, 2], [3], [4, 5, 6], [7, 8, 9]]
Logic:
For each element in the original array, check the newly-built array-so-far (result) for any entry which contains any of the same numbers as the next element using array intersection -- !(element & [*a]).empty? ...
if found, remove said entry from the result, union it with the new element from the original array -- (tmp | element) -- then add it back to the result
if not found, simply concatenate the element from the original array to the result
Someone might find a more compact method, but this works...
array = [[0,1,2],[3],[4,5],[5,6],[7,8],[8,9]]
(0...array.length).each do |a|
(a+1...array.length).each do |b|
unless array[a].to_a & array[b].to_a == []
array[a].push(array[b]).flatten!.uniq!.sort!
array.delete_at(b)
b -= 1
end
end
end
p array
=> [[0, 1, 2], [3], [4, 5, 6], [7, 8, 9]]
The solutions so far seem overly-complicated to me. I suggest this (assuming each element of arr is non-empty and contains only integers):
arr = [[0, 1, 2], [3],
[4, 5], [5, 6],
[7, 8, 9], [9, 10], [10, 11],
[12, 13], [13], [13, 14]]
arr.each_with_object([]) do |a,b|
if b.any? && b.last.last == a.first
b[-1] += a[1..-1]
else
b << a
end
end
#=> [[0, 1, 2], [3], [4, 5, 6], [7, 8, 9, 10, 11], [12, 13, 14]]
You could alternatively do it by stepping through arr with an enumerator:
enum = arr.each
b = [enum.next]
loop do
a = enum.next
if b.last.last == a.first
b[-1] += a[1..-1]
else
b << a
end
end
b

Merge N sorted arrays in ruby lazily

How does one merge N sorted arrays (or other list-like data structures) lazily in Ruby? For example, in Python you would use heapq.merge. There must be something like this built into Ruby, right?
Here's a (slightly golfed) solution that should work on arrays of any 'list-like' collections that support #first, #shift, and #empty?. Note that it is destructive - each call to lazymerge removes one item from one collection.
def minheap a,i
r=(l=2*(m=i)+1)+1 #get l,r index
m = l if l< a.size and a[l].first < a[m].first
m = r if r< a.size and a[r].first < a[m].first
(a[i],a[m]=a[m],a[i];minheap(a,m)) if (m!=i)
end
def lazymerge a
(a.size/2).downto(1){|i|minheap(a,i)}
r = a[0].shift
a[0]=a.pop if a[0].empty?
return r
end
p arrs = [ [1,2,3], [2,4,5], [4,5,6],[3,4,5]]
v=true
puts "Extracted #{v=lazymerge (arrs)}. Arr= #{arrs.inspect}" while v
Output:
[[1, 2, 3], [2, 4, 5], [4, 5, 6], [3, 4, 5]]
Extracted 1. Arr= [[2, 3], [2, 4, 5], [4, 5, 6], [3, 4, 5]]
Extracted 2. Arr= [[3], [2, 4, 5], [4, 5, 6], [3, 4, 5]]
Extracted 2. Arr= [[4, 5], [3], [4, 5, 6], [3, 4, 5]]
Extracted 3. Arr= [[4, 5], [3, 4, 5], [4, 5, 6]]
Extracted 3. Arr= [[4, 5], [4, 5], [4, 5, 6]]
Extracted 4. Arr= [[5], [4, 5], [4, 5, 6]]
Extracted 4. Arr= [[5], [5], [4, 5, 6]]
Extracted 4. Arr= [[5, 6], [5], [5]]
Extracted 5. Arr= [[6], [5], [5]]
Extracted 5. Arr= [[5], [6]]
Extracted 5. Arr= [[6]]
Extracted 6. Arr= [[]]
Extracted . Arr= [[]]
Note also that this algorithm is also lazy about maintaining the heap property - it is not maintained between calls. This probably causes it to do more work than needed, since it does a complete heapify on each subsequent call. This could be fixed by doing a complete heapify once up front, then calling minheap(a,0) before the return r line.
I ended up writing it myself using the data structures from the 'algorithm' gem. It wasn't as bad as I expected.
require 'algorithms'
class LazyHeapMerger
def initialize(sorted_arrays)
#heap = Containers::Heap.new { |x, y| (x.first <=> y.first) == -1 }
sorted_arrays.each do |a|
q = Containers::Queue.new(a)
#heap.push([q.pop, q])
end
end
def each
while #heap.length > 0
value, q = #heap.pop
#heap.push([q.pop, q]) if q.size > 0
yield value
end
end
end
m = LazyHeapMerger.new([[1, 2], [3, 5], [4]])
m.each do |o|
puts o
end
Here's an implementation which should work on any Enumerable, even infinite ones. It returns Enumerator.
def lazy_merge *list
list.map!(&:enum_for) # get an enumerator for each collection
Enumerator.new do |yielder|
hash = list.each_with_object({}){ |enum, hash|
begin
hash[enum] = enum.next
rescue StopIteration
# skip empty enumerators
end
}
loop do
raise StopIteration if hash.empty?
enum, value = hash.min_by{|k,v| v}
yielder.yield value
begin
hash[enum] = enum.next
rescue StopIteration
hash.delete(enum) # remove enumerator that we already processed
end
end
end
end
Infinity = 1.0/0 # easy way to get infinite range
p lazy_merge([1, 3, 5, 8], (2..4), (6..Infinity), []).take(12)
#=> [1, 2, 3, 3, 4, 5, 6, 7, 8, 8, 9, 10]
No, there's nothing built in to do that. At least, nothing that springs instantly to mind. However, there was a GSoC project to implement the relevant data types a couple of years ago, which you could use.

Resources