Программно определить, следует ли описывать объект с помощью «a» или «an»?

У меня есть база данных существительных (например, «дом», «восклицательный знак», «яблоко»), которые мне нужно вывести и описать в моем приложении. Трудно собрать естественное предложение для описания предмета без использования «a» или «an» – «дом БОЛЬШОЙ», «восклицательный знак – МАЛЕНЬКИЙ» и т. Д.

Есть ли какая-либо функция, библиотека или хак, которые я могу использовать в PHP, чтобы определить, целесообразнее ли описывать какое-либо данное существительное с A или AN?

Вы хотите определить подходящую неопределенную статью. Lingua::EN::Inflect – это модуль Perl, который отлично справляется. Я извлек соответствующий код и вставил его ниже. Это всего лишь куча дел и некоторые регулярные выражения, поэтому перенос на PHP не должен быть трудным. Друг портировал его на Python здесь, если кому-то это интересно.

 # 2. INDEFINITE ARTICLES # THIS PATTERN MATCHES STRINGS OF CAPITALS STARTING WITH A "VOWEL-SOUND" # CONSONANT FOLLOWED BY ANOTHER CONSONANT, AND WHICH ARE NOT LIKELY # TO BE REAL WORDS (OH, ALL RIGHT THEN, IT'S JUST MAGIC!) my $A_abbrev = q{ (?! FJO | [HLMNS]Y. | RY[EO] | SQU | ( F[LR]? | [HL] | MN? | N | RH? | S[CHKLMNPTVW]? | X(YL)?) [AEIOU]) [FHLMNRSX][AZ] }; # THIS PATTERN CODES THE BEGINNINGS OF ALL ENGLISH WORDS BEGINING WITH A # 'y' FOLLOWED BY A CONSONANT. ANY OTHER Y-CONSONANT PREFIX THEREFORE # IMPLIES AN ABBREVIATION. my $A_y_cons = 'y(b[lor]|cl[ea]|fere|gg|p[ios]|rou|tt)'; # EXCEPTIONS TO EXCEPTIONS my $A_explicit_an = enclose join '|', ( "euler", "hour(?!i)", "heir", "honest", "hono", ); my $A_ordinal_an = enclose join '|', ( "[aefhilmnorsx]-?th", ); my $A_ordinal_a = enclose join '|', ( "[bcdgjkpqtuvwyz]-?th", ); sub A { my ($str, $count) = @_; my ($pre, $word, $post) = ( $str =~ m/\A(\s*)(?:an?\s+)?(.+?)(\s*)\Z/i ); return $str unless $word; my $result = _indef_article($word,$count); return $pre.$result.$post; } sub AN { goto &A } sub _indef_article { my ( $word, $count ) = @_; $count = $persistent_count if !defined($count) && defined($persistent_count); return "$count $word" if defined $count && $count!~/^($PL_count_one)$/io; # HANDLE USER-DEFINED VARIANTS my $value; return "$value $word" if defined($value = ud_match($word, @A_a_user_defined)); # HANDLE ORDINAL FORMS $word =~ /^($A_ordinal_a)/i and return "a $word"; $word =~ /^($A_ordinal_an)/i and return "an $word"; # HANDLE SPECIAL CASES $word =~ /^($A_explicit_an)/i and return "an $word"; $word =~ /^[aefhilmnorsx]$/i and return "an $word"; $word =~ /^[bcdgjkpqtuvwyz]$/i and return "a $word"; # HANDLE ABBREVIATIONS $word =~ /^($A_abbrev)/ox and return "an $word"; $word =~ /^[aefhilmnorsx][.-]/i and return "an $word"; $word =~ /^[az][.-]/i and return "a $word"; # HANDLE CONSONANTS $word =~ /^[^aeiouy]/i and return "a $word"; # HANDLE SPECIAL VOWEL-FORMS $word =~ /^e[uw]/i and return "a $word"; $word =~ /^onc?e\b/i and return "a $word"; $word =~ /^uni([^nmd]|mo)/i and return "a $word"; $word =~ /^ut[th]/i and return "an $word"; $word =~ /^u[bcfhjkqrst][aeiou]/i and return "a $word"; # HANDLE SPECIAL CAPITALS $word =~ /^U[NK][AIEO]?/ and return "a $word"; # HANDLE VOWELS $word =~ /^[aeiou]/i and return "an $word"; # HANDLE y... (BEFORE CERTAIN CONSONANTS IMPLIES (UNNATURALIZED) "i.." SOUND) $word =~ /^($A_y_cons)/io and return "an $word"; # OTHERWISE, GUESS "a" return "a $word"; } 

