Как написать функцию диапазона кортежа в Scala? - PullRequest
7 голосов
/ 03 октября 2011

Я хочу следующую функцию range((1,1), (2,2)) которые возвращают

Seq[(Int,Int)]((1,1),(1,2),(2,1),(2,2))

Это аналог для одномерного диапазона с 1 to 2

Функция должна работать для любого кортежа scala (т. Е. Tuple2, Tuple3, Tuple4, ...) и быть безопасной для типов.

Я пробовал с

    def tupleRange[T <: Product](t1:T, t2:T):Seq[T] = {
        assert(t1.productArity == t2.productArity)
        def tail(t:Product):Product = sys.error("todo"); 
        def join(i:Int, p:Product):T = sys.error("todo");
        for(
v <- t1.productElement(0).asInstanceOf[Int] to t2.productElement(0).asInstanceOf[Int]; 
v2 <- tupleRange(tail(t1), tail(t2)))
            yield join(v,v2)
    }
    implicit def range[T <:Product](p1:T) = new { def to(p2:T) = tupleRange(p1,p2)}

Но я думаю, что выбрал неправильное направление.

Ответы [ 5 ]

7 голосов
/ 03 октября 2011

Я бы предложил то же самое, что @ziggystar предложил выше. Используйте List[Int] вместо кортежей Int s.

scala> import scalaz._
import scalaz._

scala> import Scalaz._
import Scalaz._

scala> def range(xs: List[Int], ys: List[Int]): List[List[Int]] = {
     |   (xs, ys).zipped.map((x, y) => List.range(x, y + 1)).sequence
     | }
range: (xs: List[Int], ys: List[Int])List[List[Int]]

scala> range(List(1, 2, 4), List(2, 5, 6))
res29: List[List[Int]] = List(List(1, 2, 4), List(1, 2, 5), List(1, 2, 6), 
List(1, 3, 4), List(1, 3, 5), List(1, 3, 6), List(1, 4, 4), List(1, 4, 5), 
List(1, 4, 6), List(1, 5, 4), List(1, 5, 5), List(1, 5, 6), List(2, 2, 4), 
List(2, 2, 5), List(2, 2, 6), List(2, 3, 4), List(2, 3, 5), List(2, 3, 6), 
List(2, 4, 4), List(2, 4, 5), List(2, 4, 6), List(2, 5, 4), List(2, 5, 5), 
List(2, 5, 6))

В этой реализации предполагается, что xs и ys упорядочены и имеют одинаковую длину.

4 голосов
/ 03 октября 2011

Во-первых, подумайте:

scala> 1 to 10
res0: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

Было бы неплохо иметь что-то подобное для кортежей, верно?

class RangedTuple(t: Tuple2[Int, Int]) {
  def to(t2: Tuple2[Int, Int]) = {
    (t, t2) match {
      case ((a1: Int, a2: Int), (b1: Int, b2: Int)) => {
        (for {
          i <- a1 to b1
        } yield (a1 to b1).map(j => (i, j))).flatMap(k => k)
      }
    }
  }
}

implicit def t2rt(t: Tuple2[Int, Int]) = new RangedTuple(t)

Это дает вам следующее:

scala> (1, 1) to (2, 2)
res1: scala.collection.immutable.IndexedSeq[(Int, Int)] = Vector((1,1), (1,2), (2,1), (2,2))

scala> (1, 1) to (3, 3)
res2: scala.collection.immutable.IndexedSeq[(Int, Int)] = Vector((1,1), (1,2), (1,3), (2,1), (2,2), (2,3), (3,1), (3,2), (3,3))

Это работает для вас?

2 голосов
/ 03 октября 2011

Вам потребуется отдельная версия для каждой кортежа (но вы можете использовать препроцессор для генерации каждой версии).Вот моя реализация (которая ленива):

def range2( range: Range ): Seq[(Int,Int)] = range.toStream.map( i => (i,i) )

Вы можете использовать его как:

scala> range2( 1 to 10 )
res3: Seq[(Int, Int)] = Stream((1,1), ?)
1 голос
/ 04 октября 2011

