Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[6.0] New collection methods #1010

Merged
merged 13 commits into from
Oct 7, 2024
25 changes: 25 additions & 0 deletions core/shared/src/main/scala/sigma/Colls.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,19 @@ trait Coll[@specialized A] {
*/
def apply(i: Int): A

/** The element at given index or None if there is no such element. Indices start at `0`.
*
* @param i the index
* @return the element at the given index, or None if there is no such element
*/
def get(i: Int): Option[A] = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ScalaDoc doesn't correspond to the method.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

if (isDefinedAt(i)) {
Some(apply(i))
} else {
None
}
}

/** Tests whether this $coll contains given index.
*
* The implementations of methods `apply` and `isDefinedAt` turn a `Coll[A]` into
Expand Down Expand Up @@ -76,6 +89,18 @@ trait Coll[@specialized A] {
* produces a collection ((x0, y0), ..., (xK, yK)) where K = min(N, M) */
def zip[@specialized B](ys: Coll[B]): Coll[(A, B)]

/**
* @return true if first elements of this collection form given `ys` collection, false otherwise.
* E.g. [1,2,3] starts with [1,2]
*/
def startsWith(ys: Coll[A]): Boolean
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ScalaDoc is not correct.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


/**
* @return true if last elements of this collection form given `ys` collection, false otherwise.
* E.g. [1,2,3] ends with [2,3]
*/
def endsWith(ys: Coll[A]): Boolean
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ScalaDoc is not correct.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


