How to write ConstraintStream code in scala

53 views
Skip to first unread message

spea...@gmail.com

unread,
Jul 24, 2020, 10:58:35 AM7/24/20
to OptaPlanner development
Hi,

I am wondering how to write this piece of code in Scala?

private Constraint roomConflict(ConstraintFactory constraintFactory) { // A room can accommodate at most one lesson at the same time. // Select a lesson ... return constraintFactory.from(Lesson.class) // ... and pair it with another lesson ... .join(Lesson.class, // ... in the same timeslot ... Joiners.equal(Lesson::getTimeslot), // ... in the same room ... Joiners.equal(Lesson::getRoom), // ... and the pair is unique (different id, no reverse pairs) Joiners.lessThan(Lesson::getId)) // then penalize each pair with a hard weight. .penalize("Room conflict", HardSoftScore.ONE_HARD); }


Lesson.class in java can be translated to classOf[Lesson], but scala does not support Lesson::getTimeslot, what to do?


Thanks


Stephen

Christopher Chianelli

unread,
Jul 24, 2020, 11:27:28 AM7/24/20
to OptaPlanner development
Does Scala support lambda expressions? I imagine it look like this:

private Constraint roomConflict(ConstraintFactory constraintFactory) { // A room can accommodate at most one lesson at the same time.
// Select a lesson ...
return constraintFactory.from(Lesson.class)
                        // ... and pair it with another lesson ...
                        .join(Lesson.class,
                        // ... in the same timeslot ...
                              Joiners.equal((l: Lesson) => l.getTimeslot()),
                              // ... in the same room ...
                              Joiners.equal((l: Lesson) => l.getRoom()),
                              // ... and the pair is unique (different id, no reverse pairs)
                              Joiners.lessThan((l: Lesson) => l.getId()))
                              // then penalize each pair with a hard weight.
                        .penalize("Room conflict", HardSoftScore.ONE_HARD);
}

I never programmed in Scala, so there a chance there a syntax error somewhere.

spea...@gmail.com

unread,
Jul 24, 2020, 11:47:53 AM7/24/20
to OptaPlanner development
Does not work. Scala does support lambda expression everywhere. I am confused what Joiners.equal expect as an input.

Christopher Chianelli

unread,
Jul 24, 2020, 12:01:52 PM7/24/20
to OptaPlanner development
Joiners.equal expect a single argument that is a Function from Lesson to X (Where X is any type) (there is also a variant that takes two functions).

What does your IDE report as an error message?

spea...@gmail.com

unread,
Jul 24, 2020, 3:22:55 PM7/24/20
to OptaPlanner development
My InteliJ error:

spea...@gmail.com

unread,
Jul 24, 2020, 3:30:50 PM7/24/20
to OptaPlanner development
I found the error disappear when I give a type parameter Lesson to the join function. That may have solved the problem.

def roomConflict(constraintFactory: ConstraintFactory): Constraint = {
constraintFactory.from(classOf[Lesson])

// ... and pair it with another lesson ...
    .join[Lesson](classOf[Lesson],
      // ... in the same timeslot ...
      Joiners.equal((l: Lesson) => l.getTimeslot),
      // ... in the same room ...
      Joiners.equal((l: Lesson) => l.getRoom),
      // ... and the pair is unique (different id, no reverse pairs)
      Joiners.lessThan((l: Lesson) => l.getId))

// then penalize each pair with a hard weight.
.penalize("Room conflict", HardSoftScore.ONE_HARD)
}

Christopher Chianelli

unread,
Jul 24, 2020, 3:41:47 PM7/24/20
to OptaPlanner development
I think that was the issue, given the error message. My guess to why this error occurred: Scala might not automatically infer type information from arguments.
That is, given the method signature

public <X,Y> BiJoiner<X,Y> join(Function<X,Y> map)

a call to the method (1)

join((l: Lesson) => l.getTimeslot) // (1)

Scala does not infer that the type of (1) must be BiJoiner<Lesson, Timeslot>, but instead infer BiJoiner<Nothing, Nothing>.
However, Java does infer the type of (1) must be BiJoiner<Lesson, Timeslot>.

spea...@gmail.com

unread,
Jul 24, 2020, 5:44:22 PM7/24/20
to OptaPlanner development
Yes it indeed solve the problem. except that somehow I have to convert id from Long to String, because it's giving me some error: inferred type arguments [mytimetable.Lesson,Long] do not conform to method lessThan's type parameter bounds [A,Property_ <: Comparable[Property_]]

[error]         Joiners.lessThan((l: Lesson) => l.id))


