Related
I'm used to do this in JavaScript:
var domains = "abcde".substring(0, "abcde".indexOf("cd")) // Returns "ab"
Swift doesn't have this function, how to do something similar?
edit/update:
Xcode 11.4 • Swift 5.2 or later
import Foundation
extension StringProtocol {
func index<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> Index? {
range(of: string, options: options)?.lowerBound
}
func endIndex<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> Index? {
range(of: string, options: options)?.upperBound
}
func indices<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> [Index] {
ranges(of: string, options: options).map(\.lowerBound)
}
func ranges<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> [Range<Index>] {
var result: [Range<Index>] = []
var startIndex = self.startIndex
while startIndex < endIndex,
let range = self[startIndex...]
.range(of: string, options: options) {
result.append(range)
startIndex = range.lowerBound < range.upperBound ? range.upperBound :
index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? endIndex
}
return result
}
}
usage:
let str = "abcde"
if let index = str.index(of: "cd") {
let substring = str[..<index] // ab
let string = String(substring)
print(string) // "ab\n"
}
let str = "Hello, playground, playground, playground"
str.index(of: "play") // 7
str.endIndex(of: "play") // 11
str.indices(of: "play") // [7, 19, 31]
str.ranges(of: "play") // [{lowerBound 7, upperBound 11}, {lowerBound 19, upperBound 23}, {lowerBound 31, upperBound 35}]
case insensitive sample
let query = "Play"
let ranges = str.ranges(of: query, options: .caseInsensitive)
let matches = ranges.map { str[$0] } //
print(matches) // ["play", "play", "play"]
regular expression sample
let query = "play"
let escapedQuery = NSRegularExpression.escapedPattern(for: query)
let pattern = "\\b\(escapedQuery)\\w+" // matches any word that starts with "play" prefix
let ranges = str.ranges(of: pattern, options: .regularExpression)
let matches = ranges.map { str[$0] }
print(matches) // ["playground", "playground", "playground"]
Using String[Range<String.Index>] subscript you can get the sub string. You need starting index and last index to create the range and you can do it as below
let str = "abcde"
if let range = str.range(of: "cd") {
let substring = str[..<range.lowerBound] // or str[str.startIndex..<range.lowerBound]
print(substring) // Prints ab
}
else {
print("String not present")
}
If you don't define the start index this operator ..< , it take the starting index. You can also use str[str.startIndex..<range.lowerBound] instead of str[..<range.lowerBound]
Swift 5
Find index of substring
let str = "abcdecd"
if let range: Range<String.Index> = str.range(of: "cd") {
let index: Int = str.distance(from: str.startIndex, to: range.lowerBound)
print("index: ", index) //index: 2
}
else {
print("substring not found")
}
Find index of Character
let str = "abcdecd"
if let firstIndex = str.firstIndex(of: "c") {
let index = str.distance(from: str.startIndex, to: firstIndex)
print("index: ", index) //index: 2
}
else {
print("symbol not found")
}
In Swift 4 :
Getting Index of a character in a string :
let str = "abcdefghabcd"
if let index = str.index(of: "b") {
print(index) // Index(_compoundOffset: 4, _cache: Swift.String.Index._Cache.character(1))
}
Creating SubString (prefix and suffix) from String using Swift 4:
let str : String = "ilike"
for i in 0...str.count {
let index = str.index(str.startIndex, offsetBy: i) // String.Index
let prefix = str[..<index] // String.SubSequence
let suffix = str[index...] // String.SubSequence
print("prefix \(prefix), suffix : \(suffix)")
}
Output
prefix , suffix : ilike
prefix i, suffix : like
prefix il, suffix : ike
prefix ili, suffix : ke
prefix ilik, suffix : e
prefix ilike, suffix :
If you want to generate a substring between 2 indices , use :
let substring1 = string[startIndex...endIndex] // including endIndex
let subString2 = string[startIndex..<endIndex] // excluding endIndex
Doing this in Swift is possible but it takes more lines, here is a function indexOf() doing what is expected:
func indexOf(source: String, substring: String) -> Int? {
let maxIndex = source.characters.count - substring.characters.count
for index in 0...maxIndex {
let rangeSubstring = source.startIndex.advancedBy(index)..<source.startIndex.advancedBy(index + substring.characters.count)
if source.substringWithRange(rangeSubstring) == substring {
return index
}
}
return nil
}
var str = "abcde"
if let indexOfCD = indexOf(str, substring: "cd") {
let distance = str.startIndex.advancedBy(indexOfCD)
print(str.substringToIndex(distance)) // Returns "ab"
}
This function is not optimized but it does the job for short strings.
There are three closely connected issues here:
All the substring-finding methods are over in the Cocoa NSString world (Foundation)
Foundation NSRange has a mismatch with Swift Range; the former uses start and length, the latter uses endpoints
In general, Swift characters are indexed using String.Index, not Int, but Foundation characters are indexed using Int, and there is no simple direct translation between them (because Foundation and Swift have different ideas of what constitutes a character)
Given all that, let's think about how to write:
func substring(of s: String, from:Int, toSubstring s2 : String) -> Substring? {
// ?
}
The substring s2 must be sought in s using a String Foundation method. The resulting range comes back to us, not as an NSRange (even though this is a Foundation method), but as a Range of String.Index (wrapped in an Optional, in case we didn't find the substring at all). However, the other number, from, is an Int. Thus we cannot form any kind of range involving them both.
But we don't have to! All we have to do is slice off the end of our original string using a method that takes a String.Index, and slice off the start of our original string using a method that takes an Int. Fortunately, such methods exist! Like this:
func substring(of s: String, from:Int, toSubstring s2 : String) -> Substring? {
guard let r = s.range(of:s2) else {return nil}
var s = s.prefix(upTo:r.lowerBound)
s = s.dropFirst(from)
return s
}
Or, if you prefer to be able to apply this method directly to a string, like this...
let output = "abcde".substring(from:0, toSubstring:"cd")
...then make it an extension on String:
extension String {
func substring(from:Int, toSubstring s2 : String) -> Substring? {
guard let r = self.range(of:s2) else {return nil}
var s = self.prefix(upTo:r.lowerBound)
s = s.dropFirst(from)
return s
}
}
Swift 5
let alphabet = "abcdefghijklmnopqrstuvwxyz"
var index: Int = 0
if let range: Range<String.Index> = alphabet.range(of: "c") {
index = alphabet.distance(from: alphabet.startIndex, to: range.lowerBound)
print("index: ", index) //index: 2
}
Swift 5
extension String {
enum SearchDirection {
case first, last
}
func characterIndex(of character: Character, direction: String.SearchDirection) -> Int? {
let fn = direction == .first ? firstIndex : lastIndex
if let stringIndex: String.Index = fn(character) {
let index: Int = distance(from: startIndex, to: stringIndex)
return index
} else {
return nil
}
}
}
tests:
func testFirstIndex() {
let res = ".".characterIndex(of: ".", direction: .first)
XCTAssert(res == 0)
}
func testFirstIndex1() {
let res = "12345678900.".characterIndex(of: "0", direction: .first)
XCTAssert(res == 9)
}
func testFirstIndex2() {
let res = ".".characterIndex(of: ".", direction: .last)
XCTAssert(res == 0)
}
func testFirstIndex3() {
let res = "12345678900.".characterIndex(of: "0", direction: .last)
XCTAssert(res == 10)
}
In the Swift version 3, String doesn't have functions like -
str.index(of: String)
If the index is required for a substring, one of the ways to is to get the range. We have the following functions in the string which returns range -
str.range(of: <String>)
str.rangeOfCharacter(from: <CharacterSet>)
str.range(of: <String>, options: <String.CompareOptions>, range: <Range<String.Index>?>, locale: <Locale?>)
For example to find the indexes of first occurrence of play in str
var str = "play play play"
var range = str.range(of: "play")
range?.lowerBound //Result : 0
range?.upperBound //Result : 4
Note : range is an optional. If it is not able to find the String it will make it nil. For example
var str = "play play play"
var range = str.range(of: "zoo") //Result : nil
range?.lowerBound //Result : nil
range?.upperBound //Result : nil
Leo Dabus's answer is great. Here is my answer based on his answer using compactMap to avoid Index out of range error.
Swift 5.1
extension StringProtocol {
func ranges(of targetString: Self, options: String.CompareOptions = [], locale: Locale? = nil) -> [Range<String.Index>] {
let result: [Range<String.Index>] = self.indices.compactMap { startIndex in
let targetStringEndIndex = index(startIndex, offsetBy: targetString.count, limitedBy: endIndex) ?? endIndex
return range(of: targetString, options: options, range: startIndex..<targetStringEndIndex, locale: locale)
}
return result
}
}
// Usage
let str = "Hello, playground, playground, playground"
let ranges = str.ranges(of: "play")
ranges.forEach {
print("[\($0.lowerBound.utf16Offset(in: str)), \($0.upperBound.utf16Offset(in: str))]")
}
// result - [7, 11], [19, 23], [31, 35]
Have you considered using NSRange?
if let range = mainString.range(of: mySubString) {
//...
}
I'm trying to detect the parentheses on string for example: foo(bar)baz(blim) to and reverse the content inside of the parentheses but I'm getting out of bounce range on my implementation:
func reverseInParentheses(inputString: String) -> String {
var tmpStr = inputString
var done = false
while !done {
if let lastIndexOfChar = tmpStr.lastIndex(of: "(") {
let startIndex = tmpStr.index(lastIndexOfChar, offsetBy:1)
if let index = tmpStr.firstIndex(of: ")") {
let range = startIndex..<index
let strToVerse = String(tmpStr[range])
let reversedStr = reverseStr(str: strToVerse)
tmpStr = tmpStr.replacingOccurrences(of: "(" + strToVerse + ")", with: reversedStr)
}
} else {
done = true
}
}
return tmpStr
}
How can I get the tmpStr.firstIndex(of: ")") after the startIndex any of you knows how can do that?
how can I get the tmpStr.firstIndex(of: ")") after the startIndex?
One way to do this is to "cut" the string at startIndex, and get the second half. Then use firstIndex(of:) on the substring. Since Substrings are just "views" onto the original strings from which they are cut from, firstIndexOf still returns indices of the original string.
let string = "foo(bar)baz(blim)"
if let lastIndexOfChar = string.lastIndex(of: "(") {
let startIndex = string.index(after: lastIndexOfChar)
let substring = string[startIndex..<string.endIndex] // cut off the first part of the string.
// now you have a "Substring" object
if let indexAfterOpenBracket = substring.firstIndex(of: ")") {
// prints "blim", showing that the index is indeed from the original string
print(string[startIndex..<indexAfterOpenBracket])
}
}
You can write this as an extension:
extension StringProtocol {
func firstIndex(of char: Character, after index: Index) -> Index? {
let substring = self[index..<endIndex]
return substring.firstIndex(of: char)
}
}
Now if you call tmpStr.firstIndex(of: ")", after: startIndex) in your reverseInParentheses, it should work.
You can iterate your string keeping an index as reference to compare it to the endIndex. So every time you successfully find a range you do a new search starting after the end index. Btw you should not use replacingOccurrences because it might replace words not inside parentheses as well. You can use RangeReplaceableCollection replaceSubrange and pass the reversed substring to that method.
To find the first index after character you can extend collection and return the index after the firstIndex of the element if found:
extension Collection where Element: Equatable {
func firstIndex(after element: Element) -> Index? {
guard let index = firstIndex(of: element) else { return nil }
return self.index(after: index)
}
}
Your method should look something like this:
func reverseInParentheses(inputString: String) -> String {
var inputString = inputString
var startIndex = inputString.startIndex
while startIndex < inputString.endIndex,
let start = inputString[startIndex...].firstIndex(after: "("),
let end = inputString[start...].firstIndex(of: ")") {
inputString.replaceSubrange(start..<end, with: inputString[start..<end].reversed())
startIndex = inputString.index(after: end)
}
return inputString
}
let str = "foo(bar)baz(blim)"
reverseInParentheses(inputString: str) // "foo(rab)baz(milb)"
Or extending StringProtocol and constraining Self to RangeReplaceableCollection:
extension StringProtocol where Self: RangeReplaceableCollection {
var reversingSubstringsBetweenParentheses: Self {
var startIndex = self.startIndex
var source = self
while startIndex < endIndex,
let start = source[startIndex...].firstIndex(after: "("),
let end = source[start...].firstIndex(of: ")") {
source.replaceSubrange(start..<end, with: source[start..<end].reversed())
startIndex = index(after: end)
}
return source
}
}
let str = "foo(bar)baz(blim)"
str.reversingSubstringsBetweenParentheses // "foo(rab)baz(milb)"
Updated answer of Leo Dabus in case there is written some more text after the last parentheses.
func reverseInParentheses(_ str: String) -> String {
var str = str
var startIndex = str.startIndex
Var lastIndexOfChar = str.lastIndex(of: ")") ?? startIndex
while startIndex < lastIndexOfChar {
let start = str[startIndex...].firstIndex(after: "("),
let end = str[start...].firstIndex(of: ")") {
str.replaceSubrange(start..<end, with: str[start..<end].reversed())
startIndex = str.index(after: end)
}
return str
}
extension String {
var masked: String {
// some logic which I have to write to mask string.
// I tried following and just shows x 🤦♂️
// replacingOccurrences(
// of: ".(.+).",
// with: "x",
// options: .regularExpression,
// range: nil
//)
}
}
let helloWorld = "Hello World"
print("Masked string is - \(helloWorld.masked)")
Expected output is - "Hxxxxxxxxxd"
There is a Regular Expression way with lookaround
extension String {
var masked: String {
replacingOccurrences(
of: "(?!^).(?!$)", // RegEx
with: "x", // Replacement
options: .regularExpression // Option to set RegEx
)
}
}
You can enumerate the string and apply map transform to get the expected output:
extension String {
var masked: String {
self.enumerated().map({ (index, ch) in
if index == 0
|| index == self.count - 1 {
return String(ch)
}
return "x"
}).joined()
}
}
let str = "hello"
print("after masking \(str.masked)") // Output - hxxxo
The map transform will return an array, so use joined() to convert the array back to String. Also, note that you have to typecast ch to String as String(ch) because the type of ch is 'String.Element' (aka 'Character').
extension Sequence {
func replacingEachInteriorElement(with replacement: Element) -> [Element] {
let prefix = dropLast()
return
prefix.prefix(1)
+ prefix.dropFirst().map { _ in replacement }
+ suffix(1)
}
}
extension String {
var masked: Self {
.init( replacingEachInteriorElement(with: "x") )
}
}
"Hello World".masked == "Hxxxxxxxxxd" // true
"H🦾👄🐺🥻🐸🦈🏄♂️🍯🪐d".masked == "Hello World".masked // true
"🥮".masked // 🥮
"🥶😎".masked // 🥶😎
[].replacingEachInteriorElement(with: 500) // []
My solution without using Regular Expression:
extension String {
var masked: String {
if self.count < 2 { return self }
var output = self
let range = self.index(after: self.startIndex)..<self.index(before: endIndex)
let replacement = String.init(repeating: "x", count: output.count - 2)
output.replaceSubrange(range, with: replacement)
return output
}
}
So far, I've found following solution.
extension String {
var masked: String {
var newString = ""
for index in 0..<count {
if index != 0 && index != count-1 {
newString.append(contentsOf: "x")
} else {
let array = Array(self)
let char = array[index]
let string = String(char)
newString.append(string)
}
}
return newString
}
}
If you want to leave first and last letters you can use this ->
public extension String {
var masked: String {
return prefix(1) + String(repeating: "x", count: Swift.max(0, count-2)) + suffix(1)
}
}
USAGE
let hello = "Hello"
hello.masked
// Hxxxo
OR
you can pass unmasked character count ->
public extension String {
func masked(with unmaskedCount: Int) -> String {
let unmaskedPrefix = unmaskedCount/2
return prefix(unmaskedPrefix) + String(repeating: "x", count: Swift.max(0, count-unmaskedPrefix)) + suffix(unmaskedPrefix)
}
}
USAGE
let hello = "Hello"
hello.masked(with: 2)
// Hxxxo
let number = "5555555555"
number.masked(with: 4)
// 55xxxxxx55
My code snippet is:
unwanted = " £€₹jetztabfromnow"
let favouritesPriceLabel = priceDropsCollectionView.cells.element(boundBy: UInt(index)).staticTexts[IPCUIAHighlightsPriceDropsCollectionViewCellPriceLabel].label
let favouritesPriceLabelTrimmed = favouritesPriceLabel.components(separatedBy: "jetzt").flatMap { String($0.trimmingCharacters(in: .whitespaces)) }.last
favouritesHighlightsDictionary[favouritesTitleLabel] = favouritesPriceLabelTrimmed
My problem is, this didn't work:
let favouritesPriceLabelTrimmed = favouritesPriceLabel.components(separatedBy: unwanted).flatMap { String($0.trimmingCharacters(in: .whitespaces)) }.last
I have a price like "from 3,95 €" - I want to cut all currencies "£€₹" and words like "from" or "ab"
Do you have a solution for me, what I can use here?
Rather than mess around with trying to replace or remove the right characters or using regular expressions, I'd go with Foundation's built-in linguistic tagging support. It will do a lexical analysis of the string and return tokens of various types. Use it on this kind of string and it should reliably find any numbers in the string.
Something like:
var str = "from 3,95 €"
let range = Range(uncheckedBounds: (lower: str.startIndex, upper: str.endIndex))
var tokenRanges = [Range<String.Index>]()
let scheme = NSLinguisticTagSchemeLexicalClass
let option = NSLinguisticTagger.Options()
let tags = str.linguisticTags(in: range, scheme: scheme, options: option, orthography: nil, tokenRanges: &tokenRanges)
let tokens = tokenRanges.map { str.substring(with:$0) }
if let numberTagIndex = tags.index(where: { $0 == "Number" }) {
let number = tokens[numberTagIndex]
print("Found number: \(number)")
}
In this example the code prints "3,95". If you change str to "from £28.50", it prints "28.50".
One way is to place the unwanted strings into an array, and use String's replacingOccurrences(of:with:) method.
let stringToScan = "£28.50"
let toBeRemoved = ["£", "€", "₹", "ab", "from"]
var result = stringToScan
toBeRemoved.forEach { result = result.replacingOccurrences(of: $0, with: "") }
print(result)
...yields "28.50".
If you just want to extract the numeric value use regular expression, it considers comma or dot decimal separators.
let string = "from 3,95 €"
let pattern = "\\d+[.,]\\d+"
do {
let regex = try NSRegularExpression(pattern: pattern, options: [])
if let match = regex.firstMatch(in: string, range: NSRange(location: 0, length: string.utf16.count)) {
let range = match.range
let start = string.index(string.startIndex, offsetBy: range.location)
let end = string.index(start, offsetBy: range.length)
print(string.substring(with: start..<end)) // 3,95
} else {
print("Not found")
}
} catch {
print("Regex Error:", error)
}
I asked if you had a fixed locale for this string, because then you can use the locale to determine what the decimal separator is: For example, try this in a storyboard.
let string = "some initial text 3,95 €" // define the string to scan
// Add a convenience extension to Scanner so you don't have to deal with pointers directly.
extension Scanner {
func scanDouble() -> Double? {
var value = Double(0)
guard scanDouble(&value) else { return nil }
return value
}
// Convenience method to advance the location of the scanner up to the first digit. Returning the scanner itself or nil, which allows for optional chaining
func scanUpToNumber() -> Scanner? {
var value: NSString?
guard scanUpToCharacters(from: CharacterSet.decimalDigits, into: &value) else { return nil }
return self
}
}
let scanner = Scanner(string: string)
scanner.locale = Locale(identifier: "fr_FR")
let double = scanner.scanUpToNumber()?.scanDouble() // -> double = 3.95 (note the type is Double?)
Scanners are a lot easier to use than NSRegularExpressions in these cases.
You can filter by special character by removing alphanumerics.
extension String {
func removeCharacters(from forbiddenChars: CharacterSet) -> String {
let passed = self.unicodeScalars.filter { !forbiddenChars.contains($0) }
return String(String.UnicodeScalarView(passed))
}
}
let str = "£€₹jetztabfromnow12"
let t1 = str.removeCharacters(from: CharacterSet.alphanumerics)
print(t1) // will print: £€₹
let t2 = str.removeCharacters(from: CharacterSet.decimalDigits.inverted)
print(t2) // will print: 12
Updated 1:
var str = "£3,95SS"
str = str.replacingOccurrences(of: ",", with: "")
let digit = str.removeCharacters(from: CharacterSet.decimalDigits.inverted)
print(digit) // will print: 395
let currency = str.removeCharacters(from: CharacterSet.alphanumerics)
print(currency) // will print: £
let amount = currency + digit
print(amount) // will print: £3,95
Update 2:
let string = "£3,95SS"
let pattern = "-?\\d+(,\\d+)*?\\.?\\d+?"
do {
let regex = try NSRegularExpression(pattern: pattern, options: [])
if let match = regex.firstMatch(in: string, range: NSRange(location: 0, length: string.utf16.count)) {
let range = match.range
let start = string.index(string.startIndex, offsetBy: range.location)
let end = string.index(start, offsetBy: range.length)
let digit = string.substring(with: start..<end)
print(digit) //3,95
let symbol = string.removeCharacters(from: CharacterSet.symbols.inverted)
print(symbol) // £
print(symbol + digit) //£3,95
} else {
print("Not found")
}
} catch {
print("Regex Error:", error)
}
I am currently filtering using
self.searchArray.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
let array = (Array(exampleArray) as NSArray).filteredArrayUsingPredicate(searchPredicate)
self.searchArray = array as! [String]
It is filtering the whole string, However, I only want to filter using all characters that exist before my delimiter. For example:
each array value has a delimiter which is "$%^"
example array contains [abc$%^12], [efg$%^32], [tyh$%^77]
I only want the filtering to include on all characters before $%^ which would be abc, efg, and tyh
You can do this without using NSPredicate. Stub:
let searchBarText = "h"
let exampleArray = ["abc$%^12", "efg$%^32", "tyh$%^77"]
let searchResults = exampleArray.filter {
let components = $0.componentsSeparatedByString("$%^")
return components[0].containsString(searchBarText)
}
'pure' Swift solution :-) (I don't like any help of Foundation)
let arr = ["abc$%^12", "efg$%^32", "tyh$%^77", "nodelemiter"]
let delimiter = "$%^"
func splitByDelimeter(let str: String, delimiter: String)->(String,String) {
var str1 = ""
var str2 = str
while !str2.hasPrefix(delimiter) && str2.characters.count > 0 {
str1.append(str2.removeAtIndex(str2.startIndex))
}
if (str1 == str) { return (str1, "") }
let r = Range(start: str2.startIndex, end: str2.startIndex.advancedBy(delimiter.characters.count))
str2.removeRange(r)
return (str1, str2)
}
let res = arr.map { splitByDelimeter($0, delimiter: delimiter).0 }
print(res) // ["abc", "efg", "tyh", "nodelemiter"]
let res2 = arr.map { splitByDelimeter($0, delimiter: delimiter) }
print(res2) // [("abc", "12"), ("efg", "32"), ("tyh", "77"), ("nodelemiter", "")]
it should work, even there is only delimiter, or more than one delimiter in your string. First occurrence of delimiter will split the string to two parts.
UPDATE
with help of String extension you can do it as ...
let arr = ["abc$%^12", "efg$%^32", "tyh$%^77", "nodelemiter", "$%^jhdk$%^jdhjahsd", "22lemar$%^fralemdo"]
extension String {
func splitBy(delimiter: String)->(String,String) {
var str1 = ""
var str2 = self
while !str2.hasPrefix(delimiter) && str2.characters.count > 0 {
str1.append(str2.removeAtIndex(str2.startIndex))
}
if (str1 == self) { return (str1, "") }
let r = Range(start: str2.startIndex, end: str2.startIndex.advancedBy(delimiter.characters.count))
str2.removeRange(r)
return (str1, str2)
}
func contains(string: String)->Bool {
guard !self.isEmpty else {
return false
}
var s = self.characters.map{ $0 }
let c = string.characters.map{ $0 }
repeat {
if s.startsWith(c){
return true
} else {
s.removeFirst()
}
} while s.count > c.count - 1
return false
}
}
let filtered = arr.map{ $0.splitBy("$%^").0 }.filter { $0.contains("lem") }
print(filtered) // ["nodelemiter", "22lemar"]
or you can use hasPrefix to narrow search by typing more chars to you search box ... etc.
let arr2 = ["abc$%^12", "abefg$%^32", "tyhab$%^77"]
let filtered2 = arr2.map{ $0.splitBy("$%^").0 }.filter { $0.hasPrefix("ab") }
print(filtered2) // ["abc", "abefg"]