swift how to split string but contains the separators - swift

I have a string like 100 + 8 - 9 + 10, how to get ["100", "+", "8", "-", "9", "+", "10"] in an array.
let string = "100 + 8 - 9 + 10"
let splitted = string.split(omittingEmptySubsequences: true, whereSeparator: { ["+", "-"].contains(String($0)) })
But I got ["100", "8", "9", "10"], I lost + and -, any good way? Thanks!
EDIT
Thanks for the comment, not guarantee about spaces. Could be like "100+8 - 9 ".

You can just split your string by spaces.
let splitted = string.split(separator: " ")
The value of splitted is
["100", "+", "8", "-", "9", "+", "10"]

If you don't know whether or not the string will contain spaces, you should probably use a Scanner
let string = "100 + 8 - 9 + 10"
let removed = string.replacingOccurrences(of: " ", with: "")
// "100+8-9+10"
let scanner = Scanner(string: removed)
let operators = CharacterSet(charactersIn: "+-*/")
var components: [String] = []
while !scanner.isAtEnd {
var value: Int = 0
if scanner.scanInt(&value) {
components.append("\(value)")
}
var op: NSString? = ""
if scanner.scanCharacters(from: operators, into: &op) {
components.append(op! as String)
}
}
print(components)
// ["100", "+", "8", "-", "9", "+", "10"]

Related

How can I save data in different table with same model in Realm | Swift / SwiftUI

How can I save data in different table with same model with Realm I am using Realm and Unrealm.
I use the Question model when saving the selected questions and again when saving the wrong questions I save the Question model. However, when I fetch the wrong questions after saving the wrong questions, the fetch occurs with the selected questions.
Console:
wrongQuestions: [```"50"```, ```"50"```, "49", "48", "47", "46", "45", "44", "43", "42", "41", "40", "39", "38", "37", "36", "35", "34", "33", "32", "31", "30", "29", "28", "27", "26", "25", "24", "23", "22", "21", "20", "19", "18", "17", "16", "15", "14", "13", "12", "11", "10", "9", "8", "7", "6", "5", "4", "3", "2", "1"]
As seen in the console array has two ids 50. I just want to bring up the wrong questions. How can I open a separate table in Realm? or what should i do?
My Models:
struct QuizContainer: Codable, Realmable, Hashable {
var allQuiz: [Quiz] = []
}
struct Quiz: Codable, Realmable, Hashable {
var id: String = ""
var questions: [Question] = []
var title: String = ""
static func primaryKey() -> String? {
return "id"
}
}
struct Question: Codable, Realmable, Hashable {
var id: String = ""
var question: String = ""
var imageURL: String = ""
var sections: [String : String] = [:]
var correct: String = ""
var isQuestionImage: Bool = false
var isSectionImage: Bool = false
var selected: String = ""
}
Functions:
func saveWronQuestionToRealm(wrongQuestion: Question) {
guard let realmReferance = try? Realm() else { return }
do {
try realmReferance.write({
realmReferance.add(wrongQuestion)
})
} catch let error {
print(error)
}
}
func getWrongQuestionFromToRealm() -> [Question]? {
if let realmReferance = try? Realm() {
let resultQuiz = realmReferance.objects(Question.self).reversed()
let wrongQuestion = resultQuiz.compactMap { category in
return category
}
return wrongQuestion
}
return nil
}

Map a Dictionary with array of values

