How do you check an array for a range in Ruby? - ruby

I'm writing a poker program, and I can't figure out how to handle straights.
Straight: All cards in a hand of 5 cards are consecutive values.
ex. 2..6, 3..7, 4..8, 5..9, 6..T, 7..J, 8..Q, 9..K, T..A
cards = [2, 3, 4, 5, 6, 7, 8, 9, "T", "J", "Q", "K", "A"]
How can I check a hand, which is an array, for these combinations? Preferably I can check it to see if it's 5 in a row in the cards array.

Edit 2: This is my absolutely final solution:
require 'set'
STRAIGHTS = ['A',*2..9,'T','J','Q','K','A'].each_cons(5).map(&:to_set)
#=> [#<Set: {"A", 2, 3, 4, 5}>, #<Set: {2, 3, 4, 5, 6}>,
# ...#<Set: {9, "T", "J", "Q", "K"}>, #<Set: {"T", "J", "Q", "K", "A"}>]
def straight?(hand)
STRAIGHTS.include?(hand.to_set)
end
STRAIGHTS.include?([6,3,4,5,2].to_set)
# STRAIGHTS.include?(#<Set: {6, 3, 4, 5, 2}>)
#=> true
straight?([6,5,4,3,2]) #=> true
straight?(["T","J","Q","K","A"]) #=> true
straight?(["A","K","Q","J","T"]) #=> true
straight?([2,3,4,5,"A"]) #=> true
straight?([6,7,8,9,"J"]) #=> false
straight?(["J",7,8,9,"T"]) #=> false
Edit 1: #mudasobwa upset the apple cart by pointing out that 'A',2,3,4,5 is a valid straight. I believe I've fixed my answer. (I trust he's not going to tell me that 'K','A',2,3,4 is also valid.)
I would suggest the following:
CARDS = [2, 3, 4, 5, 6, 7, 8, 9, "T", "J", "Q", "K", "A"]
STRAIGHTS = CARDS.each_cons(5).to_a
#=>[[2, 3, 4, 5, 6], [3, 4, 5, 6, 7], [4, 5, 6, 7, 8],
# [5, 6, 7, 8, 9], [6, 7, 8, 9, "T"], [7, 8, 9, "T", "J"],
# [8, 9, "T", "J", "Q"], [9, "T", "J", "Q", "K"],
# ["T", "J", "Q", "K", "A"]]
def straight?(hand)
(hand.map {|c| CARDS.index(c)}.sort == [0,1,2,3,12]) ||
STRAIGHTS.include?(hand.sort {|a,b| CARDS.index(a) <=> CARDS.index(b)})
end

If we map each card to a value (9 is 9, "T" is 10, "J" is 11, etc.), then there are two facts that are true of all straights that we can use to solve our problem:
All straights have exactly five unique card values
The difference between the last and first cards' values is always 4
And so:
CARD_VALUES = {
2 => 2, 3 => 3, 4 => 4,
5 => 5, 6 => 6, 7 => 7,
8 => 8, 9 => 9, "T" => 10,
"J" => 11, "Q" => 12, "K" => 13,
"A" => 14
}
def is_straight?(hand)
hand_sorted = hand.map {|card| CARD_VALUES[card] }
.sort.uniq
hand_sorted.size == 5 &&
(hand_sorted.last - hand_sorted.first) == 4
end
This method (1) converts each card to its numeric value with map, then (2) sorts them, and then (3) throws out duplicates with uniq. To illustrate with various hands:
hand | 4 A T A 2 | 2 2 3 3 4 | 5 6 4 8 7 | 3 6 2 8 7
---------+--------------------+--------------------+--------------------+----------------
1. map | 4 14 10 14 2 | 2 2 3 3 4 | 5 6 4 8 7 | 3 6 2 8 7
2. sort | 2 4 10 14 14 | 2 2 3 3 4 | 4 5 6 7 8 | 2 3 6 7 8
3. uniq | 2 4 10 14 | 2 3 4 | 4 5 6 7 8 | 2 3 6 7 8
Alternatively...
I originally posted the following solution, which isn't bad, but is definitely more convoluted:
If the hand is sorted, this is easy. You can use Enumerable#each_cons to check each possible straight.
CARDS = [ 2, 3, 4, 5, 6, 7, 8, 9, "T", "J", "Q", "K", "A" ]
hand = [ 4, 5, 6, 7, 8 ]
def is_straight?(hand)
CARDS.each_cons(5).any? do |straight|
hand == straight
end
end
if is_straight?(hand)
puts "Straight!"
else
puts "Not straight!"
end
# => Straight!
each_cons(5) returns each consecutive set of 5 items, so in the above example hand is first compared to [ 2, 3, 4, 5, 6 ], then [ 3, 4, 5, 6, 7 ], and then [ 4, 5, 6, 7, 8 ], which is a match, so any? returns true.
Note that this is not the most efficient solution, but unless you need to check many thousands of hands per second, this is more than adequately performant.
If your hands aren't sorted yet, you'll need to do that first. The simplest way to do that is create a Hash that maps cards to a numeric value (as above) and then use sort_by:
def sort_hand(hand)
hand.sort_by {|card| CARD_VALUES[card] }
end
hand = [ 4, "A", 2, "A", "T" ]
sort_hand(hand)
# => [ 2, 4, "T", "A", "A" ]

