How to group adjacent numbers that are the same - ruby

I need to pack if there are at least two adjacent numbers which are same in the format <number : number_of_occurrences >.
This is my input:
[2,2,2,3,4,3,3,2,4,4,5]
And the expected output:
"2:3,3,4,3:2,2,4:2,5"
So far I tried:
a = [1, 1, 1, 2, 2, 3, 2, 3, 4, 4, 5]
a.each_cons(2).any? do |s , t|
if s == t
If it's equal try a counter maybe, but thats not working.

You can use Enumerable#chunk_while (if you're on Ruby >= 2.3):
a.chunk_while { |a, b| a == b }
.flat_map { |chunk| chunk.one? ? chunk.first : "#{chunk.first}:#{chunk.size}" }
.join(',')
#=> "2:3,3,4,3:2,2,4:2,5"
You can also use Enumerable#chunk (Ruby ~1.9.3, maybe earlier):
a.chunk(&:itself)
.flat_map { |_, chunk| chunk.one? ? chunk.first : "#{chunk.first}:#{chunk.size}" }
.join(',')
#=> "2:3,3,4,3:2,2,4:2,5"

You could chunk elements together when they're equal, you could also slice the array between elements that are distinct (slice_when has been added in Ruby 2.2 ):
[2, 2, 2, 3, 4, 3, 3, 2, 4, 4, 5].slice_when { |a, b| a != b }.map do |ints|
if ints.size == 1
ints[0]
else
"#{ints[0]}:#{ints.size}"
end
end.join(',')
# "2:3,3,4,3:2,2,4:2,5"
It's mostly a matter of taste, both methods can achieve perfectly similar results, just like select and reject.

arr = [2, 2, 2, 3, 4, 3, 3, 2, 4, 4, 5]
arr.drop(1).each_with_object([[arr.first, 1]]) do |e,a|
a.last.first == e ? a[-1][-1] += 1 : a << [e, 1]
end.map { |a| a.join(':') }.join(',')
#=> "2:3,3:1,4:1,3:2,2:1,4:2,5:1"

Related

Extract items between 2 numbers in 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.

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]
# ]

Duplicate elements of array in ruby

I find a lot of reference about removing duplicates in ruby but I cannot find how to create duplicate.
If I have an array like [1,2,3] how can I map it to an array with dubbed items? [1,1,2,2,3,3]
Is there a method?
Try this one
[1, 2, 3].flat_map { |i| [i, i] }
=> [1, 1, 2, 2, 3, 3]
Here's yet another way, creating the array directly with Array#new :
array = [1, 2, 3]
repetitions = 2
p Array.new(array.size * repetitions) { |i| array[i / repetitions] }
# [1, 1, 2, 2, 3, 3]
According to fruity, #ursus's answer, #ilya's first two answers and mine have comparable performance. transpose.flatten is slower than any of the others.
#Ursus answer is the most clean, there are possible solutions:
a = [1, 2, 3]
a.zip(a).flatten
#=> [1, 1, 2, 2, 3, 3]
Or
a.inject([]) {|a, e| a << e << e} # a.inject([]) {|a, e| n.times {a << e}; a}
=> [1, 1, 2, 2, 3, 3]
Or
[a, a].transpose.flatten # ([a] * n).transpose.flatten
=> [1, 1, 2, 2, 3, 3]
Try this:
[1, 2, 3] * 2
=> [1, 2, 3, 1, 2, 3]
You might want it sorted:
([1, 2, 3] * 2).sort
=> [1, 1, 2, 2, 3, 3]

Find combinations in Ruby that are less than a certain number

