• Post Reply
  • Bookmark Topic Watch Topic
  • New Topic

Polymorphism again. (When the method depends on both what it is passed, and who calls it.)

 
Stevens Miller
Bartender
Posts: 1377
28
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I have an interface, Hull, and two implementations: SquareHull and RoundHull. A SquareHull overlaps a RoundHull, but not another SquareHull. A RoundHull never overlaps anything.

The Hull interface is simple:



Here's the program I'd like to run to test my implementations:



RoundHull's implementation is also simple:



It is SquareHull that is giving me problems. I can't provide two implementations of overlaps, as that fails to implement the interface as it is defined:



Nor can I implement the method correctly (in terms of its signature) and delegate polymorphically:



Now, if this were really my problem, there would be easy ways to make it work. But my real problem requires that the overlaps method behave very differently when a SquareHull calls it on a RoundHull versus when a SquareHull calls it on another SquareHull. Both implementations must compare all kinds of characteristics of both objects before returning a result. Thus, the method being called depends on both the caller and what the caller passes.

I thought polymorphism could get me out of this, but is there no other way than to declare all the implementations as explicit parameters in my inteface, like this?



That seems awful!

Another case of feeling like I'm missing something obvious.

Am I?
 
Ron McLeod
Bartender
Pie
Posts: 1032
65
Android Eclipse IDE Java Linux MySQL Database Redhat
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Couldn't you make a decision based on the type of the other hull?

 
Stevens Miller
Bartender
Posts: 1377
28
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Ron McLeod wrote:Couldn't you make a decision based on the type of the other hull?


Sure, that works fine, but it's what I meant when I said there were better ways to do this if it were as simple as my example. My actual problem involves querying the parameter for a number of things, then comparing them to data stored in the object that calls its own overlaps method. The issue is that the steps involved when a SquareHull needs to run overlaps on a RoundHull are utterly different than when a SquareHull runs overlaps on another SquareHull. At the level of client code that treats SquareHulls and RoundHulls as just Hulls, I want that client code to be able to take any two Hulls, h1 and h2, and be able to call h1.overlaps(h2) without regard to which implementations of Hull h1 and h2 actually are, and always get a meaningful result.
 
Stevens Miller
Bartender
Posts: 1377
28
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Now, I suppose I could have each implementation of overlaps query the parameter for its type and branch accordingly, maybe something like this:

But isn't that exactly the kind of structure that says polymorphism should be employed? I'm just not quite seeing how.
 
Ron McLeod
Bartender
Pie
Posts: 1032
65
Android Eclipse IDE Java Linux MySQL Database Redhat
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
How about using Annotations to markup the properties for the various hull designs?

With this approach it would probably be better to extend an AbstractHull class or have a Helper class rather than implementing the same code in each hull type.
 
Dave Tolls
Ranch Hand
Posts: 2095
15
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
The issue here is that you have classes that implement Hull that are tightly coupled.
The SquareHull class has to know of the existence of other Hull types in order to do its job, in this case the RoundHull.
To me that says the class is more a property and might be better represented as (say) an enum, in which you can then apply the relevant logic.

Now, this applies purely to the example given.
If, as you say, the actual logic is more complex then without seeing it it's going to be hard to give a solution, but as things stand here this is a property of a Hull and not some different class.
 
Stevens Miller
Bartender
Posts: 1377
28
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Dave Tolls wrote:The issue here is that you have classes that implement Hull that are tightly coupled.
The SquareHull class has to know of the existence of other Hull types in order to do its job, in this case the RoundHull.
To me that says the class is more a property and might be better represented as (say) an enum, in which you can then apply the relevant logic.

That seems like a promising line of thought, Dave. Are you saying that, rather than having two classes that implement Hull, I might have one Hull class that behaves differently depending upon whether or not it has been told its "type" (say) property is Round or Square?

I'm thinking you have something like this in mind:

But I'm thinking that's not quite what you are suggesting, as you said, "an enum, in which you can then apply the relevant logic." (emphasis added). I know I can have methods in an enum, but I'm not seeing how that applies here just yet. I'll mull it over further and see if I can do better, but if you can nudge me in the right direction, I'd be grateful.
 
