1
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!

3
  • There are more issues. E.g. you cannot access a CollectionType with subscripting so your last line would not work. Are you sure you want a ConnectionType as param and not an array? Commented Feb 22, 2016 at 11:01
  • @appzYourLife: CollectionType inherits from Indexable, and you can access it with subscripting. Commented Feb 22, 2016 at 11:14
  • @MartinR: ops... thank you for the correction. Commented Feb 22, 2016 at 11:16

3 Answers 3

3

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)
Sign up to request clarification or add additional context in comments.

14 Comments

Well... much better than my answer :)
Sorry! I still don't understand the first part. "The Index.Distance of a CollectionType is not necessarily Int. (It is Int for all concrete collection types that I know of, but the CollectionType protocol definition does not demand that.) Therefore you can restrict the function to all collection types for which Index.Distance == Int: func randomElement<T: CollectionType where T.Index.Distance == Int>(list: T) "
Any type conforms to CollectionType that has an assiocated type "Index". Any type assiocate with "Index" need to conform to "ForwardIndexType" type, the assiocated type "Distance" in "ForwardIndexType" protocol is Int, so other type conforms Collection also conforms to "ForwardIndexType", right? I know I am wrong. But I don't get it. Thank you very much.
@user3762515: Good point! The default type for ForwardIndexType is Int, the protocol only demands that it is a _SignedIntegerType. – See update!
The default type for the Distance is Int. Do you mean Int can be ignored? Sorry. I am too stupid.
|
2

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

1 Comment

Thanks for answering my stupid question. I am new in programming and swift. Very sorry.
0

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)]
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.