Как насчет Итератора и использования двух Seq вместо двух кортежей для инициализации?

Вот декартово класс , расширяющий Итератор .

def rangeIterator (froms: Seq[Int], tos: Seq[Int]) = {

  def range (froms: Seq[Int], tos: Seq[Int]) : Seq[Seq[Int]] = 
    if (froms.isEmpty) Nil else 
    Seq (froms.head to tos.head) ++ range (froms.tail, tos.tail) 

  new Cartesian (range (froms, tos))
}  

использование:

scala> val test = rangeIterator (Seq(1, 1), Seq(2, 2))
test: Cartesian = non-empty iterator    
scala> test.toList 
res38: List[Seq[_]] = List(List(1, 1), List(2, 1), List(1, 2), List(2, 2))

scala> val test = rangeIterator (Seq(1, 0, 9), Seq(2, 2, 11))
test: Cartesian = non-empty iterator
scala> test.toList 
res43: List[Seq[_]] = List(List(1, 0, 9), List(2, 0, 9), List(1, 1, 9), List(2, 1, 9), List(1, 2, 9), List(2, 2, 9), List(1, 0, 10), List(2, 0, 10), List(1, 1, 10), List(2, 1, 10), List(1, 2, 10), List(2, 2, 10), List(1, 0, 11), List(2, 0, 11), List(1, 1, 11), List(2, 1, 11), List(1, 2, 11), List(2, 2, 11))
1 голос
/ 04 октября 2011

Простой способ с большим количеством вырезок и вставок, перегрузка метода для каждой артерии кортежей:

def range(r: (Int, Int), s: (Int, Int)) = 
  for { p1 <- r._1 to s._1
        p2 <- r._2 to s._2 } yield (p1, p2)

def range(r: (Int, Int, Int), s: (Int, Int, Int)) = 
  for { p1 <- r._1 to s._1
        p2 <- r._2 to s._2 
        p3 <- r._3 to s._3 } yield (p1, p2, p3)

def range(r: (Int, Int, Int, Int), s: (Int, Int, Int, Int)) =    
  for // etc up to 22

В качестве альтернативы:

def range(p1: Product, p2: Product) = {

  def toList(t: Product): List[Int] = 
    t.productIterator.toList.map(_.asInstanceOf[Int])

  def toProduct(lst: List[Int]) = lst.size match {
    case 1 => Tuple1(lst(0))
    case 2 => Tuple2(lst(0), lst(1))
    case 3 => Tuple3(lst(0), lst(1), lst(2))
    //etc up to 22
  }

  def go(xs: List[Int], ys: List[Int]): List[List[Int]] = {
    if(xs.size == 1 || ys.size == 1) (xs.head to ys.head).toList.map(List(_))
    else (xs.head to ys.head).toList.flatMap(i => go(xs.tail, ys.tail).map(i :: _))
  }

  go(toList(p1), toList(p2)) map toProduct
} 

кажется, работает:

scala> range((1,2,4), (2,5,6))
res66: List[Product with Serializable] = List((1,2,4), (1,2,5), (1,2,6), 
(1,3,4), (1,3,5), (1,3,6), (1,4,4), (1,4,5), (1,4,6), (1,5,4), (1,5,5),
(1,5,6), (2,2,4), (2,2,5), (2,2,6), (2,3,4), (2,3,5), (2,3,6), (2,4,4), 
(2,4,5), (2,4,6), (2,5,4), (2,5,5), (2,5,6))

Ваша основная проблема заключается в том, что, поскольку Scala имеет статическую типизацию, метод должен иметь тип возвращаемого значения, поэтому у вас никогда не будет единственного метода, который возвращает и Seq[(Int, Int)], и Seq[(Int, Int, Int)], и все другие артерии кортежа. , Лучшее, что вы можете сделать, - это использовать ближайший тип, который охватывает все выходы, в данном случае Product with Serializable. Вы можете, конечно, выполнить приведение к результату, например, res0.map(_.asInstanceOf[(Int, Int, Int)]).

Перегрузка метода, как в первом примере, позволяет вам использовать другой тип возвращаемого значения для каждой арности, поэтому вам не нужно выполнять приведение.

...