/** Tests whether a predicate holds for at least one element of this collection.
* @param p the predicate used to test elements.
* @return `true` if the given predicate `p` is satisfied by at least one element of this collection, otherwise `false`
Expand Down
14 changes: 14 additions & 0 deletions core/shared/src/main/scala/sigma/data/CollsOverArrays.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ class CollOverArray[@specialized A](val toArray: Array[A], val builder: CollBuil

@inline def zip[@specialized B](ys: Coll[B]): PairColl[A, B] = builder.pairColl(this, ys)

@inline def startsWith(ys: Coll[A]): Boolean = toArray.startsWith(ys.toArray)

@inline def endsWith(ys: Coll[A]): Boolean = toArray.endsWith(ys.toArray)

def append(other: Coll[A]): Coll[A] = {
if (toArray.length <= 0) return other
val result = if (VersionContext.current.isJitActivated) {
Expand Down Expand Up @@ -350,6 +354,16 @@ class PairOfCols[@specialized L, @specialized R](val ls: Coll[L], val rs: Coll[R

def zip[@specialized B](ys: Coll[B]): PairColl[(L,R), B] = builder.pairColl(this, ys)

def startsWith(ys: Coll[(L, R)]): Boolean = ys match {
case yp: PairOfCols[L, R] => ls.startsWith(yp.ls) && rs.startsWith(yp.rs)
case _ => toArray.startsWith(ys.toArray)
}

def endsWith(ys: Coll[(L, R)]): Boolean = ys match {
case yp: PairOfCols[L, R] => ls.endsWith(yp.ls) && rs.endsWith(yp.rs)
case _ => toArray.endsWith(ys.toArray)
}

override def indices: Coll[Int] = if (ls.length <= rs.length) ls.indices else rs.indices

override def flatMap[B: RType](f: ((L, R)) => Coll[B]): Coll[B] =
Expand Down
15 changes: 15 additions & 0 deletions core/shared/src/main/scala/sigma/reflection/ReflectionData.scala
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ object ReflectionData {
mkMethod(clazz, "apply", Array[Class[_]](classOf[Int])) { (obj, args) =>
obj.asInstanceOf[Coll[_]].apply(args(0).asInstanceOf[Int])
},
mkMethod(clazz, "get", Array[Class[_]](classOf[Int])) { (obj, args) =>
obj.asInstanceOf[Coll[_]].get(args(0).asInstanceOf[Int])
},
mkMethod(clazz, "append", Array[Class[_]](classOf[Coll[_]])) { (obj, args) =>
obj.asInstanceOf[Coll[Any]].append(args(0).asInstanceOf[Coll[Any]])
},
Expand All @@ -170,6 +173,18 @@ object ReflectionData {
},
mkMethod(clazz, "map", Array[Class[_]](classOf[Function1[_, _]], classOf[RType[_]])) { (obj, args) =>
obj.asInstanceOf[Coll[Any]].map(args(0).asInstanceOf[Any => Any])(args(1).asInstanceOf[RType[Any]])
},
mkMethod(clazz, "reverse", Array[Class[_]]()) { (obj, args) =>
obj.asInstanceOf[Coll[Any]].reverse
},
mkMethod(clazz, "distinct", Array[Class[_]]()) { (obj, args) =>
obj.asInstanceOf[Coll[Any]].distinct
},
mkMethod(clazz, "startsWith", Array[Class[_]](classOf[Coll[_]])) { (obj, args) =>
obj.asInstanceOf[Coll[Any]].startsWith(args(0).asInstanceOf[Coll[Any]])
},
mkMethod(clazz, "endsWith", Array[Class[_]](classOf[Coll[_]])) { (obj, args) =>
obj.asInstanceOf[Coll[Any]].endsWith(args(0).asInstanceOf[Coll[Any]])
}
)
)
Expand Down
26 changes: 26 additions & 0 deletions core/shared/src/test/scala/sigma/CollsTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,32 @@ class CollsTests extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers
}
}

property("Coll.startsWith") {
val minSuccess = minSuccessful(50)
forAll(collGen, minSuccess) { col =>
val n = col.length / 2
val prefix = col.take(n)
val pairs = col.zip(col)
pairs.startsWith(prefix.zip(prefix)) shouldBe true
col.startsWith(prefix) shouldBe true
val pairOfCols = new PairOfCols[Int, Int](col, col)
pairOfCols.startsWith(pairOfCols.take(n)) shouldBe true
}
}

property("Coll.endsWith") {
val minSuccess = minSuccessful(50)
forAll(collGen, minSuccess) { col =>
val n = col.length / 2
val suffix = col.slice(n, col.length)
col.endsWith(suffix) shouldBe true
val pairs = col.zip(col)
pairs.endsWith(suffix.zip(suffix)) shouldBe true
val pairOfCols = new PairOfCols[Int, Int](col, col)
pairOfCols.endsWith(pairOfCols.slice(n, col.length)) shouldBe true
}
}

property("Coll.equals") {
def checkColls(repl: Coll[_], coll: Coll[_]) = {
assert(coll == repl)
Expand Down
16 changes: 16 additions & 0 deletions data/shared/src/main/scala/sigma/SigmaDataReflection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,22 @@ object SigmaDataReflection {
mkMethod(clazz, "flatMap_eval", Array[Class[_]](classOf[MethodCall], classOf[Coll[_]], classOf[Function1[_,_]], classOf[ErgoTreeEvaluator])) { (obj, args) =>
obj.asInstanceOf[SCollectionMethods.type].flatMap_eval(args(0).asInstanceOf[MethodCall],
args(1).asInstanceOf[Coll[Any]], args(2).asInstanceOf[Any => Coll[Any]])(args(3).asInstanceOf[ErgoTreeEvaluator])
},
mkMethod(clazz, "reverse_eval", Array[Class[_]](classOf[MethodCall], classOf[Coll[_]], classOf[ErgoTreeEvaluator])) { (obj, args) =>
obj.asInstanceOf[SCollectionMethods.type].reverse_eval(args(0).asInstanceOf[MethodCall],
args(1).asInstanceOf[Coll[Any]])(args(2).asInstanceOf[ErgoTreeEvaluator])
},
mkMethod(clazz, "distinct_eval", Array[Class[_]](classOf[MethodCall], classOf[Coll[_]], classOf[ErgoTreeEvaluator])) { (obj, args) =>
obj.asInstanceOf[SCollectionMethods.type].distinct_eval(args(0).asInstanceOf[MethodCall],
args(1).asInstanceOf[Coll[Any]])(args(2).asInstanceOf[ErgoTreeEvaluator])
},
mkMethod(clazz, "startsWith_eval", Array[Class[_]](classOf[MethodCall], classOf[Coll[_]], classOf[Coll[_]], classOf[ErgoTreeEvaluator])) { (obj, args) =>
obj.asInstanceOf[SCollectionMethods.type].startsWith_eval(args(0).asInstanceOf[MethodCall],
args(1).asInstanceOf[Coll[Any]], args(2).asInstanceOf[Coll[Any]])(args(3).asInstanceOf[ErgoTreeEvaluator])
},
mkMethod(clazz, "endsWith_eval", Array[Class[_]](classOf[MethodCall], classOf[Coll[_]], classOf[Coll[_]], classOf[ErgoTreeEvaluator])) { (obj, args) =>
obj.asInstanceOf[SCollectionMethods.type].endsWith_eval(args(0).asInstanceOf[MethodCall],
args(1).asInstanceOf[Coll[Any]], args(2).asInstanceOf[Coll[Any]])(args(3).asInstanceOf[ErgoTreeEvaluator])
}
)
)
Expand Down
149 changes: 126 additions & 23 deletions data/shared/src/main/scala/sigma/ast/methods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1020,7 +1020,7 @@ object SCollectionMethods extends MethodsContainer with MethodByNameUnapply {
SFunc(Array(ThisType, tIV, SInt), SInt, paramIVSeq),
26, PerItemCost(baseCost = JitCost(20), perChunkCost = JitCost(10), chunkSize = 2))
.withIRInfo(MethodCallIrBuilder, javaMethodOf[Coll[_], Any, Int]("indexOf"))
.withInfo(MethodCall, "")
.withInfo(MethodCall, "Returns index of a collection element, or -1 if not found")

/** Implements evaluation of Coll.indexOf method call ErgoTree node.
* Called via reflection based on naming convention.
Expand Down Expand Up @@ -1052,8 +1052,7 @@ object SCollectionMethods extends MethodsContainer with MethodByNameUnapply {
baseCost = JitCost(10), perChunkCost = JitCost(1), chunkSize = 10)

val ZipMethod = SMethod(this, "zip",
SFunc(Array(ThisType, tOVColl), SCollection(STuple(tIV, tOV)), Array[STypeParam](tIV, tOV)),
29, Zip_CostKind)
SFunc(Array(ThisType, tOVColl), SCollection(STuple(tIV, tOV)), Array[STypeParam](tIV, tOV)), 29, Zip_CostKind)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall, "")

Expand All @@ -1069,29 +1068,133 @@ object SCollectionMethods extends MethodsContainer with MethodByNameUnapply {
}
}

// ======== 6.0 methods below ===========

private val reverseCostKind = Append.costKind

val ReverseMethod = SMethod(this, "reverse",
SFunc(Array(ThisType), ThisType, paramIVSeq), 30, reverseCostKind)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall, "")

/** Implements evaluation of Coll.reverse method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def reverse_eval[A](mc: MethodCall, xs: Coll[A])
(implicit E: ErgoTreeEvaluator): Coll[A] = {
val m = mc.method
E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], xs.length, m.opDesc) { () =>
xs.reverse
}
}

private val distinctCostKind = PerItemCost(baseCost = JitCost(60), perChunkCost = JitCost(5), chunkSize = 100)

val DistinctMethod = SMethod(this, "distinct",
SFunc(Array(ThisType), ThisType, paramIVSeq), 31, distinctCostKind)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall, "Returns inversed collection.")

/** Implements evaluation of Coll.reverse method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def distinct_eval[A](mc: MethodCall, xs: Coll[A])
(implicit E: ErgoTreeEvaluator): Coll[A] = {
val m = mc.method
E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], xs.length, m.opDesc) { () =>
xs.distinct
}
}

private val startsWithCostKind = Zip_CostKind

val StartsWithMethod = SMethod(this, "startsWith",
SFunc(Array(ThisType, ThisType), SBoolean, paramIVSeq), 32, startsWithCostKind)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall, "Returns true if this collection starts with given one, false otherwise.",
ArgInfo("prefix", "Collection to be checked for being a prefix of this collection."))

/** Implements evaluation of Coll.zip method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def startsWith_eval[A](mc: MethodCall, xs: Coll[A], ys: Coll[A])
(implicit E: ErgoTreeEvaluator): Boolean = {
val m = mc.method
E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], xs.length, m.opDesc) { () =>
xs.startsWith(ys)
}
}

private val endsWithCostKind = Zip_CostKind

val EndsWithMethod = SMethod(this, "endsWith",
SFunc(Array(ThisType, ThisType), SBoolean, paramIVSeq), 33, endsWithCostKind)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall, "Returns true if this collection ends with given one, false otherwise.",
ArgInfo("suffix", "Collection to be checked for being a suffix of this collection."))

/** Implements evaluation of Coll.zip method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def endsWith_eval[A](mc: MethodCall, xs: Coll[A], ys: Coll[A])
(implicit E: ErgoTreeEvaluator): Boolean = {
val m = mc.method
E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], xs.length, m.opDesc) { () =>
xs.endsWith(ys)
}
}

val GetMethod = SMethod(this, "get",
SFunc(Array(ThisType, SInt), SOption(tIV), Array[STypeParam](tIV)), 34, ByIndex.costKind)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"Returns Some(element) if there is an element at given index, None otherwise.",
ArgInfo("index", "Index of an element (starting from 0).")
)

private val v5Methods = super.getMethods() ++ Seq(
SizeMethod,
GetOrElseMethod,
MapMethod,
ExistsMethod,
FoldMethod,
ForallMethod,
SliceMethod,
FilterMethod,
AppendMethod,
ApplyMethod,
IndicesMethod,
FlatMapMethod,
PatchMethod,
UpdatedMethod,
UpdateManyMethod,
IndexOfMethod,
ZipMethod
)

private val v6Methods = v5Methods ++ Seq(
ReverseMethod,
DistinctMethod,
StartsWithMethod,
EndsWithMethod,
GetMethod
)

/** This method should be overriden in derived classes to add new methods in addition to inherited.
* Typical override: `super.getMethods() ++ Seq(m1, m2, m3)`
*/
override protected def getMethods(): Seq[SMethod] = super.getMethods() ++
Seq(
SizeMethod,
GetOrElseMethod,
MapMethod,
ExistsMethod,
FoldMethod,
ForallMethod,
SliceMethod,
FilterMethod,
AppendMethod,
ApplyMethod,
IndicesMethod,
FlatMapMethod,
PatchMethod,
UpdatedMethod,
UpdateManyMethod,
IndexOfMethod,
ZipMethod
)
override protected def getMethods(): Seq[SMethod] = {
if (VersionContext.current.isV6SoftForkActivated) {
v6Methods
} else {
v5Methods
}
}

}