def roomConflict(constraintFactory: ConstraintFactory): Constraint = {
constraintFactory.from(classOf[Lesson])
// ... and pair it with another lesson ...
.join[Lesson](classOf[Lesson],
// ... in the same timeslot ...
      Joiners.equal((l: Lesson) => l.timeslot),
      // ... in the same room ...
      Joiners.equal((l: Lesson) => l.room),
      // ... and the pair is unique (different id, no reverse pairs)
      Joiners.lessThan((l: Lesson) => l.id.toString))

// then penalize each pair with a hard weight.
.penalize("Room conflict", HardSoftScore.ONE_HARD)
}

Geoffrey De Smet

unread,
Jul 25, 2020, 2:25:28 PM7/25/20
to optapla...@googlegroups.com

There are users using ConstraintStreams in Kotlin, for example:
  https://twitter.com/fabOnTw/status/1271424534084816903
I'd love to see some scala examples too.

The trick is to understand the method signatures indeed.

inferred type arguments [mytimetable.Lesson,Long] do not conform to method lessThan's type parameter bounds [A,Property_ <: Comparable[Property_]]

It seems to say that Scala's Long doesn't implement Comparable ?!?

In any case, don't do id.toString(): no need to do that.

With kind regards,
Geoffrey De Smet

--
You received this message because you are subscribed to the Google Groups "OptaPlanner development" group.
To unsubscribe from this group and stop receiving emails from it, send an email to optaplanner-d...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/optaplanner-dev/11aaf0d2-056b-40e2-af0d-5f13b0746d61o%40googlegroups.com.

spea...@gmail.com

unread,
Jul 26, 2020, 10:04:50 PM7/26/20
to OptaPlanner development
I hope there is more scala example of writing constraints. For example, I am stuck again in writing a groupBy constraints.

Let's say I want to write a constraint to say that "each skater can at most skate 3 sessions a day", I would think something like the following but I have hard time get the groupBy working, I think I got the key part right, which is a Tuple2, but what type parameter do I specify in count[?]():

def skaterSkateAtMostThreeSessionsADay(constraintFactory: ConstraintFactory): Constraint = {
import java.time.DayOfWeek
constraintFactory.from(classOf[Session])
.groupBy[Tuple2[Skater,DayOfWeek]]((l:Session) => (l.skaternth.skater,l.timeslot.dayOfWeek),count[Tuple2[Skater,DayOfWeek]]())
.filter((skater:Tuple2[Skater,DayOfWeek],count:Int) => count>3)
.penalize("skaterSkateAtMostThriceADay", HardSoftScore.ONE_HARD)
}

To unsubscribe from this group and stop receiving emails from it, send an email to optapla...@googlegroups.com.

Lukas Petrovicky

