If I have a string, e.g. spider, how do you create a new string that starts at the first vowel and ends with the last character of the initial string.
For example:
- spider would be ider
- elephant would be elephant
- campus would be ampus
Thank you for the help.
Simple solution with a custom CharacterSet as String extension
extension String {
func substringFromFirstVowel() -> String
{
let vowelCharacterSet = CharacterSet(charactersIn: "aeiouAEIOU")
guard let range = self.rangeOfCharacter(from: vowelCharacterSet) else { return self }
return self.substring(from: range.lowerBound)
}
}
"elephant".substringFromFirstVowel() // elephant
"spider".substringFromFirstVowel() // ider
"campus".substringFromFirstVowel() // ampus
Try this little function
func firstVowel(input : String) -> String {
var firstVowel = true
let vowels = "aAeEiIoOuU".characters
var result = ""
for char in input.characters {
if(!firstVowel) {
result.append(char)
}
if(vowels.contains(char) && firstVowel) {
firstVowel = false
result.append(char)
}
}
return result
}
print(firstVowels(input: "elephant")) //prints elephant
print(firstVowels(input: "Spider")) //prints ider
Related
Target: The following function shall iterate over an array of objects and check a specific property of all objects. This property is a string and shall be matched with a user input via regex. If there's a match the object shall be added to an array which will further be passed to another function.
Problem: I don't know how to set up regex in Swift 3. I'm rather new in Swift at all, so an easily understandable solution would be very helpful :)
How it currently looks like:
func searchItems() -> [Item] {
var matches: [Item] = []
if let input = readLine() {
for item in Storage.storage.items { //items is a list of objects
if let query = //regex with query and item.name goes here {
matches.append(item)
}
}
return matches
} else {
print("Please type in what you're looking for.")
return searchItems()
}
}
This is what Item looks like (snippet):
class Item: CustomStringConvertible {
var name: String = ""
var amount: Int = 0
var price: Float = 0.00
var tags: [String] = []
var description: String {
if self.amount > 0 {
return "\(self.name) (\(self.amount) pcs. in storage) - \(price) €"
} else {
return "\(self.name) (SOLD OUT!!!) - \(price) €"
}
}
init(name: String, price: Float, amount: Int = 0) {
self.name = name
self.price = price
self.amount = amount
}
}
extension Item: Equatable {
static func ==(lhs: Item, rhs: Item) -> Bool {
return lhs.name == rhs.name
}
}
Solved. I just edited this post to get a badge :D
For the purpose of letting the answer to be generic and clear, I will assume that the Item model is:
struct Item {
var email = ""
}
Consider that the output should be a filtered array of items that contains items with only valid email.
For such a functionality, you should use NSRegularExpression:
The NSRegularExpression class is used to represent and apply regular
expressions to Unicode strings. An instance of this class is an
immutable representation of a compiled regular expression pattern and
various option flags.
According to the following function:
func isMatches(_ regex: String, _ string: String) -> Bool {
do {
let regex = try NSRegularExpression(pattern: regex)
let matches = regex.matches(in: string, range: NSRange(location: 0, length: string.characters.count))
return matches.count != 0
} catch {
print("Something went wrong! Error: \(error.localizedDescription)")
}
return false
}
You can decide if the given string does matches the given regex.
Back to the example, consider that you have the following array of Item Model:
let items = [Item(email: "invalid email"),
Item(email: "email#email.com"),
Item(email: "Hello!"),
Item(email: "example#example.net")]
You can get the filtered array by using filter(_:) method:
Returns an array containing, in order, the elements of the sequence
that satisfy the given predicate.
as follows:
let emailRegex = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
let emailItems = items.filter {
isMatches(emailRegex, $0.email)
}
print(emailItems) // [Item(email: "email#email.com"), Item(email: "example#example.net")]
Hope this helped.
You can do the same with filter function
let matches = Storage.storage.items.filter({ $0.yourStringPropertyHere == input })
I am updating my code to swift3.0 but getting ambiguous refrence to member? What wrong i might be doing. Here is the method I am getting error in.
open class func parseJsonTenantList(_ list: [NSDictionary]?, strElementName: String, attrName1: String, attrNameValue2: String) -> [TenantRegister]
{
var renantList: [TenantRegister] = []
var key: String?
if let dict : [NSDictionary] = list {
var value: String?
for i in 0..<dict.count {
/// if attribute name doesn't match then it returns nil
if let s1: AnyObject = dict[i].value(forKey: attrName1)
{
key = s1 as? String
}
if let s2: AnyObject = dict[i].value(forKey: attrNameValue2)
{
value = s2 as? String
}
if (!(String.stringIsNilOrEmpty(value) && String.stringIsNilOrEmpty(key)))
{
let t: TenantRegister = TenantRegister()
t.name = key
t.tenantId = Guid(value!)
renantList.append(t)
}
}
}
return renantList
}
The issue is you are using NSDictionary, to solved your problem simply cast the list to Swift's native type [[String:Any]] and then use subscript with it instead of value(forKey:)
if let dict = list as? [[String:Any]] {
var value: String?
for i in 0..<dict.count {
/// if attribute name doesn't match then it returns nil
if let s1 = dict[i][attrName1] as? String
{
key = s1
}
if let s2 = dict[i][attrNameValue2] as? String
{
value = s2
}
if (!(String.stringIsNilOrEmpty(value) && String.stringIsNilOrEmpty(key)))
{
let t: TenantRegister = TenantRegister()
t.name = key
t.tenantId = Guid(value!)
renantList.append(t)
}
}
}
In Swift use native type Dictionary [:] and Array [] instead of NSDictionary and NSArray to overcome this type of issues.
I am using the following extension to make sure a string has at least 1 number, 1 letter and between 5-15 characters in length and I feel that it can be more efficient. Any suggestions?
func checkPassword(password : String) -> Bool{
if password.characters.count > 15 || password.characters.count < 5 {
return false
}
let capitalLetterRegEx = ".*[A-Za-z]+.*"
let texttest = NSPredicate(format:"SELF MATCHES %#", capitalLetterRegEx)
let capitalresult = texttest.evaluate(with: password)
let numberRegEx = ".*[0-9]+.*"
let texttest1 = NSPredicate(format:"SELF MATCHES %#", numberRegEx)
let numberresult = texttest1.evaluate(with: password)
let specialRegEx = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLKMNOPQRSTUVWXYZ0123456789"
let texttest2 = NSPredicate(format:"SELF MATCHES %#", specialRegEx)
let specialresult = !texttest2.evaluate(with: password)
if !capitalresult || !numberresult || !specialresult {
return false
}
return true
}
Using Regex
Regex is one approach, but if using it, we may combine your specifications into a single regex search, making use of the positive lookahead assertion technique from the following Q&A:
Regex to validate password strength
Here, using the regex:
^(?=.*[A-Za-z])(?=.*[0-9])(?!.*[^A-Za-z0-9]).{5,15}$
// where:
// (?=.*[A-Za-z]) Ensures string has at least one letter.
// (?=.*[0-9]) Ensures string has at least one digit.
// (?!.*[^A-Za-z0-9]) Ensures string has no invalid (non-letter/-digit) chars.
// .{5,15} Ensures length of string is in span 5...15.
Where I've include also a negative lookahead assertion (?!...) to invalidate the password given any invalid characters.
We may implement the regex search as follows:
extension String {
func isValidPassword() -> Bool {
let regexInclude = try! NSRegularExpression(pattern: "^(?=.*[A-Za-z])(?=.*[0-9])(?!.*[^A-Za-z0-9]).{5,15}$")
return regexInclude.firstMatch(in: self, options: [], range: NSRange(location: 0, length: characters.count)) != nil
}
}
let pw1 = "hs1bés2" // invalid character
let pw2 = "12345678" // no letters
let pw3 = "shrt" // short
let pw4 = "A12345" // ok
print(pw1.isValidPassword()) // false
print(pw2.isValidPassword()) // false
print(pw3.isValidPassword()) // false
print(pw4.isValidPassword()) // true
Using Set / CharacterSet
A Swift native approach is using sets of explicitly specified Character's:
extension String {
private static var numbersSet = Set("1234567890".characters)
private static var alphabetSet = Set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLKMNOPQRSTUVWXYZ".characters)
func isValidPassword() -> Bool {
return 5...15 ~= characters.count &&
characters.contains(where: String.numbersSet.contains) &&
characters.contains(where: String.alphabetSet.contains)
}
}
Or, similarly, using the Foundation method rangeOfCharacter(from:) over CharacterSet's:
extension String {
private static var numbersSet = CharacterSet(charactersIn: "1234567890")
private static var alphabetSet = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLKMNOPQRSTUVWXYZ")
func isValidPassword() -> Bool {
return 5...15 ~= characters.count &&
rangeOfCharacter(from: String.numbersSet) != nil &&
rangeOfCharacter(from: String.alphabetSet) != nil
}
}
If you'd also like to reject passwords that contain any character that is not in the specified sets, you could add a search operation on the (inverted) union of your sets (possibly you also allow some special characters that you'd like to include in this union). E.g., for the CharacterSet example:
extension String {
private static var numbersSet = CharacterSet(charactersIn: "1234567890")
private static var alphabetSet = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLKMNOPQRSTUVWXYZ")
func isValidPassword() -> Bool {
return 5...15 ~= characters.count &&
rangeOfCharacter(from: String.numbersSet.union(String.alphabetSet).inverted) == nil &&
rangeOfCharacter(from: String.numbersSet) != nil &&
rangeOfCharacter(from: String.alphabetSet) != nil
}
}
let pw1 = "hs1bés2" // invalid character
let pw2 = "12345678" // no letter
let pw3 = "shrt" // too short
let pw4 = "A12345" // OK
print(pw1.isValidPassword()) // false
print(pw2.isValidPassword()) // false
print(pw3.isValidPassword()) // false
print(pw4.isValidPassword()) // true
Using pattern matching
Just for the discussion of it, yet another approach is using native Swift pattern matching:
extension String {
private static var numberPattern = Character("0")..."9"
private static var alphabetPattern = Character("a")..."z"
func isValidPassword() -> Bool {
return 5...15 ~= characters.count &&
characters.contains { String.numberPattern ~= $0 } &&
lowercased().characters.contains { String.alphabetPattern ~= $0 }
}
}
let pw1 = "hs1bs2"
let pw2 = "12345678"
let pw3 = "shrt"
let pw4 = "A12345"
print(pw1.isValidPassword()) // true
print(pw2.isValidPassword()) // false
print(pw3.isValidPassword()) // false
print(pw4.isValidPassword()) // true
Just note that this approach will allow letters with diacritics (and similar) to pass as the minimum 1 letter specification, e.g.:
let diacritic: Character = "é"
print(Character("a")..."z" ~= diacritic) // true
let pw5 = "12345é6"
print(pw5.isValidPassword()) // true
as these are contained the the Character range "a"..."z"; see e.g. the excellent answer in the following thread:
What does it mean that string and character comparisons in Swift are not locale-sensitive?
Thank you for your responses. I used them to create this:
extension String {
private static var numbersSet = Set("1234567890".characters)
private static var alphabetSet = Set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLKMNOPQRSTUVWXYZ".characters)
func isValidPassword() -> Bool {
let characterset = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLKMNOPQRSTUVWXYZ0123456789")
return 5...15 ~= characters.count &&
characters.contains(where: String.numbersSet.contains) &&
characters.contains(where: String.alphabetSet.contains) &&
self.rangeOfCharacter(from: characterset.inverted) != nil
}
}
I was trying to change hello_world to helloWorld by this snippet of code (Swift 3.0):
import Foundation
let oldLine = "hello_world"
let fullRange = NSRange(location: 0, length: oldLine.characters.count)
let newLine = NSMutableString(string: oldLine)
let regex = try! NSRegularExpression(pattern: "(_)(\\w)", options: [])
regex.replaceMatches(in: newLine, options: [], range: fullRange,
withTemplate: "\\L$2")
The result was newLine = "helloLworld"
I used "\\L$2" as template because I saw this answer: https://stackoverflow.com/a/20742304/5282792 saying \L$2 is the pattern for the second group's uppercase in replacement template. But it didn't work in NSRegularExpression.
So can I replace a string with its uppercase with a replacement template pattern in NSRegularExpression.
One way to work with your case is subclassing NSRegularExpression and override replacementString(for:in:offset:template:) method.
class ToUpperRegex: NSRegularExpression {
override func replacementString(for result: NSTextCheckingResult, in string: String, offset: Int, template templ: String) -> String {
guard result.numberOfRanges > 2 else {
return ""
}
let matchingString = (string as NSString).substring(with: result.rangeAt(2)) as String
return matchingString.uppercased()
}
}
let oldLine = "hello_world"
let fullRange = NSRange(0..<oldLine.utf16.count) //<-
let tuRegex = try! ToUpperRegex(pattern: "(_)(\\w)")
let newLine = tuRegex.stringByReplacingMatches(in: oldLine, range: fullRange, withTemplate: "")
print(newLine) //->helloWorld
This doesn't answer the question pertaining regex, but might be of interest for readers not necessarily needing to use regex to perform this task (rather, using native Swift)
extension String {
func camelCased(givenSeparators separators: [Character]) -> String {
let charChunks = characters.split { separators.contains($0) }
guard let firstChunk = charChunks.first else { return self }
return String(firstChunk).lowercased() + charChunks.dropFirst()
.map { String($0).onlyFirstCharacterUppercased }.joined()
}
// helper (uppercase first char, lowercase rest)
var onlyFirstCharacterUppercased: String {
let chars = characters
guard let firstChar = chars.first else { return self }
return String(firstChar).uppercased() + String(chars.dropFirst()).lowercased()
}
}
/* Example usage */
let oldLine1 = "hello_world"
let oldLine2 = "fOo_baR BAX BaZ_fOX"
print(oldLine1.camelCased(givenSeparators: ["_"])) // helloWorld
print(oldLine2.camelCased(givenSeparators: ["_", " "])) // fooBarBazBazFox
Eventually I want to be able to input a string like "\mycard{front1}{back1} \mycard{front2}{back2} \mycard{front3}{back3}" and return the front and back of each card.
I found this website on NSRegularExpression, but I'm having a hard time adjusting it to my problem.
Here is what I have so far.
import Foundation
func rangeFromNSRange(nsRange: NSRange, forString str: String) -> Range<String.Index>? {
let fromUTF16 = str.utf16.startIndex.advancedBy(nsRange.location, limit: str.utf16.endIndex)
let toUTF16 = fromUTF16.advancedBy(nsRange.length, limit: str.utf16.endIndex)
if let from = String.Index(fromUTF16, within: str), let to = String.Index(toUTF16, within: str) {
return from ..< to
}
return nil
}
do {
// let input = "My name is Taylor Swift"
// let regex = try NSRegularExpression(pattern: "My name is (.*)", options: NSRegularExpressionOptions.CaseInsensitive)
let input = "mycard{front}{back}"
let regex = try NSRegularExpression(pattern: "mycard{(.*)}{(.*)}", options: NSRegularExpressionOptions.CaseInsensitive)
let matches = regex.matchesInString(input, options: [], range: NSMakeRange(0, input.characters.count))
if let match = matches.first {
let range = match.rangeAtIndex(1)
if let swiftRange = rangeFromNSRange(range, forString: input) {
let name = input.substringWithRange(swiftRange)
}
}
} catch {
// regex was bad!
}
As stated in my comment you need to escape the { and }. That results in the following regex: mycard\\{(.*)\\}\\{(.*)\\}.
You then might want to change your match logic a little bit to output the expected results:
if let match = matches.first {
for i in 1..<match.numberOfRanges {
let range = match.rangeAtIndex(i)
if let swiftRange = rangeFromNSRange(range, forString: input) {
let name = input.substringWithRange(swiftRange)
print(name)
}
}
}
Which outputs
front
back
If you want to match multiple cards use the following regex:
mycard\\{([^{]*)\\}\\{([^{]*)\\}
Then iterate over the matches
for match in matches {
for i in 1..<match.numberOfRanges {
let range = match.rangeAtIndex(i)
if let swiftRange = rangeFromNSRange(range, forString: input) {
let name = input.substringWithRange(swiftRange)
print(name)
}
}
}
For the input mycard{front}{back} mycard{front1}{back1} the output correctly is
front
back
front1
back1
I gave up on regex. I just don't think it will do the trick here. I came up with another solution.
import Foundation
extension String {
subscript (r: Int) -> Character? {
var cur = 0
for char in self.characters {
if cur == r {
return char
}
cur += 1
}
return nil
}
subscript (r: Range<Int>) -> String {
return substringWithRange(Range(start: startIndex.advancedBy(r.startIndex), end: startIndex.advancedBy(r.endIndex)))
}
func parseBrackets () -> [String]? {
var list: [String] = []
var level = 0
var start = 0
for var i=0; i < self.characters.count - 1; i++ {
if self[i] == "{" {
level += 1
if level == 1 {
start = i + 1
}
} else if self[i] == "}" {
if level == 1 {
list.append(self[start..<i])
}
level -= 1
}
}
if list.count > 0 {
return list
} else {
return nil
}
}
}
let testString = "mycard{f{}ront}{termins{x}{n}} mycard{front1}{back1} mycard{front2}{back2}"
let list = testString.parseBrackets()
for a in list! {
print(a)
}
Which gives the desired output
f{}ront
termins{x}{n}
front1
back1
front2