Мне нужно было это для проекта C #, так что вот C # -ный порт упомянутого выше кода Python . Не забудьте включить using System.Text.RegularExpressions; в исходном файле.

 private string GetIndefiniteArticle(string noun_phrase) { string word = null; var m = Regex.Match(noun_phrase, @"\w+"); if (m.Success) word = m.Groups[0].Value; else return "an"; var wordi = word.ToLower(); foreach (string anword in new string[] { "euler", "heir", "honest", "hono" }) if (wordi.StartsWith(anword)) return "an"; if (wordi.StartsWith("hour") && !wordi.StartsWith("houri")) return "an"; var char_list = new char[] { 'a', 'e', 'd', 'h', 'i', 'l', 'm', 'n', 'o', 'r', 's', 'x' }; if (wordi.Length == 1) { if (wordi.IndexOfAny(char_list) == 0) return "an"; else return "a"; } if (Regex.Match(word, "(?!FJO|[HLMNS]Y.|RY[EO]|SQU|(F[LR]?|[HL]|MN?|N|RH?|S[CHKLMNPTVW]?|X(YL)?)[AEIOU])[FHLMNRSX][AZ]").Success) return "an"; foreach (string regex in new string[] { "^e[uw]", "^onc?e\b", "^uni([^nmd]|mo)", "^u[bcfhjkqrst][aeiou]" }) { if (Regex.IsMatch(wordi, regex)) return "a"; } if (Regex.IsMatch(word, "^U[NK][AIEO]")) return "a"; else if (word == word.ToUpper()) { if (wordi.IndexOfAny(char_list) == 0) return "an"; else return "a"; } if (wordi.IndexOfAny(new char[] { 'a', 'e', 'i', 'o', 'u' }) == 0) return "an"; if (Regex.IsMatch(wordi, "^y(b[lor]|cl[ea]|fere|gg|p[ios]|rou|tt)")) return "an"; return "a"; } 

Я также искал такое решение, но в JavaScript. Поэтому я поместил его в JS, вы можете проверить фактический проект в github https://github.com/rigoneri/indefinite-article.js

