How can I compare strings to an array to determine highest index value? - ruby

I have several strings that I need to compare with values in an array to determine which has the highest index number. For example, the data looks like this:
array = [2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K', 'A']
v1 = "4"
v2 = "A"
v3 = "8"
How would I write it so that it would compare each value and return the fact that v2 is the winner based on the index number for A being 12?

A short version:
array = [2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K', 'A']
target = [4, "A", 8]
target & array #=> [4, "A", 8]
array & target #=> [4, 8, "A"]
(array & target ).last #=> "A"
target = ["B", "C"]
(array & target ).last #=> nil

array = [2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K', 'A']
v1 = "4"
v2 = "A"
v3 = "8"
array.reverse.map(&:to_s).find { |e| [v1, v2, v3].include?(e) }
# => "A"
or
array.reverse.map(&:to_s).find(&[v1, v2, v3].method(:include?))
# => "A"

You could write:
array = [2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K', 'A']
a = array.map(&:to_s)
#=> ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"]
target = ["4", "A", "8"]
(target & a).empty? ? nil : a[target.map { |s| a.index(s) }.compact.max]
#=> "A"
target = ["B", "C"]
(target & a).empty? ? nil : a[target.map { |s| a.index(s) }.compact.max]
#=> nil
I have assumed that array may not be sorted.

Related

Multiply all even indexed integers by two

Wanting to take a fixnum of integers and multiply all even(indexed) integers by two. I figured the best way to do this is first turn fixnum into an array. So lets say the following number of 16 digits: a = 4408041234567901
I know I could:
a.to_s.split('')
Which will return 'a' to an array of 'stringed' numbers. But then I cant follow up with:
a.map!.with_index {|i,n| i.even? n*2}
Guess I'm kinda stuck on how to create a method to do this. So my question may even be how to turn that group of numbers into an array of fixnums/integers instead of strings.
I would prefer to remove the conditional altogether from the loop, by creating an Enumerator that contains the coefficients you want to multiply by (2 for even indexes and 1 for odd.)
coef = [2, 1].cycle
This essentially creates an Enumerator that alternately returns 2 and 1 when next is called on it. You can then use this to simplify your map to:
a.to_s.each_char.map { |v| v.to_i * coef.next }
To change it to an Array, you could do
a = 4408041234567901
arr = a.to_s.chars.map(&:to_i)
# => [4, 4, 0, 8, 0, 4, 1, 2, 3, 4, 5, 6, 7, 9, 0, 1]
You can also multiply alternate numbers by 2
arr = a.to_s.chars.map.with_index {|n,i| i.even? ? n.to_i * 2 : n.to_i }
# => [8, 4, 0, 8, 0, 4, 2, 2, 6, 4, 10, 6, 14, 9, 0, 1]
Improving a little bit, you can use a Hash to find the number to be multiplied.
h = {true => 2, false => 1}
a.to_s.each_char.map.with_index {|n,i| n.to_i * h[i.even?]}
EDIT
I can explain each step, But it will be better if you can try to figure it out on your own. Open irb, type a.to_s and check the output. Then type a.to_s.chars and inspect the output and so on..
a = 4408041234567901
even_odd = [:even, :odd].cycle
#=> #<Enumerator: [:even, :odd]:cycle>
If the indexing starts with the highest-order (leftmost) digit:
a.to_s.each_char.map { |d|
(even_odd.next == :even) ? 2*d.to_i : d.to_i }
#=> [8, 4, 0, 8, 0, 4, 2, 2, 6, 4, 10, 6, 14, 9, 0, 1]
If the indexing starts with the ones digit:
s = a.to_s
even_odd.next if s.size.even?
s.each_char.map { |d| ( even_odd.next == :even) ? 2*d.to_i : d.to_i }
#=> [4, 8, 0, 16, 0, 8, 1, 4, 3, 8, 5, 12, 7, 18, 0, 2]
Here are the steps for the example when zero-based indexing starts with the highest-order digit.
Array#cycle converts the array [:even, :odd] to an enumerator:
even_odd = [:even, :odd].cycle
even_odd.next #=> :even
even_odd.next #=> :odd
even_odd.next #=> :even
even_odd.next #=> :odd
...
b = a.to_s
#=> "4408041234567901"
enum0 = b.each_char
#=> #<Enumerator: "4408041234567901":each_char>
The enumerator enum0 passes the digits of b to map. I could have instead written:
b = a.to_s.chars
# => ["4", "4", "0", "8", "0", "4", "1", "2",
# "3", "4", "5", "6", "7", "9", "0", "1"]
but that creates an intermediate array. The enumerator does not and therefore is more efficient. Continuing...
enum1 = enum0.map
#=> #<Enumerator: #<Enumerator: "4408041234567901":each_char>:map>
You can think of this as a "compound enumerator". We can see its contents by converting it to an array:
enum1.to_a
#=> ["4", "4", "0", "8", "0", "4", "1", "2",
# "3", "4", "5", "6", "7", "9", "0", "1"]
The method each will pass each element of the enumerator into the block. Proof:
enum1.each { |d| (enum.next == :even) ? 2*d.to_i : d.to_i }
# => [8, 4, 0, 8, 0, 4, 2, 2, 6, 4, 10, 6, 14, 9, 0, 1]
We can manually step through the elements of enum1 by using Enumerator#next. We will assign the value to the block variable d and perform the calculation in the block to map the digit d:
d = enum1.next
#=> "4"
(enum.next == :even) ? 2*d.to_i : d.to_i
#=> (:even == :even) ? 2*"4".to_i : "4".to_i
#=> (true) ? 8 : 4
#=> 8 ("4" is mapped to 8)
d = enum1.next
#=> "4"
(enum.next == :even) ? 2*d.to_i : d.to_i
#=> (:odd == :even) ? 2*"4".to_i : "4".to_i
#=> (false) ? 8 : 4
#=> 4 ("4" is mapped to 4)
d = enum1.next
#=> "0"
#=> (:even == :even) ? 2*"0".to_i : "0".to_i
#=> (true) ? 0 : 0
#=> 8 ("0" is mapped to 0)
and so on.

How do you check an array for a range in 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

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 Array - Delete first 10 digits

I have an array in Ruby and I would like to delete the first 10 digits in the array.
array = [1, "a", 3, "b", 2, "c", 4, "d", 5, "a", 1, "z", 7, "e", 21, "q", 30, "a", 4, "t", 7, "m", 5, 1, 2, "q", "s", "l", 13, 46, 31]
It would ideally return
['a', 'b', 'c', 'd', 'a', 'z', 'e', 'q', 0, 'a', 4, t, 7, m, 5 , 1, 2, q, s, 1, 13, 46, 31]
By removing the first 10 digits (1,3,2,4,5,1,7,2,1,3).
Note that 21(2 and 1) and 30(3 and 0) both have 2 digits
Here's what I've tried
digits = array.join().scan(/\d/).first(10).map{|s|s.to_i}
=> [1,3,2,4,5,1,7,2,1,3]
elements = array - digits
This is what I got
["a", "b", "c", "d", "a", "z", "e", 21, "q", 30, "a", "t", "m", "q", "s", "l", 13, 46, 31]
Now it looks like it took the difference instead of subtracting.
I have no idea where to go from here. and now I'm lost. Any help is appreciated.
To delete 10 numbers:
10.times.each {array.delete_at(array.index(array.select{|i| i.is_a?(Integer)}.first))}
array
To delete 10 digits:
array = [1, "a", 3, "b", 2, "c", 4, "d", 5, "a", 1, "z", 7, "e", 21, "q", 30, "a", 4, "t", 7, "m", 5, 1, 2, "q", "s", "l", 13, 46, 31]
i = 10
while (i > 0) do
x = array.select{|item| item.is_a?(Integer)}.first
if x.to_s.length > i
y = array.index(x)
array[y] = x.to_s[0, (i-1)].to_i
else
array.delete_at(array.index(x))
end
i -= x.to_s.length
end
array
Unfortunately not a one-liner:
count = 10
array.each_with_object([]) { |e, a|
if e.is_a?(Integer) && count > 0
str = e.to_s # convert integer to string
del = str.slice!(0, count) # delete up to 'count' characters
count -= del.length # subtract number of actually deleted characters
a << str.to_i unless str.empty? # append remaining characters as integer if any
else
a << e
end
}
#=> ["a", "b", "c", "d", "a", "z", "e", "q", 0, "a", 4, "t", 7, "m", 5, 1, 2, "q", "s", "l", 13, 46, 31]
I would be inclined to do it like this.
Code
def doit(array, max_nbr_to_delete)
cnt = 0
array.map do |e|
if (e.is_a? Integer) && cnt < max_nbr_to_delete
cnt += e.to_s.size
if cnt <= max_nbr_to_delete
nil
else
e.to_s[cnt-max_nbr_to_delete..-1].to_i
end
else
e
end
end.compact
end
Examples
array = [ 1, "a", 3, "b", 2, "c", 4, "d", 5, "a", 1, "z", 7, "e", 21, "q",
30, "a", 4, "t", 7, "m", 5, 1, 2, "q", "s", "l", 13, 46, 31]
doit(array, 10)
#=> ["a", "b", "c", "d", "a", "z", "e", "q", 0, "a", 4,
# "t", 7, "m", 5, 1, 2, "q", "s", "l", 13, 46, 31]
doit(array, 100)
#=> ["a", "b", "c", "d", "a", "z", "e", "q", "a", "t", "m", "q", "s", "l"]
Explanation
Each element e of the array that is not an integer is mapped to e.
For each non-negative integer n having d digits, suppose cnt is the number of digits that map has already been removed from the string. There are three possibilities:
if cnt >= max_nbr_to_delete, no more digits are to be removed, so e (itself) is returned
if cnt + d <= max_nbr_to_delete all d digits of e are to be removed, which is done by mapping e to nil and subsequently removing nil elements
if cnt < max_nbr_to_delete and cnt + d > max_nbr_to_delete, e.to_s[cnt+d-max_nbr_to_delete..-1].to_i is returned (i.e. the first cnt+d-max_nbr_to_delete digits of e are removed).

Joining multiple ordered arrays in Ruby

Lets say I have this:
a = [1, 2, 3, 4, 5]
b = ['a', 'b', 'c', 'd', 'e']
c = ['ABC', 'DEF', 'GHI', 'JKL', 'MNO']
And I want this:
d = [[1, 'a', 'ABC'], [2, 'b', 'DEF'], ...]
How can I accomplish this in Ruby?
I tried with .zip
r = []
r.zip(a, b, c)
puts r
But didn't work.
You need to do as below :-
a = [1, 2, 3, 4, 5]
b = ['a', 'b', 'c', 'd', 'e']
c = ['ABC', 'DEF', 'GHI', 'JKL', 'MNO']
a.zip(b,c)
# => [[1, "a", "ABC"], [2, "b", "DEF"], [3, "c", "GHI"], [4, "d", "JKL"], [5, "e", "MNO"]]
One thing to remember here - Array#zip returns an array of size, equal to the size of the receiver array object.
# returns an array of size 2, as the same as receiver array size.
[1,2].zip([1,5,7]) # => [[1, 1], [2, 5]]
# below returns empty array, as the receiver array object is also empty.
[].zip([1,2,3,4,5]) # => []
For the same reason as I explained above r.zip(a, b, c) returns [].
[a,b,c].reduce(:zip).map(&:flatten)
d = [a,b,c].transpose
[[1, "a", "ABC"], [2, "b", "DEF"], [3, "c", "GHI"], [4, "d", "JKL"], [5, "e", "MNO"]]

Resources