SamAm
SamAm

Reputation: 39

Concatenate values returned from an array in Swift

I'm training on Swift, doing some exercises with arrays. I would like to pick up randomly 6 letters in an array containing the 26 letters of the alphabet, then concatenate them to generate a string.

I have no problem picking the values, but when it comes to concatenate them, I get error messages.

Here is my code :

var alphabet = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]

var a = alphabet.randomElement()
var b = alphabet.randomElement()
var c = alphabet.randomElement()
var d = alphabet.randomElement()
var e = alphabet.randomElement()
var f = alphabet.randomElement()

var password = a + b + c + d + e + f

print(password)

What am I doing wrong ?

Upvotes: 1

Views: 787

Answers (6)

hefeicoder
hefeicoder

Reputation: 143

As others already mentioned, randomElement() can return nil as the reference says:

An easy solution will be like the following.

let password = alphabet[Int.random(in: 0...25)]
              + alphabet[Int.random(in: 0...25)]
              + alphabet[Int.random(in: 0...25)]
              + alphabet[Int.random(in: 0...25)]
              + alphabet[Int.random(in: 0...25)]
              + alphabet[Int.random(in: 0...25)]

In this way, basically you tell swift that you want to handle the array out of range error.

Upvotes: 1

deepak
deepak

Reputation: 78

you can try this method

func randomPassword(digit : Int) -> String {
    var password = ""
    for _ in 0 ..< digit {
        let number = Int.random(in: 97...122)
        password += "\(UnicodeScalar(number)!)"
    }
    return  password.uppercased()
}

let password = randomPassword(digit: 6)
print(password)

Upvotes: 1

Alexander
Alexander

Reputation: 63399

Your alphabet is currently an [String] (a.k.a. Array<String>). You should probably change it to an [Character] instead.

Even better, since randomElement is defined on Collection, and Collection whose Element is Character would work. String is a Collection, and its Element is Character, so it fits the bill perfectly. The nicest way to do that is by breaking apart a string literal:

let alphabet = "abcdefghijklmnopqrstuvwxyz"

randomElement() returns a T? (in this case, String?, a.k.a. Optional<String>), not just a T. Because if alphabet were to be empty (it's not, but if it was), then there would be no elements to return. The only sane thing to do is return nil instead.

Since in your case you can be certain that alphabet is non-empty, and that randomElement() will always return a valid non element, you're justified to force unwrap the result with the ! operator. Now don't make a bad habit of this. You have good reason to force unwrap here, but it's not to be used as a first-resort method of optional handling.

You're also repeating yourself a lot. You can improve this by using a loop of some kind or another.

In this case, you can use a neat little trick. Starting with a range like 0..<6, which has 6 elements, you can call map on it to transform its elements (which are 0, 1, ... 5). In this case, you woudl transform each element by ignoring it, and just replacing it with a random element frmo the alphabet:

(0..<6).map { _ in alphabet.randomElement()! }

The result is an Array<Character>. All you need is to turn it into a string. Final code:

let alphabet = "abcdefghijklmnopqrstuvwxyz"
let password = String((0..<6).map { _ in alphabet.randomElement()! })

Upvotes: 4

PGDev
PGDev

Reputation: 24341

Instead of creating all these a, b, c, d, e, f variables, you can simply use reduce(_:_:) to get the expected result.

let password = (1...6).reduce("") { (result, _) -> String in
    return result + (alphabet.randomElement() ?? "")
}

Upvotes: 0

matt
matt

Reputation: 535989

Actually I think the simplest solution is to declare an extension at the top level of your file, allowing you to concatenate Optional Strings coherently:

func +(lhs:String?, rhs:String?) -> String {
    return (lhs ?? "") + (rhs ?? "")
}

Now your original code will compile and run correctly without changes (except that I changed var to let everywhere to eliminate compiler warnings):

let alphabet = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]

let a = alphabet.randomElement()
let b = alphabet.randomElement()
let c = alphabet.randomElement()
let d = alphabet.randomElement()
let e = alphabet.randomElement()
let f = alphabet.randomElement()

let password = a + b + c + d + e + f

print(password) // cpndmz

Upvotes: 1

PGDev
PGDev

Reputation: 24341

a, b, c, d, e, f are of type String?. So you need to unwrap them either using if-let or force-unwrapping before, i.e.

Using force-unwrapping,

let a = alphabet.randomElement()!
let b = alphabet.randomElement()!
let c = alphabet.randomElement()!
let d = alphabet.randomElement()!
let e = alphabet.randomElement()!
let f = alphabet.randomElement()!

Using if-let,

if let a = alphabet.randomElement(), let b = alphabet.randomElement(), let c = alphabet.randomElement(), let d = alphabet.randomElement(), let e = alphabet.randomElement(), let f = alphabet.randomElement() {
    let password = a + b + c + d + e + f
    print(password)
}

Using if-let is safer than force-unwrapping.

Note: Use let instead of var when using constant values.

Upvotes: 1

Related Questions