var LabelAQI = "-"
var LabelExtraInfo = "-"
var arrAQI = [NSString]()
var AQI1:NSString = "55a"
var AQI2:NSString = "95a"
var AQI3:NSString = "66"
var AQI4:NSString = "25"
var AQI5:NSString = "88b"
var AQI6:NSString = "#"
arrAQI[0...5] = [AQI1, AQI2, AQI3, AQI4, AQI5, AQI6]
Using Swift, I'm getting AQI (Air Quality Index) data from a website in the form of NSString. AQI1 to AQI6 is essentially AQI data for every hour (e.g. 12am to 5am, I'm having up to AQI24 in the actual code though). "#" means data is not yet available. The integer is the AQI, and sometimes there may be associated extra information represented by "a"/"b" with the integers which means "Carbon Monoxide present"/"Sulphur Dioxide present". What I'm trying to do is to:
1) Display the last integer from arrAQI (excluding "#", and not displaying "a"/"b", which is "88") on LabelAQI
2) Display "Carbon Monoxide present"/"Sulphur Dioxide present" on LabelExtraInfo if there is "a"/"b" with the last integer from arrAQI, if not, leave LabelExtraInfo.text = "-"
I'm only a few weeks into programming. Can anybody help with this? Is there a better way to do what I want to do? Thank you so much in advance.
It usually helps to break the problem down into multiple functions. Here, you need to do two things – process the string, extracting a number and possible extra info. Then, loop over the array of strings until you find a suitable entry.
First the string processing. Given a string, you want to return either a pair (the reading and the extra info), or “not found”. Whenever you need to return something or not found, an optional is a good choice. So you want a function that takes a string and returns an optional (Int,String) pair:
func extractData(input: String) -> (Int,String)?
But you could go one step further and define an enum to represent the different bits of extra info:
enum AirQualityInfo {
case CarbonMonoxide,
SulphurDioxide,
NoInfo
}
func extractData(input: String) -> (Int,AirQualityInfo)?
This way, all your nasty string processing is contained within extractData.
Within that function, you want to check for a known trailing character, then strip that off, and if what remains is a number, return the two values. But if what remains isn’t a number, return nil:
func extractData(input: String) -> (Int,AirQualityInfo)? {
let stripped: String
let extraInfo: AirQualityInfo
switch last(input) {
case .Some("a"):
stripped = dropLast(input)
extraInfo = .CarbonMonoxide
case .Some("b"):
stripped = dropLast(input)
extraInfo = .SulphurDioxide
default:
stripped = input
extraInfo = .NoInfo
}
return stripped.toInt().map { ($0,extraInfo) }
}
You could, as others have suggested, use regular expressions for this, but personally I think this is overkill given your data parsing needs are so specific.
Once you have this function, you can loop over the array in reverse order, checking each value using the function, until you find a valid value:
for idx in reverse(indices(arrAQI)) {
if let (index, extraInfo) = extractData(arrAQI[idx]) {
LabelAQI = toString(index)
switch extraInfo {
case .CarbonMonoxide:
LabelExtraInfo = "Carbon Monoxide Present"
case .SulphurDioxide:
LabelExtraInfo = "SulphurDioxide Present"
case .NoInfo:
LabelExtraInfo = "-"
}
// stop going around the loop
break
}
}
You could also factor out that conversion of the string out further as well:
extension AirQualityInfo {
var displayString: String {
switch self {
case .CarbonMonoxide:
return "Carbon Monoxide Present"
case .SulphurDioxide:
return "SulphurDioxide Present"
case .NoInfo:
return "-"
}
}
}
for idx in reverse(indices(arrAQI)) {
if let (index, extraInfo) = extractData(arrAQI[idx]) {
LabelAQI = toString(index)
LabelExtraInfo = extraInfo.displayString
break
}
}
Finally, if you’re feeling super-adventurous, you could write a function that does that finding and mapping operation in one shot:
func findSome<C: CollectionType, T>
(source: C, match: C.Generator.Element -> T?)
-> T? {
for element in source {
if let x = match(element) {
return x
}
}
return nil
}
if let (index, extraInfo) = findSome(reverse(arrAQI),extractData) {
LabelAQI = toString(index)
LabelExtraInfo = extraInfo.displayString
}
By the way, a few other Swift tips: it’s generally better to use String rather than NSString unless you have a specific need for something to be an NSString (which it doesn’t look like here); you don’t have to name the types when declaring them – you can write let str = "hello" rather than let str: String = "hello" which tends to make code look a little cleaner and easier to read; and it’s best to use let rather than var unless you explicitly need to change (“mutate”) the value later in the code… so given all that, here’s how you could declare your original array:
let AQI1 = "55a"
let AQI2 = "95a"
let AQI3 = "66"
let AQI4 = "25"
let AQI5 = "88b"
let AQI6 = "#"
let arrAQI = [AQI1,AQI2,AQI3,AQI4,AQI5,AQI6,]
This could by done by exploding string into array of objects, and then check each of them:
var LabelAQI = "-"
var LabelExtraInfo = "-"
var stringFromServer = "55a 95a 66 25 88b #"
var objectsSeparated = stringFromServer.componentsSeparatedByString(" ")
for object in objectsSeparated
{
if object.hasSuffix("a")
{
LabelAQI = object.substringToIndex(object.endIndex.predecessor())
LabelExtraInfo = "Carbon Monoxide present"
}
else if object.hasSuffix("b")
{
LabelAQI = object.substringToIndex(object.endIndex.predecessor())
LabelExtraInfo = "Sulphur Dioxide present"
}
else if object.hasSuffix("#")
{
LabelAQI = object.substringToIndex(object.endIndex.predecessor())
LabelExtraInfo = "-"
}
else
{
LabelAQI = object
LabelExtraInfo = "-"
}
}
UPDATE
To find the last object, that doesn't have '#' suffix you could use following code:
var LabelAQI = "-"
var LabelExtraInfo = "-"
var stringFromServer = "55a 95a 66 25 88b #"
var objectsSeparated = stringFromServer.componentsSeparatedByString(" ")
var index = objectsSeparated.count
while 0 >= index
{
var object = objectsSeparated[index]
index--
if object.hasSuffix("#")
{
//this object has '#' suffix -> skip it
continue
}
else
{
//found non '#' object
if object.hasSuffix("a")
{
LabelAQI = object.substringToIndex(object.endIndex.predecessor())
LabelExtraInfo = "Carbon Monoxide present"
}
else if object.hasSuffix("b")
{
LabelAQI = object.substringToIndex(object.endIndex.predecessor())
LabelExtraInfo = "Sulphur Dioxide present"
}
else
{
LabelAQI = object
LabelExtraInfo = "-"
}
break
}
}
There are a number of ways you could do this, here's one suggestion using NSScanner:
var LabelAQI = "-"
var LabelExtraInfo = "-"
var arrAQI = [NSString]()
let AQI1:NSString = "55a", AQI2 = "95a", AQI3 = "66", AQI4 = "25", AQI5 = "88b", AQI6 = "#"
arrAQI.extend([AQI1, AQI2, AQI3, AQI4, AQI5, AQI6])
for AQI in reverse(arrAQI) {
if AQI != "#" {
var str:NSString?
let numberSet = NSCharacterSet.decimalDigitCharacterSet()
let letterSet = NSCharacterSet(charactersInString: "ab")
let scanner = NSScanner(string: AQI as String)
if scanner.scanCharactersFromSet(numberSet, intoString:&str) {
LabelAQI = str as! String
if scanner.scanCharactersFromSet(letterSet, intoString: &str) {
switch str as! String {
case "a":
LabelExtraInfo = "Carbon Monoxide present"
case "b":
LabelExtraInfo = "Sulphur Dioxide present"
default:
break
}
}
break
}
}
}
LabelAQI // "88"
LabelExtraInfo // "Sulphur Dioxide present"
I first of all reverse the array and loop through it for the last non-hash symbol entry. I then scan the string first for a number and second for the letters a/b using NSCharacterSet searches. Once found we can break out of the loop and all is done.
Note: this line of code arrAQI[0...5] = [AQI1, AQI2, AQI3, AQI4, AQI5, AQI6] didn't work, so I replaced it with arrAQI.extend([AQI1, AQI2, AQI3, AQI4, AQI5, AQI6]).
Using regular expressions, you can easily get the numbers out of a string. You probably want to do something along these lines:
let regex = NSRegularExpression(pattern: "[\a-z\s]", options: nil, error: nil)
let aq1value = regex?.stringByReplacingMatchesInString(arrAQI[0], options: nil, range: NSMakeRange(0, count(arrAQI[0])), withTemplate: "")
This will take your string and remove all alphabetic characters, leaving you with only numbers. You can then cast the string as an integer as you wish!
Related
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
}
}
I have a string which contains English and Arabic together. I am using an API, that is why I cannot set an indicator in it.
What I want to get is: the Arabic and English split into tow parts. Here is a sample String:
"بِاسْمِكَ رَبِّي وَضَعْتُ جَنْبِي، وَبِكَ أَرْفَعُهُ، فَإِنْ أَمْسَكْتَ نَفْسِي فَارْحَمْهَا، وَإِنْ أَرْسَلْتَهَا فَاحْفَظْهَا، بِمَا تَحْفَظُ بِهِ عِبَادَكَ الصَّالِحِينَ.Bismika rabbee wadaAAtu janbee wabika arfaAAuh, fa-in amsakta nafsee farhamha, wa-in arsaltaha fahfathha bima tahfathu bihi AAibadakas-saliheen. In Your name my Lord, I lie down and in Your name I rise, so if You should take my soul then have mercy upon it, and if You should return my soul then protect it in the manner You do so with Your righteous servants.",
I cannot find how to split it into 2 parts that I get Arabic and English into two different parts.
What I want:
so there can be any language, my problem is to only take out English or Arabic language and show them in respective fields.
How can I achieve it?
You can use a Natural Language Tagger, which would work even if both scripts are intermingled:
import NaturalLanguage
let str = "¿como? بداية start وسط middle начать средний конец نهاية end. 從中間開始. "
let tagger = NLTagger(tagSchemes: [.script])
tagger.string = str
var index = str.startIndex
var dictionary = [String: String]()
var lastScript = "other"
while index < str.endIndex {
let res = tagger.tag(at: index, unit: .word, scheme: .script)
let range = res.1
let script = res.0?.rawValue
switch script {
case .some(let s):
lastScript = s
dictionary[s, default: ""] += dictionary["other", default: ""] + str[range]
dictionary.removeValue(forKey: "other")
default:
dictionary[lastScript, default: ""] += str[range]
}
index = range.upperBound
}
print(dictionary)
and print the result if you'd like:
for entry in dictionary {
print(entry.key, ":", entry.value)
}
yielding :
Hant : 從中間開始.
Cyrl : начать средний конец
Arab : بداية وسط نهاية
Latn : ¿como? start middle end.
This is still not perfect since the language tagger only checks to which script the most number of letters in a word belong to. For example, in the string you're working with, the tagger would consider الصَّالِحِينَ.Bismika as one word. To overcome this, we could use two pointers and traverse the original string and check the script of words individually. Words are defined as contiguous letters:
let str = "بِاسْمِكَ رَبِّي وَضَعْتُ جَنْبِي، وَبِكَ أَرْفَعُهُ، فَإِنْ أَمْسَكْتَ نَفْسِي فَارْحَمْهَا، وَإِنْ أَرْسَلْتَهَا فَاحْفَظْهَا، بِمَا تَحْفَظُ بِهِ عِبَادَكَ الصَّالِحِينَ.Bismika rabbee wadaAAtu janbee wabika arfaAAuh, fa-in amsakta nafsee farhamha, wa-in arsaltaha fahfathha bima tahfathu bihi AAibadakas-saliheen. In Your name my Lord, I lie down and in Your name I rise, so if You should take my soul then have mercy upon it, and if You should return my soul then protect it in the manner You do so with Your righteous servants."
let tagger = NLTagger(tagSchemes: [.script])
var i = str.startIndex
var dictionary = [String: String]()
var lastScript = "glyphs"
while i < str.endIndex {
var j = i
while j < str.endIndex,
CharacterSet.letters.inverted.isSuperset(of: CharacterSet(charactersIn: String(str[j]))) {
j = str.index(after: j)
}
if i != j { dictionary[lastScript, default: ""] += str[i..<j] }
if j < str.endIndex { i = j } else { break }
while j < str.endIndex,
CharacterSet.letters.isSuperset(of: CharacterSet(charactersIn: String(str[j]))) {
j = str.index(after: j)
}
let tempo = String(str[i..<j])
tagger.string = tempo
let res = tagger.tag(at: tempo.startIndex, unit: .word, scheme: .script)
if let s = res.0?.rawValue {
lastScript = s
dictionary[s, default: ""] += dictionary["glyphs", default: ""] + tempo
dictionary.removeValue(forKey: "glyphs")
}
else { dictionary["other", default: ""] += tempo }
i = j
}
You can use the NaturalLanguageTagger as answered by #ielyamani but the only limitation is that it is iOS 12+
If you are trying to do this on earlier iOS versions, you can take a look at NSCharacterSet
You can create your own characterset to check whether a string has english characters and numbers
extension String {
func containsLatinCharacters() -> Bool {
var charSet = NSCharacterSet(charactersInString: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
charSet = charSet.invertedSet
let range = (self as NSString).rangeOfCharacterFromSet(charSet)
if range.location != NSNotFound {
return false
}
return true
}
}
Another option is to use the charactersets already available:
let nonLatinString = string.trimmingCharacters(in: .alphanumerics)//symbols will still get through
let latinString = string.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)//symbols and non-latin characters wont get through
With these you can get the strings you want quite easily. But if these are not good enough, you can look to create your own characterset, use union, intersect etc to filter out the wanted and the unwanted characters.
Step 1:
You have to split whole string into an array by "." as I can see there are "." between sentence.
Step 2:
Pass each sentence to determine its language and append into different string.
Final Code
//add in your viewController
enum Language : String {
case arabic = "ar"
case english = "en"
}
override func viewDidLoad() {
super.viewDidLoad()
//make array of string
let kalmaArray = "بِاسْمِكَ رَبِّي وَضَعْتُ جَنْبِي، وَبِكَ أَرْفَعُهُ، فَإِنْ أَمْسَكْتَ نَفْسِي فَارْحَمْهَا، وَإِنْ أَرْسَلْتَهَا فَاحْفَظْهَا، بِمَا تَحْفَظُ بِهِ عِبَادَكَ الصَّالِحِينَ.Bismika rabbee wadaAAtu janbee wabika arfaAAuh, fa-in amsakta nafsee farhamha, wa-in arsaltaha fahfathha bima tahfathu bihi AAibadakas-saliheen. In Your name my Lord, I lie down and in Your name I rise, so if You should take my soul then have mercy upon it, and if You should return my soul then protect it in the manner You do so with Your righteous servants.".components(separatedBy: ".")
splitInLanguages(kalmaArray: kalmaArray)
}
private func splitInLanguages(kalmaArray: [String]){
var englishText = ""
var arabicText = ""
for kalma in kalmaArray {
if kalma.count > 0 {
if let language = NSLinguisticTagger.dominantLanguage(for: kalma) {
switch language {
case Language.arabic.rawValue:
arabicText.append(kalma)
arabicText.append(".")
break
default: // English
englishText.append(kalma)
englishText.append(".")
break
}
} else {
print("Unknown language")
}
}
}
debugPrint("Arabic: ", arabicText)
debugPrint("English: ", englishText)
}
I hope it will help you to split the string in two language. Let me know if you are still having any issue.
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 Swift based iOS app and one of the features allows you to comment on a post. Anyway, users can add "#mentions" in their posts to tag other people. However I want to stop the user from adding a username with a capital letter.
Is there anyway I can convert a string, so that the #usernames are all in lowercase?
For example:
I really enjoy sightseeing with #uSerABC (not allowed)
I really enjoy sightseeing with #userabc (allowed)
I know there is a property for the string in swift called .lowercaseString - but the problem with that, is that it makes the entire string lowercase and thats not what I want. I only want the #username to be in lower case.
Is there any way around this with having to use the .lowercase property.
Thanks for your time, Dan.
This comes from a code I use to detect hashtags, I've modified to detect mentions:
func detectMentionsInText(text: String) -> [NSRange]? {
let mentionsDetector = try? NSRegularExpression(pattern: "#(\\w+)", options: NSRegularExpressionOptions.CaseInsensitive)
let results = mentionsDetector?.matchesInString(text, options: NSMatchingOptions.WithoutAnchoringBounds, range: NSMakeRange(0, text.utf16.count)).map { $0 }
return results?.map{$0.rangeAtIndex(0)}
}
It detects all the mentions in a string by using a regex and returns an NSRange array, by using a range you have the beginning and the end of the "mention" and you can easily replace them with a lower case version.
Split the string into two using the following command -
let arr = myString.componentsSeparatedByString("#")
//Convert arr[1] to lower case
//Append to arr[0]
//Enjoy
Thanks to everyone for their help. In the end I couldn't get any of the solutions to work and after a lot of testing, I came up with this solution:
func correctStringWithUsernames(inputString: String, completion: (correctString: String) -> Void) {
// Create the final string and get all
// the seperate strings from the data.
var finalString: String!
var commentSegments: NSArray!
commentSegments = inputString.componentsSeparatedByString(" ")
if (commentSegments.count > 0) {
for (var loop = 0; loop < commentSegments.count; loop++) {
// Check the username to ensure that there
// are no capital letters in the string.
let currentString = commentSegments[loop] as! String
let capitalLetterRegEx = ".*[A-Z]+.*"
let textData = NSPredicate(format:"SELF MATCHES %#", capitalLetterRegEx)
let capitalResult = textData.evaluateWithObject(currentString)
// Check if the current loop string
// is a #user mention string or not.
if (currentString.containsString("#")) {
// If we are in the first loop then set the
// string otherwise concatenate the string.
if (loop == 0) {
if (capitalResult == true) {
// The username contains capital letters
// so change it to a lower case version.
finalString = currentString.lowercaseString
}
else {
// The username does not contain capital letters.
finalString = currentString
}
}
else {
if (capitalResult == true) {
// The username contains capital letters
// so change it to a lower case version.
finalString = "\(finalString) \(currentString.lowercaseString)"
}
else {
// The username does not contain capital letters.
finalString = "\(finalString) \(currentString)"
}
}
}
else {
// The current string is NOT a #user mention
// so simply set or concatenate the finalString.
if (loop == 0) {
finalString = currentString
}
else {
finalString = "\(finalString) \(currentString)"
}
}
}
}
else {
// No issues pass back the string.
finalString = inputString
}
// Pass back the correct username string.
completion(correctString: finalString)
}
Its certainly not the most elegant or efficient solution around but it does work. If there are any ways of improving it, please leave a comment.
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)
}
}