Как расширить коллекции Scala методом argmax? - PullRequest
3 голосов
/ 16 июня 2010

Я хотел бы добавить ко всем коллекциям, где это имеет смысл, метод argMax . Как это сделать? Использовать импликации?

Ответы [ 7 ]

4 голосов
/ 16 июня 2010

Функция argmax (насколько я понимаю из Википедия )

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)

4 голосов
/ 16 июня 2010

В 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 .Также обратите внимание, что его ответ каррирован, что обеспечивает превосходный вывод типа.

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)
2 голосов
/ 16 июня 2010

Да, обычным способом было бы использовать шаблон 'pimp my library' для украшения вашей коллекции.Например ( 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...
1 голос
/ 23 июня 2010

Вот способ сделать это с помощью неявного шаблона построителя. Он имеет преимущество перед предыдущими решениями в том, что работает с любым 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)
1 голос
/ 16 июня 2010

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

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

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

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

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

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

implicit def argMaxImplicit[A](t: Traversable[A]) = new {
    def argMax() = ...
}
0 голосов
/ 01 ноября 2012

Основываясь на других ответах, вы можете довольно легко объединить сильные стороны каждого (минимальные вызовы на 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))
  }
}
0 голосов
/ 11 сентября 2012

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

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]).

...