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

У меня есть база данных существительных (например, "дом", "восклицательный знак", "яблоко" ), которые мне нужно вывести и описать в моем приложении. Трудно собрать естественное предложение для описания предмета без использования "a" или "an" - "дом BIG", "восклицательный знак - МАЛАЯ" и т.д.

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

Ответ 1

Вы хотите определить подходящую неопределенную статью. 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 JUST MAGIC!)

my $A_abbrev = q{
(?! FJO | [HLMNS]Y.  | RY[EO] | SQU
  | ( F[LR]? | [HL] | MN? | N | RH? | S[CHKLMNPTVW]? | X(YL)?) [AEIOU])
[FHLMNRSX][A-Z]
};

# 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 =~ /^[a-z][.-]/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";
}

Ответ 2

Мне понадобилось это для проекта С#, так что здесь приведен порт С#

Ответ 3

Я также искал такое решение, но в 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][A-Z]/)) {
        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";
}

Ответ 4

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

Ответ 5

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

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

Ответ 6

Ищете такое решение, поэтому спасибо 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][A-Z]/', $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";
}

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

Ответ 7

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

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

Ответ 8

Я написал порт PHP популярного JS a-vs-code, как описано в этом postoverflow post fooobar.com/info/64385/....

Страница 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
)