How can I split an array by delimiters in Ruby? - ruby

For example, if I have an array like this:
[:open, 1, :open, 2, 3, :close, 4, :close, :open, 5, :close]
I want to get this:
[[1, [2, 3], 4], [5]]
The :open effectively becomes [ and :close becomes ]

You could probably do this with a stack, but it's pretty easy to design recursively:
#!/usr/bin/env ruby
x = [:open, 1, :open, 2, 3, :close, 4, :close, :open, 5, :close]
def parse(list)
result = []
while list.any?
case (item = list.shift)
when :open
result.push(parse(list))
when :close
return result
else
result.push(item)
end
end
return result
end
puts parse(x).inspect
Note that this will destroy your original array. You should clone it before passing it in if you want to preserve it.

ar = [:open, 1, :open, 2, 3, :close, 4, :close, :open, 5, :close]
p eval(ar.inspect.gsub!(':open,', '[').gsub!(', :close', ']'))
#=> [[1, [2, 3], 4], [5]]

The same to steenslag, but a little cleaner
a = [:open, 1, :open, 2, 3, :close, 4, :close, :open, 5, :close]
eval(a.to_s.gsub(':open,','[').gsub(', :close',']'))
#=> [[1, [2, 3], 4], [5]]

Related

Why does changing a duped object alter the original? [duplicate]

