Cannot assign through subscript to Swift String - swift

I have a class that contains a name, an image, a dashed form of the name, and the length of the name. For example, I could have "dog", an image of a dog, "---", and name length 3.
I just want to set name and pic for each object and have dashName and nameLength set automatically.
class Answer {
var name = "name"
var image: UIImage?
var dashName = "name"
var nameLength = 0
init(){
var a = 0
nameLength = name.characters.count
while a <= nameLength {
if (name[a] == " ") {dashName[a] = " "}
else {dashName[a] = "-"}
a += 1
}
}
}
The problem is the error that says: "cannot assign through subscript: subscript is get-only" and another error that says: "subscript is unavailable: cannot subscript String with an Int"

Because String's subscript operator is get-only, use map method instead, like:
class Answer {
var name = "name"
var image: UIImage?
var dashName = "name"
var nameLength = 0
init(){
dashName = String(name.map {$0 == " " ? " " : "-"})
}
}

As mentioned before,
Swift's String class 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).

The subscript operator for String is get-only, which means you can only read from a string using it, and have to use something else to write to a mutable String.
You can solve this issue, and clean up the code by using a map function on name
Swift 4
class Answer {
var name = "name"
var image: UIImage?
var dashName = "name"
var nameLength = 0
init()
{
nameLength = name.count
dashName = name.map { $0 == " " ? " " : "-" }.joined()
}
}
Swift 3
class Answer {
var name = "name"
var image: UIImage?
var dashName = "name"
var nameLength = 0
init()
{
nameLength = name.characters.count
dashName = name.characters.map { $0 == " " ? String(" ") : String("-") }.joined()
}
}

Related

Shuffle characters in a string in Swift

Can the new Swift 5 shuffle() method be used (directly, or in a more complicated incantation) to randomly shuffle the characters in a Swift string variable? (of length greater than 1)
You can simply try this nifty code
extension String {
func shuffleString(minLength : Int) -> String{
return self.count > minLength ? String(self.shuffled()) : self
}
}
var string = "Whatever is your string"
print(string.shuffleString(minLength: 1))
You can maybe try something like this.
var str = "Hello"
var shuffledString = String(str.shuffled())
try this function
func shuffleString(word: String) -> String {
var chars = Array(word.characters)
var result = ""
while chars.count > 0 {
let index = Int(arc4random_uniform(UInt32(chars.count - 1)))
chars[index].writeTo(&result)
chars.removeAtIndex(index)
}
return result
}
Try this one
var str = "Shuffle me please"
var shuffledStr: [Character]
if !str.isEmpty {
shuffledStr = str.shuffled()
print(String(shuffledStr))
}

In Swift How do I iterate over an array getting 2 variables when each pair of elements is a String? and a String

