1

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 ?

SamAm
  • 39
  • 1
  • 5
  • What is the error? DId you try printing `var a`, what is the output? – Rob Feb 20 '20 at 14:31
  • Is `alphabet` intentionally an `Array` rather than an `Array`? – Alexander Feb 20 '20 at 15:01
  • 1
    I nominated the question for re-opening because the linked question this is supposed to be a duplicate of was actually about a different problem, although the overall task in which it occurred was the same – JeremyP Feb 20 '20 at 15:27

6 Answers6

4

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()! })
Alexander
  • 59,041
  • 12
  • 98
  • 151
  • 1
    Just note that OP's `alphabet` is an array of strings, not of characters.So you probably need to join them. – Martin R Feb 20 '20 at 14:55
  • @MartinR `flatMap` does the trick: `String((0 ..< 6).flatMap { _ in alphabet.randomElement()! })` – JeremyP Feb 20 '20 at 15:34
  • 1
    @JeremyP Or `(0 ..< 6).map { _ in alphabet.randomElement()! }.joined()`. [As I said before](https://stackoverflow.com/questions/26860829/unexpectedly-found-nil-while-unwrapping-an-optional/26861120#comment42282987_26861120): “Swift is the new Perl – TIMTOWTDI!” – Martin R Feb 20 '20 at 15:40
  • @Alexander-ReinstateMonica I think you need to change your first paragraph because, you don't get a `Character?` in this case, you get a `String?`. – JeremyP Feb 20 '20 at 15:42
  • @MartinR I like: `let a = "abcdefghijklmnopqrstuvwxyz" ; let r = String((0 ..< 6).compactMap { _ in a.randomElement() })` – JeremyP Feb 20 '20 at 15:43
  • @JeremyP I would rather check if OP to see that they really meant to have a `[String]` instead of `[Character]` – Alexander Feb 20 '20 at 16:07
  • 1
    @LeoDabus: Jeremy edited his comment, my response does not apply anymore (I have deleted it now). – Martin R Feb 20 '20 at 16:16
  • @Alexander-ReinstateMonica You could recommend he change the declaration of `alphabet` but the assumption must currently be he means an array of strings. I'm only asking you to change it because your answer is otherwise the best and I want to upvote it, but I can't with the factual error in the first line. – JeremyP Feb 20 '20 at 16:18
  • 1
    @Alexander-ReinstateMonica Great. I took the liberty of editing a missed occurrence of `Character` but also upvoted. Also, in the circumstances, you don't need to convert the string "abcdefghijklmnopqrstuvwxyz" to an array because you can apply `.randomElement()` directly to a string. – JeremyP Feb 20 '20 at 17:07
1

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.

Adrian
  • 16,233
  • 18
  • 112
  • 180
PGDev
  • 23,751
  • 6
  • 34
  • 88
  • Thank you very much,, it works !!! But I would like to understand why, what is the rule with "!" character ? When do you need to put one ? – SamAm Feb 20 '20 at 14:34
  • 2
    @SamAm You should read up on Optional, an important concept in swift – Joakim Danielson Feb 20 '20 at 14:36
  • 2
    @SamAm https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html – koen Feb 20 '20 at 14:36
  • Thank you very much for you help, guys – SamAm Feb 20 '20 at 14:37
  • 2
    @SamAm Concatenation operation deals with non-optional values. So, you need to unwrap any optional values before using `+` operation. For unwrapping you either using `!` or `if-let` statement. Do read more about optionals. – PGDev Feb 20 '20 at 14:38
1

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
matt
  • 515,959
  • 87
  • 875
  • 1,141
1

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)
deepak
  • 78
  • 4
1

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.

hefeicoder
  • 123
  • 1
  • 7
0

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() ?? "")
}
PGDev
  • 23,751
  • 6
  • 34
  • 88
  • This is nice but I just can't condone teaching a beginner to use `!`. I suggest `result + (alphabet.randomElement() ?? "")` as in my answer. – matt Feb 20 '20 at 16:56
  • @matt Updated that. Thanks. – PGDev Feb 20 '20 at 16:59
  • The only case where `randomElement()` returns `nil` is for an *empty* collection. If the alphabet is a non-empty array (known at compile time) then I do not see a problem in force-unwrapping. – Martin R Feb 20 '20 at 19:15