temp gets #board.dup, and #board array is modified. However, temp gets modified as well! I have tried reading all the related documentations but still couldn't figure out an explanation.
class Test
def initialize
#board = [[1,2],[3,4], [5,6]]
end
def modify
temp = #board.dup #Also tried .clone
print 'temp: ';p temp
print '#board: ';p #board
#board.each do |x|
x << "x"
end
print "\ntemp: ";p temp
print '#board: ';p #board
end
end
x = Test.new
x.modify
Output:
temp: [[1, 2], [3, 4], [5, 6]]
#board: [[1, 2], [3, 4], [5, 6]]
temp: [[1, 2, "x"], [3, 4, "x"], [5, 6, "x"]] # <= Why did it change?
#board: [[1, 2, "x"], [3, 4, "x"], [5, 6, "x"]]
What can I do to ensure temp doesn't get modified?
You have Array with Arrays, so you dup the first array but, inside object point to the same instance. In this case you just modify the same source.
like here:
arr = [[1, 2, 3]]
arr2 = arr.dup
arr2[0] << 1
p arr
# => [[1, 2, 3, 1]]
p arr2
# => [[1, 2, 3, 1]]
So you must use dup for all array instance like this.
arr = [[1, 2, 3]]
arr3 = arr.map(&:dup)
arr3[0] << 1
p arr
# => [[1, 2, 3]]
p arr3
# => [[1, 2, 3, 1]]
In your case use this map.
class Test
def initialize
#board = [[1,2],[3,4], [5,6]]
end
def modify
temp = #board.map(&:dup) # dup all
print 'temp: ';p temp
print '#board: ';p #board
#board.each do |x|
x << "x"
end
print "\ntemp: ";p temp
print '#board: ';p #board
end
end
x = Test.new
x.modify
# temp: [[1, 2], [3, 4], [5, 6]]
# #board: [[1, 2], [3, 4], [5, 6]]
#
# temp: [[1, 2], [3, 4], [5, 6]]
# #board: [[1, 2, "x"], [3, 4, "x"], [5, 6, "x"]]
The reason is clone and dup produce a shallow copy. Thus the object ids of the inner arrays are still the same: those are the same arrays.
What you need is a deep clone. There is no built-in method in the standard library able to do that.
From the docs:
dup produces a shallow copy of obj—the instance variables of obj are copied, but not the objects they reference.
That said: You will need to make a deep-copy of your array.
When you are using the ActiveSupport gem (Rails) you can use its deep_dup method instead of just calling dup:
temp = #board.deep_dup
Without gems mashaling might be a easy solution to deep dup almost everything, without knowing about the internals of an object:
temp = Marshal.load(Marshal.dump(#board))
You can add a deep_dup method for nested arrays:
class Array
def deep_dup
map {|x| x.deep_dup}
end
end
# To handle the exception when deepest array contains numeric value
class Numeric
def deep_dup
self
end
end
class Test
def initialize
#board = [[1,2], [3,4], [5,6]]
end
def modify
temp = #board.deep_dup
...
end
end
x = Test.new
x.modify
# temp: [[1, 2], [3, 4], [5, 6]]
# #board: [[1, 2], [3, 4], [5, 6]]
# temp: [[1, 2], [3, 4], [5, 6]]
# #board: [[1, 2, "x"], [3, 4, "x"], [5, 6, "x"]]

Actual Hash getting modified when copy is modified in ruby

I am trying to copy a Hash, and then later modifying the copy of hash. But when I compare the copy with the original one, even the original hash value is getting modified.
I have tried using this:
def deep_copy(o)
Marshal.load(Marshal.dump(o))
end
h1 = {:a => 'foo'}
h2 = deep_copy(h1)
h1[:a] << 'bar'
p h2
I have also tried doing this:
def dumpable_hash(h)
return h unless h.default_proc
copy = h.clone
copy.default = nil # clear the default_proc
copy
end
Hash object(which I want to copy and keep its original unmodified):
#original = {0=>{0=>[0, 4, 5, 6], 2=>[3, 7], 1=>[1, 2]}, 1=>{0=>[0, 4, 5, 6], 2=>[1], 1=>[2, 3, 7]}, 2=>{0=>[0, 4, 6], 1=>[1, 2, 5], 2=>[3, 7]}, 3=>{0=>[0, 4], 2=>[1, 2, 3, 6, 7], 1=>[5]}, 4=>{0=>[4], 2=>[1, 5], 1=>[2, 3, 6, 7, 0]}, 5=>{1=>[0, 1, 2, 5], 2=>[3, 6, 7], 0=>[4]}, 6=>{1=>[0, 1, 2, 5, 4], 2=>[3, 6, 7], 0=>[]}}
Tried copying the original into another object, using the given answer also.
Method used for updating its clone,
#outer loop
(1..5).each do |i|
#assigning original to another object in every loop
copy = #original.clone
(-6..0).each do |row|
if copy[row.abs][0].include? k
copy[row.abs][0] -= [k]
copy[row.abs][1] += [k]
puts "row #{row.abs}, col #{k}"
break
end
end
end
When the loop is over both the original and copy are updated.
Please help, I have been trying this from an hour now.
If want to copy one hash to another you can do it just like this. Then you can manipulate the copied hash or even do it in the loop. And then manipulate the copied hash it for your task. In here it copies the key-value pair for the hash,
#original = {0=>{0=>[0, 4, 5, 6], 2=>[3, 7], 1=>[1, 2]}, 1=>{0=>[0, 4, 5, 6], 2=>[1], 1=>[2, 3, 7]}, 2=>{0=>[0, 4, 6], 1=>[1, 2, 5], 2=>[3, 7]}, 3=>{0=>[0, 4], 2=>[1, 2, 3, 6, 7], 1=>[5]}, 4=>{0=>[4], 2=>[1, 5], 1=>[2, 3, 6, 7, 0]}, 5=>{1=>[0, 1, 2, 5], 2=>[3, 6, 7], 0=>[4]}, 6=>{1=>[0, 1, 2, 5, 4], 2=>[3, 6, 7], 0=>[]}}
copy = Hash.new
#original.each do |k, v|
copy[k] = v.dup
end
p copy #prints the copied hash
I think you need to do deep_dup here to completely separate one hash content from another.
h1 = {a: "foo"}
h2 = h1.deep_dup
h2[:a] << "bar"
puts h2 #returns {:a => "foobar"}
puts h1 # returns {:a => "foo"}
Use dup.
h1 = {a:1, b:2}
h2 = h1.dup
h2[:c] = 3
puts h1
{:a=>1, :b=>2}
puts h2
{:a=>1, :b=>2, :c=>3}
If you have a nested hash, you can use ActiveSupport deep_dup.
def deep_dup
each_with_object(dup) do |(key, value), hash|
hash[key.deep_dup] = value.deep_dup
end
end
You are modifying the original hash (Appending to h1 hash). Modify the deep copied one and you can see the original is staying as before,
def deep_copy(o)
Marshal.load(Marshal.dump(o))
end
h1 = {:a => 'foo'}
h2 = deep_copy(h1)
h2[:a] << 'bar'
p h2 #prints the cloned one
p h1 #prints the original one
See this for futher information about marshaling library here.

Mapping enumerators

Using an Enumerator in Ruby is pretty straightforward:
a = [1, 2, 3]
enumerator = a.map
enumerator.each(&:succ) # => [2, 3, 4]
But can I do something similar with nested collections?
a = [[1, 2, 3], [4, 5, 6]]
a.map(&:map) # => [#<Enumerator: [1, 2, 3]:map>, #<Enumerator: [4, 5, 6]:map>]
But now how do I get [[2, 3, 4], [5, 6, 7]]?
This could always be done with a block:
a = [[1, 2, 3], [4, 5, 6]]
a.map { |array| array.map(&:succ) } # => [[2, 3, 4], [5, 6, 7]]
But I was wondering if there was a way that avoided the use of a block, partly because I find it annoying to have to type |array| array and also partly because I'm curious to find a way to do it.
Ideally, it would feel like this psuedocode:
a.map.map(&:succ)
# perhaps also something like this
a.map(&:map).apply(&:succ)
The only way I know of doing this is to do the following:
a = [[1, 2, 3], [4, 5, 6]]
a.map { |b| b.map(&:succ) } # => [[2, 3, 4], [5, 6, 7]]
Mainly because of the combination of Array#map/Enumerable#map and Symbol#to_proc, you cannot pass a second variable to the block that #map yields for, and thus pass another variable to the inner #map:
a.map(1) { |b, c| c } # c => 1, but this doesn't work :(
So you have to use the block syntax; Symbol#to_proc actually returns a proc that takes any number of arguments (you can test this by doing :succ.to_proc.arity, which returns -1). The first argument is used as the receiver, and the next few arguments are used as arguments to the method - this is demonstrated in [1, 2, 3].inject(&:+). However,
:map.to_proc.call([[1, 2, 3], [4, 5, 6]], &:size) #=> [3, 3]
How? :map.to_proc creates this:
:map.to_proc # => proc { |receiver, *args, &block| receiver.send(:map, *args, &block) }
This is then called with the array of arrays as an argument, with this block:
:size.to_proc # => proc { |receiver, *args, &block| receiver.send(:size, *args, &block) }
This results in .map { |receiver| receiver.size } being effectively called.
This all leads to this - since #map doesn't take extra arguments, and passes them to the block as parameters, you have to use a block.
To my knowledge there is no specific implementation as per the way you requested it.
You could just create a recursive function to handle this such as:
def map_succ(a)
a.map {|arr| arr.is_a?(Array) ? map_succ(arr) : arr.succ}
end
Then it will work no matter how deeply nested the Array's are (caveat if the elements do not respond to #succ this will fail).
If you really wanted to you could monkey_patch Array (IN NO WAY RECOMMENDED)
#note if the element does not respond to `#succ` I have nullified it here
class Array
def map_succ
map do |a|
if a.is_a?(Array)
a.map_succ
elsif a.respond_to?(:succ)
a.succ
#uncomment the lines below to return the original object in the event it does not respond to `#succ`
#else
#a
end
end
end
end
Example
a = [[1, 2, 3], [4, 5, 6], [7, 8, 9, [2, 3, 4]], {"test"=>"hash"}, "F"]
a.map_succ
#=> [[2, 3, 4], [5, 6, 7], [8, 9, 10, [3, 4, 5]], nil, "G"]
The nil is because Hash does not have a #succ method.
UPDATE
Based on this SO Post a similar syntax could be supported but note that recursion is still probably your best bet here so that you can support any depth rather than an explicit one.
#taken straight from #UriAgassi's from post above
class Symbol
def with(*args, &block)
->(caller, *rest) { caller.send(self, *rest, *args, &block) }
end
end
Then
a = [[1,2,3],[4,5,6]]
a.map(&:map.with(&:succ))
#=> [[2, 3, 4], [5, 6, 7]]
a << [7,8,[9,10]]
#=> [[2, 3, 4], [5, 6, 7],[7,8,[9,10]]]
a.map(&:map.with(&:succ))
#=> NoMethodError: undefined method `succ' for [9, 10]:Array

Built in way of doing successive Ruby evals on the same object

Edit: I just realized my prior example was bad.
Borrowing #ZachKemp's idea (and changing it):
class Object
def eval_multi(*methods)
#methods.inject(self) { |memo, m| memo.send(m) }
methods.inject(self) { |memo, m| eval("#{memo}.#{m}") }
end
end
[1,2,3].eval_multi("product([4,5,6])", :transpose)
=> [[1, 1, 1, 2, 2, 2, 3, 3, 3], [4, 5, 6, 4, 5, 6, 4, 5, 6]]
I'm wondering if there's a built-in way of doing this without having to write the eval_multi method above.
You can do this with inject:
module Enumerable
def multimap(*methods)
methods.inject(self){|result, method| result.map(&method) }
end
end
arr = ["1 a", "1 b", "2 c", "2 a"]
arr.multimap(:split, :reverse, :join)
#=> ["a1", "b1", "c2", "a2"]
(I renamed the method because eval already has another meaning in Ruby).
You could just use one map, simple and clean.
arr.map{ |o| o.split.reverse.join }
You can do it with a little change in the way you pass the arguments.
[[:product, [4,5,6]], [:transpose]].inject([1,2,3]){|m, a| m.send(*a)}
# => [[1, 1, 1, 2, 2, 2, 3, 3, 3], [4, 5, 6, 4, 5, 6, 4, 5, 6]]
Or, by modifying send a little bit, you can do it like this:
class Object
def send_splat a; send(*a) end
end
[[:product, [4,5,6]], [:transpose]].inject([1,2,3], &:send_splat)
# => [[1, 1, 1, 2, 2, 2, 3, 3, 3], [4, 5, 6, 4, 5, 6, 4, 5, 6]]

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