I want to convert a dictionary to an Array, by showing each [String: [String]] element as a string in the array (but the value may be empty).
So ["A": ["1","2"], "b": [], "c": ["5", "6"]] will give ["b", "A1", "A2", "c5", "c6"].
I want to use map to do this, as the code I have feels unwieldy:
let messages: [String: [String]] = ["A": ["1","2"], "b": [], "c": ["5", "6"]]
var result: [String] = []
for message in messages {
if !message.value.isEmpty {
for value in message.value {
result.append(message.key + value)
}
} else {
result.append(message.key)
}
}
How can I create the solution using map?
A little bit shortened version using map
let messages: [String: [String]] = ["A": ["1","2"], "b": [], "c": ["5", "6"]]
let result = messages.map { dict in
return dict.value.isEmpty ? ["\(dict.key)"] : dict.value.map { "\(dict.key)\($0)" }
}.flatMap{$0}
A possible solution is to use reduce(into:_:), (I prefer this one):
let messages: [String: [String]] = ["A": ["1","2"], "b": [], "c": ["5", "6"]]
let output = messages.reduce(into: [String]()) { result, current in
guard !current.value.isEmpty else { result.append(current.key); return }
let sub = current.value.map { current.key + $0 }
result.append(contentsOf: sub)
}
print("Output: \(output)")
Output:
$> Output: ["c5", "c6", "A1", "A2", "b"]
With a map and a flatMap, I found it less intuitive:
let output2 = messages.map { element -> [String] in
guard !element.value.isEmpty else { return [element.key] }
let sub = element.value.map { element.key + $0 }
return sub
}.flatMap { $0 }
print("Output2: \(output2)")
Which then can be simplified with a direct call to flatMap, but that (map + flatMap) was to show the process, since this one will save you an iteration (you do only one instead of two):
let output3 = messages.flatMap { element -> [String] in
guard !element.value.isEmpty else { return [element.key] }
let sub = element.value.map { element.key + $0 }
return sub
}
print("Output3: \(output3)")
I made the closure explicit enough, but that's up to you if you want to shorten them with ternary if etc.

Remove last elements of a collection while a condition in Swift

I am attempting to remove "" & " " from the back of a string array until the last item contains some text, but my implementation isn't picking up " ".
My implementation so far:
var array = ["A", "B", "", "C", "D", " ", " ", ""]
while true {
if (array.last == " " || array.last == "") {
array.removeLast()
} else {
break
}
}
My desired output is
["A", "B", "", "C", "D"]
, but my current output is
["A", "B", "", "C", "D", " ", " "]
, where the while loop simply breaks after encountering " "
Any advice why is it not picking up the " "?
I don't know why they have drop(while:) and did not implement dropLast(while:). The implementation bellow works on any collection:
extension Collection {
func dropLast(while predicate: (Element) throws -> Bool) rethrows -> SubSequence {
guard let index = try indices.reversed().first(where: { try !predicate(self[$0]) }) else {
return self[startIndex..<startIndex]
}
return self[...index]
}
}
"123".dropLast(while: \.isWholeNumber) // ""
"abc123".dropLast(while: \.isWholeNumber) // "abc"
"123abc".dropLast(while: \.isWholeNumber) // "123abc"
And extending RangeReplaceableCollection we can implement remove(while:) and removeLast(while:) as well:
extension RangeReplaceableCollection {
mutating func remove(while predicate: (Element) throws -> Bool) rethrows {
guard let index = try indices.first(where: { try !predicate(self[$0]) }) else {
removeAll()
return
}
removeSubrange(..<index)
}
mutating func removeLast(while predicate: (Element) throws -> Bool) rethrows {
guard let index = try indices.reversed().first(where: { try !predicate(self[$0]) }) else {
removeAll()
return
}
removeSubrange(self.index(after: index)...)
}
}
var string = "abc123"
string.removeLast(while: \.isWholeNumber)
string // "abc"
var string2 = "abc123"
string2.remove(while: \.isLetter)
string2 // "123"
var array = ["A", "B", "", "C", "D", " ", " ", ""]
array.removeLast { $0 == "" || $0 == " " }
array // ["A", "B", "", "C", "D"]
One way to solve this is to reverse the collection (which is done lazily) and drop the unwanted items until you encounter the wanted ones. Afterwards, reverse the collection again.
let array = ["A", "B", "", "C", "D", " ", " ", ""]
let filtered = array.reversed().drop(while: {
$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
}).reversed() as [String]
print(filtered) // "["A", "B", "", "C", "D"]\n"
Note that the check for " " may fail if it's not a normal space, for example a non-breaking space (Unicode checkpoint U+00A0). This may be the issue you're having in the first place. So trim the string (it removes characters from the start and end only) and check whether the result is an empty string.
Move your condition to while and make sure you're checking on the correct array after the operation.
var array = ["A", "B", "", "C", "D", " ", " ", ""]
while array.last == " " || array.last == "" {
array.removeLast()
}
print(array) // ["A", "B", "", "C", "D"]
Just for fun, lets extend Array with this functionality in a generic way while also externally providing the condition for more flexibility.
Similar to Arrays having a drop(while:), we can make a dropLast(while:) like so:
extension Array {
func dropLast(while handler: (Element)->Bool) -> Array {
var array = self
while let last = array.last, handler(last) {
array.removeLast()
}
return array
}
}
Usage Example:
let array = ["", "A", "B", "", "C", "D", " ", " ", ""]
let modified = array.dropLast { $0.trimmingCharacters(in: .whitespaces).isEmpty }
print(modified) //["", "A", "B", "", "C", "D"]
Bonus:
It can handle other types of arrays too, and since the condition is not baked into the functionality, it's flexible and reusable.
let array = [0, 1, 2, 3, 0, 5, 6, 7, 0, -1, 0, -2]
//Drop (from tail) all numbers less than 1
let modified = array.dropLast(while: { (val) -> Bool in
return val < 1
})
print(modified) //[0, 1, 2, 3, 0, 5, 6, 7]
Basically your solution plays fine. But you can make it more generic for "", " ", "", " ", .... :
import Foundation
var array = ["A", "B", "", "C", "D", " ", " ", ""]
while true {
let shouldRemoveLast = array.last?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ?? false
if (shouldRemoveLast) { array.removeLast() } else { break }
}
For completeness, here's a one liner, functional programming style, extension, that removes the last elements that satisfy the given criteria:
extension Collection {
func dropLast(while predicate: (Element) -> Bool) -> SubSequence {
indices.reversed().first { !predicate(self[$0]) }.map(prefix(through:)) ?? prefix(0)
}
}
Same code, but in a more imperative style:
extension Collection {
func dropLast(while predicate: (Element) -> Bool) -> SubSequence {
if let lastValidIndex = indices.reversed().first(where: { !predicate(self[$0]) }) {
return prefix(through: lastValidIndex)
} else {
return prefix(0)
}
}
}

