Value of type '[String]' has no member 'replacingOccurrences'? - swift

I have a problem
Value of type [String] has no member replacingOccurrences
When I call replacingOccurrences it does not seem to work for Array types.
func translateEquipement(equipment: [String]) -> [String]{
var result = equipment
let equipmentsSonoVideo = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"]
for equipmentSonoVideo in equipmentsSonoVideo {
result = result.replacingOccurrences(of: equipmentSonoVideo, with: NSLocalizedString(equipmentSonoVideo, comment: "") options: NSString.CompareOptions.literal, range: nil)
}
return result
}
My function below is for showing equipment:
func showEquipments(place: Place) {
var sonoShowed = false
var videoShowed = false
if let equipments = place.equipments {
for equipment in equipments {
// equipement
if equipment == "Sono" {
// equipement sono
self.sonoLabel.text = equipment
sonoShowed = true
}
else if equipment == "Video projecteur" {
// equipement video
self.videoLabel.text = equipment
videoShowed = true
}
}
}
if !sonoShowed {
self.sonoContainer.isHidden = true
}
if !videoShowed {
self.videoContainer.isHidden = true
}
}

As the error says you can not use that method on an array so you need to access each string in the array individually. I did this using map like this
func translateEquipement(equipment: [String]) -> [String]{
var result = equipment
let equipmentsSonoVideo = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"]
for item in equipmentsSonoVideo {
result = result.map {
$0.replacingOccurrences(of: item,
with: NSLocalizedString(item, comment: ""),
options: NSString.CompareOptions.literal, range: nil)
}
}
return result
}

If you like, you can create an extension for [String] so you can use replacingOccurrences as you are in your current code:
extension Array where Element == String {
func replacingOccurrences(of target: String, with replacement: String, options: NSString.CompareOptions) -> Self
{
return self.map( { $0.replacingOccurrences(of: target, with: replacement, options: options) } )
}
}
If you require the NSRange parameter that you're specifying nil for simply add it.

Related

Efficient sort array by ParentId swift

