I am trying to remove classes with identical fields from a collection using foldLeft
function in Scala.
For example, a Box
class is defined as: class Box(color: String = "", size: Int = 0)
Let's create a simple collection Seq(Box, Box, Box)
and try to filter it by the size
field.
val s = Seq(Box("green", 1), Box("blue", 1), Box("red", 3))
s.foldLeft(Seq(s.head)){ (boxes, nextBox) =>
if (boxes.last.size != nextBox.size) { // throws an exception
boxes : nextBox
} else {
boxes
}
}
The above code throws an exception: value size cannot be accessed as a member of Box
.
The above code works fine with primitive types and for Seq(1, 1, 3)
will produce 1, 3
.
It doesn't help if you explicitly tell the compiler (boxes: Seq[Box], nextBox: Box)
, it doesn't help even if you cast the value returned by last
to Box
.
Can you please explain why?
CodePudding user response:
The above code throws an exception: value size cannot be accessed as a member of Box.
Because Box
doesn't have any public field called size
; it has a constructor parameter called size
which is different.
You can fix that doing class Box(color: String = "", val size: Int = 0)
to make the constructor argument a field; or by using a case class that does that for you (and much more).
PS: It doesn't throw an exception, it fails with a compiler error; two very different things.
Also, what you are trying to do is just distinctBy
; as I always say the Scaladoc is your friend.
final case class Box(color: String = "", size: Int = 0)
val result = boxes.distinctBy(_.size)
Since you seem very new to the language and with many basic errors / misconceptions I would advise you to pick some course or read a book about the language. Also, I would encourage you to join the Scala Discord server where you may ask questions and get more interactive help.
CodePudding user response:
Some code must be missing from your description, since you can't call Box(...)
if Box
is just a class
. You could if it were a case class
and then size would be accessible too, since a case class exposes all it's constructor arguments as members, whereas a standard class does not unless explicitly annotated with val
:
scala> case class Box(color: String = "", size: Int = 0)
class Box
scala> val s = Seq(Box("green", 1), Box("green", 1), Box("green", 3))
val s: Seq[Box] = List(Box(green,1), Box(green,1), Box(green,3))
scala> s.foldLeft(Seq(s.head)){ (boxes, nextBox) =>
| if (boxes.last.size != nextBox.size) {
| boxes : nextBox
| } else {
| boxes
| }
| }
val res0: Seq[Box] = List(Box(green,1), Box(green,3))
Alternatively (notice the new of val
in the class constructor and new
to instantiate them):
scala> class Box(val color: String = "", val size: Int = 0)
class Box
scala> val s = Seq(new Box("green", 1), new Box("green", 1), new Box("green", 3))
val s: Seq[Box] = List(Box@159424e2, Box@29bcf51d, Box@1e54a6b1)
scala> s.foldLeft(Seq(s.head)){ (boxes, nextBox) =>
| if (boxes.last.size != nextBox.size) {
| boxes : nextBox
| } else {
| boxes
| }
| }
val res0: Seq[Box] = List(Box@159424e2, Box@1e54a6b1)