Swift fast low level String lastIndexOf - string

I need an implementation of lastIndexOf that is as fast as possible.
I am finding that the String advance function is extremely slow.
I tried using the c function strrchr, and tried copying the string to NSData and using pointers but I can't get the syntax right.
My string will always have 1 byte characters and the string i'm searching for "|" is always 1 byte also.
Any implementation using advance will be too slow but here is the fastest example I could find:
func indexOf(target: String, startIndex: Int) -> Int
{
var startRange = advance(self.startIndex, startIndex)
var range = self.rangeOfString(target, options: NSStringCompareOptions.LiteralSearch, range: Range<String.Index>(start: startRange, end: self.endIndex))
if let range = range {
return distance(self.startIndex, range.startIndex)
} else {
return -1
}
}
func lastIndexOf(target: String) -> Int
{
var index = -1
var stepIndex = self.indexOf(target)
while stepIndex > -1
{
index = stepIndex
if stepIndex + target.length < self.length
{
stepIndex = indexOf(target, startIndex: stepIndex + target.length)
}
else
{
stepIndex = -1
}
}
return index
}
This is an example of the string I need to parse.
var str:String = "4|0|66|5|0|3259744|6352141|1|3259744|WSMxt208L54yZ5irtHC3|Mc02|efland,nc|36.027992|-79.2212834|0|4|6|0|3259744|6352141|46|14|1|0|7|7|3259744|6352141|4|1|0|8|8|3259744|6352141|4|0|22|9|0|3259744|6352141|2|3|Room1|2|72|86330534|1|0|10|9|3259744|6352141|4|1|0|11|10|3259744|6352141|4|1|0|12|11|3259744|6352141|4|1|0|13|12|3259744|6352141|4|0|4|14|0|3259744|6352141|46|24|0|5|15|0|3259744|6352141|46|654|0|66|0|0|3259744|6352141|1|3259744|WSMxt208L54yZ5irtHC3|Mc02|efland,nc|36.027992|-79.2212834|0|4|16|0|3259744|6352141|46|4sageReceived:4|0|66|5|0|3259744|6352141|1|3259744|WSMxt208L54yZ5irtHC3|Mc02|efland,nc|36.027992|-79.2212834|0|4|6|0|3259744|6352141|46|14|1|0|7|7|3259744|6352141|4|1|0|8|8|3259744|6352141|4|0|22|9|0|3259744|6352141|2|3|Room1|2|72|86330534|1|0|10|9|3259744|6352141|4|1|0|11|10|3259744|6352141|4|1|0|12|11|3259744|6352141|4|1|0|13|12|3259744|6352141|4|0|4|14|0|3259744|6352141|46|24|0|5|15|0|3259744|6352141|46|654|0|66|0|0|3259744|6352141|1|3259744|WSMxt208L54yZ5irtHC3|Mc02|efland,nc|36.027992|-79.2212834|0|4|16|0|3259744|6352141|46|4352141|1|3259744|WSMxt208L54yZ5irtHC3|Mc02|efland,nc|36.027992|-79.2212834|0|4|6|0|3259744|6352141|46|14|1|0|7|7|3259744|6352141|4|1|0|8|8|3259744|6352141|4|0|22|9|0|3259744|6352141|2|3|Room1|2|72|86330534|1|0|10|9|3259744|6352141|4|1|0|11|10|3259744|6352141|4|1|0|12|11|3259744|6352141|4|1|0|13|12|3259744|6352141|4|0|4|14|0|3259744|6352141|46|24|0|5|15|0|3259744|6352141|46|654|0|66|0|0|3259744|6352141|1|3259744|WSMxt208L54yZ5irtHC3|Mc02|efland,nc|36.027992|-79.2212834|0|4|16|0|3259744|6352141|46|4TCPListener.onReceived: 4|0|66|5|0|3259744|6352141|1|3259744|WSMxt208L54yZ5irtHC3|Mc02|efland,nc|36.027992|-79.2212834|0|4|6|0|3259744|6352141|46|14|1|0|7|7|3259744|6352141|4|1|0|8|8|3259744|6352141|4|0|22|9|0|3259744|6352141|2|3|Room1|2|72|86330534|1|0|10|9|3259744|6352141|4|1|0|11|10|3259744|6352141|4|1|0|12|11|3259744|6352141|4|1|0|13|12|3259744|6352141|4|0|4|14|0|3259744|6352141|46|24|0|5|15|0|3259744|6352141|46|654|0|66|0|0|3259744|6352141|1|3259744|WSMxt208L54yZ5irtHC3|Mc02|efland,nc|36.027992|-79.2212834|0|4|16|0|3259744|6352141|46|4preParse
4|0|66|5|0|3259744|6352141|1|3259744|WSMxt208L54yZ5irtHC3|Mc02|efland,nc|36.027992|-79.221283"