Disemvowel in swift

We can remove all of the vowels from the string in javascript just like this:
function disemvowel(str) {
str = str.replace(/([aeiouAEIOU])/g, '')
return str;
}
I implement it the same function in swift, just curious that how can write it shorter just as javascript?
func disemvowelTheString(string: String) -> String {
var replacedString = string
let vowels = ["a", "e", "i", "o", "u", "A", "E", "I", "O", "U"]
for vowel in vowels {
if string.containsString(vowel) {
replacedString = replacedString.stringByReplacingOccurrencesOfString(vowel, withString: "")
}
}
return replacedString
}
One option would be to filter any vowels from the input string's characters:
func removeVowels(input: String) -> String {
let vowels: [Character] = ["a", "e", "i", "o", "u", "A", "E", "I", "O", "U"]
let result = String(input.characters.filter { !vowels.contains($0) })
return result
}
Another solution using flatMap :
let string = "Hi how are you?"
let k = String(string.characters.flatMap(){
if(!["a", "e", "i", "o", "u", "A", "E", "I", "O", "U"].contains($0))
{
return $0
}else{
return nil
}
})
Prints: H hw r y?
Cheers!
Using the same regular expression:
func disemvowelTheString(string: String) -> String {
return string.stringByReplacingOccurrencesOfString(
"[aeiou]",
withString: "",
options: [.RegularExpressionSearch, .CaseInsensitiveSearch]
)
}
.CaseInsensitiveSearch takes care of AEIOU, the same way you could use /[aeiou]/gi in javascript.

How to get rid of array brackets while printing

While printing an array how to get rid of brackets from left and right?
var array = ["1", "2", "3", "4"]
println("\(array)") //It prints [1, 2, 3, 4]
var arrayWithoutBracketsAndCommas = array. //some code
println("\(arrayWithoutBracketsAndCommas)") //prints 1 2 3 4
You could do:
extension Array {
var minimalDescription: String {
return " ".join(map { "\($0)" })
}
}
["1", "2", "3", "4"].minimalDescription // "1 2 3 4"
With Swift 2, using Xcode b6, comes the joinWithSeparator method on SequenceType:
extension SequenceType where Generator.Element == String {
...
public func joinWithSeparator(separator: String) -> String
}
Meaning you could do:
extension SequenceType {
var minimalDescrption: String {
return map { String($0) }.joinWithSeparator(" ")
}
}
[1, 2, 3, 4].minimalDescrption // "1 2 3 4"
Swift 3:
extension Sequence {
var minimalDescription: String {
return map { "\($0)" }.joined(separator: " ")
}
}