Skip to content

Commit

Permalink
Merge pull request #22 from pellucidanalytics/topic/cols
Browse files Browse the repository at this point in the history
Refactor Frame API to use Cols/Rows.
  • Loading branch information
mrvisser committed Aug 26, 2014
2 parents 5781914 + d853c01 commit dd1673d
Show file tree
Hide file tree
Showing 24 changed files with 1,334 additions and 626 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ scalaVersion in ThisBuild := "2.11.2"

crossScalaVersions in ThisBuild := Seq("2.10.4", "2.11.2")

scalacOptions in ThisBuild ++= Seq("-deprecation", "-feature", "-unchecked")
scalacOptions in ThisBuild ++= Seq("-deprecation", "-feature", "-unchecked", "-language:higherKinds")


maxErrors in ThisBuild := 5
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@ trait JsonExtractor extends JsonModule {

implicit object JsonValueRowExtractor extends RowExtractor[JsonValue, JsonPath, Variable] {
type P = List[(JsonPath, Column[JsonValue])]
def prepare[Row](frame: Frame[Row, JsonPath], cols: List[JsonPath]): Option[P] =
Some(cols map { key =>
key -> frame.column[JsonValue](key).column

def prepare(cols: Series[JsonPath, UntypedColumn], keys: List[JsonPath]): Option[P] =
Some(keys flatMap { key =>
cols(key).value.map(_.cast[JsonValue]).map(key -> _)
})

def extract[Row](frame: Frame[Row, JsonPath], key: Row, row: Int, cols: P): Cell[JsonValue] =
def extract(row: Int, cols: P): Cell[JsonValue] =
Cell.fromOption(inflate(for {
(path, col) <- cols
value <- col.foldRow(row)(Some(_), {
Expand All @@ -52,5 +53,5 @@ trait JsonExtractor extends JsonModule {
}

def frameToJson(frame: Frame[_, JsonPath]): JsonValue =
JsonValue.jsonArray(frame.columns.as[JsonValue].iterator.toVector flatMap (_._2.value))
JsonValue.jsonArray(frame.get(Cols.all.as[JsonValue]).to[Vector] flatMap (_._2.value))
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,6 @@ trait JsonLoader extends JsonModule {

value.getOrElse(Iterable.empty) ++ fields ++ indices
})
Frame(Index(Array.range(0, objs.size)), columns.toList: _*)
ColOrientedFrame(Index(Array.range(0, objs.size)), Series(columns.toSeq: _*))
}
}
8 changes: 6 additions & 2 deletions framian/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ name := "framian"
libraryDependencies ++= {
import Dependencies._
Seq(
Compile.spire, Compile.jodaTime, Compile.jodaConvert,
Test.discipline, Test.specs2, Test.scalaCheck, Test.spireLaws)
Compile.spire,
Test.discipline,
Test.specs2,
Test.scalaCheck,
Test.spireLaws
)
}

libraryDependencies += (
Expand Down
252 changes: 252 additions & 0 deletions framian/src/main/scala/framian/AxisSelection.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
/* _____ _
* | ___| __ __ _ _ __ ___ (_) __ _ _ __
* | |_ | '__/ _` | '_ ` _ \| |/ _` | '_ \
* | _|| | | (_| | | | | | | | (_| | | | |
* |_| |_| \__,_|_| |_| |_|_|\__,_|_| |_|
*
* Copyright 2014 Pellucid Analytics
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package framian

import scala.collection.SortedMap
import scala.reflect.ClassTag

import spire.algebra._
import spire.implicits._

import shapeless._
import shapeless.ops.function._
import shapeless.ops.nat._

trait AxisSelection[K, A] {
def extractorFor(cols: Series[K, UntypedColumn]): (Int => Cell[A])

def foreach[I, U](index: Index[I], cols: Series[K, UntypedColumn])(f: (I, Int, Cell[A]) => U): Unit = {
val get = extractorFor(cols)
index.foreach { (key, row) =>
f(key, row, get(row))
}
}

def cellMap[B](f: Cell[A] => Cell[B]): AxisSelection[K, B]

def map[B](f: A => B): AxisSelection[K, B]
def filter(f: A => Boolean): AxisSelection[K, A]
def recoverWith(pf: PartialFunction[NonValue, Cell[A]]): AxisSelection[K, A]
def recover(pf: PartialFunction[NonValue, A]): AxisSelection[K, A]
}

trait AxisSelectionLike[K, A, This[K, A] <: AxisSelectionLike[K, A, This]] extends AxisSelection[K, A] {
def cellMap[B](f: Cell[A] => Cell[B]): This[K, B]
def orElse(that: This[K, A]): This[K, A]
def flatMap[B](f: A => This[K, B]): This[K, B]
def zipWith[B, C](that: This[K, B])(f: (A, B) => C): This[K, C]

def map[B](f: A => B): This[K, B] = cellMap(_ map f)
def filter(p: A => Boolean): This[K, A] = cellMap(_ filter p)
def recoverWith(pf: PartialFunction[NonValue, Cell[A]]): This[K, A] = cellMap(_ recoverWith pf)
def recover(pf: PartialFunction[NonValue, A]): This[K, A] = cellMap(_ recover pf)
def zip[B](that: This[K, B]): This[K, (A, B)] = zipWith(that)(_ -> _)
}

trait AxisSelectionCompanion[Sel[K, A] <: AxisSelectionLike[K, A, Sel]] {

type All[K, A] <: AllAxisSelection[K, A] with Sel[K, A]
def All: AllCompanion
trait AllCompanion {
def apply[K, A](extractor: RowExtractor[A, K, Variable]): All[K, A]
}

type Pick[K, S <: Size, A] <: PickAxisSelection[K, S, A] with Sel[K, A]
def Pick: PickCompanion
trait PickCompanion {
def apply[K, S <: Size, A](keys: List[K], extractor: RowExtractor[A, K, S]): Pick[K, S, A]
}

type Wrapped[K, A] <: WrappedAxisSelection[K, A] with Sel[K, A]
def Wrapped: WrappedCompanion
trait WrappedCompanion {
def apply[K, A](sel: AxisSelection[K, A]): Wrapped[K, A]
}

sealed trait Bridge[K, A] extends AxisSelectionLike[K, A, Sel] { self: Sel[K, A] =>
def orElse(that: Sel[K, A]): Sel[K, A] =
Wrapped(ops.OrElse[K, A](this, that))
def flatMap[B](f: A => Sel[K, B]): Sel[K, B] =
Wrapped(ops.Bind[K, A, B](this, f))
def zipWith[B, C](that: Sel[K, B])(f: (A, B) => C): Sel[K, C] =
Wrapped(ops.Zipped[K, A, B, C](this, that, (a, b) => a.zipMap(b)(f)))
}

sealed trait SizedAxisSelection[K, Sz <: Size, A] extends Bridge[K, A] { self: Sel[K, A] =>
val extractor: RowExtractor[A, K, Sz]

def getOrElse(all: => List[K]): List[K] = fold(all)(keys => keys)

def extractorFor(cols: Series[K, UntypedColumn]): (Int => Cell[A]) = {
val colKeys = getOrElse(cols.index.keys.toList)
extractor.prepare(cols, colKeys) match {
case Some(p) => { row => extractor.extract(row, p) }
case None => { row => NA }
}
}

override def foreach[I, U](index: Index[I], cols: Series[K, UntypedColumn])(f: (I, Int, Cell[A]) => U): Unit = {
val colKeys = getOrElse(cols.index.keys.toList)
for (p <- extractor.prepare(cols, colKeys)) {
index foreach { (key, row) =>
f(key, row, extractor.extract(row, p))
}
}
}

def fold[B](all: => B)(f: List[K] => B): B
def as[B](implicit extractor0: RowExtractor[B, K, Sz]): Sel[K, B]
}

trait AllAxisSelection[K, A] extends SizedAxisSelection[K, Variable, A] { self: Sel[K, A] =>
val extractor: RowExtractor[A, K, Variable]
def fold[B](all: => B)(f: List[K] => B): B = all

def cellMap[B](f: Cell[A] => Cell[B]): All[K, B] =
All(extractor cellMap f)

def as[B](implicit extractor0: RowExtractor[B, K, Variable]): Sel[K, B] =
All(extractor0)
def asListOf[B](implicit extractor0: RowExtractor[B, K, Fixed[Nat._1]]): Sel[K, List[Cell[B]]] =
as(RowExtractor.collectionOf)
def asVectorOf[B](implicit extractor0: RowExtractor[B, K, Fixed[Nat._1]]): Sel[K, Vector[Cell[B]]] =
as(RowExtractor.collectionOf)
def asArrayOf[B: ClassTag](implicit extractor0: RowExtractor[B, K, Fixed[Nat._1]]): Sel[K, Array[B]] =
as(RowExtractor.denseCollectionOf)
}

trait PickAxisSelection[K, S <: Size, A] extends SizedAxisSelection[K, S, A] { self: Sel[K, A] =>
val keys: List[K]
val extractor: RowExtractor[A, K, S]

def fold[B](all: => B)(f: List[K] => B): B = f(keys)

def cellMap[B](f: Cell[A] => Cell[B]): Pick[K, S, B] =
Pick(keys, extractor cellMap f)

def variable: Pick[K, Variable, A] = this.asInstanceOf[Pick[K, Variable, A]]

def as[B](implicit extractor0: RowExtractor[B, K, S]): Sel[K, B] =
Pick(keys, extractor0)
def asListOf[B](implicit extractor0: RowExtractor[B, K, Fixed[Nat._1]]): Sel[K, List[Cell[B]]] =
variable.as(RowExtractor.collectionOf)
def asVectorOf[B](implicit extractor0: RowExtractor[B, K, Fixed[Nat._1]]): Sel[K, Vector[Cell[B]]] =
variable.as(RowExtractor.collectionOf)
def asArrayOf[B: ClassTag](implicit extractor0: RowExtractor[B, K, Fixed[Nat._1]]): Sel[K, Array[B]] =
variable.as(RowExtractor.denseCollectionOf)
}

trait WrappedAxisSelection[K, A] extends Bridge[K, A] { self: Sel[K, A] =>
def sel: AxisSelection[K, A]

def cellMap[B](f: Cell[A] => Cell[B]): Sel[K, B] =
Wrapped(sel.cellMap(f))

def extractorFor(cols: Series[K, UntypedColumn]): (Int => Cell[A]) =
sel.extractorFor(cols)

override def foreach[I, U](index: Index[I], cols: Series[K, UntypedColumn])(f: (I, Int, Cell[A]) => U): Unit =
sel.foreach(index, cols)(f)
}

object ops {
trait Op[K, A] extends AxisSelection[K, A] {
def cellMap[B](f: Cell[A] => Cell[B]): AxisSelection[K, B]

def map[B](f: A => B): AxisSelection[K, B] = cellMap(_ map f)
def filter(p: A => Boolean): AxisSelection[K, A] = cellMap(_ filter p)
def recoverWith(pf: PartialFunction[NonValue, Cell[A]]): AxisSelection[K, A] = cellMap(_ recoverWith pf)
def recover(pf: PartialFunction[NonValue, A]): AxisSelection[K, A] = cellMap(_ recover pf)
}

case class Zipped[K, A, B, C](
fst: AxisSelection[K, A],
snd: AxisSelection[K, B],
combine: (Cell[A], => Cell[B]) => Cell[C]
) extends Op[K, C] {

def extractorFor(cols: Series[K, UntypedColumn]): (Int => Cell[C]) = {
val get1 = fst.extractorFor(cols)
val get2 = snd.extractorFor(cols)

{ row => combine(get1(row), get2(row)) }
}

def cellMap[D](f: Cell[C] => Cell[D]): AxisSelection[K, D] =
Zipped[K, A, B, D](fst, snd, { (a, b) => f(combine(a, b)) })
}

def OrElse[K, A](fst: AxisSelection[K, A], snd: AxisSelection[K, A]): AxisSelection[K, A] =
Zipped[K, A, A, A](fst, snd, _ orElse _)

case class Bind[K, A, B](
sel: AxisSelection[K, A],
k: A => AxisSelection[K, B]
) extends Op[K, B] {
def extractorFor(cols: Series[K, UntypedColumn]): (Int => Cell[B]) = {
val get = sel.extractorFor(cols)

{ row => get(row).map(k).flatMap(_.extractorFor(cols)(row)) }
}

def cellMap[C](f: Cell[B] => Cell[C]): AxisSelection[K, C] =
Bind[K, A, C](sel, { a => k(a).cellMap(f) })
}
}

import Nat._

def all[K] = All[K, Rec[K]](RowExtractor[Rec[K], K, Variable])

def unsized[K](cols: Seq[K]) =
Pick(cols.toList, RowExtractor[Rec[K], K, Variable])

def sized[K, N <: Nat](s: Sized[List[K], N]): Pick[K, Fixed[N], Rec[K]] =
Pick(s.unsized, RowExtractor[Rec[K], K, Fixed[N]])

def apply[K](c0: K): Pick[K, Fixed[_1], Rec[K]] =
sized(Sized[List](c0))

def apply[K](c0: K, c1: K): Pick[K, Fixed[_2], Rec[K]] =
sized(Sized[List](c0, c1))

def apply[K](c0: K, c1: K, c2: K, c3: K): Pick[K, Fixed[_4], Rec[K]] =
sized(Sized[List](c0, c1, c2, c3))

def apply[K](c0: K, c1: K, c2: K, c3: K, c4: K): Pick[K, Fixed[_5], Rec[K]] =
sized(Sized[List](c0, c1, c2, c3, c4))

def apply[K](c0: K, c1: K, c2: K, c3: K, c4: K, c5: K): Pick[K, Fixed[_6], Rec[K]] =
sized(Sized[List](c0, c1, c2, c3, c4, c5))

def apply[K](c0: K, c1: K, c2: K, c3: K, c4: K, c5: K, c6: K): Pick[K, Fixed[_7], Rec[K]] =
sized(Sized[List](c0, c1, c2, c3, c4, c5, c6))

def apply[K](c0: K, c1: K, c2: K, c3: K, c4: K, c5: K, c6: K, c7: K): Pick[K, Fixed[_8], Rec[K]] =
sized(Sized[List](c0, c1, c2, c3, c4, c5, c6, c7))

def apply[K](c0: K, c1: K, c2: K, c3: K, c4: K, c5: K, c6: K, c7: K, c8: K): Pick[K, Fixed[_9], Rec[K]] =
sized(Sized[List](c0, c1, c2, c3, c4, c5, c6, c7, c8))

def apply[K](c0: K, c1: K, c2: K, c3: K, c4: K, c5: K, c6: K, c7: K, c8: K, c9: K): Pick[K, Fixed[_10], Rec[K]] =
sized(Sized[List](c0, c1, c2, c3, c4, c5, c6, c7, c8, c9))
}
22 changes: 22 additions & 0 deletions framian/src/main/scala/framian/Cell.scala
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,28 @@ sealed trait Cell[+A] {
@inline final def filterNot(p: A => Boolean): Cell[A] =
if (isNonValue || !p(this.get)) this else NA

/** If this cell is a [[NonValue]] and `pf` is defined for it, then this
* will return `Value(pf(this))`, otherwise it will return this cell as-is.
*
* @param pf the partial function to map the non-value.
* @see [[recoverWith]]
*/
final def recover[A0 >: A](pf: PartialFunction[NonValue, A0]): Cell[A0] = this match {
case (nonValue: NonValue) if pf isDefinedAt nonValue => Value(pf(nonValue))
case value => value
}

/** If this cell is a [[NonValue]] and `pf` is defined for it, then this
* will return `pf(this)`, otherwise it will return this cell as-is.
*
* @param pf the partial function to map the non-value.
* @see [[recover]]
*/
final def recoverWith[A0 >: A](pf: PartialFunction[NonValue, Cell[A0]]): Cell[A0] = this match {
case (nonValue: NonValue) if pf isDefinedAt nonValue => pf(nonValue)
case value => value
}

/** Returns true if this [[Cell]]'s value is available and
* meaningful ''and'' the predicate `p` returns true when applied
* to that value. Otherwise, returns false.
Expand Down
41 changes: 41 additions & 0 deletions framian/src/main/scala/framian/Cols.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* _____ _
* | ___| __ __ _ _ __ ___ (_) __ _ _ __
* | |_ | '__/ _` | '_ ` _ \| |/ _` | '_ \
* | _|| | | (_| | | | | | | | (_| | | | |
* |_| |_| \__,_|_| |_| |_|_|\__,_|_| |_|
*
* Copyright 2014 Pellucid Analytics
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package framian

sealed trait Cols[K, A] extends AxisSelectionLike[K, A, Cols] {
def toRows: Rows[K, A] = this match {
case Cols.All(e) => Rows.All(e)
case Cols.Pick(keys, e) => Rows.Pick(keys, e)
case Cols.Wrapped(sel) => Rows.Wrapped(sel)
}
}

object Cols extends AxisSelectionCompanion[Cols] {
case class All[K, A](extractor: RowExtractor[A, K, Variable]) extends Cols[K, A] with AllAxisSelection[K, A]
object All extends AllCompanion

case class Pick[K, S <: Size, A](keys: List[K], extractor: RowExtractor[A, K, S]) extends Cols[K, A] with PickAxisSelection[K, S, A]
object Pick extends PickCompanion

case class Wrapped[K, A](sel: AxisSelection[K, A]) extends Cols[K, A] with WrappedAxisSelection[K, A]
object Wrapped extends WrappedCompanion
}
Loading

0 comments on commit dd1673d

Please sign in to comment.