Given an array of dictionaries some of which have ParentId I need to sort them in ancestry order.
I have a working algorithm, but I am not sure it is actually efficient.
How can this be improved?
Sample data:
var dicts = [["ParentId": "eee82", "Id":"a0dq1"],
["ParentId": "pqrst", "Id":"eee82"],
["ParentId": "aaa1", "Id":"pqrst"]]
Sample output
["pqrst", "eee82", "a0dq1"]
I ran below in playground
import Foundation
// GIVEN this source...
var dicts = [["ParentId": "eee82", "Id":"a0dq1"],
["ParentId": "pqrst", "Id":"eee82"],
["ParentId": "aaa1", "Id":"pqrst"]]
func findParents(source: [[String:String]], this: [String:String]) -> [[String:String]] {
var results = [[String:String]]()
if let parentId = this["ParentId"],
let parent = source.first(where: { $0["Id"] == parentId }) {
results.append(parent)
results.append(contentsOf: findParents(source: source, this: parent))
}
return results
}
var this = dicts.first!
var accounts = (findParents(source: dicts, this: this) + [this])
var sorted = [[String:String]]()
var hasParentMap = [String: Bool]()
for account in accounts {
let parentId = account["ParentId"]
let hasParent = accounts.first(where: { $0["Id"] == parentId }) != nil
hasParentMap[account["Id"]!] = !(parentId == nil || !hasParent)
}
while sorted.count != accounts.count {
for account in accounts {
if sorted.first(where: { $0["Id"] == account["Id"] }) != nil {
continue
}
if hasParentMap[account["Id"]!] == false {
sorted.insert(account, at: 0)
continue
} else if let parentId = account["ParentId"] {
let parentIndex = sorted.firstIndex(where: { $0["Id"] == parentId })
if parentIndex == nil {
continue
}
sorted.insert(account, at: parentIndex! + 1)
}
}
}
dump (accounts.map({ $0["Id"]! })) // ["eee82", "pqrst", "a0dq1"]
// ...we want to get this output
dump (sorted.map({ $0["Id"]! })) // ["pqrst", "eee82", "a0dq1"]
Update removed the numerical ids to avoid confusion
Here's the visual illustration of what I am trying to achieve
To make things easier I created a Person type:
struct Person: Comparable, CustomStringConvertible {
let id: String
let parentID: String
var description: String {
return "[\(id), \(parentID)]"
}
static func < (lhs: Self, rhs: Self) -> Bool {
return lhs.id < rhs.id
}
init?(dict: [String: String]) {
guard let id = dict["Id"] else { return nil }
guard let parentID = dict["ParentId"] else { return nil }
self.id = id
self.parentID = parentID
}
func toDictionary() -> [String: String] {
return ["Id": id, "ParentId": parentID]
}
}
Here is our data:
var dicts = [
["ParentId": "2", "Id":"3"],
["ParentId": "1", "Id":"2"],
["ParentId": "42", "Id":"1"],
["ParentId": "100", "Id":"88"],
["ParentId": "88", "Id":"77"],
["ParentId": "77", "Id":"66"],
["ParentId": "77", "Id":"55"],
]
Here are our people converted to structs:
var people = dicts.compactMap { Person(dict: $0) }
Here are a few methods to operate on our array of people:
extension Array where Element == Person {
func tree(root: Person) -> [Person] {
[root] + children(of: root)
.flatMap { tree(root: $0) }
}
func topLevelParents() -> [Person] {
return filter { parent(of: $0) == nil }
}
func children(of parent: Person) -> [Person] {
return filter { $0.parentID == parent.id }.sorted()
}
func parent(of child: Person) -> Person? {
return first { child.parentID == $0.id }
}
}
Get all people who don't have parents:
let topLevelParents = people.topLevelParents().sorted()
print("topLevelParents: \(topLevelParents)")
Construct the tree of descendants for each parent and flatten into an array:
let results = topLevelParents.flatMap({ people.tree(root: $0) })
print("results: \(results)")
Convert back to a dictionary:
let dictionaryResults = results.map { $0.toDictionary() }
print("dictionaryResults: \(dictionaryResults)")

How to replace limited number of occurrences in string

Let's say that I have the string "blabla[R]bla[R]blaaa[R]blabla[R]bla[R]bla".
The regular replacingOccurrences replaces all occurrences. I want to replace only 3.
newString = myString.replacingOccurrences(of: "[R]", with: "(X)")
to make the result "blabla(X)bla(X)blaaa(X)blabla[R]bla[R]bla".
You can get the first 3 ranges occurrences of that string and then you can iterate the ranges in reverse order replacing the subranges:
var string = "blabla[R]bla[R]blaaa[R]blabla[R]bla[R]bla"
var ranges: [Range<String.Index>] = []
var start = string.startIndex
while start < string.endIndex,
let range = string.range(of: "[R]", range: start..<string.endIndex) {
ranges.append(range)
start = range.upperBound
if ranges.count == 3 { break }
}
for range in ranges.reversed() {
string.replaceSubrange(range, with: "(X)")
}
print(string) // blabla(X)bla(X)blaaa(X)blabla[R]bla[R]bla
Here's a useful extension to String that add a count parameter to replacingOccurrences. This includes support for ranges and options (such as backwards).
extension String {
func replacingOccurrences<Target, Replacement>(of target: Target, with replacement: Replacement, count: Int, options: String.CompareOptions = [], range searchRange: Range<String.Index>? = nil) -> String where Target : StringProtocol, Replacement : StringProtocol {
var matches = [Range<String.Index>]()
var sRange = searchRange ?? Range(startIndex..<endIndex)
while matches.count < count && !sRange.isEmpty {
if let mRange = range(of: target, options: options, range: sRange, locale: nil) {
matches.append(mRange)
if options.contains(.backwards) {
sRange = Range(sRange.lowerBound..<mRange.lowerBound)
} else {
sRange = Range(mRange.upperBound..<sRange.upperBound)
}
} else {
break
}
}
var res = self
for range in matches.sorted(by: { $0.lowerBound > $1.lowerBound }) {
res.replaceSubrange(range, with: replacement)
}
return res
}
}
let test = "blabla[R]bla[R]blaaa[R]blabla[R]bla[R]bla"
let res1 = test.replacingOccurrences(of: "[R]", with: "(x)", count: 3)
print(res1)
let res2 = test.replacingOccurrences(of: "[R]", with: "(x)", count: 3, options: [ .backwards ])
print(res2)
Output:
blabla(x)bla(x)blaaa(x)blabla[R]bla[R]bla
blabla[R]bla[R]blaaa(x)blabla(x)bla(x)bla