Dave Tolls
Ranch Hand
Posts: 2095
15
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Bear in mind that I don't actually know the requirements here, or what logic is involved exactly.
Not too sure I understand the concept of overlapping hulls, or how that fits in with the problem as a whole.

The reason I say that the logic should sit with the shapes (somehow) is because...well...the shapes seem to be the thing that drives this.

An enum with a method that does the magic, makes more sense to me in terms of keeping related details together than sticking the same logic in the Hull code.

Again, take this with a pinch of salt...
 
Junilu Lacar
Bartender
Pie
Posts: 8784
81
Android Eclipse IDE IntelliJ IDE Java Linux Mac Scala Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
You're getting warmer, I think. Try to encapsulate the overlapping rules in the HullShape enum instead. That is, in the HullShape enum definition, you would have a List of HullShape tuples that can overlap each other. Any pairing that's not in that list don't overlap.
 
Junilu Lacar
Bartender
Pie
Posts: 8784
81
Android Eclipse IDE IntelliJ IDE Java Linux Mac Scala Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

See, no F-in instanceof!

Edit: in case you saw it before my edit, I had the wrong method signature for Pair.equals() -- corrected now.

Another note: maybe Pair should be a private static final class.  Sorry for the many edits in rapid succession. This code is just off the top of my head so you should try it before you buy it. 
 
Dave Tolls
Ranch Hand
Posts: 2095
15
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
And a big round of applause for my assistant, ladies and gentlemen!


(I'd have done it a little differently, but the concept's the same, and it was possibly down to differing interpretations)
 
Tobias Bachert
Ranch Hand
Posts: 43
7
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Small addition to Junilus idea: a map might be more practicable for this case:
 
Junilu Lacar
Bartender
Pie
Posts: 8784
81
Android Eclipse IDE IntelliJ IDE Java Linux Mac Scala Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Since List.contains() uses equals() to check for inclusion of an object, you can get away with not overriding Pair.hashCode(); just depends on how you feel about that kind of "risky" behavior. I would do it but would probably leave a reminder as a comment:
 
Stevens Miller
Bartender
Posts: 1377
28
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I really appreciate the help, Dave (Edit: Junilu and Tobias contributed while I was writing this comment. Your help is also much appreciated!). I realize it's hard to be specific without more details, but the actual problem is complicated enough that I wanted to start by abstracting out what I think the question is in a more general way.

To get into the nitty-gritty, what I am working on is an old problem: I want to know if two shapes in the same flat plane overlap each other in whole or in part. The complication I am coping with is that the method I use to make this determination depends on both of the types of the two shapes in question.

In all, I have three different types of shape, each of which I call a "hull." The three types are: circular, rectangular, and polygonal.

A circular hull is fully defined by the x and y coordinates of its center, and its radius.

