I've been asked to write a piece of code that returns the odd elements of an array when a the second argument is true and even elements if it is false. So far I have written one half and I need a method that can select the odd elements.
def odds_and_evens(string, return_odds)
if return_odds != false
string.chars.to_a
end
end
puts odds_and_evens("abcdefgh", true)
If you add this code you have 2 handy methods to select odd or even values from an array
class Array
def odd_values
values_at(* each_index.select(&:odd?))
end
def even_values
values_at(* each_index.select(&:even?))
end
end
class Array
def odd_values
e = [false, true].cycle
select { e.next }
end
def even_values
e = [true, false].cycle
select { e.next }
end
end
arr = "Here is a simple illustration of these methods".split
arr.odd_values
#=> ["is", "simple", "of", "methods"]
arr.even_values
#=> ["Here", "a", "illustration", "these"]
def odds_and_evens(string, return_odds)
string.chars.select.with_index{|_, i| return_odds ? i.odd? : i.even?}
end
odds_and_evens("abcdefgh", true) # => ["b", "d", "f", "h"]
odds_and_evens("abcdefgh", false) # => ["a", "c", "e", "g"]
There are some excellent answers here on finding even and odd numbered characters, but if you ever needed the flexibility to find other types of numbers, you may end up rewriting with something more 'crude' such as this:
def odds_and_evens(string, return_odds)
new_string = string.chars.to_a
even_odd_string = []
new_string.each_with_index do |letter, index|
if return_odds != true && index %2 == 0
even_odd_string << letter
elsif return_odds != false && index %2 == 1
even_odd_string << letter
end
end
even_odd_string
end
puts odds_and_evens("abcdefgh", true)
Using Range#step:
def odds_and_evens(string, return_odds)
start = return_odds ? 1: 0
(start...string.size).step(2).map { |i| string[i] }
# OR string.chars.values_at(*(start...string.size).step(2))
end
odds_and_evens("abcdefgh", true) # => ["b", "d", "f", "h"]
odds_and_evens("abcdefgh", false) # => ["a", "c", "e", "g"]
Here is one more way this can be done:
def odds_and_evens(string, return_odds)
string.chars.each_slice(2).collect {|pair| return_odds ? pair.last : pair.first }
end
p odds_and_evens("abcdefgh", false)
#=> ["a", "c", "e", "g"]
p odds_and_evens("abcdefgh", true)
#=> ["b", "d", "f", "h"]
Another variant can be:
def odds_and_evens(string, return_odds)
indices = (0...string.size).select {|i| return_odds ? i.odd? : i.even? }
string.chars.values_at(*indices)
end
The accepted answer is okay but it’s slow.
This is 2-3 times faster. Also, it does not monkey-patch the Array class hence it can be used on other objects, for example, ActiveRecod::Relation
def self.even_elements(list)
list.values_at(*0.step(list.size - 1, 2))
end
def self.odd_elements(list)
list.values_at(*1.step(list.size - 1, 2))
end
Related
I wrote a function which takes all elements with odd indexes and concatenates them with all elements with even indexes in a given string n times:
def encrypt(text,n)
while n > 0
n -= 1
str_array = text.split("")
even_values = str_array.select.with_index { |_, i| i.even? }
odd_values = str_array.select.with_index { |_, i| i.odd? }
text = (odd_values+even_values).join
end
puts text
end
puts 'Type your message to encrypt:'
text = gets
puts 'Type number of times to run encryption:'
n = gets.to_i
encrypt(text,n)
The issue is that in case the input text has odd number of elements the function will return a text in 2 lines. At the same time if number of elements of input text is even the output is a 1 line text which is what I want.
Console output with 10 elements in input text:
Type your message to encrypt:
abcdefghij
Type number of times to run encryption:
1
bdfhjacegi
Console output with 11 elements in input text:
Type your message to encrypt:
abcdefghijk
Type number of times to run encryption:
1
bdfhj
acegik
For n = 1
def doit(str)
enum = [false, true].cycle
str.each_char.partition { enum.next }.map(&:join).join
end
doit "abcdefghij"
#=> "bdfhjacegi"
doit "abcdefghijk"
#=> "bdfhjacegik"
For n > 1
n.times.reduce(str) { |s,_| doit(s) }
For example:
4.times.reduce(str) { |s,_| doit(s) }
#=>"ejdichbgafk"
See Enumerable#reduce (a.k.a. inject)
Now consider the calculations performed by doit.
First consider the enumerator enum, created with the method Array#cycle:
enum = [false, true].cycle
#=> #<Enumerator: [false, true]:cycle>
We can use the method Enumerator#next to see what values this enumerator enum will generate:
enum.next
#=> false
enum.next
#=> true
enum.next
#=> false
... ad infinitum
Now let's reset the enumerator using Enumerator#rewind:
enum.rewind
#<Enumerator: [false, true]:cycle>
Suppose
str = "abcdefghijk"
Then
e = str.each_char
#=> #<Enumerator: "abcdefghijk":each_char>
We can convert this enumerator to an array to see what elements it will generate.
e.to_a
#=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"]
Continuing,
a = e.partition { enum.next }
#=> [["b", "d", "f", "h", "j"], ["a", "c", "e", "g", "i", "k"]]
b = a.map(&:join)
#=> ["bdfhj", "acegik"]
b.join
#=> "bdfhjacegik"
Note a.map(&:join) is shorthand for a.map { |arr| arr.join }.
See String#each_char, Enumerable#partition and Array#join.
I am creating a method to test if a string contains only array elements in a game of scrabble.
Given this array:
hand = ["b", "l", "c", "o", "h", "e", "a"] if word is "beach"
Originally I used this block:
def uses_available_letters?(word, letters_in_hand)
input = word.split("")
input.index{ |x| !letters_in_hand.include?(x) }.nil?
end
Given this array:
hand = ["b", "l", "c", "o", "h", "e", "a", "c"]
if word = "beach", method returns true. However, if word = "beeeach", it will still return true even though the array only contains 1 "e".
SO, I tried deleting the array element after it was compared:
def uses_available_letters?(word, letters_in_hand)
input = word.split("")
input.each do
if letters_in_hand.include?(input[i])
letters_in_hand.delete(input[i])
else
return false
end
end
end
BUT, given "beacch", false is returned even though there are 2 c's in the array. So it seems every like letter is being deleted.
Send help!
The trouble with Ruby array operations is most treat them as a set of unique values, as in:
%w[ a a b b c c ] - %w[ a b c ]
# => []
Where that removes every a, b and c, not just the first. The same goes for delete unless you use a very specific index, but that gets messy in a hurry since deleting shuffles the remaining indexes, etc.
I'd consider storing the hand as a letter/count pair, as in:
def hand_count(str)
str.chars.group_by(&:itself).map { |l,a| [ l, a.length ] }.to_h
end
Where that gives you a hash instead of an array:
hand_count('example')
# => {"e"=>2, "x"=>1, "a"=>1, "m"=>1, "p"=>1, "l"=>1}
So now you can write a "sub" method:
def hand_sub(str, sub)
hand = hand_count(str)
hand_count(sub).each do |l, c|
# Unless the letter is present in the hand...
unless (hand.key?(l))
# ...this isn't possible.
return false
end
# Subtract letter count
hand[l] -= c
# If this resulted in a negative number of remaining letters...
if (hand[l] < 0)
# ...this isn't possible.
return false
end
end
# Convert back into a string...
hand.map do |l, c|
# ...by repeating each letter count times.
l * c
end.join
end
Where that works quite simply:
hand_sub('example', 'exam')
# => "epl"
hand_sub('example', 'expel')
# => "am"
hand_sub('example', 'plexi')
# => false
hand_sub('example', 'ell')
# => false
def uses_available_letters?(word, hand)
result = true
word.split('').each do |letter|
if hand.include?(letter)
hand.delete_at(hand.index(letter))
else
result = false
end
end
result
end
2.6.5 :065 > uses_available_letters?('beeeach', %w[b l e h c a e e])
=> true
2.6.5 :066 > uses_available_letters?('beach', %w[b l e h c a])
=> true
2.6.5 :067 > uses_available_letters?('beeeach', %w[b l e h c a])
=> false
2.6.5 :068 > uses_available_letters?('beeeach', %w[b l e d h e c e r a])
=> true
Here are two ways that could be done.
Compare counting hashes
def uses_available_letters?(word, letters_in_hand)
word_tally = word.each_char.tally
letters_in_hand_tally = letters_in_hand.tally
word_tally.all? { |k,v| letters_in_hand_tally[k].to_i >= v }
end
letters_in_hand = ["b", "l", "c", "e", "o", "h", "e", "a"]
uses_available_letters?("belch", letters_in_hand)
#=> true
uses_available_letters?("belche", letters_in_hand)
#=> true
uses_available_letters?("beleche", letters_in_hand)
#=> false
When word = "beleche",
word_tally
#=> {"b"=>1, "e"=>3, "l"=>1, "c"=>1, "h"=>1}
letters_in_hand_tally
#=> {"b"=>1, "l"=>1, "c"=>1, "e"=>2, "o"=>1, "h"=>1, "a"=>1}
See Enumerable#tally, which was introduced in Ruby v2.7. To support earlier versions of Ruby one could use the form of Hash::new that takes an argument (here zero), called the default value, and no block.
def uses_available_letters?(word, letters_in_hand)
word_tally = tally_ho(word.chars)
letters_in_hand_tally = tally_ho(letters_in_hand)
word_tally.all? { |k,v| letters_in_hand_tally[k].to_i >= v }
end
def tally_ho(arr)
arr.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
end
If letters_in_hand_tally does not have a key k, letters_in_hand_tally[k] #=> nil, resulting in the comparison nil >= v, which would raise an exception. It is for that reason I wrote letters_in_hand_tally[k].to_i, as nil.to_i #=> 0 and all values of word_tally will be positive integers.
One could alternatively write the block as follows:
{ |k,v| letters_in_hand_tally.key?(k) && letters_in_hand_tally[k] >= v }
Treat duplicate letters in letters_in_hand as distinct letters
This second method assumes letters_in_hand is given by a string, rather than an array. For example:
letters_in_hand = "blceohea"
Of course we could always form that string from the array as a first step, but I think it's more pleasing for letters_in_hand to be a string, just as word is. The following method makes use of the fact that String#index accepts an optional second argument that equals the index into the string where the search is to begin:
def uses_available_letters?(word, letters_in_hand)
start_index = Hash.new(0)
word.each_char.all? do |c|
i = letters_in_hand.index(c, start_index[c])
return false if i.nil?
start_index[c] = i + 1
end
true
end
uses_available_letters?("belch", letters_in_hand)
#=> true
uses_available_letters?("belche", letters_in_hand)
#=> true
uses_available_letters?("belecher", letters_in_hand)
#=> false
I am trying to figure out a way to filter two arrays into one based on guessing the letters within one o them.. so basically hangman. But if I had
word_array = ["b", "u", "s", "b", "o", "i"]
hidden_array = Array.new(word_array.length, "-")
p hidden_array
I would want to then print to the console ["b", "-", "-", "b", "-", "-"] if "b" were guessed. What would be a good beginner way to create this array that will change over time? Should it maybe be a hash? Thanks!
All of the solutions so far revolve around arrays, but don't forget a string is basically a character array anyway. Just use strings:
word = 'busboi'
guesses = 'bs'
word.tr('^'+guesses, '-')
# => "b-sb--"
The String#tr method converts all letters in the first argument to the mapping in the second argument, so you can do things like ROT13, simple cyphers and such, or in this case use the negation feature ^ to invert the first set and replace all non-matching characters.
You can keep track of the found letters in an array and make a method to do the printing
word_array = ["b", "u", "s", "b", "o", "i"]
found_letters = []
def hangman_prompt(found_letters)
word_array.map do |char|
found_letters.include?(char) ? char : "-"
end.join(" ")
end
Then you could use this in an input loop like so:
loop do
puts hangman_prompt(found_letters)
puts "what is your guess?"
input = gets.chomp
if word_array.include? input
found_letters << input
end
end
I'm using Array#map here which creates a new array of the same length. Each of the original array items are passed to the block, which determines how they could be copied to the new array.
One way to do it:
word_array = ["b", "u", "s", "b", "o", "i"]
word_array_i = word_array.map.with_index { |e,i| [e,i] }
#=> [["b", 0], ["u", 1], ["s", 2], ["b", 3], ["o", 4], ["i", 5]]
p hidden_array = Array.new(word_array.length, "_")
until hidden_array == word_array
puts 'make a guess'
guess = gets.chomp
if word_array.include? guess
puts 'correct guess'
ar = word_array_i.select { |arr| arr.first == guess }
.flatten.select { |e| e.class == Fixnum }
ar.each { |e| hidden_array[e] = guess }
else
puts 'incorrect guess'
end
p hidden_array
puts
end
puts 'game complete'
Key methods to research here Array#include?, Enumerator#with_index.
I wouldn't use arrays, just strings.
Code
def replace_underscores(word, hidden_word, guess)
word.scan(Regexp.new(guess)) { hidden_word[Regexp.last_match.begin(0)] = guess }
hidden_word
end
Example
word = "busboi"
hidden_word = "_" * word.length
#=> "______"
replace_underscores(word, hidden_word, "a") # guess "a"
#=> "______"
replace_underscores(word, hidden_word, "b") # guess "b"
#=> "b__b__"
replace_underscores(word, hidden_word, "r") # guess "r"
#=> "b__b__"
replace_underscores(word, hidden_word, "o") # guess "o"
#=> "b__bo_"
replace_underscores(word, hidden_word, "u") # guess "u"
#=> "bu_bo_"
replace_underscores(word, hidden_word, "s") # guess "s"
#=> "busbo_"
To check if the hidden_word has been guessed:
def guessed?(hidden_word)
hidden_word.count('_').zero?
end
guessed?(hidden_word)
#=> false
Let's permit one more guess.
replace_underscores(word, hidden_word, "i") # guess "i"
#=> "busboi"
guessed?(hidden_word)
#=> true
Notes
I've used the method String#scan with a block that is executed for each match. 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.) The method MatchData.begin is used to obtain the index of the character in str which is to be replaced by the letter just guessed.
I am trying to delete elements from an array if its index is greater than a certain value. I am looking to do something like this:
a = ["a", "b", "c"]
b = a.delete_if {|x| x.index > 1 }
I took a look at drop, delete_if, etc. I tried completing this using each_with_index like this:
new_arr = []
a.each_with_index do |obj, index|
if index > 1
obj.delete
end
new_arry << obj
end
How can I delete an array element if it's array position is greater than a certain value?
Here are some other ways to return a sans elements at indices >= index, which is probably better expressed as "returning the first index elements". All below return ["a", "b"]).
a = ["a", "b", "c", "d", "e"]
index = 2
Non-destructive (i.e., a is not altered)
a[0,index]
index.times.map { |i| a[i] }
Destructive (a is modified or "mutated")
a.object_id #=> 70109376954280
a = a[0,index]
a.object_id #=> 70109377839640
a.object_id #=> 70109377699700
a.replace(a.first(index))
a.object_id #=> 70109377699700
You can use slice! and give it a range. It is a destructive method as indicated by the !, so it will mutate your array.
a = [1, 2, 3, 4]
a.slice!(2..-1)
a = [1, 2]
Array#first gives you the first n elements.
b = a.first(1)
# => ["a"]
If you want to do it in a destructive way, then this will do:
a.pop(a.length - 1)
a # => ["a"]
You can append with_index:
a = ["a", "b", "c"]
a.delete_if.with_index { |x, i| i > 1 }
a #=> ["a", "b"]
Another example:
a = ("a".."z").to_a
a.delete_if.with_index { |x, i| i.odd? }
#=> ["a", "c", "e", "g", "i", "k", "m", "o", "q", "s", "u", "w", "y"]
Going by your question, "How can I delete an array element if it's array position is greater than a certain value?".
I assume what you want is that the final array you have should contain only elements before the specified index.
You can just do this:
your_array.select { |element| your_array.index(element) < max_index }
E.g
figures = [1,2,3,4,5,6]
figures.select{ |fig| figures.index(fig) < 3 }
# => [1, 2, 3]
I am trying to count the number of similar prefix beginnings to a string in Ruby. e.g; input "ababaa" should output 11;
ababaa = 6
babaa = 0
abaa = 3
baa = 0
aa = 1
a = 1
I have got as far as the code below, using a nested loop to go through each of the above as an array, however it looks as though Ruby is currently outputting the count of just the first Array object, "ababaa".
Solved, thanks :)
def string_suffix(string)
num = 0
ary = []
string.length.times do
ary << string[num..string.length]
num = num + 1
end
result = 0
ary.each do |x| # ["ababaa", "babaa", "abaa", "baa", "aa", "a"]
x.chars.each_with_index do |c,index|
break unless c == string[index]
result = result + 1
end
end
return result
end
I have looked far and wide and still cannot solve the issue, It looks like the (final, nested) array is breaking after the first iteration of the 'ary' Array and just returning that output.
You are returning the result while you are still in the loop. You need to move result = 0 out of the loop, and move the return result statement outside of the loop too. At the moment the function is going through the first iteration of the loop ("ababaa", for which all characters match), but you want result to equal the sum of all results.
Additionally, instead of doing:
count = 0
x.chars.each do |x|
if x == string[count]
count = count + 1
result = result + 1
else
count = count + 1
end
end
You could use the function each_with_index, to get
x.chars.each_with_index do |c,index|
if c == string[index]
result = result + 1
end
end
However, since you are trying to count how many characters in the substring are a prefix of string, you want to break when you first find a character c that is not equal to string[index], so that you don't end up counting extra characters. The loop then becomes:
x.chars.each_with_index do |c,index|
if c == string[index]
result = result + 1
else
break
end
end
I noticed you are returning the result inside your second loop, at the end. This means that after you've gone through the first item in your array the function returns just the result for the first item. Move your return statement to outside the loop.
As I understand, the problem is this: given a string s, for each i = 0..s.size-1, compute the number of leading characters of s[0..-i-1] that match the corresponding characters (i.e., at same offsets) of s[i..-1], and sum these s.size subtotals.
Here's a Ruby-like way to do that, using Enumerable#reduce (aka inject) and Enumerable#take_while:
str = "ababaa"
arr = str.chars
(0...arr.size).reduce(0) do |tot,i|
tot + arr[0..-i-1].zip(arr[i..-1]).take_while { |x,y| x == y }.size
end
#=> 11
The steps:
arr = str.chars
#=> ["a", "b", "a", "b", "a", "a"]
r = 0...arr.size
#=> 0...6
When the first element of r is passed to the block, the block variables are set to:
tot = 0
i = 0
The block calculation is therefore as follows:
a = arr[0..-i-1].zip(arr[i..-1])
#=> arr[0..-1].zip(arr[0..-1])
#=> arr.zip(arr)
#=> ["a", "b", "a", "b", "a", "a"].zip(["a", "b", "a", "b", "a", "a"])
#=> [["a", "a"], ["b", "b"], ["a", "a"], ["b", "b"], ["a", "a"], ["a", "a"]]
b = a.take_while { |x,y| x == y }
#=> [["a", "a"], ["b", "b"], ["a", "a"], ["b", "b"], ["a", "a"], ["a", "a"]]
tot + b.size
#=> 0 + 6
#=> 6
Note that this calculation will always equal arr.size for the first element of arr passed to the block.
When the next element of arr is passed to the block, the block variable i is set to 1. tot, which we just computed, equals 6. The block calculation is therefore:
a = arr[0..-i-1].zip(arr[i..-1])
#=> arr[0..-2].zip(arr[1..-1])
#=> ["a", "b", "a", "b", "a"].zip(["b", "a", "b", "a", "a"])
#=> [["a", "b"], ["b", "a"], ["a", "b"], ["b", "a"], ["a", "a"]]
b = a.take_while { |x,y| x == y }
#=> []
tot + b.size
#=> 6 + 0
#=> 6
The remaining calculations are similar. After all elements of arr have been sent to the block, reduce returns the value of tot.