Swift: assign to variable in switch case-let-where

Is it possible to make an assignment to the artist variable before it is used in the where subclause?
var artist
switch fullline {
case let path where path.hasPrefix("Monet"):
artist = "Monet"
case let path where path.hasPrefix("Cezanne"):
artist = "Cezanne"
default: ()
}
Closure:
case let path where { () -> Bool in let artist = "Monet"; return path.hasPrefix(artist) }:
Error:
() -> Bool' is not convertible to 'Bool'
Context:
I have lines of freeform text with artist name as the prefix that requires
massaging to output consistent humanly readable text. e.g.
Monet : Snow at Argenteuil 02, 1874
Monet - Snow at Argenteuil, 1874, 3rd Floor Collections
Monet, Claude - 1875, Snow in Argenteuil
Cezzane - Vase of Flowers, 1880-81, print
Cezzane, Paul 1900-1903 Vase of Flowers
Cezzane - Vase with Flowers, 1895-1896
There will be a code fragments that performs detailed processing/categorizing
for each artist. Hence the processing logic is artist dependent.
I would like to define similar to the following construct
switch fullline
hasPrefix(artist = "Monet")
-> code logic 1
get_birthday(artist)
hasPrefix(artist = "Cezzane")
-> code logic 2
get_birthday(artist)
With a little modification to the Alexander's struct, you can write something like this:
struct PrefixMatcherWithHandler {
var handler: (String)->Void
var string: String
init(_ string: String, handler: #escaping (String)->Void) {
self.string = string
self.handler = handler
}
static func ~= (prefix: String, matcher: PrefixMatcherWithHandler) -> Bool {
if matcher.string.hasPrefix(prefix) {
matcher.handler(prefix)
return true
} else {
return false
}
}
}
var fullline: String = "Monet, Claude"
var artist: String? = nil
let matcher = PrefixMatcherWithHandler(fullline) {str in
artist = str
}
switch matcher {
case "Monet":
break
case "Cezanne":
break
default: break
}
print(artist ?? "") //->Monet
But having some side-effect in boolean operators like ~= makes your code less readable and can easily make unexpected result.
If you just want to reduce some redundant reference to a same thing, switch-statement may not be a good tool for it.
For example, you can get the same result without defining specific matcher types:
var fullline: String = "Monet, Claude"
var artist: String? = nil
if let match = ["Monet", "Cezanne"].first(where: {fullline.hasPrefix($0)}) {
artist = match
}
print(artist ?? "") //->Monet
ADDED for updated parts of the question
The following code behaves slightly different than prefix-matching, but I believe you do not want to match "Mon" to the line Monet, Claude - 1875, Snow in Argenteuil.
extension String {
var firstWord: String? {
var result: String? = nil
enumerateSubstrings(in: startIndex..<endIndex, options: .byWords) {str, _, _, stop in
result = str
stop = true
}
return result
}
}
func get_birthday(_ artist: String) {
//What do you want to do?
print(artist)
}
var fullline: String = "Monet, Claude - 1875, Snow in Argenteuil"
switch fullline.firstWord {
case let artist? where artist == "Monet":
//code dedicated for "Monet"
get_birthday(artist)
case let artist? where artist == "Cezanne":
//code dedicated for "Cezanne"
get_birthday(artist)
default:
break
}
When you can retrieve data suitable for switch-statement, the code would be far more intuitive and readable.
You're giving that closure where a boolean is expected. Not sure why you would want to do this, but you could make it work by using () to invoke the closure.
var artist
switch fullline {
case let path where { () -> Bool in let artist = "Monet"; return path.hasPrefix(artist) }():
artist = "Monet"
case let path where path.hasPrefix("Cezanne"):
artist = "Cezanne"
default: ()
}
Here is how I would do this:
import Foundation
struct PrefixMatcher {
let string: String
init(_ string: String) { self.string = string }
static func ~= (prefix: String, matcher: PrefixMatcher) -> Bool {
return matcher.string.hasPrefix(prefix)
}
}
extension String {
var prefix: PrefixMatcher { return PrefixMatcher(self) }
}
let fullline = "Monet 123456789"
let artist: String?
switch fullline.prefix {
case "Monet": artist = "Monet"
case "Cezanne": artist = "Cezanne"
default: artist = nil
}
print(artist as Any)
More general solution:
import Foundation
struct PredicateMatcher<Pattern> {
typealias Predicate = (Pattern) -> Bool
let predicate: Predicate
static func ~=(pattern: Pattern,
matcher: PredicateMatcher<Pattern>) -> Bool {
return matcher.predicate(pattern)
}
}
extension String {
var prefix: PredicateMatcher<String> {
return PredicateMatcher(predicate: self.hasPrefix)
}
}
You can achieve this by switching over a tuple of your enum and your optional.
Optional is an enum too, so you can switch both of them
enum SomeSnum {
case a, b, c
}
let someString: String? = "something"
let esomeEnum = SomeSnum.b
switch(esomeEnum, someString) {
case (.b, .some(let unwrappedSomething)) where unwrappedSomething.hasPrefix("so"):
print("case .b, \(unwrappedSomething) is unwrapped, and it has `so` prefix")
case (.a, .none):
print("case .a, and optional is nil")
default:
print("Something else")
}
You can also do an if statement
if case let (.b, .some(unwrappedSomething)) = (esomeEnum, someString), unwrappedSomething.hasPrefix("so") {
} else if case (.a, .none) = (esomeEnum, someString) {
} else {
}