I did not want to participate, but I can’t keep silence looking at all these oversophisticated solutions around.
hand = [2, 5, 7, 'A', 'J'].map(&:to_s)
'23456789TJQKA' =~ hand.sort_by{|hc| '23456789TJQKA'.index(hc)}.join ||
'A23456789TJQK' =~ hand.sort_by{|hc| 'A23456789TJQK'.index(hc)}.join
In a not lame hardcoded manner:
suit = '23456789TJQKA'
suit =~ hand.sort_by{|hc| suit.index(hc)}.join ||
suit.rotate(-1) =~ hand.sort_by{|hc| suit.rotate(-1).index(hc)}.join

Generate list of valid hands:
valid_hands = cards[0..8].each_with_index.map{|b,i| cards[i..i+4]}
#=> [[2, 3, 4, 5, 6], [3, 4, 5, 6, 7], [4, 5, 6, 7, 8], [5, 6, 7, 8, 9], [6, 7, 8, 9, "T"], [7, 8, 9, "T", "J"], [8, 9, "T", "J", "Q"], [9, "T", "J", "Q", "K"], ["T", "J", "Q", "K", "A"]]
Once you have the list of all valid hands, you can now check if provided hand is among any? of them (valid ones) or not:
if valid_hands.any? { |h| (h - hand).empty? }
puts "Valid hand"
else
puts "Not Valid"
end
UPDATE
In-case 2, 3, 4, 5, "A", 2, 3, 4, "K", "A", 2, 3, "Q", "K", "A", 2, "J", "Q", "K", "A" are also considered as valid hands, calculate them as follows:
valid_hands = cards.each_with_index.map { |b,i| i < 9 ? cards[i..i+4] : cards[0..i-9] + cards[i..-1] }
# => [[2, 3, 4, 5, 6], [3, 4, 5, 6, 7], [4, 5, 6, 7, 8], [5, 6, 7, 8, 9], [6, 7, 8, 9, "T"], [7, 8, 9, "T", "J"], [8, 9, "T", "J", "Q"], [9, "T", "J", "Q", "K"], ["T", "J", "Q", "K", "A"], [2, "J", "Q", "K", "A"], [2, 3, "Q", "K", "A"], [2, 3, 4, "K", "A"], [2, 3, 4, 5, "A"]]

