Filter a list based on additional parameter values

Is there a more elegant way to filter a list based on additional parameter values?

def f(dates: List[Date], start: Option[Long], end: Option[Long]): List[Date] = { (start, end) match { case (Some(s), Some(e)) => dates filter (_.getTime > s) filter (_.getTime < e) case (Some(s), None) => dates filter (_.getTime > s) case (None, Some(e)) => dates filter (_.getTime < e) case (None, None) => dates } } 

With three optional parameter values, this will be 9 cases, etc.

+6
source share
6 answers

One way would be as follows:

 def f(dates: List[Date], start: Option[Long], end: Option[Long]): List[Date] = dates.filter( d => start.map(d.getTime > _).getOrElse(true) ) .filter( d => end.map(d.getTime < _).getOrElse(true) ) 

or, even more concise, you can use forall for parameters:

 def f(dates: List[Date], start: Option[Long], end: Option[Long]): List[Date] = dates.filter( d => start.forall(d.getTime > _) ) .filter( d => end.forall(d.getTime < _) ) 
+8
source

In the case of an arbitrary number of filters:

You can first change the parameter to List [Option [Date => Boolean]]. Then connect all the filters that are actually there. Then apply the combined filter.

 def f(dates : List[Date], filters : List[Option[Date => Boolean]]) = { val combFilter = filters.foldLeft((d : Date) => true)((comb, filter) => if(filter.isDefined) (d : Date) => comb(d) && filter.get(d) else comb) dates.filter(combFilter) } 

And if you have dates, a beginning and an end, you can call it like this:

 f(dates, List(start.map(s => _.getTime > s), end.map(e => _.getTime < e)) 
+5
source

I think the key in your question is how to convert the parameters provided to a meaningful condition. Then you can increase this method to any number of parameters (filtering conditions).

Using an internal conversion (so your code knows what to do with the parameter), I would approach like this:

 def f(dates: List[Date], start: Option[Long], end: Option[Long]): List[Date] = { val filters = List(start.map(x=>{y:Long=>y>x}), end.map(x=>{y:Long=>y<x})).flatten dates.flatMap(date => if (filters.forall(filter => filter(date.getTime))) Some(date) else None) } 
+1
source

I agree with colleagues that it is better to have separate filters for each condition. Use the .filter method for .filter . However, you also need to convert Data to Long . This can be done using an implicit converter:

 implicit def dateToLong(d:Date) = d.getTime 

Then you just filter:

 dates.view.filter(_ >= start).filter(_ <= finish) 

If you still need additional filters, then the implicit β€œpimp-my library” may help

 implicit class ListWithOptionalFilters[T](datas:List[T]){ def filterOpt(f:Option[T=>Boolean]) = datas.filter(d => f.forall(d)) } 

Then you do it like this:

 def f(dates: List[Date], start: Option[Long], end: Option[Long]) = { val startFilter = start.map(l => (d:Date) => d>=l) val endFilter = end.map(l => (d:Date) => d<=l) dates.filterOpt(startFilter).filterOpt(endFilter) } 
0
source
 val l1 = startDate.map { d => dates.filter(_ > d) } val l2 = endDate.map { d => dates.filter(_ < d) } l1.getOrElse(dates).intersect(l2.getOrElse(dates)) 
0
source

In this particular case, I would use:

 def f(dates: List[Date], oStart: Option[Long], oEnd: Option[Long]): List[Date] = { val start = oStart.getOrElse(Long.MinValue) val end = oEnd.getOrElse(Long.MaxValue) dates.filter(date => date.getTime > start && date.getTime < end) } 

You can almost certainly generalize some Min / Max Monoid for your start and end arguments and combine them with Ordering to produce a single-line image, but I will leave this as an exercise for the reader.

0
source

Source: https://habr.com/ru/post/956484/


All Articles