How to remove duplicate characters from a string in Swift

ruby has the function string.squeeze, but I can't seem to find a swift equivalent.
For example I want to turn bookkeeper -> bokepr
Is my only option to create a set of the characters and then pull the characters from the set back to a string?
Is there a better way to do this?
Edit/update: Swift 4.2 or later
You can use a set to filter your duplicated characters:
let str = "bookkeeper"
var set = Set<Character>()
let squeezed = str.filter{ set.insert($0).inserted }
print(squeezed) // "bokepr"
Or as an extension on RangeReplaceableCollection which will also extend String and Substrings as well:
extension RangeReplaceableCollection where Element: Hashable {
var squeezed: Self {
var set = Set<Element>()
return filter{ set.insert($0).inserted }
}
}
let str = "bookkeeper"
print(str.squeezed) // "bokepr"
print(str[...].squeezed) // "bokepr"
I would use this piece of code from another answer of mine, which removes all duplicates of a sequence (keeping only the first occurrence of each), while maintaining order.
extension Sequence where Iterator.Element: Hashable {
func unique() -> [Iterator.Element] {
var alreadyAdded = Set<Iterator.Element>()
return self.filter { alreadyAdded.insert($0).inserted }
}
}
I would then wrap it with some logic which turns a String into a sequence (by getting its characters), unqiue's it, and then restores that result back into a string:
extension String {
func uniqueCharacters() -> String {
return String(self.characters.unique())
}
}
print("bookkeeper".uniqueCharacters()) // => "bokepr"
Here is a solution I found online, however I don't think it is optimal.
func removeDuplicateLetters(_ s: String) -> String {
if s.characters.count == 0 {
return ""
}
let aNum = Int("a".unicodeScalars.filter{$0.isASCII}.map{$0.value}.first!)
let characters = Array(s.lowercased().characters)
var counts = [Int](repeatElement(0, count: 26))
var visited = [Bool](repeatElement(false, count: 26))
var stack = [Character]()
var i = 0
for character in characters {
if let num = asciiValueOfCharacter(character) {
counts[num - aNum] += 1
}
}
for character in characters {
if let num = asciiValueOfCharacter(character) {
i = num - aNum
counts[i] -= 1
if visited[i] {
continue
}
while !stack.isEmpty, let peekNum = asciiValueOfCharacter(stack.last!), num < peekNum && counts[peekNum - aNum] != 0 {
visited[peekNum - aNum] = false
stack.removeLast()
}
stack.append(character)
visited[i] = true
}
}
return String(stack)
}
func asciiValueOfCharacter(_ character: Character) -> Int? {
let value = String(character).unicodeScalars.filter{$0.isASCII}.first?.value ?? 0
return Int(value)
}
Here is one way to do this using reduce(),
let newChar = str.characters.reduce("") { partial, char in
guard let _ = partial.range(of: String(char)) else {
return partial.appending(String(char))
}
return partial
}
As suggested by Leo, here is a bit shorter version of the same approach,
let newChar = str.characters.reduce("") { $0.range(of: String($1)) == nil ? $0.appending(String($1)) : $0 }
Just Another solution
let str = "Bookeeper"
let newChar = str.reduce("" , {
if $0.contains($1) {
return "\($0)"
} else {
return "\($0)\($1)"
}
})
print(str.replacingOccurrences(of: " ", with: ""))
Use filter and contains to remove duplicate values
let str = "bookkeeper"
let result = str.filter{!result.contains($0)}
print(result) //bokepr