object STupleMethods extends MethodsContainer {
Expand Down
13 changes: 13 additions & 0 deletions sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,19 @@ trait GraphBuilding extends Base with DefRewriting { IR: IRContext =>
val i = asRep[Int](argsV(0))
val d = asRep[t](argsV(1))
xs.getOrElse(i, d)
case SCollectionMethods.ReverseMethod.name =>
xs.reverse
case SCollectionMethods.DistinctMethod.name =>
xs.distinct
case SCollectionMethods.StartsWithMethod.name =>
val ys = asRep[Coll[t]](argsV(0))
xs.startsWith(ys)
case SCollectionMethods.EndsWithMethod.name =>
val ys = asRep[Coll[t]](argsV(0))
xs.endsWith(ys)
case SCollectionMethods.GetMethod.name =>
val idx = asRep[Int](argsV(0))
xs.get(idx)
case _ => throwError()
}
case (opt: ROption[t]@unchecked, SOptionMethods) => method.name match {
Expand Down
16 changes: 16 additions & 0 deletions sc/shared/src/main/scala/sigma/compiler/ir/GraphIRReflection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,22 @@ object GraphIRReflection {
},
mkMethod(clazz, "exists", Array[Class[_]](classOf[Base#Ref[_]])) { (obj, args) =>
obj.asInstanceOf[ctx.Coll[Any]].exists(args(0).asInstanceOf[ctx.Ref[Any => Boolean]])
},
// V6 methods
mkMethod(clazz, "reverse", Array[Class[_]]()) { (obj, _) =>
obj.asInstanceOf[ctx.Coll[Any]].reverse
},
mkMethod(clazz, "distinct", Array[Class[_]]()) { (obj, _) =>
obj.asInstanceOf[ctx.Coll[Any]].distinct
},
mkMethod(clazz, "startsWith", Array[Class[_]](classOf[Base#Ref[_]])) { (obj, args) =>
obj.asInstanceOf[ctx.Coll[Any]].startsWith(args(0).asInstanceOf[ctx.Ref[ctx.Coll[Any]]])
},
mkMethod(clazz, "endsWith", Array[Class[_]](classOf[Base#Ref[_]])) { (obj, args) =>
obj.asInstanceOf[ctx.Coll[Any]].endsWith(args(0).asInstanceOf[ctx.Ref[ctx.Coll[Any]]])
},
mkMethod(clazz, "get", Array[Class[_]](classOf[Base#Ref[_]])) { (obj, args) =>
obj.asInstanceOf[ctx.Coll[_]].apply(args(0).asInstanceOf[ctx.Ref[Int]])
}
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import sigma.compiler.ir.{Base, IRContext}
implicit def eA: Elem[A];
def length: Ref[Int];
def apply(i: Ref[Int]): Ref[A];
def get(index: Ref[Int]): Ref[WOption[A]];
def getOrElse(index: Ref[Int], default: Ref[A]): Ref[A];
def map[B](f: Ref[scala.Function1[A, B]]): Ref[Coll[B]];
def zip[B](ys: Ref[Coll[B]]): Ref[Coll[scala.Tuple2[A, B]]];
Expand All @@ -29,6 +30,10 @@ import sigma.compiler.ir.{Base, IRContext}
def updateMany(indexes: Ref[Coll[Int]], values: Ref[Coll[A]]): Ref[Coll[A]];
def slice(from: Ref[Int], until: Ref[Int]): Ref[Coll[A]];
def append(other: Ref[Coll[A]]): Ref[Coll[A]];
def reverse: Ref[Coll[A]]
def distinct: Ref[Coll[A]]
def startsWith(ys: Ref[Coll[A]]): Ref[Boolean];
def endsWith(ys: Ref[Coll[A]]): Ref[Boolean];
};
trait CollBuilder extends Def[CollBuilder] {
def fromItems[T](items: Ref[T]*)(implicit cT: Elem[T]): Ref[Coll[T]];
Expand Down
Loading
Loading