Поиск элементов в списке scala, а также знание того, какой предикат был удовлетворен

У меня есть следующая проблема в scala. Мне нужно найти первый элемент в списке всех, который удовлетворяет функции предиката с двумя условиями в ИЛИ. Проблема в том, что я хотел бы получить элемент, но также знать, какое из двух условий было выполнено. Вот простой пример:

val l1 = List("A", "B", "AA", "BB")
val l2 = List("AA", "BB", "A", "B")

def c1(s: String) = s.startsWith("B")
def c2(s: String) = s.length == 2

println(l1.find(s => c1(s) || c2(s)))
println(l2.find(s => c1(s) || c2(s)))

результат:

Some(B)
Some(AA)

Для случая l1 я хотел бы иметь некоторое возвращаемое значение (например, строку), указывающее, что c1 был удовлетворен (c2 для случая l2). Возможным решением может быть определение var перед тестом и установка его в функциях c1 и c2, но я хотел бы найти решение в более «функциональном стиле», возможно, что-то, что возвращает кортеж, например: (элемент найден, условие выполнено ).

Заранее спасибо за помощь


person Filippo Tabusso    schedule 01.02.2010    source источник


Ответы (3)


я бы сделал так:

Скала 2.8:

def find2p[T](l: List[T], p1: T => Boolean, p2: T => Boolean) = 
  l.view.map(el => (el, p1(el), p2(el))).find(t => t._2 || t._3)

Скала 2.7:

def find2p[T](l: List[T], p1: T => Boolean, p2: T => Boolean) = 
  l.projection.map(el => (el, p1(el), p2(el))).find(t => t._2 || t._3)

view/projection гарантирует, что сопоставление будет выполняться по запросу, а не применяться ко всему списку.

person Daniel C. Sobral    schedule 01.02.2010
comment
Обобщается на список предикатов def findPredsOr[T](l: List[T], ps: List[T => Boolean]): Option[(T, List[Boolean])] = l.view.map(el => (el, ps.map(_.apply(el)))).find(t => t._2.contains(true)) - person retronym; 01.02.2010
comment
Отличное решение. Спасибо, что позволили мне открыть вид/проекцию, это кажется очень полезным! - person Filippo Tabusso; 01.02.2010

def find[T](l1 : List[T], c1 : T => Boolean, c2 : T => Boolean) = ((None : Option[(String, T)]) /: l1)( (l, n) => l match {
    case x : Some[_] => l
    case x if c1(n) => Some("c1", n)
    case x if c2(n) => Some("c2", n)
    case _ => None
})

scala> find(l1, c1, c2)
res2: Option[(String, java.lang.String)] = Some((c1,B))

scala> find(l2, c1, c2)
res3: Option[(String, java.lang.String)] = Some((c2,AA))

В зависимости от ваших требований вы можете иметь параметр Map[T => Boolean, String] для возвращаемых строк меток: def find[T](l1 : List[T], fs : Map[T => Boolean, String]) или определить свои собственные операторы.

Это оценит весь список, где поиск прерывается для первого найденного элемента.

person Thomas Jung    schedule 01.02.2010

Вот вариант ответов Даниэля (и Ретронима).

Если вам просто нужен предикат (из списка), который был успешным, вы можете использовать

def findP[T](list: Iterable[T], preds: Iterable[T=>Boolean]) = {
  list.view.map( x => (x , preds.find( _(x) )) ).find( _._2.isDefined )
}

В качестве альтернативы вы можете использовать список именованных предикатов:

def findP[T](list: Iterable[T],preds: Iterable[(T=>Boolean,String)]) = {
  list.view.map(x => (x , preds.find( _._1(x) ))).find( _._2.isDefined )
}

scala> findP(
     |   List(1,2,3,4,5,6),
     |   List( ((i:Int)=>i>4,"Fred") , ((i:Int)=>(i%6)==0,"Barney"))
     | )
res2: Option[(Int, Option[((Int) => Boolean, String)])] =
  Some((5,Some((<function1>,Fred))))

Результат немного загроможден, но его можно достаточно легко развернуть, чтобы получить именно то, что вы просили:

def findP[T](list: Iterable[T],preds: Iterable[(T=>Boolean,String)]) = {
  list.view.map(x => (x , preds.find( _._1(x) ))).find( _._2.isDefined ) match {
    case Some((i,Some((_,s)))) => Some((i,s))
    case _ => None
  }
}

(Это код для 2.8; переключите «вид» на «проекцию» для 2.7.)

person Rex Kerr    schedule 01.02.2010