I want to iterate over an array String?, String repeated pair but I cannot form the "for case let (a,b) in array" correctly.
The best I have come up with is to create a temp struct of {String?, String} and create an array of the temp structs and then iterate it but I would like to skip this step.
Below is the basic example with the last for loop showing the error Xcode reports.
class Foo {
var s1: String?
var s2: String?
var s3: String?
}
let foo = Foo()
foo.s1="Test1"
foo.s2=nil
foo.s3="Test3"
let fooArray = [foo.s1, ", ", foo.s2, "; ", foo.s3,"."]
let fooArray1 = [foo.s1,foo.s2, foo.s3]
var text:String = ""
for case let prop? in fooArray1 {
text = text + prop + " / "
}
print(text)
// The above works but now I want to use a different separator
//base on the property name
text=""
for case let (prop, sep) in fooArray { // Error <= Expression Type
// [String?] is ambiguous without more context
text = text + prop + sep
}
print(text)
Here is what I have come up with
struct temp {
var prop:String?
var sep:String
init(_ prop:String?, _ sep:String) {
self.prop=prop
self.sep=sep
}
let ary:[temp] = [ temp(foo.s1,", "), temp(foo.s2,"; "), temp(foo.s3,".") ]
text = ""
for a in ary {
if let p = a.prop {
text = text + p + a.sep
}
}
print (text)
is there another way just using the for loop
for (a,b) in fooArray {
...
}
As noted by #RJE, the inferred type of fooArray, as defined in your code, is [String?].
Here is one way to make it work:
class Foo {
var s1: String?
var s2: String?
var s3: String?
}
let foo = Foo()
foo.s1 = "Test1"
foo.s2 = nil
foo.s3 = "Test3"
let fooArray1 = [foo.s1, foo.s2, foo.s3]
let separators = [", ", "; ", "."]
var text = ""
for i in fooArray1.indices {
if let p = fooArray1[i] {
text = text + p + separators[i]
}
}
print (text) //Test1, Test3.
Or
let zipped = zip(fooArray1, separators)
let text = zipped.map { tuple -> String in
if case let (x?, y) = tuple {
return x + y
} else {
return ""
}
}.joined()
print (text) //Test1,Test3.
Or
let fooArray = [foo.s1, ", ", foo.s2, "; ", foo.s3, "."]
var text = ""
var step = 1
var index = 0
while index < fooArray.count {
if let str = fooArray[index] {
step = 1
text += str
} else {
step = 2
}
index += step
}
print(text) //Test1, Test3.
It would be better to define the initializer this way :
class Foo {
var s1: String?
var s2: String?
var s3: String?
init(s1: String?, s2: String?, s3: String?) {
self.s1 = s1
self.s2 = s2
self.s3 = s3
}
}
let foo = Foo(s1: "Test1", s2: nil, s3: "Test3")
P.S: The desired output seems to be more appropriate for a description property of the Foo class.
Thanks for the answer I was hoping through this question to get a better understanding of how to use [for] parameters. But the while solution is the solution I would probably use with the following modifications
text = ""
var index = 0
while index < fooArray.count {
if let prop = fooArray[index] {
index += 1
let sep = fooArray[index]!
index += 1
text = text + prop + sep
} else {
index += 2
}
}

How to make a function that swaps 3 characters in a string?

For example, if I had a string RedSox and wanted to change it to SoxRed?
I'm thinking it would be something like :
func swapString (String: String) -> String {
var stringReplaced = String
var result = stringReplaced.Select(x=> x == 'A' ? 'B' : (x=='B' ? "A" : x)).ToArray()
stringReplaced = String(result)
return stringReplaced
}
this function takes the last 3 characters of a string and appends them to the beginning, there are definitely less verbose ways of doing this but it works. it will throw an error if passed a string with < 3 characters.
import UIKit
let string = "RedSox"
func changeString ( _ string : String) -> String {
var characters : Array<Character> = []
for character in string.characters {
characters.append(character)
}
var characters2 : Array<Character> = []
var position = characters.count - 3
while position < characters.count {
characters2.append(characters[position])
position += 1
}
characters.removeLast()
characters.removeLast()
characters.removeLast()
characters2.append(contentsOf: characters)
return (String(characters2))
}
var newString = changeString(string)
print (newString)
Just use the methods which the String class already provides.
It's always a good idea putting these kind of "helper" methods in an extension.
// Define String extension
extension String {
func swappedString(count swapCount: Int) -> String {
guard self.characters.count > swapCount else {
return self
}
let index = self.index(self.endIndex, offsetBy: -swapCount)
let first = self.substring(from: index)
let second = self.substring(to: index)
return first + second
}
}
// Use it
"RedSox".swappedString(count: 3) //= SoxRed

String.Index does not have a member named advancedBy

This is the swift code I have as an exercise from my class, I am re-writing it(as I am new to it).
At the position of
let end = starIndex.advancedBy(position), I am getting an error message of String.Index does not have a member named advancedBy I am not sure what this means or how to correct it just yet, many thanks for any help in understanding what I am doing wrong.
//: Playground - noun: a place where people can play
import UIKit
var str : String = "Hello, playground"
str
let strFix = " Can not change"
str = "Good bye"
//strFix = "Testing"
var str2 = str + "F"
var townName = "NeverLand"
let population = 30000
let numOfStopLight : Int = 10
print("\(townName) has populations of \(population)
and has \ (numOfStopLight) Stop Lights")
for c in townName.unicodeScalars
{
print ("\(c)")
}
let starIndex = str.startIndex
let position = 3
let end = starIndex.advancedBy(position)
let charAt = str[end]
let range = starIndex...end
str[range]
let aChar: Character = "\u{1F60E}"
str.append(aChar)
let dollarSign = "\u{24}" //$, Unicode scalar U+0024
let blackHeart = "\u{2665}" // , Unicode scalar U+2665
let sparklingHeart = "\u{1F496}" // , Unicod scalar U=1F496
str.append(Character(sparklingHeart))
for c in str.characters
{
print("\(c)")
}
for c in str.unicodeScalars
{
print("\(c.value)")
}
/////////////////////
struct Car
{
var make : String = ""
var price : Double = 0.0
let color : String = "RED"
func getMske()->String
{
return make
}
mutating func setMake (m: String)
{
make = m
}
func showCar()->String
{
return "Make:\(make) Price=\(price) Color= \(color)"
}
}
//let us make some Car
var myCar = Car(make: "Nissan", price: 45000.0)
print(myCar.showCar())
myCar.setMake("Nissan2016")
print (myCar.showCar())
//show pass value
func doChange (var c: Car)
{
c.setMake("Toyota")
print (c.showCar())
}
doChange(myCar)
print (myCar.showCar())
//let us have a class
class Xcar
{
var make :String = ""
var price : Double = 0.0
var color : String = ""
init(m :String, p: Double, c: String)
{
self.make = m
self.price = p
self.color = c
}
func setMake(m: String)
{
self.make = m
}
func showXCar()->String
{
return "Make: \(make) Price=\(price) Color=\(color)"
}
}
func doChangex( c:Xcar)
{
c.setMake("BMW")
print("\(c.showXCar())")
}
var hisCar = Xcar(m: "Fiat", p: 15000.0, c: "Blue")
print ("\(hisCar.showXCar())")
doChangex(hisCar)
//notice the function did change
//because it was a class
print ("\(hisCar.showXCar())")
//What is optional
let a: Float?
let b: Float?
let c :Float?
a = 10
b = 20
c = 5
//this is implicit unrap
let ave = (a! + b! + c!)/3.0
if let x = a, y = b, z = c
{
print ("\((x + y + z) / 3.0)")
}
else
{
print ("missing value...")
}
// note Array, Double, Float, Int and Dictionary are all struct
var intArray = [Int] ()
intArray.append(50)
intArray.append(100)
intArray.append(600)
intArray.maxElement()
intArray.capacity
intArray.count
intArray.description
intArray.dropFirst()
intArray.first
intArray.description
intArray.removeFirst()
intArray.description
for c in intArray
{
print("\(c)")
}
var dict = [Int :String ] ()
dict = [235 :"Jack", 100: "Joe", 60: "Lisa"]
dict.description
for(key, value) in dict
{
print ("\(key)")
}
var name = dict[235]
Try doing it in one line
let startIndex = str.startIndex.advancedBy(3)
The code has a number of issues and we don't know what the expected output is, but here are a few items:
The line with just 'str' on it should be removed.
You may be running an older version of Xcode or OS (or both). This is Swift2 and you should be using Xcode 7 or higher.
Also, there are 'var' that should be 'let' (hisCar) and there are a number of unused variables.
The good news is once you fix the issues that, the code runs correctly (as far as I can tell)

Proper way to concatenate optional swift strings?

struct Person {
var firstName: String?
var lastName: String?
}
Now I want to construct a fullName string that contains either just their first or last name (if that's all that is available), or if we have both, their first and last name with a space in the middle.
var fullName: String?
if let first = person.firstName {
fullName = first
}
if let last = person.lastName {
if fullName == nil {
fullName = last
} else {
fullName += " " + last
}
}
or
var fullName = ""
if let first = person.firstName {
fullName = first
}
if let last = person.lastName {
fullName += fullName.count > 0 ? (" " + last) : last
}
Are we just supposed to nest if let's? Nil coalescing seems appropriate but I can't think of how to apply it in this scenario. I can't help but feeling like I'm doing optional string concatenation in an overly complicated way.
compactMap would work well here, combined with .joined(separator:):
let f: String? = "jo"
let l: String? = "smith"
[f,l] // "jo smith"
.compactMap { $0 }
.joined(separator: " ")
It doesn't put the space between if one is nil:
let n: String? = nil
[f,n] // "jo"
.compactMap { $0 }
.joined(separator: " ")
Somewhere, I believe in the swift book, I ran into this pattern, from when before you could have multiple lets in a single if:
class User {
var lastName : String?
var firstName : String?
var fullName : String {
switch (firstName, lastName) {
case (.Some, .Some):
return firstName! + " " + lastName!
case (.None, .Some):
return lastName!
case (.Some, .None):
return firstName!
default:
return ""
}
}
init(lastName:String?, firstName:String?) {
self.lastName = lastName
self.firstName = firstName
}
}
User(lastName: nil, firstName: "first").fullName // -> "first"
User(lastName: "last", firstName: nil).fullName // -> "last"
User(lastName: nil, firstName: nil).fullName // -> ""
User(lastName: "last", firstName: "first").fullName // -> "first last"
An even briefer solution, given swift 3.0:
var fullName : String {
return [ firstName, lastName ].flatMap({$0}).joined(separator:" ")
}
Sometimes simple is best:
let first = p.first ?? ""
let last = p.last ?? ""
let both = !first.isEmpty && !last.isEmpty
let full = first + (both ? " " : "") + last
This works if there is no first or last, if there is a first but no last, if there is a last but no first, and if there are both a first and a last. I can't think of any other cases.
Here's an idiomatic incorporation of that idea into a calculated variable; as an extra benefit, I've allowed full to be nil just in case both the other names are nil:
struct Person {
var first : String?
var last : String?
var full : String? {
if first == nil && last == nil { return nil }
let fi = p.first ?? ""
let la = p.last ?? ""
let both = !fi.isEmpty && !la.isEmpty
return fi + (both ? " " : "") + la
}
}
Here is an alternative method:
let name =
(person.first != nil && person.last != nil) ?
person.first! + " " + person.last! :
person.first ?? person.last!
For those who are want to check nil and "" value as well you can do something like this:
var a: String? = nil
let b = "first value"
let c: String? = nil
let d = ""
let e = "second value"
var result = [a,b,c,d,e].compactMap{ $0 }.filter { $0 != "" }.joined(separator:", ")
print(result)
//first value, second value
I like oisdk's approach but I didn't like the empty string if both were nil. I would rather have nil.
func add(a a: String?, b: String?, separator: String = " ") -> String? {
let results = [a, b].flatMap {$0}
guard results.count > 0 else { return nil }
return results.joinWithSeparator(separator)
}
What oisdk answered was great, but I needed something very specific along the lines of the OP's original question.
Writing for Swift 4.x, I created this extension which works well when populating other strings, such as text labels. I have also updated it to include a function for handling an array if needed.
extension String {
static func combine(first: String?, second: String?) -> String {
return [first, second].compactMap{ $0 }.joined(separator: " ")
}
static func combine(strings: [String?]) -> String {
return strings.compactMap { $0 }.joined(separator: " ")
}
}
An example of this populating a text label with two optional strings:
print(String.combine(first: "First", second: "Last")) // "First Last"
print(String.combine(first: "First", second: nil)) // "First"
print(String.combine(first: nil, second: "Last")) // "Last"
If you have an array of optional strings, you can call the array function:
print(String.combine(strings: ["I", "Have", nil, "A", "String", nil, "Here"]))
// "I Have A String Here"
for swift 4
let name: String? = "Foo"
let surname: String? = "Bar"
let fullname = (name ?? "") + " " + (surname ?? "")
print(fullname)
// Foo Bar
func getSingleValue(_ value: String?..., seperator: String = " ") -> String? {
return value.reduce("") {
($0) + seperator + ($1 ?? "")
}.trimmingCharacters(in: CharacterSet(charactersIn: seperator) )
}
It's too bad that there isn't more support for operators on the Optional enum, but I overloaded the standard concatenation operator (+) like this:
func +<T: StringProtocol>(lhs: Optional<T>, rhs: Optional<T>) -> String {
return [lhs, rhs].compactMap({ $0 }).joined()
}
Then you can use it like this:
let first: String? = "first"
let last: String? = nil
first + first // "firstfirst"
first + last // "first"
last + first // "first"
last + last // ""
Add-On:
Consider you have Struct:
struct Person {
var firstName: String?
var lastName: String?
}
you can use the CustomStringConvertible
extension Person: CustomStringConvertible {
var description: String {
[firstName, lastName].compactMap { $0 }.joined(separator: " ")
}
}
let person1 = Person(firstName "Jeba", lastName: "Moses")
print(person1) // Prints "Jeba Moses"
let person2 = Person(firstName: "Ebenrick", lastName: nil)
print(person2) // Prints "Ebenrick"