Here is a Swift 2.0 Answer
func lastIndexOf(s: String) -> Int? {
if let r: Range<Index> = self.rangeOfString(s, options: .BackwardsSearch) {
return self.startIndex.distanceTo(r.startIndex)
}
return Optional<Int>()
}
Tests
func testStringLastIndexOf() {
let lastIndex = "0|2|45|7|9".lastIndexOf("|")
XCTAssertEqual(lastIndex, 8)
}
func testStringLastIndexOfNotFound() {
let lastIndex = "0123456789".lastIndexOf("|")
XCTAssertEqual(lastIndex, nil);
}

You can use strrchr in Swift
import Darwin
let str = "4|0|66|5|0|3259744|6352141|1|3259744"
func stringLastIndexOf(src:String, target:UnicodeScalar) -> Int? {
let c = Int32(bitPattern: target.value)
return src.withCString { s -> Int? in
let pos = strrchr(s, c)
return pos != nil ? pos - s : nil
}
}
stringLastIndexOf(str, "|") // -> {Some 28}
stringLastIndexOf(str, ",") // -> nil

You can use Objective C files in a Swift project; in these you can use plain C code and make a function which uses strrchr. Then you can call this from Swift.

If you do this in order to get all substring delimited by "|", you might test this approach:
import Foundation
let s = "4|0|66|5|0|3259744|6352141|1|3259744|WSMxt208L54yZ5irtHC3|..."
let a = s.componentsSeparatedByString("|")
The built in functions are sometimes very fast and you may be getting the required performance even by using String.
If you really need to get only the position of the last "|", you could work with utf16 representation, where advancing over the characters should be faster.
I think this should work:
let utf16String = s.utf16
var i = s.utf16Count - 1
while i >= 0 {
if utf16String[i] == 124 {
break
}
i--
}
println(i)

If the characters are guaranteed as single byte, the data is huge and performance is critical then it may be worth converting to an array of bytes (UInt8) and perform the operations directly on them. You can then convert the part that you need back to a String.
Also note that Optimised builds may be much faster than Debug builds so you should do any performance testing with the optimiser on. It may also be worth checking that the optimised versions are too slow at the moment.

Related

Optimal algorithm for this string decompression

