user1120144
user1120144

Reputation:

How to filter list on multiple criteria in ruby?

I want to make a filter class in which I have three methods: name, genre, and year. I want to filter a list of movies, in which every object has a name, title and a year, on multiple criteria.

I can't figure out how to do this. For example:

Filter.name("Batman") & Filter.genre("Drama") | !Filter.year(2001)

I tried to keep the filter fields in a list [name => batman, genre=>drama], and so on, but the operators (especially or (|)) are bugging me.

I'm accepting any ideas.

Upvotes: 1

Views: 7739

Answers (3)

the Tin Man
the Tin Man

Reputation: 160551

The functionality you want fits naturally into Enumerable's select, which Array inherits.

Because you're talking about a list, AKA, array, it's a natural fit to use select to determine whether your movies meet your criteria by building upon that, instead of passing them into a method.

A separate Filter class will not fit the Ruby-way as well. Consider sub-classing Array for your movie list, and adding a filter method that takes your criteria as parameters.

Here's how I'd write that functionality, but doing it in a way I consider more Ruby-like:

require 'pp'

class Movies < Array

  def initialize(*movies)
    super(movies)
  end

  def filter(criteria={})
    self.select{ |m| 
      criteria.all?{ |c, v| 
        m.send(c, v) 
      }
    }
  end
end

class Movie

  def initialize(attributes={})
    @movie = attributes
  end

  def name(t)
    case t
    when Regexp
      @movie[:name][t]
    when String
      @movie[:name] == t
    end
  end

  def genre(g)
    @movie[:genre] == g
  end

  def year(y)
    @movie[:year] == y
  end

end

movies = Movies.new(
  Movie.new(name: 'Batman', genre: 'Drama', year: 2001),
  Movie.new(name: 'The Shawshank Redemption', genre: 'Drama', year: 1994),
  Movie.new(name: 'Raiders of the Lost Ark', genre: 'Action', year: 1981)
)

pp movies.filter(year: 1981)
pp movies.filter(genre: 'Drama', year: 2001)
pp movies.filter(name: /redemption/i)

I used a hash for the Movie class, because it's quicker. It's also a dirtier way to go, but it works.

Here's the output of running the code:

[#<Movie:0x007ff7b191ffb8
  @movie={:name=>"Raiders of the Lost Ark", :genre=>"Action", :year=>1981}>]
[#<Movie:0x007ff7b1920260
  @movie={:name=>"Batman", :genre=>"Drama", :year=>2001}>]
[#<Movie:0x007ff7b19200d0
  @movie={:name=>"The Shawshank Redemption", :genre=>"Drama", :year=>1994}>]

The actual flow of filtering the movies fits into how we normally write Ruby code. It could be done even better, but that's a start.

Upvotes: 5

AnandVeeramani
AnandVeeramani

Reputation: 1556

Try this:

let ary contains the list of objects

ary.select{|obj| obj.name == 'batman' && obj.genre == 'drama' || obj.year != 2001}

I have assumed that you have objects in a list, watch out for the && and || operator pririties

Or you expecting this?

class Filter
  def self.search_by(ary, name, genre, year)
    ary.select{|obj| obj.name == name && obj.genre == genre || obj.year != year}
  end
end

ary = all_films # assume function returns all films
Filter.search_by(ary, 'batman', 'drama', 2001)

Upvotes: 8

Jankeesvw
Jankeesvw

Reputation: 710

| is a binary OR operator

|| is the logical OR Operator. If any of the two operands are non zero then then condition becomes true.

So you need to take the latter,

Like this:

Filter.name("Batman") && Filter.genre("Drama") || !Filter.year(2001)

See http://www.tutorialspoint.com/ruby/ruby_operators.htm for more info.

Upvotes: 1

Related Questions