Вот фрагмент кода:

  function indefinite_article(phrase) { // Getting the first word var match = /\w+/.exec(phrase); if (match) var word = match[0]; else return "an"; var l_word = word.toLowerCase(); // Specific start of words that should be preceeded by 'an' var alt_cases = ["honest", "hour", "hono"]; for (var i in alt_cases) { if (l_word.indexOf(alt_cases[i]) == 0) return "an"; } // Single letter word which should be preceeded by 'an' if (l_word.length == 1) { if ("aedhilmnorsx".indexOf(l_word) >= 0) return "an"; else return "a"; } // Capital words which should likely be preceeded by 'an' if (word.match(/(?!FJO|[HLMNS]Y.|RY[EO]|SQU|(F[LR]?|[HL]|MN?|N|RH?|S[CHKLMNPTVW]?|X(YL)?)[AEIOU])[FHLMNRSX][AZ]/)) { return "an"; } // Special cases where a word that begins with a vowel should be preceeded by 'a' regexes = [/^e[uw]/, /^onc?e\b/, /^uni([^nmd]|mo)/, /^u[bcfhjkqrst][aeiou]/] for (var i in regexes) { if (l_word.match(regexes[i])) return "a" } // Special capital words (UK, UN) if (word.match(/^U[NK][AIEO]/)) { return "a"; } else if (word == word.toUpperCase()) { if ("aedhilmnorsx".indexOf(l_word[0]) >= 0) return "an"; else return "a"; } // Basic method of words that begin with a vowel being preceeded by 'an' if ("aeiou".indexOf(l_word[0]) >= 0) return "an"; // Instances where y follwed by specific letters is preceeded by 'an' if (l_word.match(/^y(b[lor]|cl[ea]|fere|gg|p[ios]|rou|tt)/)) return "an"; return "a"; } 

Сделайте массив с гласными в нем. Проверьте, находится ли первая буква слова, которое вы проверяете, в массиве гласных. Будет работать, если не иметь дело с акронимами.

Сложно писать с нуля, tbh. Если слово начинается с гласного, оно получает «a»; если он начинается с согласного, он получает «an». Программно это легко сделать – если у вас есть какие-либо краевые случаи (например, вы можете использовать английский стиль английского языка «исторический случай»), вы можете обрабатывать их индивидуально.

Это похоже на использование инфлектора, только с правилом грамматики «a» / «a» вместо множественного числа. Посмотрите, как CakePHP или Rails обрабатывают перегиб для более подробного обсуждения концепции, в том числе о том, как обращаться с крайними случаями – вы не хотите замалчивать «оленей» как «оленей» во множественном числе, например, или «гусь», как «гусы», поэтому их нужно обрабатывать индивидуально, точно так же, как ваши собственные краевые дела, такие как «вселенная» или аспирированные / без наддува «H».

Смотрел именно такое решение, поэтому спасибо marcog. Вот попытка портировать версию python вашего друга (я не знаю python или perl, поэтому возможны некоторые ошибки):

 function indefinite_article($word) { // Lowercase version of the word $word_lower = strtolower($word); // An 'an' word (specific start of words that should be preceeded by 'an') $an_words = array('euler', 'heir', 'honest', 'hono'); foreach($an_words as $an_word) { if(substr($word_lower,0,strlen($an_word)) == $an_word) return "an"; } if(substr($word_lower,0,4) == "hour" and substr($word_lower,0,5) != "houri") return "an"; // An 'an' letter (single letter word which should be preceeded by 'an') $an_letters = array('a','e','f','h','i','l','m','n','o','r','s','x'); if(strlen($word) == 1) { if(in_array($word_lower,$an_letters)) return "an"; else return "a"; } // Capital words which should likely by preceeded by 'an' if(preg_match('/(?!FJO|[HLMNS]Y.|RY[EO]|SQU|(F[LR]?|[HL]|MN?|N|RH?|S[CHKLMNPTVW]?|X(YL)?)[AEIOU])[FHLMNRSX][AZ]/', $word)) return "an"; // Special cases where a word that begins with a vowel should be preceeded by 'a' $regex_array = array('^e[uw]','^onc?e\b','^uni([^nmd]|mo)','^u[bcfhjkqrst][aeiou]'); foreach($regex_array as $regex) { if(preg_match('/'.$regex.'/',$word_lower)) return "a"; } // Special capital words if(preg_match('/^U[NK][AIEO]/',$word)) return "a"; // Not sure what this does else if($word == strtoupper($word)) { $array = array('a','e','d','h','i','l','m','n','o','r','s','x'); if(in_array($word_lower[0],$array)) return "an"; else return "a"; } // Basic method of words that begin with a vowel being preceeded by 'an' $vowels = array('a','e','i','o','u'); if(in_array($word_lower[0],$vowels)) return "an"; // Instances where y follwed by specific letters is preceeded by 'an' if(preg_match('/^y(b[lor]|cl[ea]|fere|gg|p[ios]|rou|tt)/', $word_lower)) return "an"; // Default to 'a' return "a"; } 

Там один бит (ниже комментария «// Не уверен, что это делает»), что я не знал, что он сделал. Если кто-нибудь сможет понять это, я буду рад узнать.

Проблема с системой, основанной на правилах, заключается в том, что они плохо справляются с краевыми случаями и что они сложны. Если вы можете основывать свои решения на фактических данных, вы сделаете лучше. В этом ответе я описываю, как вы можете использовать wikipedia для создания словаря поиска и ссылку на (очень простую) реализацию javascript с использованием такого словаря.

Префикс-словарь будет довольно хорошо справляться с акронимами и цифрами, хотя с некоторыми усилиями вы, вероятно, могли бы сделать лучше.

Я написал порт PHP популярного JS a-vs-кода, как описано в этой статье stackoverflow https://stackoverflow.com/a/1288473/1526020 .

Страница Github: https://github.com/UseAllFive/a-vs-an .

Например

 $result = $aVsAn->query('0800 number'); print_r($result); 

Возвращает

 Array ( [aCount] => 8 [anCount] => 25 [prefix] => 08 [article] => an )