Extract items between 2 numbers in Ruby - ruby

Problem:
Given an array of numbers in Ruby, return the groups of numbers that appear between 1 and 2.
The numbers 1 and 2 do not appear in between other 1's and 2's (there are no subsets of subsets).
Example 1
input: [1, 3, 2, 1, 4, 2]
output: [[1, 3, 2], [1, 4, 2]]
Example 2
input: [0, 1, 3, 2, 10, 1, 5, 6, 7, 8, 7, 5, 2, 3, 1, -400, 2, 12, 16]
output: [ [1, 3, 2], [1, 5, 6, 7, 8, 7, 5, 2], [1, -400, 2] ]
My hunch is to use a combination of #chunk and #drop_while or a generator.
Thanks in advance.

This is an option using [Enumerable#slice_when][1]:
ary1 = [1, 3, 2, 1, 4, 2]
ary2 = [0, 1, 3, 2, 10, 1, 5, 6, 7, 8, 7, 5, 2, 3, 1, -400, 2, 12, 16]
For example:
stop = [1, 2]
ary2.slice_when{ |e| stop.include? e }
.each_slice(2).map { |a, b| b.unshift(a.last) if b }
.reject { |e| e.nil? || (e.intersection stop).empty? }
#=> [[1, 3, 2], [1, 5, 6, 7, 8, 7, 5, 2], [1, -400, 2]]
Other option
More verbose but clearer, given the input:
input = %w(b a b c a b c a c b c a c a)
start = 'a'
stop = 'b'
Using Enumerable#each_with_object, why not use the good old if then else?:
tmp = []
pickup = false
input.each_with_object([]) do |e, res|
if e == start
pickup = true
tmp << e
elsif pickup && e == stop
tmp << e
res << tmp
tmp = []
pickup = false
elsif pickup
tmp << e
end
end
#=> [["a", "b"], ["a", "b"], ["a", "c", "b"]]
[1]: https://ruby-doc.org/core-2.7.0/Enumerable.html#method-i-slice_when

Sounds like an interview question. I'll explain the simplest algorithm I can think of:
You loop through the array once and build the output as you go. When you encounter 1, you store it and the subsequent numbers into another temporary array. When you encounter 2, you put the array in the output array. The edge cases are:
another 1 after you start building the temporary array
a 2 when you don't have a temporary array
First case is easy, always build a new temp array when you encounter a 1. For the second one, you have to check whether you have any items in your temporary array and only append the temp array to your output if it's not empty.
That should get you started.

You could use chunk and Ruby's flip-flop operator:
input = [0, 1, 3, 2, 10, 1, 5, 6, 7, 8, 7, 5, 2, 3, 1, -400, 2, 12, 16]
input.chunk { |i| true if i==1..i==2 }.each { |_, ary| p ary }
Output:
[1, 3, 2]
[1, 5, 6, 7, 8, 7, 5, 2]
[1, -400, 2]

For all people wanting to take a walk on the beach but for obvious reasons can't:
class Flipflop
def initialize(flip, flop) #flip and flop being boolean-returning lambdas
#state = false
#flip = flip
#flop = flop
end
def flipflop(x) #logic taken from The Ruby Programming Language page 111
if !#state
result = #flip[x]
if result
#state = !#flop[x]
end
result
else
#state = !#flop[x]
true
end
end
end
ff = Flipflop.new( ->(x){x == 1}, ->(x){x == 2} )
input = [0, 1, 3, 2, 10, 1, 5, 6, 7, 8, 7, 5, 2, 3, 1, -400, 2, 12, 16]
res = input.select{|el| ff.flipflop(el) }.slice_before(1) #an Enumerator
p res.to_a
# =>[[1, 3, 2], [1, 5, 6, 7, 8, 7, 5, 2], [1, -400, 2]]
For strings, ff = Flipflop.new( ->(x){x.chomp == "BEGIN"}, ->(x){x.chomp == "END"} ) or something like that should work.

Since you commented and added that you are actually reading a file, I deleted my old answer (which was faulty anyways, as #Stefan pointed out) and cam up with this. You can paste this in a file and run it, the DATA IO contains everything that appears after __END__. In your application you would replace it with your File.
class Chunker
BEGIN_INDICATOR = "BEGIN"
END_INDICATOR = "END"
def initialize(io)
#io = io
end
def each
return enum_for(:each) if !block_given?
chunk = nil
while !io.eof? do
line = io.readline.chomp
if line == BEGIN_INDICATOR
chunk = []
chunk << line
elsif line == END_INDICATOR
chunk << line
yield chunk.freeze
chunk = nil
elsif chunk
chunk << line
end
end
end
private
attr_reader :io
end
chunker = Chunker.new(DATA)
chunker.each do |chunk|
p chunk
end
# or, thanks to the `return enum_for(:each) if !block_given?` line:
chunker.each.with_index do |chunk, index|
p "at #{index} is #{chunk}"
end
__END__
ignore
BEGIN
some
thing
END
BEGIN
some
other
thing
END
maybe ignore as well
ยดยดยด
You could enhance it to throw EOF when `each` is called multiple times or whatever suits your needs.

Related

Ruby array processing add an unexpected nil to the new array?

New to Ruby (coming from Python) and try to experiment this exercise:
(mixed the array items by taking first, last in rotating fashion)
Expected Output to be - [1, 7, 2, 6, 3, 5, 4]. But I did not expect 'nil' at the end... The orig. array can contain even or odd size of numbers.
Can someone shed the light of this unexpected? Thanks in advance.
[Updates - re-write the example from Ruby Cookbook p.162 Array ]
nums = (1..7).to_a # [1, 2, 3, 4, 5, 6, 7]
mixed = []
#middle = nums.length / 2
#index = 0
until nums.empty?
mixed << nums.shift(). #get 1st element out
mixed << nums.pop() #get last element out
#index += 1
end
print mixed # Got [1, 7, 2, 6, 3, 5, 4, nil]
What's happening is that the total num of elements in the array is odd so the last value is put into mixed on 'shift' and then there is no element left in the array. This will solve your issue:
nums = (1..7).to_a # [1, 2, 3, 4, 5, 6, 7]
mixed = []
#middle = nums.length / 2
#index = 0
until nums.empty?
mixed << nums.shift()
mixed << nums.pop() unless nums.empty?
#index += 1
end
print mixed # Got [1, 7, 2, 6, 3, 5, 4]
Another way is: If the num of elements is odd then run the loop till n-1 and then get the last element out using shift/pop (doesn't matter if you use shift or pop at the end, you will get the same element.)
The Cookbook method can be made non-destructive (avoid modifying nums) as follows.
def doit(nums)
nums.size.times.map { |i| i.even? ? nums[i/2] : nums[-i/2] }
end
doit [1, 2, 3, 4, 5, 6, 7]
#=> [1, 7, 2, 6, 3, 5, 4]
doit [1, 2, 3, 5, 6, 7]
#=> [1, 7, 2, 6, 3, 5]
Here is another (non-destructive) way to do that.
def doit(nums)
n = nums.size/2
nums.first(n).zip(nums.last(n).reverse).flatten.tap do |a|
a << nums[n] if nums.size.odd?
end
end
doit [1, 2, 3, 4, 5, 6, 7]
#=> [1, 7, 2, 6, 3, 5, 4]
doit [1, 2, 3, 5, 6, 7]
#=> [1, 7, 2, 6, 3, 5]

Algorithm Backtracking: How to do recursion without storing state

Normally in backtracking, we take a helper function which takes in an initial state and each recursive call takes care of its own computation and pass the result to the next recursion call. Theoretically, we denote this through unseen and seen variable.
For example, in permutation for a string we will use this program:
def permute(str)
return str if str.length < 2
permute_helper(str, "")
end
def permute_helper(unseen, seen)
#base case
if unseen.length <= 0
p seen
return
else
(0..unseen.length-1).each do |i|
buffer = unseen
buffer = buffer.split('')
buffer.delete_at(i)
buffer = buffer.join('')
permute_helper(buffer, seen+unseen[i])
end
end
end
permute('abc')
Thie will print out the required results.
I was asked to do this without using two variables in a recent interview. without storing state in the seen variable. I couldn't think through the whole at that time but I would like to ask how to do backtracking without storing states ?
The permutations of the string "cd" is ["cd", "dc"]. If we now wish to obtain the permutations of the string "bcd" we simply replace each element of this array with three strings, each having "b" at a different position. "cd" becomes "bcd", "cbd" and "cdb" and "dc" becomes "bdc", "dbc" and "dba". The permutations of "bcd" are therefore
["bcd", "cbd", "cdb", "bdc", "dbc", "dba"]
If we now wish to obtain the permutations of "abcd", we replace each element of the above six-element array with four strings, each with "a" in a different position. For example, "bcd" becomes "abcd", "bacd", "bcad" and "bcda". The structure of the recursion should now be obvious.
def permute(str)
case str.length
when 0, 1
str
when 2
[str, str.reverse]
else
first = str[0]
sz = str.size-1
permute(str[1..-1]).flat_map { |s| (0..sz).map { |i| s.dup.insert(i,first) } }
end
end
permute('')
#=> ""
permute('a')
#=> "a"
permute('ab')
#=> ["ab", "ba"]
permute('abc')
#=> ["abc", "bac", "bca", "acb", "cab", "cba"]
permute('abcd')
#=> ["abcd", "bacd", "bcad", "bcda", "acbd", "cabd", "cbad", "cbda",
# "acdb", "cadb", "cdab", "cdba", "abdc", "badc", "bdac", "bdca",
# "adbc", "dabc", "dbac", "dbca", "adcb", "dacb", "dcab", "dcba"]
str is of course the "unseen" variable.
#CarySwoveland's answer an explanation is awesome, per usual. For those looking to permute an array, consider this functional approach. While this uses an auxiliary lambda all_pos, no extra state parameter is used to accumulate the result.
def permute ((x, *xs))
all_pos = lambda do |(y,*ys)|
if y.nil?
[[ x ]]
else
[[ x, y, *ys ]] + (all_pos.call ys) .map { |rest| [ y, *rest ] }
end
end
if x.nil? or xs.empty?
[[x]]
else
(permute xs) .flat_map &all_pos
end
end
permute [1,2,3,4]
# [ [1, 2, 3, 4]
# , [2, 1, 3, 4]
# , [2, 3, 1, 4]
# , [2, 3, 4, 1]
# , [1, 3, 2, 4]
# , [3, 1, 2, 4]
# , [3, 2, 1, 4]
# , [3, 2, 4, 1]
# , [1, 3, 4, 2]
# , [3, 1, 4, 2]
# , [3, 4, 1, 2]
# , [3, 4, 2, 1]
# , [1, 2, 4, 3]
# , [2, 1, 4, 3]
# , [2, 4, 1, 3]
# , [2, 4, 3, 1]
# , [1, 4, 2, 3]
# , [4, 1, 2, 3]
# , [4, 2, 1, 3]
# , [4, 2, 3, 1]
# , [1, 4, 3, 2]
# , [4, 1, 3, 2]
# , [4, 3, 1, 2]
# , [4, 3, 2, 1]
# ]

How can i avoid using the same element in my two sum solution

So I am trying to get a solution to my two sum problem and I am stuck, I need to print the indices for the elements which add up to the target and my solution will return an element twice if it is one half of the target
def two_sum(nums, target)
num_hash = Hash.new(0)
nums.each_with_index do |num,idx|
num_hash[num] = idx
if num_hash.key?(target - num) && target % num != 0
return [num_hash[num], idx]
end
end
end
So I don't think the problem is related to the number being 1/2 of the target, it just seems to be "if a solution is found, it returns the same index twice". For instance, using the sample set [2, 7, 11, 15]
two_sum([2, 7, 11, 15], 14) # => [2, 7, 11, 15]
So, 7 is half of 14, which is the target, and instead of returning the index 1 twice, as you suggest it would, it returns the original input array (the result of nums.each_with_index. However, if we try passing a target of 9, it behaves as you describe:
two_sum([2, 7, 11, 15], 9) # => [1, 1]
The reason for this, is because of the line:
return [num_hash[num], idx]
you have already set num into the num_hash (num_hash[num] = idx) and then you are returning both the idx and num_hash[num], which is also idx. So what you want to do is:
return [num_hash[target - num], idx]
and then to 'fix' all the elements being returned when no result is found, just return [] at the end of the method:
def two_sum(nums, target)
num_hash = Hash.new(0)
nums.each_with_index do |num,idx|
num_hash[num] = idx
if num_hash.key?(target - num) && target % num != 0
return [num_hash[target - num], idx]
end
end
[]
end
and now:
two_sum([2, 7, 11, 15], 14) # => []
two_sum([2, 7, 11, 15], 9) # => [0, 1]
Note: you also have a problem with the code where, if you have the same number twice, it doesn't find the answer:
two_sum([2, 7, 11, 7, 15], 14) # => []
left for you to figure out, just wanted to point this out to you.
You can use the method Array#combination to advantage here.
def two_sum(nums, target)
nums.each_index.to_a.combination(2).select { |i,j| nums[i] + nums[j] == target }
end
two_sum([2, 7, 11, 15], 14)
#=> []
two_sum([2, 7, 11, 15], 9)
#=> [[0, 1]]
two_sum([2, 4, 7, 5], 9)
#=> [[0, 2], [1, 3]]
two_sum([2, 2, 2, 2], 4)
#=> [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]]
two_sum([2, 4, 7, 5], 8)
#=> []
For
nums = [2, 4, 7, 5]
target = 9
the steps are as follows.
a = nums.each_index
#=> #<Enumerator: [2, 4, 7, 5]:each_index>
We can see the elements that will be generated by this enumerator by converting it to an array.
b = a.to_a
#=> [0, 1, 2, 3]
Next,
c = b.combination(2)
#=> #<Enumerator: [0, 1, 2, 3]:combination(2)>
c.to_a
#=> [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]]
The rest is straightforward as select merely selects those pairs of indices passed to it (i,j) whose corresponding values, num[i] and num[j], sum to target.
I think what you want is ...
return [num_hash[target-num], idx]

Looping through array and appending every even item to new array

I am trying to loop through an array, adding every other item to a new array.
def yes_no(arr)
i = 0
new_array = []
while i != arr.size
arr.select.each_with_index {|value , index| index.even?}
new_array << value
i += 1
end
new_array
end
The code is supposed to return a new array with values by their order. For:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
it should return:
[1, 3, 5, 7, 9, 2, 6, 10, 8, 4]
The first value of the initial array is always taken. I believe I have the correct logic with my code, but I need some help completing this problem.
Here is another example:
arr = ['this', 'code', 'is', 'right', 'the']
// returns ['this', 'is', 'the', 'right', 'code']
I'm not sure how to fix your code, but here's another way to get the expected result:
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
new_array = []
until arr.empty?
new_array << arr.shift
arr.rotate!
end
new_array
#=> [1, 3, 5, 7, 9, 2, 6, 10, 8, 4]
Note that arr is being modified, you might want to dup it.

Conditionally inserting data into an array

Alright, can someone help me how to properly iterate a dynamic size array?
Here's what I mean:
my_array = [1, 2, 3, 4, 2, 5, 15, 2] # <= there are three 2 inside my_array
Then I would like to add "good" everytime the iteration hit integer 2, I tried several method but not find the way, here's the best method I've tried(still not resulting in what I want)
# First Method
for i in 0..(my_array.length - 1)
my_array.insert(i + 1, "good") if my_array[i] == 2
end
p my_array # => [1, 2, "good", 3, 4, 2, "good", 5, 15, 2]
# Second Method
for i in 0..(my_array.length - 1)
my_array[i + 1] = "good" if my_array[i] == 2
end
p my_array # => [1, 2, "good", 4, 2, "good", 15, 2, "good"]
The first method is not good because it's not showing "good" after the last 2, I guess this because the iteration could not reach the last integer 2(in the last array) and that is expected because the array size is changed bigger everytime "good" is inserted.
The second one is also bad, because I replace the data after every 2 with "good" string.
Now can someone point it out to me how can I doing this properly so I can produce it like this:
p my_array # => [1, 2, "good", 3, 4, 2, "good", 5, 15, 2, "good"]
All "good" is added without replacing any data.
Any help is appreciated, thank you very much.
You'd have a better time transforming this into a new array than modifying in-place:
my_array = [1, 2, 3, 4, 2, 5, 15, 2]
def add_good(a)
a.flat_map do |value|
case (value)
when 2
[ 2, 'good' ]
else
value
end
end
end
puts add_good(my_array).inspect
# => [1, 2, "good", 3, 4, 2, "good", 5, 15, 2, "good"]
The flat_map method is useful for situations where you want to create zero or more entries in the resulting array. map is a 1:1 mapping, flat_map is a 1:N.
It's also possible to make this much more generic:
def fancy_insert(a, insert)
a.flat_map do |value|
if (yield(value))
[ value, insert ]
else
value
end
end
end
result = fancy_insert(my_array, 'good') do |value|
value == 2
end
puts result.inspect
That way you can pass in an arbitrary block and value to be inserted.
Why not using the .each_with_index method:
arr = [1, 2, 3, 4, 5, 2, 3, 5]
arr.each_with_index {|e, i| arr.insert(i+1, "good") if e == 2}
Fast and furious.
Here's another way you can do it, using an Enumerator:
my_array = [1, 2, 3, 4, 2, 5, 15, 2]
enum = my_array.each
#=> #<Enumerator: [1, 2, 3, 4, 2, 5, 15, 2]:each>
my_array = []
loop do
x = enum.next
my_array << x
my_array << "good" if x == 2
end
my_array
#=> [1, 2, "good", 3, 4, 2, "good", 5, 15, 2, "good"]
Enumerator#next raises a StopInteration exception when the enumerator is already on the last element. Kernel#loop handles the exception by breaking the loop. That's why you will often see loop used when stepping through an enumerator.

Resources