3

I want the function below to separate the sentences into an array and the questions into an array and inserting "," where the "." and "?" belong. At the moment it's printing both in the same array. Any ideas on how to fix this?

func separateAllSentences() {

    // needs to print just the sentences
    func separateDeclarations() { // AKA Separate sentences that end in "."
        if userInput.range(of: ".") != nil { // Notice how lowercased() wasn't used
            numSentencesBefore = userInput.components(separatedBy: ".") // Hasn't subtracted 1 yet
            numSentencesAfter = numSentencesBefore.count - 1
            separateSentencesArray = Array(numSentencesBefore)
            print("# Of Sentences = \(numSentencesAfter)")
            print(separateSentencesArray)
        } else {
            print("There are no declarations found.")
        }
    }

    // needs to print just the questions
    func separateQuestions() { // Pretty Self Explanitory
        if userInput.range(of: "?") != nil {
            numQuestionsBefore = userInput.components(separatedBy: "?")
            numQuestionsAfter = numQuestionsBefore.count - 1
            separateQuestionsArray = Array(numQuestionsBefore)
            print("# Of Questions = \(numQuestionsAfter)")
            print(separateQuestionsArray)
        } else {
            print("There are no questions found. I have nothing to solve. Please rephrase the work to solve as a question.")
        }
    }

    // TODO: - Separate Commas
    func separateCommas() {

    }

    separateDeclarations()
    separateQuestions()
}

Console Prints Out:

Ned rode his bike 7 miles to the library. He took a shortcut on the way home which was only 5 miles long. How many miles did Ned ride altogether?

[# Of Sentences = 2]

["Ned rode his bike 7 miles to the library", "\nHe took a shortcut on the way home which was only 5 miles long", "\nHow many miles did Ned ride altogether?\n"]

[# Of Questions = 1]

["Ned rode his bike 7 miles to the library.\nHe took a shortcut on the way home which was only 5 miles long.\nHow many miles did Ned ride altogether", "\n"]

Ned rode his bike 7 miles to the library. He took a shortcut on the way home which was only 5 miles long. How many miles did Ned ride altogether?

It Should Print Out

[# Of Sentences = 2]

[# Of Questions = 1]

Sentences: ["Ned rode his bike 7 miles to the library. He took a shortcut on the way home which was only 5 miles long."]

Questions: ["How many miles did Ned ride altogether?"]

ThatGuy
  • 31
  • 6

3 Answers3

2

This snippet could use some refactoring to replace the common code but it works as is.

let punctuation = CharacterSet(charactersIn: ".?")
let sentences = userInput.components(separatedBy: punctuation)

let questions = sentences.filter {
  guard let range = userInput.range(of: $0) else { return false }
  let start = range.upperBound
  let end   = userInput.index(after: start)
  let punctuation = userInput.substring(with: Range(uncheckedBounds: (start, end)))
  return punctuation == "?"
}
let statements = sentences.filter {
  guard let range = userInput.range(of: $0) else { return false }
  let start = range.upperBound
  let end   = userInput.index(after: start)
  let punctuation = userInput.substring(with: Range(uncheckedBounds: (start, end)))
  return punctuation == "."
}

Looking at the closure first, the range variable contains the indices of the sentence in the user input. We want to get the punctuation trailing that particular sentence so we start with its upper bound and find the next index past it. Using substring, we extract the punctuation and compare it to either . or ?.

Now that we have code that will return true or false whether we have a question or statement sentence, we use filter to iterate over the array of all sentences and return only an array of questions or statements.

Price Ringo
  • 3,424
  • 1
  • 19
  • 33
  • @ThatGuy you should follow this answer – Rajan Maheshwari Nov 23 '16 at 17:41
  • I'm sorta new to swift and don't understand: (.filter),(uncheckedBounds: ) or (.upperBound). Could you explain? @RajanMaheshwari – ThatGuy Nov 23 '16 at 17:47
  • filter / map / reduce are very powerful Functional Programming tools that you will learn to appreciate. They provide a framework for manipulating collections without having to write the boiler plate code yourself. The Range type and its methods are the preferred way to access and manipulate String types. Can't help more here in the comments. Good luck. – Price Ringo Nov 23 '16 at 17:56
  • That makes sense! Thanks for the help! @PriceRingo – ThatGuy Nov 23 '16 at 17:59
2

I would suggest not separating based upon the presence of a character, but rather enumate using the .bySentences option (which can more gracefully handle punctuation which is not terminating a sentence). Then iterate once through your string, appending to the appropriate array, e.g. in Swift 3:

var questions  = [String]()
var statements = [String]()
var unknown    = [String]()

let string = "Ned deployed version 1.0 of his app. He wrote very elegant code. How much money did Ned make?"

string.enumerateSubstrings(in: string.startIndex ..< string.endIndex, options: .bySentences) { string, _, _, _ in
    if let sentence = string?.trimmingCharacters(in: .whitespacesAndNewlines), let lastCharacter = sentence.characters.last {
        switch lastCharacter {
        case ".":
            statements.append(sentence)
        case "?":
            questions.append(sentence)
        default:
            unknown.append(sentence)
        }
    }
}

print("questions:  \(questions)")
print("statements: \(statements)")
print("unknown:    \(unknown)")
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • self. must be added before [statements.append(sentence)] and the other cases – ThatGuy Nov 23 '16 at 17:55
  • I'm new to swift could you explain the enumerateSubstrings? I understand everything except for that and trimmingCharacters(in: .whitespacesAndNewLines) – ThatGuy Nov 23 '16 at 17:57
  • Re `enumerateSubstrings`, this just repeatedly calls the closure for substrings matching criteria dictated by the supplied `options` (`.bySentences` in this case). This is just more powerful than scanning for `.` as it ensures that punctuation within a sentence (e.g. the "version 1.0") won't be mistaken for a period at the end of a sentence. The `trimmingCharacters(in: .whitespacesAndNewLines)` just trims the spaces at the end of the sentences so that I can just grab the last character and be assured that it won't be some extraneous space. – Rob Nov 23 '16 at 18:08
  • Re adding `self`, in Swift, it will assume it's `self`, so that's generally redundant. The only time you need to add the `self.` prefix is if you're doing this inside a closure (e.g. inside the completion handler of a network request, inside a closure dispatched to some queue, if your `questions`, `statements`, etc., were properties rather than local variables, etc.). In this case, I was using local variables so `self.` was not needed. Obviously, go ahead and add the `self.` references where needed in your case. – Rob Nov 23 '16 at 18:12
  • By the way, if you wanted to go nuts and try to actually parse these English sentences, you could consider [`NSLinguisticTagger`](http://nshipster.com/nslinguistictagger/). It seems like overkill in this case, but if you're trying to parse the sentences, it's something to consider and has the ability to find sentence terminators, too. – Rob Nov 23 '16 at 18:16
  • Thank you I did not know half of this stuff and what you've said is really informative. Thanks for the additional suggestion (NSLinguisticTagger). That will probably be really useful and I didn't know about it. – ThatGuy Nov 23 '16 at 18:32
-1

i make a simplest solution solution

func separateDeclarations(userInput: String) { // AKA Separate sentences that end in "."
let array = userInput.components(separatedBy: ".")
let arrayQ = userInput.components(separatedBy: "?")

arrayQ.map { (string) -> String in
    if let question = string.components(separatedBy: ".").last {
        return question
    } else {
        return ""
    }
}

array.map { (string) -> String in
    if let question = string.components(separatedBy: "?").last {
        return question
    } else {
        return ""
    }
} 
}
Vadim Kozak
  • 420
  • 3
  • 11