3

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?

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Jonathan
  • 53
  • 1
  • 1
  • 6

6 Answers6

14

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"
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
1

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"
Community
  • 1
  • 1
Alexander
  • 59,041
  • 12
  • 98
  • 151
  • How is this efficient as related to my answer below ? I would guess that it is as expensive as using reduce ? – Sandeep May 10 '17 at 22:26
  • @Sandeep This is is a linear time algorithm. Each `contains` call is called on a set, which is `O(1)`, unlike `String.range(of:)`, which is `O(n)`. – Alexander May 10 '17 at 23:15
0

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)
}
Jonathan
  • 53
  • 1
  • 1
  • 6
0

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 }
Sandeep
  • 20,908
  • 7
  • 66
  • 106
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: ""))
  • 1
    Code dumps do not make for good answers. You should explain *how* and *why* this solves their problem. I recommend reading, "[How do I write a good answer?"](//stackoverflow.com/help/how-to-answer). This can help future users learn and eventually apply that knowledge to their own code. You are also likely to have positive feedback/upvotes from users, when the code is explained. – John Conde Feb 28 '21 at 13:29
-1

Use filter and contains to remove duplicate values

let str = "bookkeeper"
let result = str.filter{!result.contains($0)}
print(result) //bokepr
ajay_nasa
  • 2,278
  • 2
  • 28
  • 45