Reputation: 555
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
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
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
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]
}
Upvotes: 2