Related
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]
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.
I have a string a with a lot of digits, for example a = '4408 0412 3456 7893'.
I want every second value (starts from index=0) to be multiplied by 2. And I wrote this code, but it's like a too big (and wrong) and complicated:
a = '4408 0412 3456 7893'
b = a.delete(' ')
card = []
c = b.split(//)
c.each_with_index do |value, index|
card << ((value.to_i) *2) if index % 2 == 0
end
Because at the end I have an array like [8, 0, 0, 2, 6, 10, 14, 18] and but I should have another string like '8408042264106148183'.
To turn your string into an array with integers:
array = '4408 0412 3456 7893'.delete(' ').split('').map(&:to_i)
# result: [4, 4, 0, 8, 0, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 3]
To change the array to an array where every second (?) value is doubled:
array.each_with_index {|v,i| i.even? ? array[i] = v*2 : array[i] = v}
# result: [8, 4, 0, 8, 0, 4, 2, 2, 6, 4, 10, 6, 14, 8, 18, 3]
To make a string of it again:
array.join('')
# result: "8408042264106148183"
Is this what you're looking for?
I'm trying to write my own transpose method. I'm wondering how the different forms of concatenation are affecting my code.
multi = [[1,3,5],[2,4,6],[7,9,8]]
new = Array.new(multi.length, [])
multi.each do |c|
c.each_with_index do |x,y|
new[y] += [x]
end
end
new #=> [[1, 3, 5], [2, 4, 6], [7, 9, 8]]
multi = [[1,3,5],[2,4,6],[7,9,8]]
new = Array.new(multi.length, [])
multi.each do |c|
c.each_with_index do |x,y|
new[y] << x
end
end
new #=> [[1, 3, 5, 2, 4, 6, 7, 9, 8], [1, 3, 5, 2, 4, 6, 7, 9, 8], [1, 3, 5, 2, 4, 6, 7, 9, 8]]
Why do they not work in an identical fashion?
With
new = Array.new(multi.length, [])
# => [[], [], []]
the elements in new refer to the same Array objects. Check their id:
new.map {|e| e.object_id}
# => [1625920, 1625920, 1625920]
The first code snippet gives you the expected result because new[y] += [x] assigns to new[y] a new Array object, so each element in new now doesn't refer to the same object:
new.map {|e| e.object_id}
# => [22798480, 22798440, 22798400]
With the second code snippet, each element in new still refers to the original Array object.
I know the idiomatic way to do a for loop in Ruby is to use an Enumerator like .each, but I'm running into a problem: I'd like to iterate over a subset of an Array and modify those elements. Calling .map! with a subset like ary[0..2] or .slice(0..2) doesn't seem to do it; presumably because that slicing operator is creating a new Array?
Desired behavior with for instead of iterator:
iter_ind = [2,3,4]
my_ary = [1,3,5,7,9,11]
for j in iter_ind
my_ary[j] = my_ary[j] + 1
# some other stuff like an exchange operation maybe
end
=> [1, 3, 6, 8, 10, 11]
Things that don't work:
irb(main):032:0> ar[2..4].map! {|el| el = el+1}
=> [6, 8, 10]
irb(main):033:0> ar
=> [1, 3, 5, 7, 9, 11]
irb(main):034:0> ar.slice(2..4).map! {|el| el = el+1}
=> [6, 8, 10]
irb(main):035:0> ar
=> [1, 3, 5, 7, 9, 11]
irb(main):036:0> ar[2..4].collect! {|el| el = el+1}
=> [6, 8, 10]
irb(main):037:0> ar
=> [1, 3, 5, 7, 9, 11]
Try this.
In example below I implemented something that could be named map_with_index. each_with_index if no block given returns iterator. I use it to map our array.
ary = [1, 3, 5, 7, 9, 11]
ary.each_with_index.map { |elem, index| index.between?(2, 4) ? elem += 1 : elem }
# => [1, 3, 6, 8, 10, 11]
You may also try the following:
?> ary = [1, 3, 5, 7, 9, 11]
=> [1, 3, 5, 7, 9, 11]
?> ary.map!.with_index {|item, index| index.between?(2, 4) ? item += 1 : item}
=> [1, 3, 6, 8, 10, 11]
?> ary
=> [1, 3, 6, 8, 10, 11]
You could use Array#each_index if you don't mind referencing the array twice:
ary = [1, 3, 5, 7, 9, 11]
ary.each_index { |i| ary[i] += 1 if i.between? 2, 4 }
#=> [1, 3, 6, 8, 10, 11]
Or if you don't want to iterate the whole array, this would work, too:
ary = [1, 3, 5, 7, 9, 11]
ary[2..4] = ary[2..4].map { |el| el + 1 }
ary
#=> [1, 3, 6, 8, 10, 11]