This is for a tic tac toe game. I have an array board with nine string elements, and a nested array WIN_COMBINATIONS with position combinations from board:
board = ["X", "X", "X", " ", " ", " ", " ", " ", " "]
WIN_COMBINATIONS = [
[0, 1, 2],
[0, 3, 6],
[0, 4, 8],
[3, 4, 5],
[6, 7, 8],
[6, 4, 2],
[1, 4, 7],
[2, 5, 8]
]
How do I choose the array combinations from board that are all "X" or all "O" using the combinations found in WIN_COMBINATIONS?
For example
a different board than the one above in which X wins in the right diagonal.
board = ["X", "O", "X", "O", "X", "O", "X", "X", "O"]
# X | O | X
# ---+---+---
# O | X | O
# ---+---+---
# X | X | O
won?(board) #=> [2,4,6]
A slight variation of ndn's answer:
board = %w(X O X
O X O
X X O)
WIN_COMBINATIONS.select { |c| board.values_at(*c).join =~ /XXX|OOO/ }
#=> [[6, 4, 2]]
Explanation:
select returns all elements for which the block returns true.
values_at returns the values at the specified indices:
board.values_at(*[0, 1, 2])
#=> ["X", "O", "X"]
* converts the array to an argument list, so the above becomes values_at(0, 1, 2)
join returns a string with the concatenated elements:
["X", "O", "X"].join
#=> "XOX"
=~ checks if the string matches the regular expression /XXX|OOO/, i.e. either XXX or OOO
You can replace select with find if you just want to retrieve the first winning combination.
Edit Code incorporates Stefan's suggestion.
Do this once:
WIN_COMBINATIONS.each(&:sort!)
Then,
h = board.each_index.group_by{|i| board[i]}
# => {"X"=>[0, 1, 2], " "=>[3, 4, 5, 6, 7, 8]}
WIN_COMBINATIONS.find{|a| (h["X"] & a) == a or (h["O"] & a) == a}
# => [0, 1, 2]
WIN_COMBINATIONS.find do |combination|
values_at_positions = board.values_at(*combination).uniq
values_at_positions.size == 1 and ['X', 'O'].include?(*values_at_positions)
end # => [0, 1, 2]
Just out of curiosity (a slightly updated #Stefan’s answer):
WIN_COMBINATIONS.index do |wc|
board.values_at(*wc).join =~ /(?<l>\w)\k<l>\k<l>/
# or, as suggested by #Stefan: board.values_at(*wc).join =~ /(\w)\1\1/
end
#⇒ 5
Here we match the combinations to three same symbols, which is likely the most semantically correct interpretation of tic-tac-toe game.
You can try this.
def won?(board)
xpos = []
opos = []
who_won = nil;
board.each_with_index{|x,i| xpos << i if x == "X" }
board.each_with_index{|x,i| xpos << i if x == "O" }
WIN_COMBINATIONS.each do |com|
temp = com & xpos
who_won = "X" if temp == com
temp = com & opos
who_won = "O" if temp == com
break if !who_won.nil?
end
return who_won
end
won?(board) #=> X if x wins, O if O wins. nil if no body wins.
Its untested but should work.
Related
I write something like this in Ruby:
if a.max == a[0]
brand = b[0]
elsif a.max == a[1]
brand = b[1]
elsif a.max == a[2]
brand = b[2]
elsif a.max == a[3]
brand = b[3]
end
a and b both are unique arrays.
Is there any way to check all if and elsif's in the same condition?
Only one condition for a[0], a[1], a[2] and a[3]?
Array#index might help in cases like these (assuming the size of a and b is the same):
brand = b[a.index(a.max)]
In cases in which the array a might be empty, you will need an additional condition to avoid an error:
index = a.index(a.max)
brand = b[index] if index
Two more ways:
a = [3, 1, 6, 4]
b = [2, 8, 5, 7]
b[a.each_index.max_by { |i| a[i] }]
#=> 5
or
b[a.each_with_index.max_by(&:first).last]
#=> 5
Assuming a and b have the same size, e.g.
a = [2, 5, 8, 1]
b = [:a, :b, :c, :d]
you could combine zip and max:
a.zip(b).max.last # or more explicit: a.zip(b).max_by(&:first).last
#=> :c # or reversed: b.zip(a).max_by(&:last).first
or max_by and with_index:
b.max_by.with_index { |_, i| a[i] }
#=> :c
If your array has multiple maxima, you may want to get the indices of the array that correspond to all the maxima:
a = [10, 12, 12]
b = [:a, :b, :c]
# Compute and store the maximum once, to avoid re-computing it in the
# loops below:
a_max = a.max
idxs = a.each_with_index.select{ |el, idx| el == a_max }.map{ |el, idx| idx }
# or:
idxs = a.each_with_index.map{ |el, idx| idx if el == a_max }.compact
puts "#{idxs}"
# [1, 2]
puts "#{idxs.map{ |idx| b[idx] }}"
# [:b, :c]
I want to compare three arrays transitively to see if there are any shared elements:
arrs = [
["AAA", "", ""],
["", "", "CCC"],
["AAA", "BBB", "CCC"]
]
I want to return a matrix that compares the elements transitively. That is, if two arrays share any of the same elements, or if they match with a third record, return 1. Otherwise return 0.
With this example the result should be:
result = [
[1, 1, 1],
[1, 1, 1],
[1, 1, 1]
]
result[0][0] is 1 because if we compare arrs[0] with arrs[0] (compare itself), they share "AAA".
result[0][1] is 1 because if we compare arrs[0] and arrs[1] there are no shared elements, but both arrs[0] & arrs[2] and arrs[1] & arrs[2] return an intersecting element, so we return 1
result[0][2] is 1 because if we compare arrs[0] with arrs[2], they share "AAA"
We repeat the process for all other array combinations in arrs.
It's really not that tricky, you just need to double-map here:
def transitive(arr)
arr.map do |a|
arr.map do |b|
(a & b).any? ? 1 : 0
end
end
end
A more Ruby approach is to use true or false, but 1 and 0 is fine if you can handle the ternary to convert it.
How it works:
arrs = [
["AAA", "", ""],
["", "", "CCC"],
["AAA", "BBB", "CCC"]
]
transitive(arrs)
# => [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
Not a very exciting example. Here's one that has more variety:
arrs = [
%w[ A B C ],
%w[ A D E ],
%w[ D E F ]
]
transitive(arrs)
# => [[1, 1, 0], [1, 1, 1], [0, 1, 1]]
Where that has some misses.
Assumption
We are given an array arr of size n, each element being an array of size m. We wish to construct another n x m array, a, such each element a[i][j] equals 1 (else 0) if all of the following arrays are non-empty:
a[i] & a[i] # non-empty if a is non-empty
a[i] & a[(i+1)%n]
a[(i+1)%n] & a[(i+2)%n]
a[(j-1)%n] & a[j]
This is what I interpret "transitive" to mean. Note that I've assumed the transitive relation "wraps around" from the last to the first element of arr.
Let's consider an example.
arr = [["A", "B", "C"],
["A", "D", "E"],
["D", "E", "F"]]
Suppose we wish to compute a[i][j] of the array a being constructed. This equals 1 (else 0) if the following arrays are all non-empty:
a[1] & a[1] #=> a[1] => ["A", "D", "E"]
a[1] & a[2%3] #=> a[1] & a[2] => ["D"]
a[(i+1)%n] & a[(i+2)%n] #=> a[2] & a[1] => []
Note that had (a[1] & a[2%3]).empty? #=> true, it would not be necessary to compute the third expression (or any following expressions if arr were larger).
For i #=> 0,
a[0,0] = (arr[0] & arr[0]).any?
#=> arr[0].any? #=> true, hence = 1
a[0,1] = (arr[0] & arr[1]).any?
#=> ["A"].any? #=> true, hence = 1
a[0,2] = (arr[0] & arr[1]).any? && (arr[1] & arr[2]).any?
#=> (a[0,1] == 1) && ["D"].any? => true && true => true, hence = 1
For i #=> 1,
a[1,1] = (arr[1] & arr[1]).any?
#=> arr[1].any? #=> true, hence = 1
a[1,2] = (arr[1] & arr[2]).any?
#=> ["D"].any? #=> true, hence = 1
a[1,0] = (arr[1] & arr[2]).any? && (arr[2] & arr[0]).any?
#=> (a[1,2] == 1) && [].any? => true && false => true, hence = 0
For i #=> 2,
a[2,2] = (arr[2] & arr[2]).any?
#=> arr[2].any? #=> true, hence = 1
a[2,0] = (arr[2] & arr[0]).any?
#=> [].any? #=> false, hence = 0
a[2,1] = (arr[2] & arr[0]).any? && (arr[0] & arr[1]).any?
#=> (a[2,0] == 1) && ["A"].any? => false && true => false, hence = 0
Code
require 'set'
def transitive(arr)
n = arr.size
st = n.times.with_object(Set.new) do |i,st|
(i..i+n-1).each do |j|
if j==i
st << [i,j]
else
jj = j % n
jprev = (j-1) % n
break unless st.include?([i,jprev]) & (arr[jprev] & arr[jj]).any?
st << [i,jj]
end
end
end
Array.new(n) do |i|
Array.new(arr.first.size) { |j| st.include?([i,j]) ? 1 : 0 }
end
end
Example
For arr defined earlier,
transitive(arr)
#=> [[1, 1, 1],
# [0, 1, 1],
# [0, 0, 1]]
Explanation
The steps are as follows:
n = arr.size
#=> 3
st = n.times.with_object(Set.new) do |i,st|
(i..i+n-1).each do |j|
if j==i
st << [i,j]
else
jj = j % n
jprev = (j-1) % n
break unless st.include?([i,jprev]) & (arr[jprev] & arr[jj]).any?
st << [i,jj]
end
end
end
#=> #<Set: {[0, 0], [0, 1], [0, 2], [1, 1], [1, 2], [2, 2]}>
st is a set of the transitive elements of arr. This shows that elements of arr with indices [0, 2] (order matters) are transitive, but those with indices [2, 0] are not (because st does not contain [2, 0]). Notice that once [2, 0] it was determined to not be transitive it was not necessary to check [2, 1].
The last step uses the method Array::new:
Array.new(n) {|i| Array.new(arr.first.size) {|j| st.include?([i,j]) ? 1 : 0}}
#=> [[1, 1, 1],
# [0, 1, 1],
# [0, 0, 1]]
Input :
arr = [4,2,'b',5,'c','a',7]
Output:
[2,4,5,7,'a','b','c']
I can think of this :
int_arr = arr.select {|x| x.instance_of?(Integer)}.sort
str_arr = arr.select {|x| x.instance_of?(String)}.sort
int_arr + str_arr
Please suggest a efficient way to do it.
One way is to partition your array by those elements being Integer, to make sure they remain first and then sort the elements of each array:
[4,2,'b',5,'c','a',7].partition { |e| e.is_a?(Integer) }.flat_map(&:sort)
# [2, 4, 5, 7, "a", "b", "c"]
arr.sort_by { |e| [e.is_a?(Integer) ? 0 : 1, e] }
#=> [2, 4, 5, 7, "a", "b", "c"]
[22, 'efg', 0, 'abc', -4].sort_by { |e| [e.is_a?(Integer) ? 0 : 1, e] }
#=> [-4, 0, 22, "abc", "efg"]
See Enumerable#sort_by. sort_by performs pair-wise comparisons of 2-element arrays using the method Array#<=>. See especially the third paragraph of that doc.
The following could be used if, as in the example, the integers are single digits and the strings are single characters.
arr.sort_by(&:ord)
#=> [2, 4, 5, 7, "a", "b", "c"]
See Integer#ord and String#ord.
I want to select all elements from my array such that the element immediately after it is not one plus the current element.
I tried this:
data_col = ["46", "47", "48", "49", "50", "51", "52"]
data_col.select.with_index{|string, index| data_col[index+1] && string.to_i != data_col[index+1].to_i + 1 }
# => ["46", "47", "48", "49", "50", "51"]
Every element is selected even though I would expect none of them (except the last one) gets selected. How can I modify my statement to get what I want?
(numbers + [nil]).each_cons(2).reject { |x, y| x.next == y }.map(&:first)
Add a nil at the end. We will use that to always include the last element:
[1, 2, 3] + [nil] # => [1, 2, 3, nil]
Pair each two consecutive elements:
[1, 2, 3].each_cons(2).to_a # => [[1, 2], [2, 3]]
Remove the pairs that don't meet your criteria:
[1, 2, 5, 6, nil].each_cons(2).reject { |x, y| x.next == y }
# => [[2, 5], [6, nil]]
Get the numbers out of the pairs:
[[2, 5], [6, nil]].map(&:first) # => [2, 6]
This is one way to use Enumerable#each_cons.
arr = [1, 3, 4, 6, 7, 9]
arr.each_cons(2).
each_with_object([]) { |(x,y),a| a << x unless x.next==y } << arr[-1]
#=> [1, 4, 7, 9]
Another option is to step through an enumerator using Enumerator#next and Enumerator#peek.
enum = arr.to_enum
a = []
loop do
x = enum.next
a << x unless x.succ == enum.peek
end
a << arr[-1]
#=> [1, 4, 7, 9]
When the enumerator is at its end, Enumerator#peek generates a StopIteration exception which is handled by Kernel#loop by breaking out of the loop.
Try
data_col.select.with_index { |number_string, index| data_col[index + 1].to_i != 1 + number_string.to_i }
Or, if don't mind that you have the result elements as integer,
n_col = data_col.map(&:to_i)
n_col.select.with_index{ |n, i| n_col[i + 1] != n + 1 }
data_col = ["46", "47", "48", "49", "50", "51", "52"]
def method(arg)
results = []
i = 0
while i < arg.length
current_num = arg[i].to_i
next_num = arg[i + 1].to_i
next_num - current_num != 1 ? results << current_num : nil
i += 1
end
return results
end
p method(data_col) #[52]
This will also return just [52] like you were looking for, it uses a simple while loop, and is pretty easy to understand what's going on.
I need to implement Array#flatten. This implementation removes all nested arrays:
a = [1, 2, [3, [4, 5]]]
def my_flatten(arr)
arr.reduce([]) do |result, item|
item.is_a?(Array) ? result + my_flatten(item) : result << item
end
end
my_flatten(a) #=> [1, 2, 3, 4, 5]
Prompt how to implement such behavior
a.flatten(1) #=> [1, 2, 3, [4, 5]]
Introduce a parameter to specify the max depth (defaulting to nil) and a parameter to keep track of the current depth (0 on the initial call and then incremented by 1 on each recursive call):
def my_flatten(arr, max_depth = nil, current_depth = 0)
arr.reduce([]) do |result, item|
item.is_a?(Array) && (max_depth.nil? || current_depth < max_depth) ?
result + my_flatten(item, max_depth, current_depth + 1) : result << item
end
end
You could replace the ?: with an if/else if you felt this was more readable:
def my_flatten(arr, max_depth = nil, current_depth = 0)
arr.reduce([]) do |result, item|
if item.is_a?(Array) && (max_depth.nil? || current_depth < max_depth)
result + my_flatten(item, max_depth, current_depth + 1)
else
result << item
end
end
end
This now returns the expected results:
my_flatten(a) #=> [1, 2, 3, 4, 5]
my_flatten(a, 1) #=> [1, 2, 3, [4, 5]]
I created something similar a long time back. Here is the gist link.
Code from gist:
class Array
def my_flatten(level = nil)
rb_flatten(self, [], level)
end
private
# apply recursion based on the level
# when no level provided, then produce a complete flatten array
# when level is given, then produce a flatten array flattened till that certain level
def rb_flatten(array, result, level)
array.each do |value|
if ((value.is_a? Array) && (level.nil? || (level && level > 0)))
rb_flatten(value, result, (level.nil? ? level : ((level || 0 ) - 1)))
else
result << value
end
end
return result
end
end
Hope that helps.
you can also use Proc like this.
class Array
def my_flatten(level = nil)
p = ->(arr, exp, lvl) do
arr.each { |val| Array === val && (!level || lvl < level) ? p.(val, exp, lvl+1) : exp << val }
exp
end
p.(self, [], 0)
end
end
a = [1, 2, [3, [4, 5]]]
p a.my_flatten
# => [1, 2, 3, 4, 5]
p a.my_flatten(0)
# => [1, 2, [3, [4, 5]]]
p a.my_flatten(1)
# => [1, 2, 3, [4, 5]]
p a.my_flatten(2)
# => [1, 2, 3, 4, 5]