unread,
Jul 27, 2020, 3:07:52 AM7/27/20
to optapla...@googlegroups.com
On Mon, Jul 27, 2020 at 4:05 AM <spea...@gmail.com> wrote:
def skaterSkateAtMostThreeSessionsADay(constraintFactory: ConstraintFactory): Constraint = {
import java.time.DayOfWeek
constraintFactory.from(classOf[Session])
.groupBy[Tuple2[Skater,DayOfWeek]]((l:Session) => (l.skaternth.skater,l.timeslot.dayOfWeek),count[Tuple2[Skater,DayOfWeek]]())

At this point, you have a UniStream. (from() will result in a UniConstraintStream.)
Therefore, the groupBy you're trying to implement has the following signature:


You need to code against that signature in Scala. This should not require anything specific - just provide a Function<Session> and a UniConstraintCollector<Session, ?, Integer> instance, most likely obtained via the ConstraintCollectors class.
 
    .filter((skater:Tuple2[Skater,DayOfWeek],count:Int) => count>3)

At this point, you have a BiConstraintStream. Therefore, the filter signature in question is this:

In other words, the filter requires you to provide a BiPredicate<DayOfWeek, Integer>.
Doing it in Scala is not any different from using any other Java code in your application.
 
-- 

Lukáš Petrovický

He/Him/His

Principal Software Engineer, Business Automation

Red Hat Czech, s. r. o.

lukas.pe...@redhat.com    IM: triceo/lpetrovi

spea...@gmail.com

unread,
Jul 27, 2020, 9:55:10 AM7/27/20
to OptaPlanner development
The groupBy is exactly where I am having trouble, compiler does not accept the second argument I pass to groupBy:

val x: UniConstraintCollector[(Skater, DayOfWeek), _, Integer] = count[(Skater, DayOfWeek)]()
constraintFactory.from(classOf[Session])
.groupBy[Tuple2[Skater,DayOfWeek]]((l:Session) => (l.skaternth.skater,l.timeslot.dayOfWeek),x)

lukas.p...@redhat.com    IM: triceo/lpetrovi

Lukas Petrovicky

unread,
Jul 27, 2020, 10:04:29 AM7/27/20
to optapla...@googlegroups.com
On Mon, Jul 27, 2020 at 3:55 PM <spea...@gmail.com> wrote:
The groupBy is exactly where I am having trouble, compiler does not accept the second argument I pass to groupBy:

I already gave an explanation for why. The collector in question would be of type UniConstraintCollector<Session, ?, Integer>.

I am not familiar with Scala syntax, but your Collector type appears to use Skater and DayOfWeek, as opposed to just Session.
You're counting Session instances that share the same Skater and DayOfWeek - and the Tuple takes care of that. But the collector still operates on the original Session, not on the Tuple.

--

Lukáš Petrovický

He/Him/His

Principal Software Engineer, Business Automation

Red Hat Czech, s. r. o.

lukas.pe...@redhat.com    IM: triceo/lpetrovi

spea...@gmail.com

unread,
Jul 27, 2020, 11:08:57 AM7/27/20
to OptaPlanner development
I tried using UniConstraintCollector<Session,?,Integer> before, and it has the same error, that is why I am trying all kinds of possibilities for A in UniConstraintCollector<A,?,Integer>:

val x: UniConstraintCollector[Session, _, Integer] = count[Session]()

constraintFactory.from(classOf[Session])
.groupBy[Tuple2[Skater,DayOfWeek]]((l:Session) => (l.skaternth.skater,l.timeslot.dayOfWeek),x)

lukas.p...@redhat.com    IM: triceo/lpetrovi

spea...@gmail.com

unread,
Jul 27, 2020, 12:10:52 PM7/27/20
to OptaPlanner development
I found if I do not pass type parameter to groupBy, it will work as following.

constraintFactory.from(classOf[Session])
.groupBy((l:Session) => (l.skaternth.skater,l.timeslot.dayOfWeek),count[Session]())
.filter((_,count) => count>3)

But why would I need to pass type parameter in join below but can not pass type parameter in groupBy?

constraintFactory.from(classOf[Lesson])

// ... and pair it with another lesson ...
.join[Lesson](classOf[Lesson],
// ... in the same timeslot ...
Joiners.equal((l: Lesson) => l.getTimeslot),
// ... in the same room ...
Joiners.equal((l: Lesson) => l.getRoom),
// ... and the pair is unique (different id, no reverse pairs)
Joiners.lessThan((l: Lesson) => l.getId))

Lukas Petrovicky

unread,
Jul 28, 2020, 6:26:41 AM7/28/20
to optapla...@googlegroups.com
On Mon, Jul 27, 2020 at 6:11 PM <spea...@gmail.com> wrote:
But why would I need to pass type parameter in join below but can not pass type parameter in groupBy?

from() requires you to provide a class so that we know what objects to go through. It decides what's going to come into the stream.
With join(), you're essentially doing another from() - so the same rule applies.
With groupBy(), we already have all the inputs from earlier, and therefore we don't need to ask for their types.

--

Lukáš Petrovický

He/Him/His

Principal Software Engineer, Business Automation

Red Hat Czech, s. r. o.

lukas.pe...@redhat.com    IM: triceo/lpetrovi

spea...@gmail.com

unread,
Jul 29, 2020, 9:41:37 AM7/29/20
to OptaPlanner development
Thanks for the explanation, its all clear.

lukas.p...@redhat.com    IM: triceo/lpetrovi

Reply all
Reply to author
Forward
0 new messages