2013-02-11 17 views
10

Tôi muốn hiển thị một bộ sưu tập gồm products nơi tôi bao gồm các sản phẩm vừa được bán và không bán. Chỉ có tôi muốn products được bán để xuất hiện ở mặt trước của danh sách và các đối tượng không được bán sẽ xuất hiện ở cuối danh sách.Có thể sắp xếp danh sách các đối tượng tùy thuộc vào phản ứng của đối tượng riêng lẻ đối với một phương thức không?

Một cách dễ dàng cho tôi để thực hiện điều này là làm cho hai danh sách, sau đó kết hợp chúng (một danh sách các đối tượng on_sale và một danh sách không on_sale đối tượng?):

available_products = [] 
sold_products = [] 
@products.each do |product| 
    if product.on_sale? 
    available_products << product 
    else 
    sold_products << product 
    end 
end 

. . . Nhưng làm với cấu trúc của ứng dụng hiện có của tôi, điều này sẽ đòi hỏi một số tiền quá nhiều của tái cấu trúc do một sự kỳ quặc trong mã của tôi (tôi mất phân trang của tôi, và tôi không muốn refactor). Sẽ dễ dàng hơn nếu có cách sắp xếp danh sách đối tượng hiện có theo phương pháp của mô hình on_sale? trả về giá trị boolean.

Có thể lặp lại thanh lịch hơn thông qua danh sách hiện tại và sắp xếp nó theo giá trị đúng hay sai này trong đường ray không? Tôi chỉ hỏi vì có quá nhiều thứ mà tôi không biết về ẩn trong khuôn khổ/ngôn ngữ này (ruby) và tôi muốn biết liệu chúng có hoạt động được thực hiện trước tôi hay không.

Trả lời

11

Chắc chắn. Lý tưởng nhất là chúng tôi muốn làm một cái gì đó như thế này sử dụng sort_by!:

@products.sort_by! {|product| product.on_sale?} 

hoặc snazzier

@products.sort_by!(&:on_sale?) 

nhưng buồn thay, <=> không làm việc cho các phép toán luận (xem Why doesn't sort or the spaceship (flying saucer) operator (<=>) work on booleans in Ruby?) và sort_by không làm việc cho boolean giá trị, vì vậy chúng tôi cần sử dụng mẹo này (cảm ơn rohit89!)

@products.sort_by! {|product| product.on_sale? ? 0 : 1} 

Nếu bạn muốn trở nên giàu có hơn, phương pháp sort mất một khối và bên trong khối đó bạn có thể sử dụng bất kỳ logic nào bạn thích, bao gồm chuyển đổi loại và nhiều phím. Hãy thử một cái gì đó như thế này:

@products.sort! do |a,b| 
    a_value = a.on_sale? ? 0 : 1 
    b_value = b.on_sale? ? 0 : 1 
    a_value <=> b_value 
end 

hay này:

@products.sort! do |a,b| 
    b.on_sale?.to_s <=> a.on_sale?.to_s 
end 

(đặt b trước bởi vì bạn muốn "true" giá trị đến trước "false")

hoặc nếu bạn có một loại thứ:

@products.sort! do |a,b| 
    if a.on_sale? != b.on_sale? 
    b.on_sale?.to_s <=> a.on_sale?.to_s 
    else 
    a.name <=> b.name 
    end 
end 

Lưu ý rằng sort trả về một Bộ sưu tập ew, thường là một giải pháp sạch hơn, ít bị lỗi hơn, nhưng sort! sửa đổi nội dung của bộ sưu tập gốc mà bạn đã nói là một yêu cầu.

+0

Trông tuyệt vời! Nhưng tôi gặp vấn đề với nó với mã cụ thể của tôi, có lẽ bạn có thể giúp tôi với nó. Nếu tôi sử dụng một lớp lót tôi nhận được lỗi sau 'so sánh FalseClass với đúng thất bại', và nếu tôi sử dụng mã đầu tiên bạn đã đề cập, tôi nhận được' so sánh của [MyModule] :: Sản phẩm với [MyModule] :: Sản phẩm thất bại '. Không chắc chắn tại sao điều này sẽ thất bại. – Ecnalyr

+0

Một số lưu ý: 1) Một nên luôn luôn sử dụng 'sort_by' trên' sort' bất cứ khi nào có thể (nhiều khai báo, ngắn gọn hơn, hiệu quả hơn). Có thể sử dụng nó trong tất cả các đoạn được hiển thị ở trên. 2) Nó có thể tôi thích hợp hơn để liên kết với không phá hủy 'Enumerable # sort_by', cập nhật tại chỗ nên tránh bất cứ khi nào có thể. 3) '@ product.sort_by (&: on_sale?)'. – tokland

+0

Có vẻ như '<=> 'không hoạt động đúng với các boolean (trong ruby-1.9.3-p327). Nhìn vào nó ngay bây giờ ... Yeah, http://grosser.it/2010/07/30/ruby-true-false-comparison-with/ xác nhận nó cho một phiên bản trước đó – AlexChaffee

4

@products.sort_by {|product| product.on_sale? ? 0 : 1}

Đây là những gì tôi đã làm khi tôi phải sắp xếp dựa trên booleans.

4

Không cần phải sắp xếp:

products_grouped = @products.partition(&:on_sale?).flatten(1) 
0

Tăng dần và giảm dần có thể được thực hiện bằng cách thay đổi liên "false" và "true"

Products.sort_by {|product| product.on_sale? == true ? "false" : "true" }