A rectangular hull is fully defined by the xL and xR coordinates of its left and right sides, and the yT and yB coordinates of its top and bottom. (Note that a rectangular hull's left and right sides are always parallel to the Y axis, and its top and bottom sides are always parallel to the X axis.)

A polygonal hull is fully defined by an ordered, finite set of three or more coordinate pairs, {(x0, y0), (x1, y1) ... (xN-1, yN-1)}, N>2.

Now, given a single hull and a finite list of zero or more other hulls, I'd like to be able to iterate over that list and simply ask this: The problem I'm addressing arises from the fact that the way one determines if, say, a rectangular hull overlaps another rectangular hull has pretty much nothing in common with the way one determines if, say, that same rectangular hull overlaps a circular hull. Likewise, the way one determines if a polygonal hull overlaps another polygonal hull has little in common with how one determines if a polygonal hull overlaps a circular hull. Likewise, how one determines if two circular hulls overlap has little in common with how one determines if a rectangular hull overlaps a polygonal hull.

But, from the point of view of client code, the only question that matters is, "Do these two hulls overlap?"

Now, as you observed earlier, my implementations must know about each other. If a rectangular hull is asked, "Do you overlap this other hull?," that rectangular hull must know if that other hull is another rectangular hull, or if it is a circular hull, or if it is a polygonal hull, as the method by which it will answer the question, "Do you overlap this other hull?" will be chosen based on which of those three types of hull the other hull is. Just as you said, that's a tight coupling among the implementations. Yet, I don't want the client code to have to cope with it, as the question from the client's perspective continues to be, simply, "Do these two hulls overlap?"

In thinking about this, I came up with this (rather obvious) little table:

Possible Hull Type Pairs First Hull Type
CircRectPoly
Second Hull TypeCircCirc/CircRect/CircPoly/Circ
RectCirc/RectRect/RectPoly/Rect
PolyCirc/PolyRect/PolyPoly/Poly

I had thought that one method could do the jobs of two pairs, where the types are just switched (Circ/Rect and Rect/Circ, for example), but that wouldn't permit me to make the methods class methods. I think they'd have to be static.

Thus, it seemed to me that each type of hull would need three distinct methods, such that the circular hull must have a method for testing overlap against another circular hull, a second method for testing overlap against a rectangular hull, and a third method for testing overlap against a polygonal hull. Likewise, a rectangular hull would also need three distinct methods: a method for testing overlap against a circular hull, a second method for testing overlap against another rectangular hull, and a third method for testing overlap against a polygonal hull. Same for the polygonal hull. (To avoid repeating myself, these might, indeed, all call static methods, swapping the parameter positions as necessary to use a total of six, rather than nine, methods.)

So that's the more complete statement of the problem. Each type of hull knows what kind it is, and knows how to test itself against each of the three types for overlap. But the testing method is completely different for each of the other three types, regardless of the fact that client code treats all hulls the same way.

This initially looked to me like the kind of thing polymorphism could handle, but that coupling you identified has made it more complicated than I thought.

Does that give you any further ideas I might pursue?
 
Junilu Lacar
Bartender
Pie
Posts: 8784
81
Android Eclipse IDE IntelliJ IDE Java Linux Mac Scala Spring Ubuntu
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Yeah, Map is a good alternative. I just didn't like the idea of double entry to account for the fact that you were only concerned with combinations, not permutations, in HullShape pairings. But being lazy to do "double entry" into a Map forces me to program the "combinations not permutations" logic into equals(). When I tried it just now, it became apparent that it was going to be a big PITA, so I'd recommend going with Tobias' idea. It pays to have good partners when doing virtual mob programming. 

* PITA = Pain In The A$$
 
Junilu Lacar
Bartender
Pie
Posts: 8784
81
Android Eclipse IDE IntelliJ IDE Java Linux Mac Scala Spring Ubuntu
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I wrote:When I tried it just now, it became apparent that it was going to be a big PITA

It's also a nice reminder that no matter how simple you think your solution is, there's probably something that's even simpler. You just need a different perspective to see it.
 
Stevens Miller
Bartender
Posts: 1377
28
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Trying to consolidate the overlap testing entirely into an enum, I have come up with this horror:



Now, this permits me to add new subclasses of Hull without having to track down the source for every existing subclass and add the necessary overlap methods. Instead, I only have to add those methods to the HullShape enum. It also lets my client code do what I want it to, which is ask about overlapping Hulls without regard to which specific type of hull any hull is. However, the overlaps(Hull, Hull) method in the HullShapes enum is a gawdawful ugly mess.

Is this approach viable? Is it even defensible?
 
Junilu Lacar
Bartender
Pie
Posts: 8784
81
Android Eclipse IDE IntelliJ IDE Java Linux Mac Scala Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stevens Miller wrote:I have come up with this horror:

...the overlaps(Hull, Hull) method in the HullShapes enum is a gawdawful ugly mess.

Is this approach viable? Is it even defensible?

horror - agree
gawdawful ugly mess - darn straight
viable? - define "viable"...
defensible? I don't think so

What's wrong with the general approach that Tobias suggested? I wouldn't go all hardcore with unmodifiable stuff and everything since it's a private class member and you have full control over how it's accessed. I'd go with:

This way, if you have new rules or new HullShape constants, you just need to add another line in the static initialization block and/or update the existing sets to specify which shapes overlap the new shape.

This enum should have ZERO references to your Hull class. In your Hull class:

 
Tobias Bachert
Ranch Hand
Posts: 43
7
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Assuming that your methods may not be able to simply return true or false -> if you have to perform some kind of calculations to determine the result:
You could replace the huge switch block with a map containing bi-predicates which should scale better when the count of options increases:

Below an example with a Map<Class<?>, Map<Class<?>...
 
Junilu Lacar
Bartender
Pie
Posts: 8784
81
Android Eclipse IDE IntelliJ IDE Java Linux Mac Scala Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Come to think of it, you don't even need a Map<HullShape, Set<HullShape>>, you can just have Map<HullShape>, List<HullShape>> and init it in the static block like:

I don't know if you're anticipating a future explosion of new HullShape types but...
in a tweet, Sandi Metz wrote:Don't write code that guesses the future, arrange code so you can adapt to the future when it arrives.

https://twitter.com/sandimetz/status/441241600077725697

 
Piet Souris
Rancher
Posts: 1526
32
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Just entered this topic... what is the question? Is it whether two Hulls CAN overlap, or whether they DO overlap?
 
Stevens Miller
Bartender
Posts: 1377
28
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Junilu Lacar wrote:What's wrong with the general approach that Tobias suggested?

Nothing, except that he has taken my vastly over-simplified example too literally. His solution presumes that something I said about certain shapes always overlapping other shapes means you can store the true/falses in a table and look them up. That's what happens when I try to pare down the problem so that people don't get confused by the details of the implementation of the entire project.

My original statement was, alas, misleading, and Tobias has figured that out. See next comment from me for more.
 
Stevens Miller
Bartender
Posts: 1377
28
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Tobias Bachert wrote:Assuming that your methods may not be able to simply return true or false -> if you have to perform some kind of calculations to determine the result:

That's it exactly. My original example made it look like the answer was always the same for any two hulls of particular types. In the actual project, any two hulls of any two types must be testable as a pair for whether or not they intersect, and the only way to find out is to look at their particular geometry (including size, shape, and location) and "perform some kind of calculation." The problem I'm having is that the "kind of calculation" is a function of the two types of hull being tested, and that calculation varies considerably between different pairs of types.

You could replace the huge switch block with a map containing bi-predicates which should scale better when the count of options increases

Very interesting suggestion. Let me study that a bit before I say more about it (other than, "thanks again").
 
Junilu Lacar
Bartender
Pie
Posts: 8784
81
Android Eclipse IDE IntelliJ IDE Java Linux Mac Scala Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Yes, I think Tobias is on to something there with the functional interfaces. You might even be able to "register" method references if you can find the right incantation for your method signatures.  Just off the top of my head, I'm thinking of a HullOverlapChecker class. A number of these would be put into a chain of responsibility like what you have with servlet filters, then you pass any pairing of two Hulls to that chain. The HullPair would get passed down the chain until one of the HullOverlapChecker instances recognizes it as a pairing that it knows how to evaluate "overlap" on, at which point it calculates the result and signals "HANDLED!" to the chain and the result will be returned.

Does that make sense? Just Chain of Responsibility pattern the heck out of this problem. You'd create one instance of HullOverlapChecker per HullPairing combination and just chain them all up.  This is also amenable to future expansion of hull types as you would just add new HullOverlapChecker subclasses that each know how to handle one of the new types of hull pairings.

[Edit: fixed wrong pattern name: meant chain of responsibility, not chain of command]
 
Junilu Lacar
Bartender
Pie
Posts: 8784
81
Android Eclipse IDE IntelliJ IDE Java Linux Mac Scala Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
And you still won't have to use instanceof with that suggestion I made. You'd still use the enum HullShape as the Hull attribute that you check to see if a particular Hull can be handled by a HullOverlapChecker.
 
Stevens Miller
Bartender
Posts: 1377
28
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Junilu Lacar wrote:And you still won't have to use instanceof with that suggestion I made. You'd still use the enum HullShape as the Hull attribute that you check to see if a particular Hull can be handled by a HullOverlapChecker.

Doesn't Tobias's approach obviate that, relying instead on pulling the right method from a Map based on the pair of classes?

I don't fully understand his approach yet, as I need to study up more on generics. Tobias's code looks like a great point of focus for me.

I thought I could eliminate the need for casting the predicate by using a number of "? extends Hull"s where he has "?" alone, but it creates a challenging compile-time bug. I'll be back when I've studied it more...
 
Tobias Bachert
Ranch Hand
Posts: 43
7
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Using the class over an additional enum is in my opinion by far preferable as it adds some additional safety (usable as long as you have exactly one class for each type*); when using an enum you have to somewhat double-specify each type, once the enum-value and once the type for the lambda as the compiler can no longer infer the type from the specified classes, which may result in class-cast errors if misused and will disallow the usage of method references if another (...Hull,...Hull)B method with the same name exists in the same class:

Typecasting of the predicate is required at some point - if you use '? extends Hull' then you are simply delaying the cast to the get-method. (You have two Hulls -> your Lambda has to accept two Hulls, the typecast is safe as you will receive only 'matching' lambdas from the map.)

(*The implementation can be easily extended to support something similar to method resolution during runtime to support sub-types if required.)
 
Junilu Lacar
Bartender
Pie
Posts: 8784
81
Android Eclipse IDE IntelliJ IDE Java Linux Mac Scala Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I'd be interested in how Tobias' suggestion works out for you. A couple of smells I'd be wary about: the static methods and what looks to be a manually set up dispatching system.  I'd try with a few pairs of Hulls and HullShapes first, then see how difficult it would be to add more Hull types.

I haven't tried my suggestion but I still think the problem matches the context suited for applying the Chain of Responsibility/Command pattern.
 
Piet Souris
Rancher
Posts: 1526
32
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
How accurate should these overlap calculations be? I was thinking of each Hull being able to come up with a set of axes aligned rectangles that together approach its area, and that would greatly simplify overlap calculations and better: you would be independent of any particular Hull for the calculation.
 
Stevens Miller
Bartender
Posts: 1377
28
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Piet Souris wrote:How accurate should these overlap calculations be? I was thinking of each Hull being able to come up with a set of axes aligned rectangles that together approach its area, and that would greatly simplify overlap calculations and better: you would be independent of any particular Hull for the calculation.

That is a tried-and-true method used in a lot of game programs, Piet. It works best for on-screen game pieces ("sprites," game-programmers call them) that don't rotate, like the creatures in Space Invaders. But, it also works well using the method you describe, where a set of rectangles approximates the shape of the sprite. If the sprite rotates, the centers of the rectangles can be rotated around the sprite's center, while the rectangles remain aligned to the graphics context's axes. You're quite right that the overlap calculation for such structures is almost trivial and, therefore, can be blazingly fast. Issues only arise when it starts taking a lot of rectangles to approximate the shape of a complex sprite. Anything long and thin (the gun on a tank, a cigar-shaped rocket, a magic wand, etc) might be easily approximated in one orientation by one rectangle, but will take quite a few when oriented at 45-degrees to the context's axes. (Note that you don't have to use the same rectangles for every orientation: a rocket pointing up can use a single thin rectangle that is long in the Y axis, short in the X axis, while the same rocket pointing sideways can use a single, different rectangle that is long in the X axis and short in the Y axis; it's that 45-degree situation where you need a number of rectangles to get a good approximation.)

There's some discussion of it in Andrew Davison's "Killer Game Programming in Java."

(And, on that note, I will add this discussion to the Games forum.)
 
Stevens Miller
Bartender
Posts: 1377
28
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Junilu Lacar wrote:I'd be interested in how Tobias' suggestion works out for you.

I will let you know. It has the attractive quality of being based on some stuff I don't understand very well (generics and the Class class), so I can use it as justification to bone up on those topics. I'm also impressed by his use of the BiPredicate class, which is new in Java SE 8.

A couple of smells I'd be wary about: the static methods and what looks to be a manually set up dispatching system.

Agreed that statics can be a warning sign, but I'm not put off by them here. It seems to me that because the question, "Does Hull A intersect Hull B" is commutative (that is, it gets the same answer as does the question, "Does Hull B intersect Hull A"), there is no particular reason to call upon either Hull A nor Hull B to take responsibility for being able to answer it (that is, to provide the necessary method). Also, the states of A and B don't change as a side-effect of the answer, so the method need not have any direct access to either Hull's fields. And a big point (for me) is that if, say, the RectHull class included a method for determining if a RectHull had intersected a CircHull, the CircHull class would have to provide its own method for determining if a CircHull had intersected a RectHull. Those two methods would be almost identical, in violation of DRY. Locating a single, static, copy in another class takes care of that.

I can live with the dispatching system. There might be more elegant ways to do it, but Tobias's approach is clean and easily maintained. To me, it appears that what he's done is, in effect, create a database where the records include a field that is a lambda that can test two particular hull types for intersection, and the records are indexed by a primary key that is a composite formed by those particular types. It does what my ghastly nested switch statements do, but in an unghastly way. I am slightly concerned about speed, but "don't optimize" is always good advice, at least until you have to.

I'd try with a few pairs of Hulls and HullShapes first, then see how difficult it would be to add more Hull types.

Sensible. Once I understand the parts that presently baffle me, I'll do that and either report back here or post something somewhere appropriate and provide a link.

I haven't tried my suggestion but I still think the problem matches the context suited for applying the Chain of Responsibility/Command pattern.

That certainly sounds promising too, though I would again be wary of speed issues, including consistent performance. If one were doing this in a game context, one would typically have 33,333 uS to get all of one's computations done for a frame. That's actually quite a while for modern computers, but it's also important that things happen in a predictably consistent amount of time. I don't know enough about your proposal to say if that would be an issue here, but if it involves any laziness by design, that might show up in skipped frames.
 
Junilu Lacar
Bartender
Pie
Posts: 8784
81
Android Eclipse IDE IntelliJ IDE Java Linux Mac Scala Spring Ubuntu
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I would put understanding the problem and finding a solution that I understand before any considerations around performance. It's easier for me to find ways to make something faster when I fully understand how it works. Conversely, I'd have a hard time figuring out how to make something that I don't fully understand work faster.

I know graphics programs take performance considerations to a whole 'nother level but I would guess that a few dozen links in a chain of responsibility won't register anywhere near the speed limit for any modern laptop. Nevertheless, I'd still be really interested to see how a full implementation of Tobias' design would work out. I'll keep an eye out for an update from you.
 
Stevens Miller
Bartender
Posts: 1377
28
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Junilu Lacar wrote:I would put understanding the problem and finding a solution that I understand before any considerations around performance. It's easier for me to find ways to make something faster when I fully understand how it works. Conversely, I'd have a hard time figuring out how to make something that I don't fully understand work faster.

Yup. Programming from a cook-book is a bad idea.

I know graphics programs take performance considerations to a whole 'nother level but I would guess that a few dozen links in a chain of responsibility won't register anywhere near the speed limit for any modern laptop.

Indeed. And it's kind of breath-taking for me to see that I've actually said 33,333 uS is "quite a while." When I began in computer graphics, back in 1980, if we could compute a single frame in under an hour, we thought that was pretty good. What modern hardware lets us do today is beyond anything I ever imagined in those days.

Nevertheless, I'd still be really interested to see how a full implementation of Tobias' design would work out. I'll keep an eye out for an update from you.

Workin' on it! (Oh, and Tobias gets a cow for providing some elegant code that has given me a reason to learn something new.)
 
Stevens Miller
Bartender
Posts: 1377
28
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Okay, this is fascinating. I really appreciate the opportunity to learn more about generics. I've made a couple of changes and the result runs. Here's my version:



At Line 7, I replaced the lambda with an actual object so I could include more lines of code without the call to register looking ugly by including them in-line.

At Line 3, I replaced "Class<?>" with "Class<? extends Hull>." Likewise, at Line 17, I replaced "<T, U>" with "<T extends Hull, U extends Hull>." I am thinking this provides additional type-safety.

At Line 17, I replaced "BiPredicate<T, U>" with BiPredicate<Hull, Hull>," which allowed me to drop the cast that would otherwise have appeared at Line 20 (and does appear at Line 17 of Tobias's original code snippet).

Tobias and Junilu, what do you think of the changes I've made?
 
Tobias Bachert
Ranch Hand
Posts: 43
7
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stevens Miller wrote:At Line 17, I replaced "BiPredicate<T, U>" with BiPredicate<Hull, Hull>," which allowed me to drop the cast that would otherwise have appeared at Line 20

The main-problem with this change is, that you can no longer use the specific hull-types in your method/lambda. Your CircCirc predicate can no longer access methods of a CircHull as your arguments are now of the Hull-supertype instead of the specific subtype (you want a BiPredicate<CircHull, CircHull>, a BiPredicate<CircHull, RectHull> etc. instead of BiPredicate<Hull, Hull>).

I.e. previously the code would've invoked the correct testHulls method, now they would invoke the (Hull,Hull) method (as it is the only method that matches the signature BiPredicate<Hull, Hull>, the other two match the signature BiPredicate<T, U>):


Regarding your other changes:
Line 7) You can replace the lambdas with static methods as shown above, is a bit shorter and in my opinion better readable (and avoids the creation of additional classes).
Line 3) Agree, good change
 