Finding the first non-repeating character in a String using Swift

This finds the duplicates in the array, but i'm looking for something that finds the first non-repeating character in a string. I've been trying to figure out a way to do this and I cannot figure it out. This is the closest i've gotten.
var strArray = ["P","Q","R","S","T","P","R","A","T","B","C","P","P","P","P","P","C","P","P","J"]
println(strArray)
var filter = Dictionary<String,Int>()
var len = strArray.count
for var index = 0; index < len ;++index {
var value = strArray[index]
if (filter[value] != nil) {
strArray.removeAtIndex(index--)
len--
}else{
filter[value] = 1
}
}
println(strArray)
In order to tell if a character repeats itself, go through the entire array once, incrementing the count of occurrences in a dictionary:
let characters = ["P","Q","R","S","T","P","R","A","T","B","C","P","P","P","P","P","C","P","P","J"]
var counts: [String: Int] = [:]
for character in characters {
counts[character] = (counts[character] ?? 0) + 1
}
let nonRepeatingCharacters = characters.filter({counts[$0] == 1})
// ["Q", "S", "A", "B", "J"]
let firstNonRepeatingCharacter = nonRepeatingCharacters.first!
// "Q"
Here is a simple solution
let inputString = "PQRSTPRATBCPPPPPCPPJ"
func nonRepeat (_ input: String) -> String {
for char in input {
if input.firstIndex(of: char) == input.lastIndex(of: char) {
return String(char)
}
}
return ""
}
print (nonRepeat(inputString))
In the above example it would print "Q"
func firstNonRepeatedCharacter(input: String) -> Character?{
var characterCount : [Character : Int] = [:]
var uniqueCharacter: Character?
for character in input{
if let count = characterCount[character]{
characterCount[character] = count + 1
if(uniqueCharacter == character)
{
uniqueCharacter = nil
}
}
else{
characterCount[character] = 1
if(uniqueCharacter == nil){
uniqueCharacter = character
}
}
}
return uniqueCharacter
}
Without extra loop to find character from characterCount dictionary
Here is the way I have found to detect the first non-repeated character. It removes spaces and punctuation to find the actual letter or number that does not repeat.
extension String {
func removeNonAlphaNumChars() -> String {
let charSet = NSCharacterSet.alphanumericCharacterSet().invertedSet
return self
.componentsSeparatedByCharactersInSet(charSet)
.joinWithSeparator("")
}
var firstNonRepeatedCharacter: Character? {
let alphaNumString = self.removeNonAlphaNumChars()
let characters = alphaNumString.characters
let count = characters.count
guard count > 0 else { return nil }
// Find unique chars
var dict: [Character: Int?] = [:]
for (index, char) in characters.enumerate() {
if dict[char] != nil {
dict[char] = (nil as Int?)
}
else {
dict[char] = index
}
}
return dict.filter { $0.1 != nil }.sort { $0.1 < $1.1 }.first?.0
}
}
I totally wonder why the accepted answer was considered correct. They are using
.first
method of a dictionary and that according to documentation would return a random element in the dictionary and not the first element as a dictionary in swift is not ordered like an array.
please do find below an implementation that works
func firstNonRepeatingLetter(_ str: String) -> String{
var characterDict = [String : Int]()
for character in str{
let lower = character.lowercased()
if let count = characterDict[lower]{
characterDict[lower] = count + 1
}else{
characterDict[lower] = 1
}
}
let filtered = characterDict.filter { $0.value == 1}
for character in str{
let lower = character.lowercased()
if let _ = filtered[lower]{
return lower
}
}
return ""
}
firstNonRepeatingLetter("moonmen") would return "e".
We can iterate once and keep the letter counts inside a dictionary.
Then, iterate again and return first letter where we see it was encountered once only (or "_" if not found a non-repeating letter):
func firstNotRepeatingCharacter(s: String) -> Character {
var letterCounts: [String: Int] = [:]
var result: Character = "_"
for letter in s {
if let currentLetterCount = letterCounts[String(letter)] {
letterCounts[String(letter)] = currentLetterCount + 1
} else {
letterCounts[String(letter)] = 1
}
}
for letter in s {
if letterCounts[String(letter)] == 1 {
result = letter
break
}
}
return result
}
OrderedDictionary makes this easy for all Sequences of Hashables, not just Strings:
import struct OrderedCollections.OrderedDictionary
extension Sequence where Element: Hashable {
var firstUniqueElement: Element? {
OrderedDictionary(zip(self, true)) { _, _ in false }
.first(where: \.value)?
.key
}
}
/// `zip` a sequence with a single value, instead of another sequence.
public func zip<Sequence: Swift.Sequence, Constant>(
_ sequence: Sequence, _ constant: Constant
) -> LazyMapSequence<
LazySequence<Sequence>.Elements,
(LazySequence<Sequence>.Element, Constant)
> {
sequence.lazy.map { ($0, constant) }
}
func getFirstUniqueChar(string:String)->Character?{
var counts: [String: Int] = [:]
for character in string {
let charString = "\(character)"
counts[charString] = (counts[charString] ?? 0) + 1
}
let firstNonRepeatingCharacter = string.first {counts["\($0)"] == 1}
return firstNonRepeatingCharacter
}
print(getFirstUniqueChar(string: string))
import Foundation
import Glibc
var str:String = "aacbbcee"//your input string
var temp:String = ""
var dict:[Character:Int] = [:]
for char in str{
if let count = dict[char]{
dict[char] = count+1//storing values in dict and incrmenting counts o key
}
else{
dict[char] = 0
}
}
var arr:[Character] = []
for (key, value) in dict{
if value == 0{
arr.append(key)//filtering out, take characters which has value>0
} //int(arr)
}//print(arr.count)
if arr.count != 0{
outer:for char in str{//outer is labeling the loop
for i in arr{
if i == char{
print(i,"is first")//matching char with array elements if found break
break outer
}
else{
continue
}
}
}
}
else{
print("not found")
}
func firstNonRepeatedChar(string: String) -> Character {
var arr: [Character] = []
var dict: [Character : Int] = [:]
for character in string.description {
arr.append(character)
}
for character in arr {
dict[character] = (dict[character] ?? 0) + 1
}
let nonRepeatedArray = arr.filter { char in
if dict[char] == 1 {return true}
return false
}
let firstNonRepeatedChar = nonRepeatedArray.first
return firstNonRepeatedChar!
}
print(firstNonRepeatedChar(string: "strinstrig"))