I have been working on an exercise from google's dev tech guide. It is called Compression and Decompression you can check the following link to get the description of the problem Challenge Description.
Here is my code for the solution:
public static String decompressV2 (String string, int start, int times) {
String result = "";
for (int i = 0; i < times; i++) {
inner:
{
for (int j = start; j < string.length(); j++) {
if (isNumeric(string.substring(j, j + 1))) {
String num = string.substring(j, j + 1);
int times2 = Integer.parseInt(num);
String temp = decompressV2(string, j + 2, times2);
result = result + temp;
int next_j = find_next(string, j + 2);
j = next_j;
continue;
}
if (string.substring(j, j + 1).equals("]")) { // Si es un bracket cerrado
break inner;
}
result = result + string.substring(j,j+1);
}
}
}
return result;
}
public static int find_next(String string, int start) {
int count = 0;
for (int i = start; i < string.length(); i++) {
if (string.substring(i, i+1).equals("[")) {
count= count + 1;
}
if (string.substring(i, i +1).equals("]") && count> 0) {
count = count- 1;
continue;
}
if (string.substring(i, i +1).equals("]") && count== 0) {
return i;
}
}
return -111111;
}
I will explain a little bit about the inner workings of my approach. It is a basic solution involves use of simple recursion and loops.
So, let's start from the beggining with a simple decompression:
DevTech.decompressV2("2[3[a]b]", 0, 1);
As you can see, the 0 indicates that it has to iterate over the string at index 0, and the 1 indicates that the string has to be evaluated only once: 1[ 2[3[a]b] ]
The core here is that everytime you encounter a number you call the algorithm again(recursively) and continue where the string insides its brackets ends, that's the find_next function for.
When it finds a close brackets, the inner loop breaks, that's the way I choose to make the stop sign.
I think that would be the main idea behind the algorithm, if you read the code closely you'll get the full picture.
So here are some of my concerns about the way I've written the solution:
I could not find a more clean solution to tell the algorithm were to go next if it finds a number. So I kind of hardcoded it with the find_next function. Is there a way to do this more clean inside the decompress func ?
About performance, It wastes a lot of time by doing the same thing again, when you have a number bigger than 1 at the begging of a bracket.
I am relatively to programming so maybe this code also needs an improvement not in the idea, but in the ways It's written. So would be very grateful to get some suggestions.
This is the approach I figure out but I am sure there are a couple more, I could not think of anyone but It would be great if you could tell your ideas.
In the description it tells you some things that you should be awared of when developing the solutions. They are: handling non-repeated strings, handling repetitions inside, not doing the same job twice, not copying too much. Are these covered by my approach ?
And the last point It's about tets cases, I know that confidence is very important when developing solutions, and the best way to give confidence to an algorithm is test cases. I tried a few and they all worked as expected. But what techniques do you recommend for developing test cases. Are there any softwares?
So that would be all guys, I am new to the community so I am open to suggestions about the how to improve the quality of the question. Cheers!
Your solution involves a lot of string copying that really slows it down. Instead of returning strings that you concatenate, you should pass a StringBuilder into every call and append substrings onto that.
That means you can use your return value to indicate the position to continue scanning from.
You're also parsing repeated parts of the source string more than once.
My solution looks like this:
public static String decompress(String src)
{
StringBuilder dest = new StringBuilder();
_decomp2(dest, src, 0);
return dest.toString();
}
private static int _decomp2(StringBuilder dest, String src, int pos)
{
int num=0;
while(pos < src.length()) {
char c = src.charAt(pos++);
if (c == ']') {
break;
}
if (c>='0' && c<='9') {
num = num*10 + (c-'0');
} else if (c=='[') {
int startlen = dest.length();
pos = _decomp2(dest, src, pos);
if (num<1) {
// 0 repetitions -- delete it
dest.setLength(startlen);
} else {
// copy output num-1 times
int copyEnd = startlen + (num-1) * (dest.length()-startlen);
for (int i=startlen; i<copyEnd; ++i) {
dest.append(dest.charAt(i));
}
}
num=0;
} else {
// regular char
dest.append(c);
num=0;
}
}
return pos;
}
I would try to return a tuple that also contains the next index where decompression should continue from. Then we can have a recursion that concatenates the current part with the rest of the block in the current recursion depth.
Here's JavaScript code. It takes some thought to encapsulate the order of operations that reflects the rules.
function f(s, i=0){
if (i == s.length)
return ['', i];
// We might start with a multiplier
let m = '';
while (!isNaN(s[i]))
m = m + s[i++];
// If we have a multiplier, we'll
// also have a nested expression
if (s[i] == '['){
let result = '';
const [word, nextIdx] = f(s, i + 1);
for (let j=0; j<Number(m); j++)
result = result + word;
const [rest, end] = f(s, nextIdx);
return [result + rest, end]
}
// Otherwise, we may have a word,
let word = '';
while (isNaN(s[i]) && s[i] != ']' && i < s.length)
word = word + s[i++];
// followed by either the end of an expression
// or another multiplier
const [rest, end] = s[i] == ']' ? ['', i + 1] : f(s, i);
return [word + rest, end];
}
var strs = [
'2[3[a]b]',
'10[a]',
'3[abc]4[ab]c',
'2[2[a]g2[r]]'
];
for (const s of strs){
console.log(s);
console.log(JSON.stringify(f(s)));
console.log('');
}

