Ruby For loop isn't running as expected - ruby

I have this array of arrays:
WIN_COMBINATIONS = [
[0,1,2],
[3,4,5],
[6,7,8],
[0,3,6],
[0,4,8],
[6,4,2],
[1,4,7],
[2,5,8]
]
and I am defining this method:
def won?(board)
for x in WIN_COMBINATIONS
win_index_1 = x[0]
win_index_2 = x[1]
win_index_3 = x[2]
p1 = board[win_index_1]
p2 = board[win_index_2]
p3 = board[win_index_3]
if p1 == 'X' && p2 == 'X' && p3 == 'X'
return x
else
false
end
end
end
and when
board = [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']
The won? method returns every item in WIN_COMBINATIONS instead of false. I have no idea why and I would appreciate it if someone would please help.

In ruby each block, returns something even if you don't explicitly return a value, in your case the for block is returning the collection over you are iterating I think that your logic is correct but you need to make a small change, something like:
def won?(board)
for x in WIN_COMBINATIONS
win_index_1 = x[0]
win_index_2 = x[1]
win_index_3 = x[2]
p1 = board[win_index_1]
p2 = board[win_index_2]
p3 = board[win_index_3]
return x if p1 == "X" && p2 == "X" && p3 == "X"
end
false
end
The code above will return x if any of the sequences are valid, false in any other case (although you should follow the convention and return true or false if you are using the signature ?, other option is remove the ? and return nil instead of false). Hope this helps! 👍

Every block of code returns a value even if there is no explicit return keyword. for loop returns itself. With board = [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '] you never hit the return x statement and you get WIN_COMBINATIONS back.
If you replace return false with return [], it will allow you to avoid 'expected a collection that can be converted to an array with #to_ary or #to_a, but got false'. But Ruby has a bunch of nice methods to work with arrays.
By convention, methods that end with ? should return a boolean value. Your method returns an array when the if statement is true.
In your case, I'd rename the method and use the find method to iterate through WIN_COMBINATIONS:
def find_win_combination(board)
# You can use decomposition as in the next example `do |wi_0, wi_1, wi_2|`
win_combination = WIN_COMBINATIONS.find do |combination|
win_index_0 = combination[0]
win_index_1 = combination[1]
win_index_2 = combination[2]
# If it's true the find method will return the win combination
board[win_index_0] == "X" && board[win_index_1] == "X" && board[win_index_2] == "X"
end
# return an empty array if win_combination is not found
win_combination || []
end
board = [' ', ' ', ' ', 'X', 'X', 'X', ' ', ' ', ' ']
> find_win_combination(board)
=> [3, 4, 5]
board = [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']
> find_win_combination(board)
=> []
If you need won? method to return a boolean method, just use any? method on WIN_COMBINATIONS. The result of any? will be returned as a result of won`?
def won?(board)
# Here is a decomposition of each item of `WIN_COMBINATIONS` into three variables
WIN_COMBINATIONS.any? do |wi_0, wi_1, wi_2|
board[wi_0] == "X" && board[wi_1] == "X" && board[wi_2] == "X"
end
end
board = [' ', ' ', ' ', 'X', 'X', 'X', ' ', ' ', ' ']
> won?(board)
=> true
board = [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']
> won?(board)
=> false

Related

Can if statements be refactored to boolean operators like !=, ==, && and ||?

Can this be refactored into boolean? board is an array and move is and index. position_taken?(board, move) should return false if board[move] is " ", "" or nil but return true if board[move] is "X" or "O".
def position_taken?(board, move)
if board[move] == " "
false
elsif board[move] == ""
false
elsif board[move] == nil
false
else
true
end
end
Since you have less and simpler positive cases I would test for the opposite:
def position_taken?(board, move)
%w[X O].include?(board[move])
end
It will handle invalid values differently than your original approach but it does directly what the method name suggests: check if the position is taken (instead of checking if the position is not not taken).
You can use none? and pass board[move] to be compared:
[' ', '', nil].none?(nil) # false
[' ', '', nil].none?('') # false
[' ', '', nil].none?(' ') # false
[' ', '', nil].none?('t') # true
It's hard to know what your method is doing without knowing what you will pass as board argument so the best I can demo is just a string:
str=''
!!(str && !str.strip.empty?)
#=> false
str='a'
!!(str && !str.strip.empty?)
#=> true
str=' '
!!(str && !str.strip.empty?)
#=> false
str=' '
!!(str && !str.strip.empty?)
#=> false
str=nil
!!(str && !str.strip.empty?)
#=> false
I think this is logically equivalent:
def position_taken?(board, move)
!(board[move] == " " || board[move] == "" || board[move] == nil)
end
If any of the conditions are true, it will be inverted and return false. If all of the conditions are false, it will be inverted to true.
You could also put the strings you want to match against in an array and use something like !include? or, if you're using ActiveSupport, you can use exclude?.
def position_taken?(board, move)
["", " ", nil].exclude?(board[move])
end
FWIW, this code should be refactored to:
def position_taken?(board, move)
not board[move].to_s.strip.empty?
end
If only X or O is allowed, Why don't you specify them in the condition like this:
boared[idx] == 'X' || board[idx] == 'O'
I think it is much better for readability and simple.
def position_taken?(board, move)
!case board[move]; when " ", "", nil; true end
end
Found the solution. It can be refactored to this:
def position_taken?(board, move)
board[move] != " " && board[move] != ""
end

Converting an array of morphemes to a sentence in Ruby

I want to convert an array of morphemes produced by a PTB-style tokenizer:
["The", "house", "is", "n't", "on", "fire", "."]
To a sentence:
"The house isn't on fire."
What is a sensible way to accomplish this?
If we take #sawa's advice on the apostrophe and make your array this:
["The", "house", "isn't", "on", "fire", "."]
You can get what your looking for (with punctuation support!) with this:
def sentence(array)
str = ""
array.each_with_index do |w, i|
case w
when '.', '!', '?' #Sentence enders, inserts a space too if there are more words.
str << w
str << ' ' unless(i == array.length-1)
when ',', ';' #Inline separators
str << w
str << ' '
when '--' #Dash
str << ' -- '
else #It's a word
str << ' ' unless str[-1] == ' ' || str.length == 0
str << w
end
end
str
end

My media converter app crashes when handling file names with spaces

I wrote an app that uses ffmpeg to convert media files (.wav, .avi, .mp3, ... etc.). It works only with file names that have no spaces. When a file name with spaces is encountered, the app immediately closes. Can someone tell me if the string I'm using to call ffmpeg is correct, or need some characters escaped? Below is a fragment of the code:
...
...
...
#Select Media
os.chdir("c:\\d-Converter\\ffmpeg\\bin")
wrkdir = os.getcwd()
filelist = os.listdir(wrkdir)
self.formats1 = []
for filename in filelist:
(head, filename) = os.path.split(filename)
if filename.endswith(".avi") or filename.endswith(".mp4") or filename.endswith(".flv") or filename.endswith(".mov") or filename.endswith(".mpeg4") or filename.endswith(".mpeg") or filename.endswith(".mpg2") or filename.endswith(".wav") or filename.endswith(".mp3"):
self.formats1.append(filename)
self.format_combo1=wx.ComboBox(panel, size=(140, -1),value='Select Media', choices=self.formats1, style=wx.CB_DROPDOWN, pos=(300,50))
self.Bind(wx.EVT_COMBOBOX, self.fileFormats, self.format_combo1)
...
...
...
def fileFormats(self, e):
myFormats = {'audio': ('Select Format','.mp3', '.ogg', '.wav', '.wma'), 'video': ('Select Format','.flv','.mpg', '.mp4', '.mpeg')}
bad_file = ['Media not supported']
myFile = self.format_combo1.GetValue()
f_exten = (x for x in myFormats['audio'] + myFormats['video'] if myFile.endswith(x))
extension = f_exten.next()
if extension in myFormats['audio']:
self.format_combo2.SetItems(myFormats['audio'])
elif extension in myFormats['video']:
self.format_combo2.SetItems(myFormats['video'])
else:
self.format_combo2.SetItems(bad_file)
...
...
...
def convertButton(self, e):
unit1 = self.format_combo1.GetValue()
if unit1:
unit1 = self.repl_Wspace(unit1)
#Media Formats
unit2 = self.format_combo2.GetValue()
unit3 = self.format_combo3.GetValue()
unit4 = None
unit5 = self.format_combo5.GetValue()
bitRate = self.format_combo6.GetValue()
unit6 = bitRate
if unit3 == '-qmax':
unit4 = self.format_combo4.GetValue()
else:
pass
os.chdir("c:\\d-Converter\\ffmpeg\\bin")
wrkdir = os.getcwd()
newfile = unit1
stripped = newfile.strip('mpeg3aviovfl4w2c.') #Strips the extension from the original file name
progname='c:\\d-Converter\\ffmpeg\\bin\\ffmpeg.exe' + ' -i '
preset1_a='-vn -ar 44100 -ac 2 -ab'
preset1_b='-f mp3 '
preset_mp3='.mp3'
chck_unit1 = self.my_endswith(unit1)
while True:
if unit5 == 'video to mp3':
if unit6 == 'k/bs' or unit6 == '':
amsg = wx.MessageDialog(None, 'You must select a bit rate.', 'Media Converter', wx.ICON_INFORMATION)
amsg.ShowModal()
amsg.Destroy()
break
elif unit5 == 'video to mp3' and unit6 != 'k/bs' or unit6 != '':
self.button.Disable()
self.button2.Enable()
self.format_combo1.Disable()
self.format_combo2.Disable()
self.format_combo3.Disable()
self.format_combo4.Disable()
self.format_combo5.Disable()
self.format_combo6.Disable()
startWorker(self.LongTaskDone, self.LongTask3, wargs=(progname, wrkdir, unit1, preset1_a, unit6, preset1_b, stripped, preset_mp3))
break
elif unit1 != unit1.endswith(".mpg") or unit1.endswith(".mpeg") or unit1.endswith(".avi") or unit1.endswith(".mp4") or unit1.endswith(".flv"):
bmsg = wx.MessageDialog(None, 'You must select a valid format to convert to .mp3.', 'Media Converter', wx.ICON_INFORMATION)
bmsg.ShowModal()
bmsg.Destroy()
break
else:
pass
if unit1 == 'Select Media' or unit1 == '':
amsg = wx.MessageDialog(None, 'You must select a media file!', 'Media Converter', wx.ICON_INFORMATION)
amsg.ShowModal()
amsg.Destroy()
break
elif unit2 == 'Select Format' or unit2 == '' or unit2 == chck_unit1:
amsg = wx.MessageDialog(None, 'You must select a valid format', 'Media Converter', wx.ICON_INFORMATION)
amsg.ShowModal()
amsg.Destroy()
break
elif unit3 == 'Select Quality' or unit3 == '':
amsg = wx.MessageDialog(None, 'You must select quality', 'Media Converter', wx.ICON_INFORMATION)
amsg.ShowModal()
amsg.Destroy()
break
elif unit3 != 'Select Quality' or unit3 != '':
self.format_combo5.Disable()
if unit3 == '-qmax':
if unit4 == '0' or unit4 == '':
amsg = wx.MessageDialog(None, 'You must select number between 1-8.', 'Media Converter', wx.ICON_INFORMATION)
amsg.ShowModal()
amsg.Destroy()
break
else:
self.button.Disable()
self.button2.Enable()
self.format_combo1.Disable()
self.format_combo2.Disable()
self.format_combo3.Disable()
self.format_combo4.Disable()
self.format_combo5.Disable()
startWorker(self.LongTaskDone, self.LongTask2, wargs=(progname,wrkdir,unit1,unit3,unit4,stripped,unit2))
break
elif unit3 == '-sameq':
self.button.Disable()
self.button2.Enable()
self.format_combo1.Disable()
self.format_combo2.Disable()
self.format_combo3.Disable()
self.format_combo4.Disable()
self.format_combo5.Disable()
startWorker(self.LongTaskDone, self.LongTask, wargs=(progname,wrkdir,unit1,unit3,stripped,unit2))
break
def LongTask(self, progname, wrkdir, unit1, unit3, stripped, unit2):
convert_file1 = progname + wrkdir + '\\' + unit1 + ' ' + unit3 + ' ' + stripped + unit2
self.statusbar.SetStatusText("Converting: " + unit1 + "...")
os.system(convert_file1)
print convert_file1
def LongTask2(self, progname, wrkdir, unit1, unit3, unit4, stripped, unit2):
convert_file2 = progname + wrkdir + '\\' + unit1 + ' ' + unit3 + ' ' + unit4 + ' ' + stripped + unit2
self.statusbar.SetStatusText("Converting: " + unit1 + "...")
os.system(convert_file2)
...
...
...
Don't use os.system to execute your command. Instead, use subprocess, with each of your arguments as a separate entry in the arguments list:
import subprocess
progname='c:\\d-Converter\\ffmpeg\\bin\\ffmpeg.exe'
subprocess.check_call([progname, '-i', ... other args here])
This will ensure your arguments aren't interpreted incorrectly, and aren't susceptible to injection attacks.

What is the syntax for array.select?

I'm trying to use Array.select to separate out, and then delete, strings from a database that contain unwanted items. I get no errors but this does not seem to be working as hoped.
The relevant code is the last part:
totaltext = []
masterfacs = ''
nilfacs = ''
roomfacs_hash = {'lcd' => lcd2, 'wifi'=> wifi2, 'wired' => wired2, 'ac' => ac2}
roomfacs_hash.each do |fac, fac_array|
if roomfacs.include? (fac)
totaltext = (totaltext + fac_array)
masterfacs = (masterfacs + fac + ' ')
else
nilfacs = (nilfacs + fac + ' ')
end
end
finaltext = Array.new
text_to_delete = totaltext2.select {|sentences| sentences =~ /#{nilfacs}/i}
finaltext = totaltext2.delete (text_to_delete)
puts finaltext
It's probably not working because delete isn't a chainable method (the return value is the object you are trying to delete on success, or nil if not found; not the modified array). To simplify your code, just use reject
finaltext = totaltext.reject{|sentence| nilfacs.any?{|fac| sentence =~ /#{fac}/i } }

Ternary operator

I have an array d = ['foo', 'bar', 'baz'], and want to put its elements together into a string delimited by , and and at the last element so that it will become foo, bar and baz.
Here is what I'm trying to do:
s = ''
d.each_with_index { |x,i|
s << x
s << i < d.length - 1? i == d.length - 2 ? ' and ' : ', ' : ''
}
but the interpreter gives an error:
`<': comparison of String with 2 failed (ArgumentError)
However, it works with += instead of <<, but the Ruby Cookbook says that:
If efficiency is important to you, don't build a new string when you can append items onto an existing string. [And so on]... Use str << var1 << ' ' << var2 instead.
Is it possible without += in this case?
Also, there has to be a more elegant way of doing this than the code above.
You're just missing some parenthesis:
d = ['foo', 'bar', 'baz']
s = ''
d.each_with_index { |x,i|
s << x
s << (i < d.length - 1? (i == d.length - 2 ? ' and ' : ', ') : '')
}
I'd find
s << i < d.length - 1? i == d.length - 2 ? ' and ' : ', ' : ''
hard to read or maintain.
I'd probably change it to
join = case
when i < d.length - 2 then ", "
when i == d.length - 2 then " and "
when i == d.length then ""
end
s << join
Or possibly do
earlier_elements = d[0..-2].join(", ")
s = [earlier_elements, d[-1..-1]].join(" and ")
Or
joins = [", "] * (d.length - 2) + [" and "]
s = d.zip(joins).map(&:join).join
So much simpler like this:
"#{d[0...-1].join(", ")} and #{d.last}"

Resources