What is the best way or elegant way to get all elements whithout spaces and ignore "DOMICILIO" and the next elements for example :
"LOAIZA"
"HERRERA"
"JESUS" (This is my expected output)
In this case I have one string with 2 elements ("LOAIZA\nHERRERA")
["LOAIZA HERRERA", "JESUS", "DOMICILIO", "CALLE1", "CALLE2"]
var dataID = ["LOAIZA HERRERA", "JESUS", "DOMICILIO"]
for i in dataID {
if i.contains(" "){
print(i) // LOAIZA HERRERA
let dataSeparate = i.components(separatedBy: " ")
print(dataSeparate) // ["LOAIZA", "HERRERA"]
}
}
Separate the terms by " " and flatMap into a new array. Then, find the index of "DOMICILIO" if it exists and use the segment of the array up to that point.
func findResult(dataID: [String]) -> Array<String> {
let terms = dataID.flatMap { $0.components(separatedBy: " ")}
let indexOfDom = terms.firstIndex(of: "DOMICILIO")
if let indexOfDom = indexOfDom, indexOfDom > 0 {
return Array(terms[0...(indexOfDom - 1)])
} else {
return terms
}
}
Related
I am trying to split a string into an array of letters, but keep some of the letters together. (I'm trying to break them into sound groups for pronunciation, for example).
So, for example, all the "sh' combinations would be one value in the array instead of two.
It is easy to find an 's' in an array that I know has an "sh" in it, using firstIndex. But how do I get more than just the first, or last, index of the array?
The Swift documentation includes this example:
let students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua"]
if let i = students.firstIndex(where: { $0.hasPrefix("A") }) {
print("\(students[i]) starts with 'A'!")
}
// Prints "Abena starts with 'A'!"
How do I get both Abena and Akosua (and others, if there were more?)
Here is my code that accomplishes some of what I want (please excuse the rather lame error catching)
let message = "she sells seashells"
var letterArray = message.map { String($0)}
var error = false
while error == false {
if message.contains("sh") {
guard let locate1 = letterArray.firstIndex(of: "s") else{
error = true
break }
let locate2 = locate1 + 1
//since it keeps finding an s it doesn't know how to move on to rest of string and we get an infinite loop
if letterArray[locate2] == "h"{
letterArray.insert("sh", at: locate1)
letterArray.remove (at: locate1 + 1)
letterArray.remove (at: locate2)}}
else { error = true }}
print (message, letterArray)
Instead of first use filter you will get both Abena and Akosua (and others, if there were more?)
extension Array where Element: Equatable {
func allIndexes(of element: Element) -> [Int] {
return self.enumerated().filter({ element == $0.element }).map({ $0.offset })
}
}
You can then call
letterArray.allIndexes(of: "s") // [0, 4, 8, 10, 13, 18]
You can filter the collection indices:
let students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua"]
let indices = students.indices.filter({students[$0].hasPrefix("A")})
print(indices) // "[1, 4]\n"
You can also create your own indices method that takes a predicate:
extension Collection {
func indices(where predicate: #escaping (Element) throws -> Bool) rethrows -> [Index] {
try indices.filter { try predicate(self[$0]) }
}
}
Usage:
let indices = students.indices { $0.hasPrefix("A") }
print(indices) // "[1, 4]\n"
or indices(of:) where the collection elements are Equatable:
extension Collection where Element: Equatable {
func indices(of element: Element) -> [Index] {
indices.filter { self[$0] == element }
}
}
usage:
let message = "she sells seashells"
let indices = message.indices(of: "s")
print(indices)
Note: If you need to find all ranges of a substring in a string you can check this post.
Have fun!
["Kofi", "Abena", "Peter", "Kweku", "Akosua"].forEach {
if $0.hasPrefix("A") {
print("\($0) starts with 'A'!")
}
}
If you really want to use the firstIndex method, here's a recursive(!) implementation just for fun :D
extension Collection where Element: Equatable {
/// Returns the indices of an element from the specified index to the end of the collection.
func indices(of element: Element, fromIndex: Index? = nil) -> [Index] {
let subsequence = suffix(from: fromIndex ?? startIndex)
if let elementIndex = subsequence.firstIndex(of: element) {
return [elementIndex] + indices(of: element, fromIndex: index(elementIndex, offsetBy: 1))
}
return []
}
}
Recursions
Given n instances of element in the collection, the function will be called n+1 times (including the first call).
Complexity
Looking at complexity, suffix(from:) is O(1), and firstIndex(of:) is O(n). Assuming that firstIndex terminates once it encounters the first match, any recursions simply pick up where we left off. Therefore, indices(of:fromIndex:) is O(n), just as good as using filter. Sadly, this function is not tail recursive... although we can change that by keeping a running total.
Performance
[Maybe I'll do this another time.]
Disclaimer
Recursion is fun and all, but you should probably use Leo Dabus' solution.
This question already has answers here:
Refactored Solution In Swift
(2 answers)
Closed 6 years ago.
I'm attempting to solve HackerRank's Hash Table Ransom Note challenge. There are 19 test cases and I'm passing all but two of time due to timeout on larger data sets (10,000-30,000 entries).
I'm given:
1) an array of words contained in a magazine and
2) an array of words for a ransom note. My objective is to determine if the words in the magazine can be used to construct a ransom note.
I need to have enough unique elements in the magazineWords to satisfy the quantity needed by noteWords.
I'm using this code to make that determination...and it takes FOREVER...
for word in noteWordsSet {
// check if there are enough unique words in magazineWords to put in the note
if magazineWords.filter({$0==word}).count < noteWords.filter({$0==word}).count {
return "No"
}
}
What is a faster way to accomplish this task?
Below is my complete code for the challenge:
import Foundation
var magazineWords = // Array of 1 to 30,000 strings
var noteWords = // Array of 1 to 30,000 strings
enum RegexString: String {
// Letters a to z, A to Z, 1 to 5 characters long
case wordCanBeUsed = "([a-zA-Z]{1,5})"
}
func matches(for regexString: String, in text: String) -> [String] {
// Hat tip MartinR for this
do {
let regex = try NSRegularExpression(pattern: regexString)
let nsString = text as NSString
let results = regex.matches(in: text, range: NSRange(location: 0, length: nsString.length))
return results.map { nsString.substring(with: $0.range)}
} catch let error {
print("invalid regex: \(error.localizedDescription)")
return []
}
}
func canCreateRansomNote(from magazineWords: [String], for noteWords: [String]) -> String {
// figure out what's unique
let magazineWordsSet = Set(magazineWords)
let noteWordsSet = Set(noteWords)
let intersectingValuesSet = magazineWordsSet.intersection(noteWordsSet)
// constraints specified in challenge
guard magazineWords.count >= 1, noteWords.count >= 1 else { return "No" }
guard magazineWords.count <= 30000, noteWords.count <= 30000 else { return "No" }
// make sure there are enough individual words to work with
guard magazineWordsSet.count >= noteWordsSet.count else { return "No" }
guard intersectingValuesSet.count == noteWordsSet.count else { return "No" }
// check if all the words can be used. assume the regex method works perfectly
guard noteWords.count == matches(for: RegexString.wordCanBeUsed.rawValue, in: noteWords.joined(separator: " ")).count else { return "No" }
// FIXME: this is a processor hog. I'm timing out when I get to this point
// need to make sure there are enough magazine words to write the note
// compare quantity of word in magazine with quantity of word in note
for word in noteWordsSet {
// check if there are enough unique words in magazineWords to put in the note
if magazineWords.filter({$0==word}).count < noteWords.filter({$0==word}).count {
return "No"
}
}
return "Yes"
}
print(canCreateRansomNote(from: magazineWords, for: noteWords))
I don't know how to read from the test case on the contest website or what frameworks you are allowed. If Foundation is allowed, you can use NSCountedSet
import Foundation
let fileContent = try! String(contentsOf: URL(fileURLWithPath: "/path/to/file.txt"))
let scanner = Scanner(string: fileContent)
var m = 0
var n = 0
scanner.scanInt(&m)
scanner.scanInt(&n)
var magazineWords = NSCountedSet(capacity: m)
var ransomWords = NSCountedSet(capacity: n)
for i in 0..<(m+n) {
var word: NSString? = nil
scanner.scanUpToCharacters(from: .whitespacesAndNewlines, into: &word)
if i < m {
magazineWords.add(word!)
} else {
ransomWords.add(word!)
}
}
var canCreate = true
for w in ransomWords {
if ransomWords.count(for: w) > magazineWords.count(for: w) {
canCreate = false
break
}
}
print(canCreate ? "Yes" : "No")
It works by going through the input file one word at a time, counting how many times that word appears in the magazine and in the ransom note. Then if any word appear more frequently in the ransom note than in the magazine, it fails the test immediately. Run the 30,000 words test case in less than 1 second on my iMac 2012.
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?"]
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)")
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.
i make a simplest 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 ""
}
}
}
I have a string called source. This string contains tags, marked with number signs (#) on left and right side.
What is the most efficient way to get tag names from the source string.
Source string:
let source = "Here is tag 1: ##TAG_1##, tag 2: ##TAG_2##."
Expected result:
["TAG_1", "TAG_2"]
Not a very short solution, but here you go:
let tags = source.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString: " ,."))
.filter { (str) -> Bool in
return str.hasSuffix("##") && str.hasPrefix("##")
}
.map { (str) -> String in
return str.stringByReplacingOccurrencesOfString("##", withString: "")
}
Split the string at all occurences of ##:
let components = source.components(separatedBy: "##")
// Result: ["Here is tag 1: ", "TAG_1", ", tag 2: ", "TAG_2", "."]
Check that there's an odd number of components, otherwise there's an odd amount of ##s:
guard components.count % 2 == 1 else { fatalError("Unbalanced delimiters") }
Get every second element:
components.enumerated().filter{ $0.offset % 2 == 1 }.map{ $0.element }
In a single function:
import Foundation
func getTags(source: String, delimiter: String = "##") -> [String] {
let components = source.components(separatedBy: delimiter)
guard components.count % 2 == 1 else { fatalError("Unbalanced delimiters") }
return components.enumerated().filter{ $0.offset % 2 == 1 }.map{ $0.element }
}
getTags(source: "Here is tag 1: ##TAG_1##, tag 2: ##TAG_2##.") // ["TAG_1", "TAG_2"]
You can read this post and adapt the answer for your needs: Swift: Split a String into an array
If not you can also create your own method, remember a string is an array of characters, so you can use a loop to iterate through and check for a '#'
let strLength = source.characters.count;
var strEmpty = "";
for( var i=0; i < strLength; i++ )
{
if( source[ i ] == '#' )
{
var j=(i+2);
for( j; source[ (i+j) ] != '#'; j++ )
strEmpty += source[ (i+j) ]; // concatenate the characters to another variable using the += operator
i = j+2;
// do what you need to with the tag
}
}
I am more of a C++ programmer than a Swift programmer, so this is how I would approach it if I didn't want to use standard methods. There may be a better way of doing it, but I don't have any Swift knowledge.
Keep in mind if this does not compile then you may have to adapt the code slightly as I do not have a development environment I can test this in before posting.
I am parsing data from csv file to dictionary with the help of github.
After parsing I am getting this type of dictionary :-
{
"" = "";
"\"barred_date\"" = "\"\"";
"\"company_id\"" = "\"1\"";
"\"company_name\"" = "\"\"";
"\"contact_no\"" = "\"1234567890\"";
"\"created_date\"" = "\"2015-06-01 12:43:11\"";
"\"current_project\"" = "\"111\"";
"\"designation\"" = "\"Developer\"";
"\"doj\"" = "\"2015-06-01 00:00:00\"";
"\"fin_no\"" = "\"ABC001\"";
"\"first_name\"" = "\"sssd\"";
"\"last_name\"" = "\"dd\"";
"\"project_name\"" = "\"Project 1\"";
"\"qr_code\"" = "\"12345678\"";
"\"resignation_date\"" = "\"\"";
"\"status\"" = "\"1\"";
"\"work_permit_no\"" = "\"ssdda11\"";
"\"worker_id\"" = "\"1\"";
"\"worker_image\"" = "\"assets/uploads/workers/eb49364ca5c5d22f11db2e3c84ebfce6.jpeg\"";
"\"worker_image_thumb\"" = "\"assets/uploads/workers/thumbs/eb49364ca5c5d22f11db2e3c84ebfce6.jpeg\"";}
How can I convert this to simple dictionary. I need data like this "company_id" = "1"
Thanks
I recommend using CSVImporter – it takes care of things like quoted text (following RFC 4180) for you and even handles very large files without problems.
Compared to other solutions it works both asynchronously (prevents delays) and reads your CSV file line by line instead of loading the entire String into memory (prevents memory issues). On top of that it is easy to use and provides beautiful callbacks for indicating failure, progress, completion and even data mapping if you desire to.
You can use it like this to get an array of Strings per line:
let path = "path/to/your/CSV/file"
let importer = CSVImporter<[String]>(path: path)
importer.startImportingRecords { $0 }.onFinish { importedRecords in
for record in importedRecords {
// record is of type [String] and contains all data in a line
}
}
Take advantage of more sophisticated features like header structure support like this:
// given this CSV file content
firstName,lastName
Harry,Potter
Hermione,Granger
Ron,Weasley
// you can import data in Dictionary format
let path = "path/to/Hogwarts/students"
let importer = CSVImporter<[String: String]>(path: path)
importer.startImportingRecords(structure: { (headerValues) -> Void in
// use the header values CSVImporter has found if needed
print(headerValues) // => ["firstName", "lastName"]
}) { $0 }.onFinish { importedRecords in
for record in importedRecords {
// a record is now a Dictionary with the header values as keys
print(record) // => e.g. ["firstName": "Harry", "lastName": "Potter"]
print(record["firstName"]) // prints "Harry" on first, "Hermione" on second run
print(record["lastName"]) // prints "Potter" on first, "Granger" on second run
}
}
Use the CSwiftV parser instead: https://github.com/Daniel1of1/CSwiftV
It actually handles quoted text, and therefore it handles both line breaks and commas in text. SwiftCSV cost me some time in that it doesn't handle that. But I did learn about the CSV format and parsing it ;)
Parse CSV to two-dimension array of Strings (rows and columns)
func parseCsv(_ data: String) -> [[String]] {
// data: String = contents of a CSV file.
// Returns: [[String]] = two-dimension array [rows][columns].
// Data minimum two characters or fail.
if data.count < 2 {
return []
}
var a: [String] = [] // Array of columns.
var index: String.Index = data.startIndex
let maxIndex: String.Index = data.index(before: data.endIndex)
var q: Bool = false // "Are we in quotes?"
var result: [[String]] = []
var v: String = "" // Column value.
while index < data.endIndex {
if q { // In quotes.
if (data[index] == "\"") {
// Found quote; look ahead for another.
if index < maxIndex && data[data.index(after: index)] == "\"" {
// Found another quote means escaped.
// Increment and add to column value.
data.formIndex(after: &index)
v += String(data[index])
} else {
// Next character not a quote; last quote not escaped.
q = !q // Toggle "Are we in quotes?"
}
} else {
// Add character to column value.
v += String(data[index])
}
} else { // Not in quotes.
if data[index] == "\"" {
// Found quote.
q = !q // Toggle "Are we in quotes?"
} else if data[index] == "\r" || data[index] == "\r\n" {
// Reached end of line.
// Column and row complete.
a.append(v)
v = ""
result.append(a)
a = []
} else if data[index] == "," {
// Found comma; column complete.
a.append(v)
v = ""
} else {
// Add character to column value.
v += String(data[index])
}
}
if index == maxIndex {
// Reached end of data; flush.
if v.count > 0 || data[data.index(before: index)] == "," {
a.append(v)
}
if a.count > 0 {
result.append(a)
}
break
}
data.formIndex(after: &index) // Increment.
}
return result
}
Call above with the CSV data
let dataArray: [[String]] = parseCsv(yourStringOfCsvData)
Then extract the header row
let dataHeader = dataArray.removeFirst()
I assume you want an array of dictionaries (most spreadsheet data includes mulitple rows, not just one). The next loop is for that. But if you only need a single row (and header for keys) into a single dictionary, you can study below and get the idea of how to get there.
var da: [Dictionary<String, String>] = [] // Array of dictionaries.
for row in dataArray {
for (index, column) in row.enumerated() {
var d: Dictionary<String, String> = Dictionary()
d.updateValue(column, forKey: dataHeader[index])
da.append(d)
}
}