How to write String Iteration with Map Put operations in Java 8?

I am trying to convert following code into Java 8::
String s = "12345";
Map<Character,Integer> map = new HashMap<Character,Integer>();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (map.containsKey(c)) {
int cnt = map.get(c);
map.put(c, ++cnt);
} else {
map.put(c, 1);
}
}
I tried and found following way to iterate:
IntStream.rangeClosed(0, s.length).foreach(d -> {
//all statements from char to map.put
}) ;
I am not sure whether this is correct way to do it.
You can do this:
s.chars()
.mapToObj(x -> (char) x)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
.mapToObj(x -> (char) x) is necessary because .chars() gives you a stream of ints, but in order to use groupingBy you need to work with objects, not primitives
groupingBy receives a function to get the key to group by and a Collector implementation
Function.identity() is just a function that returns whatever element it is passed to it
Collectors.counting() is a collector that counts
You can use the groupingBy() and counting() collectors:
String s = "12345";
Map<Character, Long> map = s
.chars()
.mapToObj(i -> (char) i)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
System.out.println(map);
Unfortunately, there seems to be no convenient way to get a Stream<Character> from a string, hence the need to map to an IntStream using chars() and then using mapToObj() to convert it to a character stream.

Reverse every other word in string, keep punctuation Swift

So I got stuck on a coding challenge that I almost knew the answer too. And I think I have to use the subString call in Swift 4 to get it 100%. I want to reverse every OTHER word in a string, but ignore or keep the punctuation in its original place( index ).
var sample = "lets start. And not worry about proper sentences."
func reverseString(inputString: String) -> String {
let oldSentence = sample.components(separatedBy: " ")
var newSentence = ""
for index in 0...oldSentence.count - 1 {
let word = oldSentence[index]
if newSentence != "" {
newSentence += " "
}
if index % 2 == 1 {
let reverseWord = String(word.reversed())
newSentence += reverseWord
} else {
newSentence += word
}
}
return newSentence
}
reverseString(inputString: sample)
And this would be the expected output.
"lets trats. And ton worry tuoba proper secnetnes."
Notice the punctuation is not reversed.
You shouldn't use components(separatedBy: ) to split a string in words. See this article for the reason. Use enumerateSubstrings and pass in the appropriate option:
func reverseString(inputString: String) -> String {
var index = 1
var newSentence = inputString
inputString.enumerateSubstrings(in: inputString.startIndex..., options: .byWords) { substr, range, _, stop in
guard let substr = substr else { return }
if index % 2 == 0 {
newSentence = newSentence.replacingCharacters(in: range, with: String(substr.reversed()))
}
index += 1
}
return newSentence
}
print(reverseString(inputString: "lets start. And not worry about proper sentences."))
// lets trats. And ton worry tuoba proper secnetnes.
print(reverseString(inputString: "I think, therefore I'm"))
// I kniht, therefore m'I

How to make a function that compares strings?

