-1

I have some simple text processing scripts I use all the time, and I want to translate those scripts into Ruby, to get familiar with the language.

This is the first script I can't get to run:

#!/usr/bin/env ruby

@text = ARGF.read

@replacements = [{:from=>"—", :to=>". "}, {:from=>"ffl", :to=>"ffl"}, {:from=>"ffi", :to=>"ffi"}, {:from=>"fi", :to=>"fi"}, {:from=>"fl", :to=>"fl"}, {:from=>"ff", :to=>"ff"}, {:from=>"æ", :to=>"ae"}, {:from=>"é", :to=>"e"}, {:from=>"Ç", :to=>"s"}, {:from=>"ü", :to=>"u"}, {:from=>"â", :to=>"a"}, {:from=>"ä", :to=>"a"}, {:from=>"à", :to=>"a"}, {:from=>"å", :to=>"a"}, {:from=>"ç", :to=>"s"}, {:from=>"ê", :to=>"e"}, {:from=>"ë", :to=>"e"}, {:from=>"è", :to=>"e"}, {:from=>"ï", :to=>"i"}, {:from=>"î", :to=>"i"}, {:from=>"ì", :to=>"i"}, {:from=>"Ä", :to=>"a"}, {:from=>"Å", :to=>"a"}, {:from=>"É", :to=>"e"}, {:from=>"ô", :to=>"oh"}, {:from=>"ö", :to=>"oe"}, {:from=>"ò", :to=>"o"}, {:from=>"û", :to=>"uu"}, {:from=>"ù", :to=>"u"}, {:from=>"ÿ", :to=>"o"}, {:from=>"Ö", :to=>"o"}, {:from=>"Ü", :to=>"u"}, {:from=>"á", :to=>"ah"}, {:from=>"í", :to=>"ee"}, {:from=>"ó", :to=>"oh"}, {:from=>"ú", :to=>"uu"}, {:from=>"ñ", :to=>"ny"}, {:from=>"Ñ", :to=>"ny"}] 

