最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

ios - Swift Automatic Grammar Agreement Inflection: make singular string from the plural one: days -> day - Stack Overflo

programmeradmin3浏览0评论

I've created the following data model:

import Foundation


struct Effort {
    let amount: Int
    let unit: String

    var text: String {
        "\(amount) \(unit)"
    }

    var attributedText: String {

        String(
            AttributedString(localized: "^[\(amount) \(unit)](inflect: true)").characters
        )
    }

    init(amount: Int, unit: String) {
        self.amount = amount
        self.unit = unit
    }
}

If I create the value with the singular unit name, the automatic grammar agreement (/) works perfectly:

Effort(amount: 4, unit: "day").attributedString

"4 days"

Effort(amount: 1, unit: "day").attributedString

"1 day"

However, if I create this struct with already plural string, the automatic grammar agreement won't actually turn it back into singular:

Effort(amount: 1, unit: "days").attributedString

"1 days"

Is there any way to make it work both ways? I'm open to refactoring this code, even if it means it won't be so "elegant", i.e. if I have to write the inflection rule manually.

Motivation: the server already returns values in a plural form, also it would be good to make the client completely agnostic to the server response.

One idea that I'm considering looks like this:

AttributedString(localized: "^[\(amount) \(String(unit.dropLast()))](inflect: true)")

In essence, we're just dropping the last character from the unit, but this obviously is not so robust as if the system handled it on it's own.

I've created the following data model:

import Foundation


struct Effort {
    let amount: Int
    let unit: String

    var text: String {
        "\(amount) \(unit)"
    }

    var attributedText: String {

        String(
            AttributedString(localized: "^[\(amount) \(unit)](inflect: true)").characters
        )
    }

    init(amount: Int, unit: String) {
        self.amount = amount
        self.unit = unit
    }
}

If I create the value with the singular unit name, the automatic grammar agreement (https://www.swiftjectivec/morphology-in-ios-with-automatic-grammar-agreement/) works perfectly:

Effort(amount: 4, unit: "day").attributedString

"4 days"

Effort(amount: 1, unit: "day").attributedString

"1 day"

However, if I create this struct with already plural string, the automatic grammar agreement won't actually turn it back into singular:

Effort(amount: 1, unit: "days").attributedString

"1 days"

Is there any way to make it work both ways? I'm open to refactoring this code, even if it means it won't be so "elegant", i.e. if I have to write the inflection rule manually.

Motivation: the server already returns values in a plural form, also it would be good to make the client completely agnostic to the server response.

One idea that I'm considering looks like this:

AttributedString(localized: "^[\(amount) \(String(unit.dropLast()))](inflect: true)")

In essence, we're just dropping the last character from the unit, but this obviously is not so robust as if the system handled it on it's own.

Share Improve this question edited Mar 17 at 17:22 Richard Topchii asked Mar 17 at 17:09 Richard TopchiiRichard Topchii 8,2899 gold badges60 silver badges131 bronze badges 4
  • How about using an NLTagger with the lemma tag scheme to convert the plural form to the singular form before passing it to Effort? – Sweeper Commented Mar 17 at 17:21
  • What about changing unit to a an enum instead of String? Have the enum map to the appropriate String value that matches the needed key. The enum also prevents a coder from typing an invalid/unsupported unit. – HangarRash Commented Mar 17 at 17:21
  • @Sweeper could you please elaborate on your idea? Very interesting. I'm now actually just dropping this last letter, but your solution is doing the same thing, but in a proper way. – Richard Topchii Commented Mar 17 at 17:23
  • @HangarRash in my opinion, that's out of scope from this question, as this would impose a restriction on the server response, i.e. if the server returns something else than the predefined list, the decoding would fail. I'm using an automated API generator, so this would mean adding a layer in between which I'd like to avoid. So the requirements for this question are "set in stone". – Richard Topchii Commented Mar 17 at 17:24
Add a comment  | 

2 Answers 2

Reset to default 3

Consider lemmatising whatever you get from the server, so that it becomes the singular form, before you pass the string to Effort.init.

Here is an example:

import NaturalLanguage

func lemmatiseNoun(_ input: String) -> String {
    let tagger = NLTagger(tagSchemes: [.lemma])
    let string = "the \(input)"
    tagger.string = string
    tagger.setLanguage(.english, range: string.startIndex..<string.endIndex)
    let index = string.index(string.startIndex, offsetBy: 4)
    let (tag, _) = tagger.tag(at: index, unit: .word, scheme: .lemma)
    return tag?.rawValue ?? input
}

lemmatiseNoun("days") // "day"

Note that I am prefixing the word with the article "the", so that the tagger treats it as a noun. This is to avoid cases such as "leaves", where the tagger will lemmatise it as "leave" instead of "leaf" if it is just the word on its own.

As always with natural languages, there are fundamentally ambiguous cases. For example, consider "people". It is both the plural form of "person" (a human being), and also the singular form of "peoples" (the members of a particular nation, community, or ethnic group).

I won't compete with Sweeper's NLTagger but at least it is a little bit better than just dropping the last letter:

private func singular(_ word: String) -> String {
    if word.hasSuffix("ies") {
        String(word.dropLast(3)) + "y"
    } else if word.hasSuffix("ves") {
        String(word.dropLast(3)) + "f"
    } else if word.hasSuffix("oes") {
        String(word.dropLast(2))
    } else if word.hasSuffix("s") {
        String(word.dropLast())
    } else {
        word
    }
}

You can also include irregular plural nouns if you need:

private func singular(_ word: String) -> String {
    let irregularPlurals: [String: String] = [
        "children": "child",
        "men": "man",
        // ...
    ]
    if let singular = irregularPlurals[word.lowercased()] {
        return singular
    }
    if word.hasSuffix("ies") {
    //...

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论