Random Alphanumeric String Linux Swift 3

936 views Asked by At

Im having 2 problems when trying to generate a random string in Linux with Swift 3.

  1. arc4random_uniform is not available in Linux only on BSD. SO i was able to get away with using random() function. And this worked when i was generating random numbers of a variable size (See code below)

    func generateRandomNumber() -> Int
    {
       var place = 1
    
       var finalNumber = 0;
    
    #if os(Linux)
    for _ in 0..<5
    {
        place *= 10
    
        let randomNumber = Int(random() % 10) + 1
    
        finalNumber += randomNumber * place
    }
    #else
    for _ in 0..<5
    {
        place *= 10
    
        let randomNumber = Int(arc4random_uniform(10))
    
        finalNumber += randomNumber * place
    }
    #endif
    
      return finalNumber
    }
    

And that WORKS. Edit: it works but it gives me the same number every time :(

  1. When trying to generate random alphanumeric string I'm limited to using Swift String and NOT NSSTRING. Linux throws this error

original pre Linux block of code:

   func randomString(_ length: Int) -> String
   {

      let letters : NSString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
      let len = UInt32(letters.length)

      var randomString = ""

      for _ in 0 ..< length {
    let rand = arc4random_uniform(len)
    var nextChar = letters.character(at: Int(rand))
    randomString += NSString(characters: &nextChar, length: 1) as String
      }

       return randomString
    }

And the actual error I get when using above code

    error: cannot convert value of type 'NSString' to type 'String' in coercion
        randomString += NSString(characters: &nextChar, length: 1) as String

modified for linux block of code.

    func randomString(_ length: Int) -> String
    {

let letters : String = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
let len = letters.characters.count

var randomString = ""

#if os(Linux)

    for _ in 0..<length
    {
        let randomValue = (random() % len) + 1

        randomString += "\(letters[letters.index(letters.startIndex, offsetBy: Int(randomValue))])"
    }

    #else
    for _ in 0 ..< length
    {
        let rand = arc4random_uniform(UInt32(len))

        randomString += "\(letters[letters.index(letters.startIndex, offsetBy: Int(rand))])"
    }
    #endif


      return randomString
}          

but this time the error is weird it only says Illegal instruction with no extra information. I ran the docker container in interactive mode and i saw my server running and printing out when calling other functions etc.

but the thing is the function actually WORKS when i ran it in IBMs swift sandbox

enter image description here and I'm assuming its using linux also. Im very stuck and confused any help would be greatly appreciated.

(UPDATE): I ran the same function in just a linux env with a single swift file and not the Vapor swift web framework. and it works. As mentioned in my edit above it gives me the same random string everytime. I will still have to test the entire project once my build finishes. But besides that i need to know if the random() function will actually give me something new each time instead of the same crap.

4

There are 4 answers

0
Andrew Edwards On BEST ANSWER

Figured it out.

So the answer to the repeating random number/string was to just add this line before i called the random() function

srand(UInt32(time(nil)))

and I'm assuming thats what fixed the illegal instruction also. Because i don't recall changing anything else.

Needless to say here is my final result

 func generateRandomNumber() -> Int
 {
    var place = 1

    var finalNumber = 0;

    #if os(Linux)

    srand(UInt32(time(nil)))

    for _ in 0..<5
    {
        place *= 10

        let randomNumber = Int(random() % 10) + 1

        finalNumber += randomNumber * place
    }
    #else
    for _ in 0..<5
    {
        place *= 10

        let randomNumber = Int(arc4random_uniform(10))

        finalNumber += randomNumber * place
    }
    #endif

     return finalNumber
 }



 func randomString(_ length: Int) -> String
 {

    let letters : String = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    let len = letters.characters.count

    var randomString = ""

    #if os(Linux)

    srand(UInt32(time(nil)))

   for _ in 0..<length
   {
     let randomValue = (random() % len) + 1

     randomString += "\(letters[letters.index(letters.startIndex, offsetBy: Int(randomValue))])"
   }

   #else
  for _ in 0 ..< length
  {
     let rand = arc4random_uniform(UInt32(len))

     randomString += "\(letters[letters.index(letters.startIndex, offsetBy: Int(rand))])"
  }
  #endif

   return randomString
 }  
2
shallowThought On

1) Always the same number

You have to set a seed once to get "random" numbers from random():

randomSeed(Int(Date().timeIntervalSince1970)

Man page:

If no seed value is provided, the random() function is automatically seeded with a value of 1.

As the seed is always the same (1), you always get the same sequence of "random" numbers.

2) Alphanumeric string

To create your string without using NSString:

func randomString(length: Int) -> String {

    let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    let len = UInt32(letters.characters.count)

    var randomString = ""

    for _ in 0 ..< length {
        let rand = myCustomRandom(len)
        let randIndex = letters.index(letters.startIndex, offsetBy: Int(rand))
        let nextChar = letters[randIndex]
        randomString += String(nextChar)
    }

    return randomString
}
2
gavanon On

I copied and pasted your code exactly, and it doesn't compile.

fatal error: Can't form a Character from an empty String

Here's an alternative method:

// Keep at top of your code (outside of functions)
#if os(Linux)
    srandom(UInt32(time(nil)))
#endif


func getRandomNumber(_ min: Int, _ max: Int) -> Int {
    #if os(Linux)
        return Int(random() % max) + min
    #else
        return Int(arc4random_uniform(UInt32(max)) + UInt32(min))
    #endif
}


func getRandomString(_ chars: String, _ length: Int) -> String {
    var str = ""

    for _ in 1...length {
        str.append(chars.itemOnStartIndex(advancedBy: getRandomNumber(0, chars.count - 1)))
    }

    return str
}


// Best practice to define this outside of the function itself
let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
print(getRandomString(chars, 10))

This works for me on Ubuntu.

0
Serhii Didanov On

Swift 4.2, Ubuntu 16.04

let letters : String = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
let len = letters.count
var randomString:String = ""
for _ in 0 ..< length {
   let rand = Int.random(in: 0..<len)
   randomString += letters.map { String($0) }[rand]
}