@replacements.each do |pair|
  @text.gsub!(/#{pair[:from]}/, pair[:to])
end

puts @text

And here's the error I get:

/home/alec/.bei/under-boac:5: invalid multibyte char (US-ASCII)
/home/alec/.bei/under-boac:5: invalid multibyte char (US-ASCII)
/home/alec/.bei/under-boac:5: syntax error, unexpected $end, expecting '}'
@replacements = [{:from=>"—", :to=>". "}, {:from=>"ffl"...
                            ^

I based part of it on "Best practices with STDIN in Ruby?".

Community
  • 1
  • 1
magnetar
  • 6,487
  • 7
  • 28
  • 40

4 Answers4

4

This is your base code, reformatted for readability:

@replacements = [
  { :from => "—", :to => ". "  },
  { :from => "ffl", :to => "ffl" },
  { :from => "ffi", :to => "ffi" },
  { :from => "fi", :to => "fi"  },
  { :from => "fl", :to => "fl"  },
  { :from => "ff", :to => "ff"  },
  { :from => "æ", :to => "ae"  },
  { :from => "é", :to => "e"   },
  { :from => "Ç", :to => "s"   },
  { :from => "ü", :to => "u"   },
  { :from => "â", :to => "a"   },
  { :from => "ä", :to => "a"   },
  { :from => "à", :to => "a"   },
  { :from => "å", :to => "a"   },
  { :from => "ç", :to => "s"   },
  { :from => "ê", :to => "e"   },
  { :from => "ë", :to => "e"   },
  { :from => "è", :to => "e"   },
  { :from => "ï", :to => "i"   },
  { :from => "î", :to => "i"   },
  { :from => "ì", :to => "i"   },
  { :from => "Ä", :to => "a"   },
  { :from => "Å", :to => "a"   },
  { :from => "É", :to => "e"   },
  { :from => "ô", :to => "oh"  },
  { :from => "ö", :to => "oe"  },
  { :from => "ò", :to => "o"   },
  { :from => "û", :to => "uu"  },
  { :from => "ù", :to => "u"   },
  { :from => "ÿ", :to => "o"   },
  { :from => "Ö", :to => "o"   },
  { :from => "Ü", :to => "u"   },
  { :from => "á", :to => "ah"  },
  { :from => "í", :to => "ee"  },
  { :from => "ó", :to => "oh"  },
  { :from => "ú", :to => "uu"  },
  { :from => "ñ", :to => "ny"  },
  { :from => "Ñ", :to => "ny"  }
] 

@replacements.each do |pair|
  @text.gsub!( /#{ pair[:from] }/, pair[:to] )
end

That can be simplified, and the hashes combined to be one big one, and, incidently, be used as a hash should be used:

# encoding: utf-8

@replacements = {
  "—" => ". "  ,
  "ffl" => "ffl" ,
  "ffi" => "ffi" ,
  "fi" => "fi"  ,
  "fl" => "fl"  ,
  "ff" => "ff"  ,
  "æ" => "ae"  ,
  "é" => "e"   ,
  "Ç" => "s"   ,
  "ü" => "u"   ,
  "â" => "a"   ,
  "ä" => "a"   ,
  "à" => "a"   ,
  "å" => "a"   ,
  "ç" => "s"   ,
  "ê" => "e"   ,
  "ë" => "e"   ,
  "è" => "e"   ,
  "ï" => "i"   ,
  "î" => "i"   ,
  "ì" => "i"   ,
  "Ä" => "a"   ,
  "Å" => "a"   ,
  "É" => "e"   ,
  "ô" => "oh"  ,
  "ö" => "oe"  ,
  "ò" => "o"   ,
  "û" => "uu"  ,
  "ù" => "u"   ,
  "ÿ" => "o"   ,
  "Ö" => "o"   ,
  "Ü" => "u"   ,
  "á" => "ah"  ,
  "í" => "ee"  ,
  "ó" => "oh"  ,
  "ú" => "uu"  ,
  "ñ" => "ny"  ,
  "Ñ" => "ny"  
}
@replacements.each do |k,v|
  @text.gsub!(k, v)
end

Note: use the "encoding comment" to help Ruby understand the encoding of the characters.

But using the same hash, that loop can be reduced down to this, which runs extremely fast:

@text.gsub!(Regexp.union(@replacements.keys), @replacements)
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
2

If you're using non 7-bit ASCII in your source file, you need to add a header to identify what character set is employed. Example:

#!/usr/bin/env ruby
# encoding: utf-8

Further, you're doing something here that's exceedingly inefficient, requiring O(N) passes on the file when you can do this in O(1) if you construct the proper regular expression:

replace_map = Hash[@replacements.collect { |r| [ r[:from], r[:to] ] }]
replace_regex = Regexp.new("(#{replace_map.keys.collect { |r| Regexp.escape(r) }.join('|')})")

@text.gsub!(replace_regexp) do |s|
  replace_map[s[1]]
end

It would be a lot easier to work with a key/value mapping instead of the strange :from / :to pairing you have there.

tadman
  • 208,517
  • 23
  • 234
  • 262
2
#encoding: utf-8

@text = "öbñ"
@replacements = [{:from=>"—", :to=>". "}, {:from=>"ffl", :to=>"ffl"}, {:from=>"ffi", :to=>"ffi"}, {:from=>"fi", :to=>"fi"}, {:from=>"fl", :to=>"fl"}, {:from=>"ff", :to=>"ff"}, {:from=>"æ", :to=>"ae"}, {:from=>"é", :to=>"e"}, {:from=>"Ç", :to=>"s"}, {:from=>"ü", :to=>"u"}, {:from=>"â", :to=>"a"}, {:from=>"ä", :to=>"a"}, {:from=>"à", :to=>"a"}, {:from=>"å", :to=>"a"}, {:from=>"ç", :to=>"s"}, {:from=>"ê", :to=>"e"}, {:from=>"ë", :to=>"e"}, {:from=>"è", :to=>"e"}, {:from=>"ï", :to=>"i"}, {:from=>"î", :to=>"i"}, {:from=>"ì", :to=>"i"}, {:from=>"Ä", :to=>"a"}, {:from=>"Å", :to=>"a"}, {:from=>"É", :to=>"e"}, {:from=>"ô", :to=>"oh"}, {:from=>"ö", :to=>"oe"}, {:from=>"ò", :to=>"o"}, {:from=>"û", :to=>"uu"}, {:from=>"ù", :to=>"u"}, {:from=>"ÿ", :to=>"o"}, {:from=>"Ö", :to=>"o"}, {:from=>"Ü", :to=>"u"}, {:from=>"á", :to=>"ah"}, {:from=>"í", :to=>"ee"}, {:from=>"ó", :to=>"oh"}, {:from=>"ú", :to=>"uu"}, {:from=>"ñ", :to=>"ny"}, {:from=>"Ñ", :to=>"ny"}] 

# Hammer the @replacements into one Hash like {"—"=>". ", "ffl"=>"ffl"}:
from_to = Hash[@replacements.map{|h| h.values}]
# Generate one Regular expression to catch all keys:
re = Regexp.union(from_to.keys)
# Let gsub do the work in one pass:
@text.gsub!(re, from_to)

This does about the same as @tadman's code. The #encoding: utf-8 should solve your problem; the rest of the lines prevent scanning the text 38 times.

steenslag
  • 79,051
  • 16
  • 138
  • 171
0

You even can simplify:

@replacements=Hash[*%w[— .  ffl ffl ffi ffi fi fi fl fl ff ff æ ae é e Ç s ü u â a ä a à a å a ç s ê e ë e è e ï i î i ì i Ä a Å a É e ô oh ö oe ò o û uu ù u ÿ o Ö o Ü u á ah í ee ó oh ú uu ñ ny Ñ ny]]
Konstantin
  • 2,983
  • 3
  • 33
  • 55