在Ruby中解析街道地址

时间:2013-04-21 18:46:30

标签: ruby-on-rails ruby regex parsing

我正在将地址处理为数据库的各自字段格式。我可以得到房屋号码和街道类型,但试图确定最好的方法来获得没有数字和最后一个字的街道。收到的标准街道地址是:

    res[:address] = '7707 Foo Bar Blvd'

截至目前,我可以解析以下内容:

    house = res[:address].gsub(/\D/, '')
    street_type = res[:address].split(/\s+/).last

我的第一个挑战是如何获得'Foo Bar'。请注意,街道名称可以是一个,两个或三个单词。我正在努力为Ruby找到一个单行表达式解决方案。

我的第二个问题是如何改进'house'代码来处理最后有alpha的门牌号码。例如,“7707B”。

最后,如果您可以参考一个好的备忘单,其中包含有助于这些表达的示例。

5 个答案:

答案 0 :(得分:11)

如果可能的话,我建议使用一个库,因为地址解析可能很困难。查看Indirizzo Ruby gem,这样可以轻松实现:

require 'Indirizzo'
address = Indirizzo::Address.new("7707 Foo Bar Blvd")
address.number
 => "7707"
address.street
 => ["foo bar blvd", "foo bar boulevard"] 

即使您使用Indirizzo库本身,阅读其源代码可能非常有用,看看他们如何解决问题。例如,它对正则表达式进行了精细调整,以匹配地址的不同部分:

Match = {
  # FIXME: shouldn't have to anchor :number and :zip at start/end
  :number   => /^(\d+\W|[a-z]+)?(\d+)([a-z]?)\b/io,
  :street   => /(?:\b(?:\d+\w*|[a-z'-]+)\s*)+/io,
  :city     => /(?:\b[a-z][a-z'-]+\s*)+/io,
  :state    => State.regexp,
  :zip      => /\b(\d{5})(?:-(\d{4}))?\b/o,
  :at       => /\s(at|@|and|&)\s/io,
  :po_box => /\b[P|p]*(OST|ost)*\.*\s*[O|o|0]*(ffice|FFICE)*\.*\s*[B|b][O|o|0][X|x]\b/
}

源代码中的这些文件可以提供更多细节:

(但我也普遍同意@ drhenner的评论,为了让自己更容易,你可能只是在不同的领域接受这些数据输入。)

编辑:要提供有关如何删除街道后缀(例如“Blvd”)的更具体的答案,您可以使用Indirizzo的正则表达式常量(例如Suffix_Type来自{ {1}})像这样:

constants.rb

(注意我还将address = Indirizzo::Address.new("7707 Foo Bar Blvd", :expand_streets => false) address.street.map {|street| street.gsub(Indirizzo::Suffix_Type.regexp, '').strip } => ["foo bar"] 传递给初始化程序,以避免同时扩展“Blvd”和“Boulevard”替代方案,因为我们无论如何都要丢弃后缀。)

答案 1 :(得分:2)

你可以在正则表达式

中快速松散地使用命名捕获组
matches = res[:address].match(/^(?<number>\S*)\s+(?<name>.*)\s+(?<type>.*)$/)
number = matches[:number]
house = matches[:name]
street_type = matches[:type]

或者如果您希望您的正则表达式更准确,您可以替换的类型     (?<type>.*) 同     (?<type>(Blvd|Ave|Rd|St)) 并添加您想要的所有不同选项

答案 2 :(得分:1)

你可以使用类似的东西:

^\S+ (.+?) \S+$

\S匹配任何非空格字符

^匹配字符串的开头

$匹配字符串

的结尾

(.+?)捕获两者之间的任何内容。

答案 3 :(得分:0)

仔细检查数据集以确定是否尚未处理此问题。

我花了相当多的时间首先创建一个可能街道名称结尾的分类,使用正则表达式条件试图从完整的地址字符串中取出街道号码,然后结果是我的shapefile的属性表已经已经细分了这些组件。

在你继续解析地址字符串的过程之前,由于不可避免的奇怪变化(一些包裹地址用于内陆地块并且有奇怪的地址等)总是有点苦差事,所以确保你的数据集没有我已经为你做了这个!!!


但如果不这样做,请运行地址字符串,address.split(" ")创建一个'words'数组。在大多数情况下,第一个“单词”是街道号码。这适用于我的地址的约95%。 (注意:我的:地址字符串不包含城市,县,州,邮编,它们只是本地地址)

我浏览了整个地址,并从每个地址中拨出最后一个“字”。检查了这个阵列&amp;拔出任何不是“Lane”,“Road”,“Rd”或其他什么的“单词”。从这个地址结尾列表中我创建了这个巨大的匹配正则表达式对象

streetnm_endings = street_endings.map {|s| /#{s}/ }
endings_matches = Regexp.union(street_endings)

我遍历每个地址字符串shift - 输出第一个数组成员,因为这几乎总是街道号码。然后gsub out the street endings得到街道名称sans街道号码或街道名称结尾,这些数据库通常不喜欢:

parcels.each do |p|
  remainder = p.address.split(" ")
  p.streetnum = remainder.shift
  p.streetname = remainder.join(" ").gsub(endings_matches, "")
  p.save
end

它并不总是有效,但大部分时间都有效。

答案 4 :(得分:0)

我目前只是将我给予的任何内容传递给googlemaps并让他们发回一个非常容易解析的格式化街道地址。

function addressReview(addressInput) {
geocoder = new google.maps.Geocoder();
var latlng = new google.maps.LatLng(-34.397, 150.644);
geocoder.geocode( { 'address': addressInput}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
  if (results[0]) {
    var addr = results[0].formatted_address;
    var latTi = results[0].geometry.location.lat();
    var lonGi = results[0].geometry.location.lng();
    $.post('/welcome/gcode',{ add: addr , la: latTi , lo: lonGi });
    $('#cust_addy').val(addr);
  } else {
    $('#cust_addy').attr("placeholder",'Cannnot determine location');
  }
} else {
  $('#cust_addy').attr("placeholder",'Cannnot determine location');
}
});
}

在那之后,我把它分成红宝石。与.split(',')和.split('')