Stevens Miller
Bartender
Posts: 1377
28
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Tobias Bachert wrote:The main-problem with this change is, that you can no longer use the specific hull-types in your method/lambda. Your CircCirc predicate can no longer access methods of a CircHull as your arguments are now of the Hull-supertype instead of the specific subtype

Ah, so that's what you meant about avoiding the cast merely delaying it to after the get. To have access to the subclass methods (and I do need that access to get the subclass-specific properties that are used in the pair-specific overlap tests) I still have to down-cast the Hull parameters to the particular types being tested. I think I get it now.

FWIW, I did some rough timing tests on the overhead of your map-of-maps method and my nested-switch method. Total overhead on my machine for map-of-maps is about 3uS. For nested-switches, it's about 1uS. It will depend on the total time taken by the actual tests, but I predict a 2uS savings per test won't be worth replacing a rather elegant technique with my scaffold of switches and cases.

Nice work, thanks!
 
Stevens Miller
Bartender
Posts: 1377
28
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Tobias Bachert wrote:Line 7) You can replace the lambdas with static methods as shown above, is a bit shorter and in my opinion better readable (and avoids the creation of additional classes).


Oh, yeah, I like that much better. Hadn't considered static method references here, but I think that's the way to go now that I see how you've used it.
 
Stevens Miller
Bartender
Posts: 1377
28
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I wish to confess here on the record that I have, once again, colossally managed to confuse polymorphism with overloading. Someday, I hope to stop doing that.

In an attempt to use both to solve this problem without using instanceof or casting any objects of generic type, I've used polymorphism at the calling level to invoke the overlaps method for one type of hull which then uses overloading to call the specific overlaps method of the other type of hull that knows how to detect overlaps of hulls of its own type and that of the caller (which passes itself as an argument, thus selecting the proper implementation of  overlaps through overloading, and making itself available to the other method for the actual overlapping computation).

Here, again  is my code:


Another horror? (It does elevate the overloaded method signatures into the interface, but I think that's unavoidable given the tight coupling that Dave Tolls noted much earlier).
 
Dave Tolls
Ranch Hand
Posts: 2095
15
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Out of curiosity is the logic for CH x RH and RH x CH the same?
 
Stevens Miller
Bartender
Posts: 1377
28
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Dave Tolls wrote:Out of curiosity is the logic for CH x RH and RH x CH the same?

Yes. In actual practice, those println calls would be to a static method that took a CircHull and a RectHull argument, to avoid violating DRY.
 
  • Post Reply
  • Bookmark Topic Watch Topic
  • New Topic