Carl Hung
Carl Hung

Reputation: 555

Getting a random element in array by a generic function

func ramElment<X, T: CollectionType >(list: T) -> X {
    let len = UInt32(list.count)
    let element = arc4random_uniform(len)
    return list[element]
}

it pops up:

error: cannot invoke initializer for type UInt32 with an argument list of type '(T.Index.Distance)'

let len = UInt32(list.count)

I have checked the T.Index.Distance is Int type. but why can't i change the type to UInt32?

thanks!

Upvotes: 1

Views: 418

Answers (3)

Wanbok Choi
Wanbok Choi

Reputation: 5452

This is the simplest example what you want.

extension CollectionType where Index.Distance == Int {
    func randomElement() -> Self.Generator.Element {
        let randomIndex = Int(arc4random_uniform(UInt32(count)))
        return self[startIndex.advancedBy(randomIndex)]
    }
}

Upvotes: 0

Martin R
Martin R

Reputation: 539795

The Index of a CollectionType is a ForwardIndexType:

public protocol ForwardIndexType : _Incrementable {
    // ...
    typealias Distance : _SignedIntegerType = Int
    // ...
}

This means that the associated type Distance must conform to _SignedIntegerType, and (by default) is Int unless declared (or inferred) otherwise.

Example: The following is a valid type conforming to ForwardIndexType, with Distance == Int16:

struct MyIndex : ForwardIndexType {
    var value : Int16

    func advancedBy(n: Int16) -> MyIndex {
        return MyIndex(value: value + n)
    }
    func distanceTo(end: MyIndex) -> Int16 {
        return end.value - value
    }
    func successor() -> MyIndex {
        return MyIndex(value: value + 1)
    }
}

func ==(lhs : MyIndex, rhs : MyIndex) -> Bool {
    return lhs.value == rhs.value
}

And here is a (for demonstration purposes, otherwise pretty useless) type conforming to CollectionType with Index == MyIndex, Index.Distance == Int16:

struct MyCollectionType : CollectionType {

    var startIndex : MyIndex { return MyIndex(value: 0) }
    var endIndex : MyIndex { return MyIndex(value: 3) }

    subscript(position : MyIndex) -> String {
        return "I am element #\(position.value)"
    }
}

Example:

let coll = MyCollectionType()
for elem in coll {
    print(elem)
}
/*
I am element #0
I am element #1
I am element #2
*/

But we can also define a forward index type without declaring any Distance type, and using the default protocol implementations for advancedBy() and distanceTo():

struct MyOtherIndex : ForwardIndexType {
    var value : Int16

    func successor() -> MyOtherIndex {
        return MyOtherIndex(value: value + 1)
    }
}

func ==(lhs : MyOtherIndex, rhs : MyOtherIndex) -> Bool {
    return lhs.value == rhs.value
}

Now MyOtherIndex.Distance == Int because that is the default type as defined in ForwardIndexType.


So how does this apply to your function?

You cannot assume that Index.Distance is Int for an arbitrary collection.

You can restrict the function to collection types with Index.Distance == Int:

func randomElement<T: CollectionType where T.Index.Distance == Int>(list: T) 

But you can also utilize that _SignedIntegerType can be converted to and from IntMax:

func randomElement<T: CollectionType>(list: T) -> T.Generator.Element {
    let len = UInt32(list.count.toIntMax())
    let element = IntMax(arc4random_uniform(len))
    return list[list.startIndex.advancedBy(T.Index.Distance(element))]
}

Note also that the return type is determined as T.Generator.Element, and cannot be an arbitrary generic type X.

This function works with arbitrary collections, for example Array, ArraySlice, or String.CharacterView:

let array = [1, 1, 2, 3, 5, 8, 13]
let elem1 = randomElement([1, 2, 3])

let slice = array[2 ... 3]
let elem2 = randomElement(slice)

let randomChar = randomElement("abc".characters)

but also with the above custom collection type:

let mc = MyCollectionType()
let r = randomElement(mc)

Upvotes: 3

Luca Angeletti
Luca Angeletti

Reputation: 59506

In the title of your question you talk about Array. But in your code you are declaring the input param as a CollectionType.

If you really want to receive a Generic Array param then this is the code

func randomElement<T>(list: [T]) -> T {
    let len = UInt32(list.count)
    let random = Int(arc4random_uniform(len))
    return list[random]
}

enter image description here

Upvotes: 2

Related Questions