Say I have an array [1,2,3] and I want every combination of these numbers that don't exceed 4. So I would have [1,2,3].someMethod(4) and it would give me:
[1,1,1,1]
[1,1,2]
[1,3]
[2,2]
So far I have:
(1..4).flat_map{|size| [1,2,3].repeated_combination(size).to_a }
but this gives me every possible combinations, including the ones that exceed my given limit. Is there an good way to either only get combinations that add up to my limit?
arr = [1,2,3]
(arr+[0]).repeated_combination(4).select { |a| a.reduce(:+) == 4 }.map { |a| a - [0] }
#=> [[1, 3], [2, 2], [1, 1, 2], [1, 1, 1, 1]]
Change == to <= if desired.
This answer, like the others, assumes arr contains natural numbers, including 1.
results = (1..4).each.with_object([]) do |size, results|
[1,2,3].repeated_combination(size) do |combo|
results << combo if combo.reduce(:+) == 4
end
end
p results
--output:--
[[1, 3], [2, 2], [1, 1, 2], [1, 1, 1, 1]]
Parameterizing the algorithm:
def do_stuff(values, target_total)
(1..target_total).each.with_object([]) do |size, results|
values.repeated_combination(size) do |combo|
results << combo if combo.reduce(:+) == 4
end
end
end
p do_stuff([1, 2, 3], 4)
You can filter out the arrays you don't want by using the select method. Just select all the arrays that have a sum == 4 (the sum is calculated by the inject method).
all_arrs = (1..4).flat_map do |size|
[1,2,3].repeated_combination(size).to_a
end
valid_arrs = all_arrs.select do |arr|
arr.inject { |a, b| a + b } == 4
end
print valid_arrs
# Output:
# [[1, 3], [2, 2], [1, 1, 2], [1, 1, 1, 1]]
A recursive approach.
def some_method(a, n)
return [[]] if n == 0
a.select { |e| e <= n }.\
flat_map { |e| some_method(a,n-e).map { |es| ([e] + es).sort } }.\
sort.\
uniq
end
p some_method([1,2,3], 4)
# => [[1, 1, 1, 1], [1, 1, 2], [1, 3], [2, 2]]
EDIT: Here is another recursive version without filtering duplicates but with opposite order. I added comments to make it clearer.
def some_method(a, n)
return [[]] if n == 0 # bottom (solution) found
return [] if a.empty? || n < 0 # no solution
max = a.max
# search all solutions with biggest value
l = some_method(a, n-max).map { |e| [max] + e }
# search all solutions without biggest value
r = some_method(a-[max],n)
l + r
end
p some_method([1,2,3], 4)
# => [[3, 1], [2, 2], [2, 1, 1], [1, 1, 1, 1]]

Get items from Ruby Array that occur 2 or more times

Let's say I have a Ruby array.
[1,2,3,4,4,5,6,6,7,7]
I want to find the values that occur 2 or more times.
[4,6,7]
It will help my process to first determine which items occur only once then remove those. So I'd like to solve this by first finding the items that occur once.
There are probably better ways, but this is one:
> [1,2,3,4,4,5,6,6,7,7].group_by{|i| i}.reject{|k,v| v.size == 1}.keys
=> [4, 6, 7]
Breaking it down:
> a = [1,2,3,4,4,5,6,6,7,7]
=> [1, 2, 3, 4, 4, 5, 6, 6, 7, 7]
> a1 = a.group_by{|i| i}
=> {1=>[1], 2=>[2], 3=>[3], 4=>[4, 4], 5=>[5], 6=>[6, 6], 7=>[7, 7]}
> a2 = a1.reject{|k,v| v.size == 1}
=> {4=>[4, 4], 6=>[6, 6], 7=>[7, 7]}
> a2.keys
=> [4, 6, 7]
Everyone loves a really difficult to follow one liner :)
[1,2,3,4,4,5,6,6,7,7].each_with_object(Hash.new(0)) { |o, h| h[o] += 1 }.select { |_, v| v > 1 }.keys
Add some white space and some comments
[1,2,3,4,4,5,6,6,7,7].each_with_object(Hash.new(0)) { |o, h|
h[o] += 1
}.select { |_, v|
v > 1
}.keys
Enumerate and pass in our memo hash to each iteration the Hash defaults to having 0 for any key
Increment counter for the object
Select only key value pairs where the value is greater than 1
Grab just the keys
This looks quite similar to Phillip's neat answer - in theory this should use slightly less memory as it will not have to build the intermediate arrays to perform counting
Another way:
a = [1,2,3,4,4,5,6,6,7,7]
au = a.uniq
a.reject { |i| au.delete(i) }
#=> [4, 6, 7]
If efficiency is important, you could use a set:
require 'set'
s = Set.new
a.reject { |e| s.add?(e) }
#=> [4, 6, 7]
You can use Array#select to return the elements where Array#count is greater than 1:
2.1.2 :005 > arr = [1,2,3,4,4,5,6,6,7,7]
=> [1, 2, 3, 4, 4, 5, 6, 6, 7, 7]
2.1.2 :006 > arr.select { |e| arr.count(e) > 1 }.uniq
=> [4, 6, 7]
Hope this helps

Resources