I want to make a function which compares strings.
I don't want to use equal operators (==), I want it worked only with Swift language.
First I made a function which takes 2 strings, and returns bool type.
then I looped these strings with for in syntax.
And want to compare these characters, if strings have equal value, it should return true, if not, then false. Is there any better way?
func isEqual(str1:String, str2:String) -> Bool {
var result = false
for char in str1 {
}
for char2 in str2 {
}
//Compare characters.
return result
}
== works fine with Strings in Swift. For educational purposes
(as I conclude from your comment "because I'm practicing...")
you can implement it as:
func myStringCompare(str1 : String, str2 : String) -> Bool {
if count(str1) != count(str2) {
return false
}
for (c1, c2) in zip(str1, str2) {
if c1 != c2 {
return false
}
}
return true
}
zip(str1, str2) returns a sequence of pairs from the given
sequences, this is a convenient way to enumerate the strings
"in parallel".
Once you have understood how it works, you can shorten it,
for example to:
func myStringCompare(str1 : String, str2 : String) -> Bool {
return count(str1) == count(str2) && !contains(zip(str1, str2), { $0 != $1 })
}
Comparing the string length is necessary because the zip() sequence
terminates as soon as one of the strings is exhausted. Have a look at
#drewag's answer to In Swift I would like to "join" two sequences in to a sequence of tuples
for an alternative Zip2WithNilPadding sequence.
If you don't want to use the built-in zip() function (again for
educational/self-learning purposes!) then you can use the fact
that Strings are sequences, and enumerate them in parallel using
the sequence generator. This would work not only for strings but
for arbitrary sequences, as long as the underlying elements can
be tested for equality, so let's make it a generic function:
func mySequenceCompare<S : SequenceType where S.Generator.Element : Equatable>(lseq : S, rseq : S) -> Bool {
var lgen = lseq.generate()
var rgen = rseq.generate()
// First elements (or `nil`):
var lnext = lgen.next()
var rnext = rgen.next()
while let lelem = lnext, relem = rnext {
if lelem != relem {
return false
}
// Next elements (or `nil`):
lnext = lgen.next()
rnext = rgen.next()
}
// Are both sequences exhausted?
return lnext == nil && rnext == nil
}
Tests:
mySequenceCompare("xa", "xb") // false
mySequenceCompare("xa", "xa") // true
mySequenceCompare("a", "aa") // false
mySequenceCompare("aa", "a") // false
My solution differ a little as I didn't know about the zip operator, I guess is not as efficient as the one post by Martin great use of tuple.
Great question alphonse
func isEqual(str1:String, str2:String) -> Bool {
if count(str1) != count(str2){
return false
}
for var i = 0; i < count(str1); ++i {
let idx1 = advance(str1.startIndex,i)
let idx2 = advance(str2.startIndex,i)
if str1[idx1] != str2[idx2]{
return false
}
}
return true
}
As pointed by Martin each string needs its own index, as explained by him:
"The "trick" is that "πŸ‡©πŸ‡ͺ" is an "extended grapheme cluster" and consists of two Unicode code points, but counts as one Swift character."
Link for more details about extended grapheme cluster

How to replace nth character of a string with another

