2015-02-12 17 views
9

Kể từ khi Swift 1.2 (có sẵn dưới dạng beta tại thời điểm này), Apple giới thiệu loại bộ sưu tập Set.Cách lấy phần tử ngẫu nhiên từ một bộ trong Swift?

Nói, tôi có một bộ như:

var set = Set<Int>(arrayLiteral: 1, 2, 3, 4, 5) 

Bây giờ tôi muốn có được một yếu tố ngẫu nhiên ra khỏi nó. Câu hỏi là như thế nào? Set không cung cấp subscript(Int) như Array. Thay vào đó, nó có subscript(SetIndex<T>). Nhưng trước tiên, SetIndex<T> không có bộ khởi tạo có thể truy cập (do đó, tôi không thể tạo chỉ mục với số liệu tôi cần) và thứ hai ngay cả khi tôi có thể lấy chỉ mục cho phần tử đầu tiên trong một tập hợp (var startIndex = set.startIndex). có thể đến chỉ số thứ N thông qua các cuộc gọi liên tiếp đến successor().

Vì vậy, tôi có thể thấy chỉ có 2 lựa chọn vào lúc này, cả hai xấu xí và tốn kém:

  • Chuyển đổi các thiết lập vào mảng (var array = [Int](set)) và sử dụng subscript của nó (mà hoàn toàn chấp nhận Int); hoặc
  • Nhận chỉ mục của phần tử đầu tiên trong một tập hợp, đi qua chuỗi các phương pháp successor() để đến chỉ mục thứ N và sau đó đọc phần tử tương ứng thông qua chỉ số của tập hợp.

Tôi có bỏ lỡ một số cách khác không?

CẬP NHẬT

Như @rintaro chỉ ra, tôi nên sử dụng trước() để ngay lập tức nhận được chỉ số tôi muốn. Ví dụ .:

var set = Set<Int>(arrayLiteral: 1, 2, 3, 4, 5) 
let randomOffset = Int(arc4random_uniform(UInt32(set.count))) 
let random = set[advance(set.startIndex, randomOffset)] 

UPDATE 2

Nó chỉ ra rằng advance() có độ phức tạp O (N), trong đó chủ yếu làm cho nó trở thành tương đương với chỉ đơn giản là đi qua chuỗi successor().

+0

Cảm ơn @rintaro! Điều này thật đúng với gì mà tôi đã tìm kiếm. – courteouselk

Trả lời

8

Có lẽ cách tốt nhất là advance mà đi successor cho bạn:

func randomElementIndex<T>(s: Set<T>) -> T { 
    let n = Int(arc4random_uniform(UInt32(s.count))) 
    let i = advance(s.startIndex, n) 
    return s[i] 
} 

(EDIT: Heh; nhận thấy bạn thực sự cập nhật các câu hỏi để bao gồm câu trả lời này trước khi tôi thêm nó vào câu trả lời của tôi ... tốt, D)

Bạn cũng có thể đi bộ chứ không phải chỉ số (đây là suy nghĩ đầu tiên của tôi, nhưng sau đó tôi nhớ advance).

func randomElement<T>(s: Set<T>) -> T { 
    let n = Int(arc4random_uniform(UInt32(s.count))) 
    for (i, e) in enumerate(s) { 
     if i == n { return e } 
    } 
    fatalError("The above loop must succeed") 
} 
+0

Có. Tuy nhiên, phần thú vị là 'advance()' trong trường hợp Set có độ phức tạp O (N). Vì vậy, về mặt kỹ thuật, nó giống như việc đi qua chuỗi 'successor()', nó đơn giản trông ngắn gọn hơn và rõ ràng hơn. Tôi hỏi câu hỏi tương tự tại diễn đàn nhà phát triển Apple, và nó nhận được sự chú ý từ nhân viên => có thể trong một số bản phát hành trong tương lai của Swift sẽ có những công cụ tốt hơn để làm những gì tôi muốn. Bây giờ tôi có thể sẽ ở lại với 'advance()'. Các bộ mà tôi sẽ sử dụng không phải là lớn. – courteouselk

+0

Phương thức tạm dừng không còn biên dịch, như của Xcode 7 Beta 6 –

+2

Justin Lewis tất cả những gì đã thay đổi là bây giờ bạn phải gọi 'advancedBy (_ :)' trên chỉ mục 'extension Set {func randomElement() -> Element {let n = Int (arc4random_uniform (UInt32 (đếm))); let i = startIndex.advancedBy (n); return self [i];}} ' – griotspeak

1

Nếu bạn muốn có một yếu tố 'ngẫu nhiên' từ một Set sau đó bạn sử dụng:

/// A member of the set, or `nil` if the set is empty. 
var first: T? { get } 

Lấy chỉ số 0 hoặc chỉ số 1000000 làm cho không có sự khác biệt - họ là tất cả một đối tượng tùy ý.

Nhưng, nếu bạn muốn lặp lại các cuộc gọi để trả lại phần tử có thể khác nhau mỗi lần, thì first có thể không khớp với hóa đơn.

+7

Đây là khoảng cùng một thuật toán như https://www.xkcd.com/221/ –

+4

Và về giống như 'Tìm x' http://bit.ly/173dqTe – GoZoner

+0

Tất nhiên, làm thế nào bạn có thể có thể nói điều đó. Đầu tiên. khác với 'đi qua N-1, trả về Nth'? Liệu Swift có đảm bảo rằng thứ tự đi qua không đổi theo thời gian? Một số ngôn ngữ không (vì băm của Bộ phụ thuộc vào địa chỉ bộ nhớ và trong các hệ thống thu gom rác mà địa chỉ bộ nhớ thay đổi). – GoZoner

5
extension Set { 
    func randomElement() -> Element? { 
     return count == 0 ? nil : self[advance(self.startIndex, Int(arc4random()) % count)] 
    } 
} 
2

Theo ý kiến ​​trên lại cập nhật Swift, sử dụng một thay đổi nhỏ cho một phần mở rộng để Đặt:

func randomElement() -> Element? 
{ 
    let randomInt = Int(arc4random_uniform(UInt32(self.count))) 
    let index = startIndex.advancedBy(randomInt) 
    return count == 0 ? nil: self[index] 
} 
4

Trong nhanh chóng 3

extension Set { 
    public func randomObject() -> Element? { 
     let n = Int(arc4random_uniform(UInt32(self.count))) 
     let index = self.index(self.startIndex, offsetBy: n) 
     return self.count > 0 ? self[index] : nil 
    } 
} 
Các vấn đề liên quan