2010-06-16 4 views
3

Я хотел бы добавить во все коллекции, где это имеет смысл, метод argMax. Как это сделать? Использовать implicits?Как можно расширить коллекции Scala с помощью метода argmax?

+0

Коллекция не является выражением. Как он может иметь метод argmax? Вы пытаетесь найти самый большой элемент в коллекции? –

ответ

4

На Scala 2.8, это работает:

val list = List(1, 2, 3) 
def f(x: Int) = -x 
val argMax = list max (Ordering by f) 

Как указано на mkneissl, это не верните набор максимальных очков. Вот альтернативная реализация, которая делает и пытается уменьшить количество вызовов до f. Если звонки на f не так уж важны, см. mkneissl's answer. Кроме того, обратите внимание, что его ответ - карри, что обеспечивает превосходный вывод.

def argMax[A, B: Ordering](input: Iterable[A], f: A => B) = { 
    val fList = input map f 
    val maxFList = fList.max 
    input.view zip fList filter (_._2 == maxFList) map (_._1) toSet 
} 

scala> argMax(-2 to 2, (x: Int) => x * x) 
res15: scala.collection.immutable.Set[Int] = Set(-2, 2) 
+0

Удивительно! Большое спасибо. –

+0

Сбой, если f достигает максимального значения для нескольких элементов своего домена. Рассмотрим x => x * x и x от -2 до 2 – mkneissl

+0

@mkneissl А, я вижу. В самом деле. –

1

Вы можете добавить функции в существующий API в Scala, используя шаблон Pimp my Library. Вы делаете это, определяя неявную функцию преобразования. Например, у меня есть класс Vector3 для представления 3D-векторов:

class Vector3 (val x: Float, val y: Float, val z: Float) 

Предположим, что я хочу, чтобы иметь возможность масштабировать вектор, написав что-то вроде: 2.5f * v. Я не могу напрямую добавить метод * к классу Float Ofcourse, но может предоставить неявную функцию преобразования, как это:

implicit def scaleVector3WithFloat(f: Float) = new { 
    def *(v: Vector3) = new Vector3(f * v.x, f * v.y, f * v.z) 
} 

Заметит, что это возвращает объект структурного типа (The new { ... } конструкции), которая содержит * способ.

Я не проверял, но я думаю, вы могли бы сделать что-то вроде этого:

implicit def argMaxImplicit[A](t: Traversable[A]) = new { 
    def argMax() = ... 
} 
2

Да, обычным способом было бы использовать «Тачку библиотеку» шаблон для украшения вашей коллекции. Например (NB только в качестве иллюстрации, не имел в виду, чтобы быть правильным или рабочий пример):


trait PimpedList[A] { 
    val l: List[A] 

    //example argMax, not meant to be correct 
    def argMax[T <% Ordered[T]](f:T => T) = {error("your definition here")} 
} 

implicit def toPimpedList[A](xs: List[A]) = new PimpedList[A] { 
    val l = xs 
} 

scala> def f(i:Int):Int = 10 
f: (i: Int) Int 

scala> val l = List(1,2,3) 
l: List[Int] = List(1, 2, 3) 

scala> l.argMax(f) 
java.lang.RuntimeException: your definition here 
    at scala.Predef$.error(Predef.scala:60) 
    at PimpedList$class.argMax(:12) 
     //etc etc... 
4

Argmax функция (как я понимаю из Wikipedia)

def argMax[A,B](c: Traversable[A])(f: A=>B)(implicit o: Ordering[B]): Traversable[A] = { 
    val max = (c map f).max(o) 
    c filter { f(_) == max } 
} 

Если вы действительно хотите, вы можете сутенер его на коллекции

implicit def enhanceWithArgMax[A](c: Traversable[A]) = new { 
    def argMax[B](f: A=>B)(implicit o: Ordering[B]): Traversable[A] = ArgMax.argMax(c)(f)(o) 
} 

и использовать его как это

val l = -2 to 2 
assert (argMax(l)(x => x*x) == List(-2,2)) 
assert (l.argMax(x => x*x) == List(-2,2)) 

(Scala 2.8)

1

Вот как это сделать с неявным шаблоном построения. Он имеет преимущество перед предыдущими решениями, которые он работает с любым Traversable, и возвращает аналогичный Traversable. К сожалению, это очень важно. Если кто-то захочет, он, вероятно, может быть превращен в довольно уродливую складку.

object RichTraversable { 
    implicit def traversable2RichTraversable[A](t: Traversable[A]) = new RichTraversable[A](t) 
} 

class RichTraversable[A](t: Traversable[A]) { 
    def argMax[That, C](g: A => C)(implicit bf : scala.collection.generic.CanBuildFrom[Traversable[A], A, That], ord:Ordering[C]): That = { 
    var minimum:C = null.asInstanceOf[C] 
    val repr = t.repr 
    val builder = bf(repr) 
    for(a<-t){ 
     val test: C = g(a) 
     if(test == minimum || minimum == null){ 
     builder += a 
     minimum = test 
     }else if (ord.gt(test, minimum)){ 
     builder.clear 
     builder += a 
     minimum = test 
     } 
    } 
    builder.result 
    } 
} 

Set(-2, -1, 0, 1, 2).argmax(x=>x*x) == Set(-2, 2) 
List(-2, -1, 0, 1, 2).argmax(x=>x*x) == List(-2, 2) 
0

Вот вариант, основанный на принятом ответе Даниэля, который также работает для Sets.

def argMax[A, B: Ordering](input: GenIterable[A], f: A => B) : GenSet[A] = argMaxZip(input, f) map (_._1) toSet 

def argMaxZip[A, B: Ordering](input: GenIterable[A], f: A => B): GenIterable[(A, B)] = { 
    if (input.isEmpty) Nil 
    else { 
    val fPairs = input map (x => (x, f(x))) 
    val maxF = fPairs.map(_._2).max 
    fPairs filter (_._2 == maxF) 
    } 
} 

Можно также сделать вариант, который производит (B, Iterable [A]), конечно.

0

Основываясь на других ответах, вы можете легко комбинировать сильные стороны каждого (минимальные звонки на f() и т. Д.). Здесь мы имеем неявное преобразование для всех Iterables (поэтому они могут просто вызвать .argmax() прозрачно) и автономный метод, если по какой-то причине это является предпочтительным. Тесты ScalaTest для загрузки.

class Argmax[A](col: Iterable[A]) { 
    def argmax[B](f: A => B)(implicit ord: Ordering[B]): Iterable[A] = { 
    val mapped = col map f 
    val max = mapped max ord 
    (mapped zip col) filter (_._1 == max) map (_._2) 
    } 
} 

object MathOps { 
    implicit def addArgmax[A](col: Iterable[A]) = new Argmax(col) 

    def argmax[A, B](col: Iterable[A])(f: A => B)(implicit ord: Ordering[B]) = { 
    new Argmax(col) argmax f 
    } 
} 

class MathUtilsTests extends FunSuite { 
    import MathOps._ 

    test("Can argmax with unique") { 
    assert((-10 to 0).argmax(_ * -1).toSet === Set(-10)) 
    // or alternate calling syntax 
    assert(argmax(-10 to 0)(_ * -1).toSet === Set(-10)) 
    } 

    test("Can argmax with multiple") { 
    assert((-10 to 10).argmax(math.pow(_, 2)).toSet === Set(-10, 10)) 
    } 
}