How could I replace nth character of a String with another one?
func replace(myString:String, index:Int, newCharac:Character) -> String {
// Write correct code here
return modifiedString
}
For example, replace("House", 2, "r") should be equal to "Horse".
Solutions that use NSString methods will fail for any strings with multi-byte Unicode characters. Here are two Swift-native ways to approach the problem:
You can use the fact that a String is a sequence of Character to convert the string to an array, modify it, and convert the array back:
func replace(myString: String, _ index: Int, _ newChar: Character) -> String {
var chars = Array(myString) // gets an array of characters
chars[index] = newChar
let modifiedString = String(chars)
return modifiedString
}
replace("House", 2, "r")
// Horse
Alternately, you can step through the string yourself:
func replace(myString: String, _ index: Int, _ newChar: Character) -> String {
var modifiedString = String()
for (i, char) in myString.characters.enumerate() {
modifiedString += String((i == index) ? newChar : char)
}
return modifiedString
}
Since these stay entirely within Swift, they're both Unicode-safe:
replace("🏠🏑🏠🏑🏠", 2, "🐴")
// 🏠🏑🐴🏑🏠
In Swift 4 it's much easier.
let newString = oldString.prefix(n) + char + oldString.dropFirst(n + 1)
This is an example:
let oldString = "Hello, playground"
let newString = oldString.prefix(4) + "0" + oldString.dropFirst(5)
where the result is
Hell0, playground
The type of newString is Substring. Both prefix and dropFirst return Substring. Substring is a slice of a string, in other words, substrings are fast because you don't need to allocate memory for the content of the string, but the same storage space as the original string is used.
I've found this solution.
var string = "Cars"
let index = string.index(string.startIndex, offsetBy: 2)
string.replaceSubrange(index...index, with: "t")
print(string)
// Cats
Please see NateCook answer for more details
func replace(myString: String, _ index: Int, _ newChar: Character) -> String {
var chars = Array(myString.characters) // gets an array of characters
chars[index] = newChar
let modifiedString = String(chars)
return modifiedString
}
For Swift 5
func replace(myString: String, _ index: Int, _ newChar: Character) -> String {
var chars = Array(myString) // gets an array of characters
chars[index] = newChar
let modifiedString = String(chars)
return modifiedString
}
replace("House", 2, "r")
This is no longer valid and deprecated.
You can always use swift String with NSString.So you can call NSString function on swift String.
By old stringByReplacingCharactersInRange: you can do like this
var st :String = "House"
let abc = st.bridgeToObjectiveC().stringByReplacingCharactersInRange(NSMakeRange(2,1), withString:"r") //Will give Horse
For modify existing string:
extension String {
subscript(_ n: Int) -> Character {
get {
let idx = self.index(startIndex, offsetBy: n)
return self[idx]
}
set {
let idx = self.index(startIndex, offsetBy: n)
self.replaceSubrange(idx...idx, with: [newValue])
}
}
}
var s = "12345"
print(s[0])
s[0] = "9"
print(s)
I've expanded upon Nate Cooks answer and transformed it into a string extension.
extension String {
//Enables replacement of the character at a specified position within a string
func replace(_ index: Int, _ newChar: Character) -> String {
var chars = Array(characters)
chars[index] = newChar
let modifiedString = String(chars)
return modifiedString
}
}
usage:
let source = "House"
let result = source.replace(2,"r")
result is "Horse"
I think what #Greg was trying to achieve with his extension is this:
mutating func replace(characterAt index: Int, with newChar: Character) {
var chars = Array(characters)
if index >= 0 && index < self.characters.count {
chars[index] = newChar
let modifiedString = String(chars)
self = modifiedString
} else {
print("can't replace character, its' index out of range!")
}
}
usage:
let source = "House"
source.replace(characterAt: 2, with: "r") //gives you "Horse"
After looking at the Swift Docs, I managed to make this function:
//Main function
func replace(myString:String, index:Int, newCharac:Character) -> String {
//Looping through the characters in myString
var i = 0
for character in myString {
//Checking to see if the index of the character is the one we're looking for
if i == index {
//Found it! Now instead of adding it, add newCharac!
modifiedString += newCharac
} else {
modifiedString += character
}
i = i + 1
}
// Write correct code here
return modifiedString
}
Please note that this is untested, but it should give you the right idea.
func replace(myString:String, index:Int, newCharac:Character) -> String {
var modifiedString = myString
let range = Range<String.Index>(
start: advance(myString.startIndex, index),
end: advance(myString.startIndex, index + 1))
modifiedString.replaceRange(range, with: "\(newCharac)")
return modifiedString
}
I would prefer to pass a String than a Character though.
Here's a way to replace a single character:
var string = "This is the original string."
let offset = 27
let index = string.index(string.startIndex, offsetBy: offset)
let range = index...index
print("ORIGINAL string: " + string)
string.replaceSubrange(range, with: "!")
print("UPDATED string: " + string)
// ORIGINAL string: This is the original string.
// UPDATED string: This is the original string!
This works with multi-character strings as well:
var string = "This is the original string."
let offset = 7
let index = string.index(string.startIndex, offsetBy: offset)
let range = index...index
print("ORIGINAL string: " + string)
string.replaceSubrange(range, with: " NOT ")
print("UPDATED string: " + string)
// ORIGINAL string: This is the original string.
// UPDATED string: This is NOT the original string.
var s = "helloworld"
let index = ((s.count) / 2) // index is 4
let firstIndex = s.index(s.startIndex, offsetBy: index)
let secondIndex = s.index(s.startIndex, offsetBy: index)
s.replaceSubrange(firstIndex...secondIndex, with: "*")
print("Replaced string is: \(s)") //OUTPUT IS: hell*world
This is working fine to replace string using the index.
String class in Swift (till v5 and maybe later) is what other languages call a StringBuilder class, and for performance reasons, Swift does NOT provide setting character by index; If you don't care about performance a simple solution could be:
public static func replace(_ string: String, at index: Int, with value: String) {
let start = string.index(string.startIndex, offsetBy: index)
let end = string.index(start, offsetBy: 1)
string.replaceSubrange(start..<end, with: value)
}
Or as an extension:
extension String {
public func charAt(_ index: Int) -> Character {
return self[self.index(self.startIndex, offsetBy: index)];
}
public mutating func setCharAt(_ index: Int, _ new: Character) {
self.setCharAt(index, String(new))
}
public mutating func setCharAt(_ index: Int, _ new: String) {
let i = self.index(self.startIndex, offsetBy: index)
self.replaceSubrange(i...i, with: new)
}
}
Note how above needs to call index(...) method to convert integer to actual-index!? It seems, Swift implements String like a linked-list, where append(...) is really fast, but even finding the index (without doing anything with it) is a linear-time operation (and gets slower based on concatenation count).
public void createEncodedSentence() {
StringBuffer buff = new StringBuffer();
int counter = 0;
char a;
for (int i = 0; i < sentence.length(); i++) {
a = sentence.charAt(i);
if (a == '.') {
buff.append('*');
}
if (a != ' ' && a != '.') {
counter++;
}
if (counter % 3 == 0) {
buff.append("");
}
buff.append(sentence.charAt(i));
}
encodedSentence = buff.toString();
}
Strings in swift don't have an accessor to read or write a single character. There's an excellent blog post by Ole Begemann describing how strings in swift work.
Note: the implementation below is wrong, read addendum
So the right way is by taking the left part of the string up to the index -1 character, append the replacing character, then append the string from index + 1 up to the end:
func myReplace(myString:String, index:Int, newCharac:Character) -> String {
var modifiedString: String
let len = countElements(myString)
if (index < len) && (index >= 0) {
modifiedString = myString.substringToIndex(index) + newCharac + myString.substringFromIndex(index + 1)
} else {
modifiedString = myString
}
return modifiedString
}
Note: in my implementation I chose to return the original string if the index is not in a valid range
Addendum Thanks to #slazyk, who found out that my implementation is wrong (see comment), I am providing a new swift only version of the function.
func replace(myString:String, index:Int, newCharac:Character) -> String {
var modifiedString: String
if (index < 0) || (index >= countElements(myString)) {
modifiedString = myString
} else {
var start = myString.startIndex
var end = advance(start, index)
modifiedString = myString[start ..< end]
modifiedString += newCharac
start = end.successor()
end = myString.endIndex
modifiedString += myString[start ... end]
}
return modifiedString
}
#codester's answer looks very good, and it's probably what I would use myself.
It would be interesting to know how performances compare though, using a fully swift solution and bridging to objective-c instead.
Here is an efficient answerΒ :
import Foundation
func replace(myString:String, index:Int, newCharac:Character) -> String {
return myString.substringToIndex(index-1) + newCharac + myString.substringFromIndex(index)
}

Resources