I recommend writing classes to represent a Card (and maybe Deck and Hand too). Aim for an interface like this:
deck = Deck.new.shuffle!
hand = Hand.new(deck.draw 5)
hand.straight?
#=>false
puts hand
8♣ 8♦ T♠ 2♦ 7♦
The encapsulation of functionality gives you readability and makes it easy to extend (i.e. with suits)
Here's a more simplistic version, implemented as a single Card class. I did add suits though.
class Card
include Enumerable #enables sorting
attr_accessor :value, :suit
#values = [2, 3, 4, 5, 6, 7, 8, 9, "T", "J", "Q", "K", "A"]
#suits = ["♣","♦","♥","♠"]
def self.all
#values.product(#suits).map{|c| Card.new c}
end
def self.straight?(cards)
["A", *#values].each_cons(5).include?(cards.map(&:value))
end
def self.flush?(cards)
cards.map(&:suit).uniq.size == 1
end
def initialize(v)
#value, #suit = *v
end
def <=>(other) #for sorting
#values.index(value) <=> #values.index(other.value)
end
def to_s
"#{value}#{suit}"
end
end
This works as follows
deck = Card.all
puts deck
#=> 2♣ 2♦ 2♥ 2♠ 3♣ 3♦ 3♥ 3♠ 4♣ 4♦ 4♥ 4♠ 5♣ 5♦ 5♥ 5♠ 6♣ 6♦ 6♥ 6♠ 7♣ 7♦ 7♥ 7♠ 8♣ 8♦ 8♥ 8♠ 9♣ 9♦ 9♥ 9♠ T♣ T♦ T♥ T♠ J♣ J♦ J♥ J♠ Q♣ Q♦ Q♥ Q♠ K♣ K♦ K♥ K♠ A♣ A♦ A♥ A♠
hand = deck.sample 5
puts hand
#=> Q♥ 6♦ 2♣ T♠ Q♦
Card.straight?(hand)
#=>false

Step 0: Let's start with an empty class
class CardUtils
end
Step 1: Store values of card in Hash
Hash allows fast referencing of values of a card.
##card_values = {
'A' => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5,
6 => 6, 7 => 7, 8 => 8, 9 => 9, 'T' => 10,
'J' => 11, 'Q' => 12, 'K' => 13
}
Thus, you can reference the card value simply as below.
##card_values['A']
# => 1
##card_values[8]
# => 8
Step 2: Sort the hand
Apply sort! method to the hand with reference to the card values.
def self.sort(hand)
hand.sort {|x,y| ##card_values[x] <=> ##card_values[y]}
end
# => ["A", 2, 3, 4, 5, 6, 7, 8, 9, "T", "J", "Q", "K"]
Step 3: Function that tells whether two cards are consecutive
def self.is_consecutive(x, y)
val_x = ##card_values[x]
val_y = ##card_values[y]
val_x == val_y - 1 || val_x + 13 == val_y
end
# is_consecutive('A', 2)
# => true
# is_consecutive('K', 'A')
# => true
# is_consecutive('A', 3)
# => false
Step 4: Check for 'straight'
It could be done with simple iteration.
def self.has_straight(hand)
hand = sort(hand)
max_consecutive_count = 0
consecutive_count = 0
hand.each_with_index do |curr, i|
prev = hand[i - 1]
if is_consecutive(prev, curr) then
consecutive_count += 1
else
consecutive_count = 0
end
if consecutive_count > max_consecutive_count then
max_consecutive_count = consecutive_count
end
end
max_consecutive_count >= 5
end
# hand = [2, 3, 4, 5, 6, 7, 8, 9, "T", "J", "Q", "K", "A"]
# CardUtils.has_straight(hand)
# => true
Final Result
class CardUtils
##card_values = {
'A' => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5,
6 => 6, 7 => 7, 8 => 8, 9 => 9, 'T' => 10,
'J' => 11, 'Q' => 12, 'K' => 13
}
def self.is_consecutive(x, y)
val_x = ##card_values[x]
val_y = ##card_values[y]
val_x == val_y - 1 || val_x + 13 == val_y
end
def self.sort(hand)
hand.sort {|x,y| ##card_values[x] <=> ##card_values[y]}
end
def self.has_straight(hand)
hand = sort(hand)
max_consecutive_count = 0
consecutive_count = 0
hand.each_with_index do |curr, i|
prev = hand[i - 1]
if is_consecutive(prev, curr) then
consecutive_count += 1
else
consecutive_count = 0
end
if consecutive_count > max_consecutive_count then
max_consecutive_count = consecutive_count
end
end
max_consecutive_count >= 5
end
end

This is how I would write it:
hand = [3,4,5,2,'A']
def is_straight(hand)
# No need to check further if we do not have 5 unique cards.
return false unless hand.uniq.size == 5
# Note the A at beginning AND end to count A as 1 or 14.
list_of_straights = 'A23456789TJQKA'.chars.each_cons(5)
sorted_hand = hand.map(&:to_s).sort
list_of_straights.any? do |straight|
straight.sort==sorted_hand
end
end
puts is_straight(hand) #=> true
Alternatively if you do not like all the sorting you could exchange the last part to:
hand_as_stings = hand.map(&:to_s)
list_of_straights.any? do |straight|
(straight-hand_as_stings).empty?
end

Related

How to group array elements by index?

I have the array arr which I want to group by indexes given in array idx. I mean,
sub array 1 will end at index 1
sub array 2 will end at index 5
sub array 3 will end at index 7
sub array N will be formed from element at index 8 to last element
of arr
With my current code I can group the first sub array with first index of idx idx[0] = 1.
Then, How to replicate for all indexes within array idx? Thanks in advance.
My current code and output is this:
idx = [1,5,7]
arr = ['a','b','c','d','e','f','g','h','i','j','k']
arr.group_by.with_index { |z, i| i <= idx[0] }.values
=> [["a", "b"], ["c", "d", "e", "f", "g", "h", "i", "j", "k"]]
and my desired output is like this:
output --> [["a", "b"], ["c", "d", "e", "f"], ["g", "h"], ["i", "j", "k"]]
#Indexes --> 0 1 2 3 4 5 6 7 8 9 10
You can use slice_after to slice the array after each item whose index is in idx:
idx = [1, 5, 7]
arr = %w[a b c d e f g h i j k]
arr.enum_for(:slice_after).with_index { |_, i| idx.include?(i) }.to_a
#=> [["a", "b"], ["c", "d", "e", "f"], ["g", "h"], ["i", "j", "k"]]
That enum_for is (unfortunately) needed to chain slice_after and with_index.
Another solution
idx = [1, 5, 7]
arr = ['a','b','c','d','e','f','g','h','i','j','k']
from = 0
arr.map.with_index { |a, i|
if idx.include?(i)
result = arr[from..i]
from = i + 1
end
result
}.compact
=> [["a", "b"], ["c", "d", "e", "f"], ["g", "h"]]
arr = [9, 3, 1, 6, 2, 4, 0, 1, 5, 8]
end_idx = [1, 5, 7]
[-1, *end_idx, arr.size-1].uniq.each_cons(2).
map { |s,e| arr.values_at(s+1..e) }
#=> [[9, 3], [1, 6, 2, 4], [0, 1], [5, 8]]
The steps are as follow:
a = [-1, *end_idx, arr.size-1]
#=> [-1, 1, 5, 7, 9]
b = a.uniq
#=> [-1, 1, 5, 9]
c = b.each_cons(2)
#=> #<Enumerator: [-1, 1, 5, 7, 9]:each_cons(2)>
c.map { |s,e| arr.values_at(s+1..e) }
#=> [[9, 3], [1, 6, 2, 4], [0, 1], [5, 8]]
One can see the elements generated and passed to map by the enumerator c by converting c to an array.
c.to_a
#=> [[-1, 1], [1, 5], [5, 7], [7, 9]]
See Array#values_at.

Find all repeating non-overlapping substrings and cycles

I have a complex problem of string manipulation at hand.
I have a string in which I will have cycles, as well as recurrences which I need to identify and list down.
'abcabcabcabcabcdkkabclilabcoabcdieabcdowabcdppabzabx'
Following are the possible patterns ->
Actual indexes not used
abc -> 0,3,6,9,12,15,17, ..... (occurence index for recurring string),
0,3,6,9 (unique_occurence index for recurring string, 12, 15, 17
disqualified as there abc was a part of longer repeating substring)
abcd -> 12, 15, 17 (occurence index for recurring string), 12, 15, 17
(unique occurence index for recurring string)
bcda -> 13, 16, 18.. (occurence index for recurring string), (unique occurence index for recurring string) as it is an overlap for
the string abcd Hence it is something not required ab ->
0,3,6,9,12,15,17, 25, 27 ...(occurence index for recurring string),
25, 27(unique occurence index for recurring string). .....
I want to find all unique recurring occurences/recurrences, i.e. All Unique, Non-Overlapping values of recurring string. As mentioned above. And the input string may contain,
ALL cyclic patterns(abcabcabcdefdefdeflkjlkjlkj => abc, def, lkj are recurrences in cycle, but bc, ab, bcab are not expected as they are outcomes of false positives)
OR
Separately recurring patterns(abcxabcdabcm => abc is recurrence but not cycle, i.e. they are not adjecent)
Or
A mix of both(abcabcabcabcabclkabcdokabcdhuabcd => abc is a cyclic recurrence, and abcd is a non cyclic recurrence and we need to find both -> only abcd, abc are recurring, not bc, ab, bcda, etc)
Can someone propose a solution algo for this problem statement. I am trying using suffix_arrays which is not finding overlapping results as well.
A hash is constructed whose keys consist of all unique substrings of a given string that appear at least twice in the string (not overlapping) and, for each key, the value is an array of all offsets into the string where the value of the key (a substring) begins.
Code
def recurring_substrings(str)
arr = str.chars
(1..str.size/2).each_with_object({}) do |n,h|
arr.each_cons(n).map { |b| b.join }.uniq.each do |s|
str.scan(Regexp.new(s)) { (h[s] ||= []) << Regexp.last_match.begin(0) }
end
end.reject { |_,v| v.size == 1 }
end
Examples
recurring_substrings 'abjkabrjkab'
#=> {"a"=>[0, 4, 9], "b"=>[1, 5, 10], "j"=>[2, 7], "k"=>[3, 8], "ab"=>[0, 4, 9],
# "jk"=>[2, 7], "ka"=>[3, 8], "jka"=>[2, 7], "kab"=>[3, 8], "jkab"=>[2, 7]}
recurring_substrings "abcabcabcabcabcdkkabclilabcoabcdieabcdowabcdppabzabx"
#=> {"a"=>[0, 3, 6, 9, 12, 18, 24, 28, 34, 40, 46, 49],
# "b"=>[1, 4, 7, 10, 13, 19, 25, 29, 35, 41, 47, 50],
# "c"=>[2, 5, 8, 11, 14, 20, 26, 30, 36, 42], "d"=>[15, 31, 37, 43],
# "k"=>[16, 17], "l"=>[21, 23], "i"=>[22, 32], "o"=>[27, 38], "p"=>[44, 45],
# "ab"=>[0, 3, 6, 9, 12, 18, 24, 28, 34, 40, 46, 49],
# "bc"=>[1, 4, 7, 10, 13, 19, 25, 29, 35, 41], "ca"=>[2, 5, 8, 11],
# "cd"=>[14, 30, 36, 42],
# "abc"=>[0, 3, 6, 9, 12, 18, 24, 28, 34, 40], "bca"=>[1, 4, 7, 10],
# "cab"=>[2, 5, 8, 11], "bcd"=>[13, 29, 35, 41],
# "abca"=>[0, 6], "bcab"=>[1, 7], "cabc"=>[2, 8], "abcd"=>[12, 28, 34, 40],
# "abcab"=>[0, 6], "bcabc"=>[1, 7], "cabca"=>[2, 8],
# "abcabc"=>[0, 6], "bcabca"=>[1, 7], "cabcab"=>[2, 8]}
Explanation
For the first example above, the steps are as follows.
str = 'abjkabrjkab'
arr = str.chars
#=> ["a", "b", "j", "k", "a", "b", "r", "j", "k", "a", "b"]
q = str.size/2 # max size for string to repeat at least once
#=> 5
b = (1..q).each_with_object({})
#=> #<Enumerator: 1..5:each_with_object({})>
We can see which elements will be generated by this enumerator by converting it to an array. (I will do this a few more times below.)
b.to_a
#=> [[1, {}], [2, {}], [3, {}], [4, {}], [5, {}]]
The empty hashes will be built up as calculations progress.
Next pass the first element to the block and set the block variables to it using parallel assignment (sometimes called multiple assignment).
n,h = b.next
#=> [1, {}]
n #=> 1
h #=> {}
c = arr.each_cons(n)
#=> #<Enumerator: ["a", "b", "j", "k", "a", "b", "r", "j", "k", "a", "b"]:each_cons(1)>
c is an array of all substrings of length 1. At the next iteration it will be an array of all substrings of length 2 and so on. See Emumerable#each_cons.
c.to_a # Let's see which elements will be generated.
#=> [["a"], ["b"], ["j"], ["k"], ["a"], ["b"], ["r"], ["j"], ["k"], ["a"], ["b"]]
d = c.map { |b| b.join }
#=> ["a", "b", "j", "k", "a", "b", "r", "j", "k", "a", "b"]
e = d.uniq
#=> ["a", "b", "j", "k", "r"]
At the next iteration this will be
r = arr.each_cons(2)
#=> #<Enumerator: ["a", "b", "j", "k", "a", "b", "r", "j", "k", "a", "b"]:
# each_cons(2)>
r.to_a
#=> [["a", "b"], ["b", "j"], ["j", "k"], ["k", "a"], ["a", "b"],
# ["b", "r"], ["r", "j"], ["j", "k"], ["k", "a"], ["a", "b"]]
s = r.map { |b| b.join }
#=> ["ab", "bj", "jk", "ka", "ab", "br", "rj", "jk", "ka", "ab"]
s.uniq
#=> ["ab", "bj", "jk", "ka", "br", "rj"]
Continuing,
f = e.each
#=> #<Enumerator: ["a", "b", "j", "k", "r"]:each>
f.to_a # Let's see which elements will be generated.
#=> ["a", "b", "j", "k", "r"]
s = f.next
#=> "a"
r = (Regexp.new(s))
#=> /a/
str.scan(r) { (h[s] ||= []) << Regexp.last_match.begin(0) }
If h does not yet have a key s, h[s] #=> nil. h[s] ||= [], which expands to h[s] = h[s] || [], converts h[s] to an empty array before executing h[s] << Regexp.last_match.begin(0). That is, h[s] = h[s] || [] #=> nil || [] #=> [].
Within the block the MatchData object is retrieved with the class method Regexp::last_match. (Alternatively, one could substitute the global variable $~ for Regexp.last_match. For details, search for "special global variables" at Regexp.) MatchData#begin returns the index of str at which the current match begins.
Now
h #=> {"a"=>[0, 4, 9]}
The remaining calculations are similar, adding key-value pairs to h until the has given in the example has been constructed.
For further processing after #CarySwoveland's excellent answer :
def ignore_smaller_substrings(hash)
found_indices = []
new_hash = {}
hash.sort_by{|s,_| [-s.size,s]}.each{|s,indices|
indices -= found_indices
found_indices |= indices
new_hash[s]=indices unless indices.empty?
}
new_hash
end
pp ignore_smaller_substrings(recurring_substrings('abcabcabcabcabcdkkabclilabcoabcdieabcdowabcdppabzabx'))
Hash is sorted by decreasing string length (and then alphabetically), and indices are only allowed to appear once.
It outputs
{"abcabc"=>[0, 6],
"bcabca"=>[1, 7],
"cabcab"=>[2, 8],
"abcd"=>[12, 28, 34, 40],
"abc"=>[3, 9, 18, 24],
"bca"=>[4, 10],
"bcd"=>[13, 29, 35, 41],
"cab"=>[5, 11],
"ab"=>[46, 49],
"bc"=>[19, 25],
"cd"=>[14, 30, 36, 42],
"b"=>[47, 50],
"c"=>[20, 26],
"d"=>[15, 31, 37, 43],
"i"=>[22, 32],
"k"=>[16, 17],
"l"=>[21, 23],
"o"=>[27, 38],
"p"=>[44, 45]}
It doesn't answer the question exactly, but it comes a bit closer.

Recursive solution doesn't iterate correctly

I'm working through a toy problem in Ruby: how to produce all possible 10-digit phone numbers where each successive number is adjacent to the last on the keypad. I've represented the adjacent relationships between numbers, and have a recursive function, but my method isn't iterating through the whole solution space. It's just finding the first solution and returning.
Here's my code:
adjacencies = { 1 => [2, 4],
2 => [1, 3, 5],
3 => [2, 6],
4 => [1, 5, 7],
5 => [2, 4, 6, 8],
6 => [3, 5, 9],
7 => [4, 8],
8 => [5, 7, 9, 0],
9 => [6, 8],
0 => [8]
}
def append_number(partial_phone_number, to_append, adjacencies)
phone_length = 10
partial_phone_number = partial_phone_number + to_append.to_s
if (partial_phone_number.length == phone_length)
return partial_phone_number
else
adjacencies[to_append].each do |a|
return append_number(partial_phone_number, a, adjacencies)
end
end
end
(0..9).each do |n|
puts append_number("", n, adjacencies)
end
And here is the output when I run it:
0852121212
1212121212
2121212121
3212121212
4121212121
5212121212
6321212121
7412121212
8521212121
9632121212
The first time you enter the adjacencies[to_append].each, you immediately return from the method, therefore the loop will never be executed more than once.
You need to
return a list of phone numbers instead of just a single phone number
build that list somehow in your recursive call
Here is a modification of your recursive method. FIRST_DIGIT is an array of possible first digits of an n-digit phone number, n being the first argument of the method recurse. You wish to determine recurse(10).
ADJ = { 1 => [2, 4],
2 => [1, 3, 5],
3 => [2, 6],
4 => [1, 5, 7],
5 => [2, 4, 6, 8],
6 => [3, 5, 9],
7 => [4, 8],
8 => [5, 7, 9, 0],
9 => [6, 8],
0 => [8]
}
FIRST_DIGIT = (1..9).to_a
#=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
def recurse(n, nxt=FIRST_DIGIT)
nxt.each_with_object([]) do |i,a|
is = i.to_s
if n==1
a << is
else
recurse(n-1, ADJ[i]).each { |s| a << is + s }
end
end
end
recurse 1
#=> ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
recurse 2
#=> ["12", "14", "21", "23", "25", "32", "36", "41", "45",
# "47", "52", "54", "56", "58", "63", "65", "69",
# "74", "78", "85", "87", "89", "80", "96", "98"]
recurse 3
#=> ["121", "123", "125", "141", "145", "147",
# "212", "214", "232", "236", "252", "254", "256", "258",
# "321", "323", "325", "363", "365", "369",
# "412", "414", "452", "454", "456", "458", "474", "478",
# "521", "523", "525", "541", "545", "547", "563", "565",
# "569", "585", "587", "589", "580",
# "632", "636", "652", "654", "656", "658", "696", "698",
# "741", "745", "747", "785", "787", "789", "780",
# "852", "854", "856", "858", "874", "878", "896", "898", "808",
# "963", "965", "969", "985", "987", "989", "980"]
recurse(10).size
#=> 117529
[Edit: the OP has asked about the possibility of modifying the code to avoid loops. This would not be difficult. The same modification could be used to enforce other rules as well (e.g, no 666), all of which would reduce the numbers of combinations to be considered. We could do this by adding an argument so_far to recurse that is an array (or it could be a string) of all digits selected so far:
def recurse(n, so_far=[], nxt=FIRST_DIGIT)
nxt.each_with_object([]) do |i,a|
is = i.to_s
if n==1
a << is
else
< construct array 'permitted' from ADJ[i] and other rules >
recurse(n-1, so_far+[i], permitted).each { |s| a << is + s }
end
end
end
Note that having two arguments with defaults is not a problem, as recurse will initially be called with only the first argument and thereafter will be called with all three arguments.
The return statement in the each iterator bails out of the recursive call on the first iteration. Do not use return there. One possible solution is to append the result to a list (which you pass by argument) when you get to the recursive base case.

Strange Ruby 2+ Behavior with "select!"

I'm having an issue that I can't seem to find documented or explained anywhere so I'm hoping someone here can help me out. I've verified the unexpected behavior on three versions of Ruby, all 2.1+, and verified that it doesn't happen on an earlier version (though it's through tryruby.org and I don't know which version they're using). Anyway, for the question I'll just post some code with results and hopefully someone can help me debug it.
arr = %w( r a c e c a r ) #=> ["r","a","c","e","c","a","r"]
arr.select { |c| arr.count(c).odd? } #=> ["e"]
arr.select! { |c| arr.count(c).odd? } #=> ["e","r"] <<<<<<<<<<<<<<< ??????
I think the confusing part for me is clearly marked and if anyone can explain if this is a bug or if there's some logic to it, I'd greatly appreciate it. Thanks!
You're modifying the array while you're read from it while you iterate over it. I'm not sure the result is defined behavior. The algorithm isn't required to keep the object in any kind of sane state while it's running.
Some debug printing during the iteration shows why your particular result happens:
irb(main):005:0> x
=> ["r", "a", "c", "e", "c", "a", "r"]
irb(main):006:0> x.select! { |c| p x; x.count(c).odd? }
["r", "a", "c", "e", "c", "a", "r"]
["r", "a", "c", "e", "c", "a", "r"]
["r", "a", "c", "e", "c", "a", "r"]
["r", "a", "c", "e", "c", "a", "r"] # "e" is kept...
["e", "a", "c", "e", "c", "a", "r"] # ... and moved to the start of the array
["e", "a", "c", "e", "c", "a", "r"]
["e", "a", "c", "e", "c", "a", "r"] # now "r" is kept
=> ["e", "r"]
You can see by the final iteration, there is only one r, and that the e has been moved to the front of the array. Presumably the algorithm modifies the array in-place, moving matched elements to the front, overwriting elements that have already failed your test. It keeps track of how many elements are matched and moved, and then truncates the array down to that many elements.
So, instead, use select.
A longer example that matches more elements makes the problem a little clearer:
irb(main):001:0> nums = (1..10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
irb(main):002:0> nums.select! { |i| p nums; i.even? }
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[2, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[2, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[2, 4, 3, 4, 5, 6, 7, 8, 9, 10]
[2, 4, 3, 4, 5, 6, 7, 8, 9, 10]
[2, 4, 6, 4, 5, 6, 7, 8, 9, 10]
[2, 4, 6, 4, 5, 6, 7, 8, 9, 10]
[2, 4, 6, 8, 5, 6, 7, 8, 9, 10]
[2, 4, 6, 8, 5, 6, 7, 8, 9, 10]
=> [2, 4, 6, 8, 10]
You can see that it does indeed move matched elements to the front of the array, overwriting non-matched elements, and then truncate the array.
Just to give you some other ways of accomplishing what you're doing:
arr = %w( r a c e c a r )
arr.group_by{ |c| arr.count(c).odd? }
# => {false=>["r", "a", "c", "c", "a", "r"], true=>["e"]}
arr.group_by{ |c| arr.count(c).odd? }.values
# => [["r", "a", "c", "c", "a", "r"], ["e"]]
arr.partition{ |c| arr.count(c).odd? }
# => [["e"], ["r", "a", "c", "c", "a", "r"]]
And if you want more readable keys:
arr.group_by{ |c| arr.count(c).odd? ? :odd : :even }
# => {:even=>["r", "a", "c", "c", "a", "r"], :odd=>["e"]}
partition and group_by are basic building blocks for separating elements in an array into some sort of grouping, so it is good to be familiar with them.

Ruby reducing a number array into start end range array

I have an array of numbers as below:
[11, 12, 13, 14, 19, 20, 21, 29, 30, 33]
I would like to reduce this array to:
[[11,14], [19,21], [29,30], [33,33]]
Identify consequent numbers in an array and push only the start and end of its ranges.
How to achieve this?
Exactly some problem is solved to give an example for slice_before method in ruby docs:
a = [0, 2, 3, 4, 6, 7, 9]
prev = a[0]
p a.slice_before { |e|
prev, prev2 = e, prev
prev2 + 1 != e
}.map { |es|
es.length <= 2 ? es.join(",") : "#{es.first}-#{es.last}"
}.join(",")
In your case you need to tweak it a little:
a = [11, 12, 13, 14, 19, 20, 21, 29, 30, 33]
prev = a[0]
p a.slice_before { |e|
prev, prev2 = e, prev
prev2 + 1 != e
}.map { |es|
[es.first, es.last]
}
Here's another way, using an enumerator with Enumerator#next and Enumerator#peek. It works for any collection that implements succ (aka next).
Code
def group_consecs(a)
enum = a.each
pairs = [[enum.next]]
loop do
if pairs.last.last.succ == enum.peek
pairs.last << enum.next
else
pairs << [enum.next]
end
end
pairs.map { |g| (g.size > 1) ? g : g*2 }
end
Note that Enumerator#peek raises a StopInteration exception if the enumerator enum is already at the end when enum.peek is invoked. That exception is handled by Kernel#loop, which breaks the loop.
Examples
a = [11, 12, 13, 14, 19, 20, 21, 29, 30, 33]
group_consecs(a)
#=> [[11, 12, 13, 14], [19, 20, 21], [29, 30], [33, 33]]
a = ['a','b','c','f','g','i','l','m']
group_consecs(a)
#=> [["a", "b", "c"], ["f", "g"], ["i", "i"], ["l", "m"]]
a = ['aa','ab','ac','af','ag','ai','al','am']
group_consecs(a)
#=> [["aa", "ab", "ac"], ["af", "ag"], ["ai, ai"], ["al", "am"]]
a = [:a,:b,:c,:f,:g,:i,:l,:m]
group_consecs(a)
#=> [[:a, :b, :c], [:f, :g], [:i, :i], [:l, :m]]
Generate an array of seven date objects for an example, then group consecutive dates:
require 'date'
today = Date.today
a = 10.times.map { today = today.succ }.values_at(0,1,2,5,6,8,9)
#=> [#<Date: 2014-08-07 ((2456877j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-08 ((2456878j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-09 ((2456879j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-12 ((2456882j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-13 ((2456883j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-15 ((2456885j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-16 ((2456886j,0s,0n),+0s,2299161j)>]
group_consecs(a)
#=> [[#<Date: 2014-08-07 ((2456877j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-08 ((2456878j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-09 ((2456879j,0s,0n),+0s,2299161j)>
# ],
# [#<Date: 2014-08-12 ((2456882j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-13 ((2456883j,0s,0n),+0s,2299161j)>
# ],
# [#<Date: 2014-08-15 ((2456885j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-16 ((2456886j,0s,0n),+0s,2299161j)>
# ]]
This is some code I wrote for a project a while ago:
class Array
# [1,2,4,5,6,7,9,13].to_ranges # => [1..2, 4..7, 9..9, 13..13]
# [1,2,4,5,6,7,9,13].to_ranges(true) # => [1..2, 4..7, 9, 13]
def to_ranges(non_ranges_ok=false)
self.sort.each_with_index.chunk { |x, i| x - i }.map { |diff, pairs|
if (non_ranges_ok)
pairs.first[0] == pairs.last[0] ? pairs.first[0] : pairs.first[0] .. pairs.last[0]
else
pairs.first[0] .. pairs.last[0]
end
}
end
end
if ($0 == __FILE__)
require 'awesome_print'
ary = [1, 2, 4, 5, 6, 7, 9, 13, 12]
ary.to_ranges(false) # => [1..2, 4..7, 9..9, 12..13]
ary.to_ranges(true) # => [1..2, 4..7, 9, 12..13]
ary = [1, 2, 4, 8, 5, 6, 7, 3, 9, 11, 12, 10]
ary.to_ranges(false) # => [1..12]
ary.to_ranges(true) # => [1..12]
end
It's easy to change that to only return the start/end pairs:
class Array
def to_range_pairs(non_ranges_ok=false)
self.sort.each_with_index.chunk { |x, i| x - i }.map { |diff, pairs|
if (non_ranges_ok)
pairs.first[0] == pairs.last[0] ? [pairs.first[0]] : [pairs.first[0], pairs.last[0]]
else
[pairs.first[0], pairs.last[0]]
end
}
end
end
if ($0 == __FILE__)
require 'awesome_print'
ary = [1, 2, 4, 5, 6, 7, 9, 13, 12]
ary.to_range_pairs(false) # => [[1, 2], [4, 7], [9, 9], [12, 13]]
ary.to_range_pairs(true) # => [[1, 2], [4, 7], [9], [12, 13]]
ary = [1, 2, 4, 8, 5, 6, 7, 3, 9, 11, 12, 10]
ary.to_range_pairs(false) # => [[1, 12]]
ary.to_range_pairs(true) # => [[1, 12]]
end
Here's an elegant solution:
arr = [11, 12, 13, 14, 19, 20, 21, 29, 30, 33]
output = []
# Sort array
arr.sort!
# Loop through each element in the list
arr.each do |element|
# Set defaults - for if there are no consecutive numbers in the list
start = element
endd = element
# Loop through consecutive numbers and check if they are inside the list
i = 1
while arr.include?(element+i) do
# Set element as endd
endd = element+i
# Remove element from list
arr.delete(element+i)
# Increment i
i += 1
end
# Push [start, endd] pair to output
output.push([start, endd])
end
[Edit: Ha! I misunderstood the question. In your example, for the array
a = [11, 12, 13, 14, 19, 20, 21, 29, 30, 33]
you showed the desired array of pairs to be:
[[11,14], [19,21], [29,30], [33,33]]
which correspond to the following offsets in a:
[[0,3], [4,6], [7,8], [9,9]]
These pairs respective span the first 4 elements, the next 3 elements, then next 2 elements and the next element (by coincidence, evidently). I thought you wanted such pairs, each with a span one less than the previous, and the span of the first being as large as possible. If you have a quick look at my examples below, my assumption may be clearer. Looking back I don't know why I didn't understand the question correctly (I should have looked at the answers), but there you have it.
Despite my mistake, I'll leave this up as I found it an interesting problem, and had the opportunity to use the quadratic formula in the solution.
tidE]
This is how I would do it.
Code
def pull_pairs(a)
n = ((-1 + Math.sqrt(1.0 + 8*a.size))/2).to_i
cum = 0
n.downto(1).map do |i|
first = cum
cum += i
[a[first], a[cum-1]]
end
end
Examples
a = %w{a b c d e f g h i j k l}
#=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"]
pull_pairs(a)
#=> [["a", "d"], ["e", "g"], ["h", "i"], ["j", "j"]]
a = [*(1..25)]
#=> [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
# 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
pull_pairs(a)
#=> [[1, 6], [7, 11], [12, 15], [16, 18], [19, 20], [21, 21]]
a = [*(1..990)]
#=> [1, 2,..., 990]
pull_pairs(a)
#=> [[1, 44], [45, 87],..., [988, 989], [990, 990]]
Explanation
First, we'll compute the the number of pairs of values in the array we will produce. We are given an array (expressed algebraically):
a = [a0,a1,...a(m-1)]
where m = a.size.
Given n > 0, the array to be produced is:
[[a0,a(n-1)], [a(n),a(2n-2)],...,[a(t),a(t)]]
These elements span the first n+(n-1)+...+1 elements of a. As this is an arithmetic progession, the sum equals n(n+1)/2. Ergo,
t = n(n+1)/2 - 1
Now t <= m-1, so we maximize the number of pairs in the output array by choosing the largest n such that
n(n+1)/2 <= m
which is the float solution for n in the quadratic:
n^2+n-2m = 0
rounded down to an integer, which is
int((-1+sqrt(1^1+4(1)(2m))/2)
or
int((-1+sqrt(1+8m))/2)
Suppose
a = %w{a b c d e f g h i j k l}
Then m (=a.size) = 12, so:
n = int((-1+sqrt(97))/2) = 4
and the desired array would be:
[['a','d'],['e','g'],['h','i'],['j','j']]
Once n has been computed, constructing the array of pairs is straightforward.

Resources