• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Liutauras Vilda
  • Junilu Lacar
  • Jeanne Boyarsky
  • Bear Bibeault
Sheriffs:
  • Knute Snortum
  • Tim Cooke
  • Devaka Cooray
Saloon Keepers:
  • Ron McLeod
  • Stephan van Hulst
  • Tim Moores
  • Tim Holloway
  • Carey Brown
Bartenders:
  • Piet Souris
  • Frits Walraven
  • Ganesh Patekar

Exercism: Darts

 
Marshal
Posts: 14052
234
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I was browsing through community solutions in the Java track at http://exercism.io and stumbled upon what is probably Knute's solution for the Darts problem: https://exercism.io/tracks/java/exercises/darts/solutions/917be5db60e74d71a03e7aee56762d47

For comparison, my published solution for the same problem uses streams: https://exercism.io/tracks/java/exercises/darts/solutions/5946e5ad593c44b5adcc41bfc5a1e74a

My first iteration was an imperative one with pretty much the same logic. I don't know if you'd be able to access it so I'll paste the code here.

I'd like to discuss the difference in mine and Knute's solution, mainly, the choice to do the calculation in the constructor vs. in the score() method.

I chose to perform the calculation because the class looks like it is designed to be immutable given that the cartesian coordinates are passed to constructor with no getter/setter for them.
 
Junilu Lacar
Marshal
Posts: 14052
234
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Just came across this one, from here: https://exercism.io/tracks/java/exercises/darts/solutions/d747168784c24b3c80bb56cceaed075d

One thing I find a bit disconcerting about this logic is that the scores are separated from their corresponding ring boundary distances so there appears to be a misalignment of values based on my reading of the requirements. I think using > instead of >= is costing a bit of clarity and readability.

I do like the use of the Point2D class and the distance() method though.
 
Bartender
Posts: 3518
150
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
hi Junilu,

these kinds of exercises are always fun. My very first thought is: why is everyone using the sqrt? If |x| < radius, then certainly iff x^2 < radius^2?

Knutes solution has the advantage of the simplicity of it. It is immediately obvious what is going on.
Your solution is very elegant, but has the disadvantage of using the max, meaning that all possibilities must be calculated. You can limit it by having the ring-method return a tuple<boolean, score>, and using findFirst. But admittedly, that is quite heavy for a small exercise like this, and to be honest, not very elegant.
I like the solution where a Point2D is used, the best. There, you let Java do the hard work of determining the distance.

My favorite way (and one I've been using recently in determining the next (alphabetically) permutation of a given string) is to use a (static) TreeMap<distance, score>, and using the ceiling method of a TreeMap for the lookup. This way, I let Java also do the lookup part (for the distance, I would use the Point2D).

But the most interesting part for me would be the model that determines where the dart lands on the board (or, in my case, where the dart hits the wall)!
 
Saloon Keeper
Posts: 10669
228
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Junilu Lacar wrote:I chose to perform the calculation because the class looks like it is designed to be immutable given that the cartesian coordinates are passed to constructor with no getter/setter for them.


Really, either of the two is fine and depends on personal taste. I would go Knute's way, doing the calculation in the score() method. That's because I have a huge allergy for doing more than just validation in the constructor. Usually you would also have accessors for the properties that were given as constructor parameters.

If the calculation is expensive enough that you don't want to do it more than once, you'd usually cache the calculation in a transient field:

Even though the transient keyword is only used in serializable classes, I like to use it to indicate that a field is only used to cache a calculation that is based on other fields.

Of course, this particular application does not warrant caching the calculation, so I would just do the calculation in the score() method every time.

Piet Souris wrote:My very first thought is: why is everyone using the sqrt?


Because they're using the Pythagorean theorem, which is the first thing I would think to use as well.
 
Piet Souris
Bartender
Posts: 3518
150
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
@Stephan
If you add the entry: Double.MAX_VALUE, 0 to your map, you then don't need to test for null.

And yes, Pythagoras, but sqr(x) < rad iff x < rad*rad (x >= 0, assuming)

----------------------------------------------------------------

What about using polar coordinates and a suitable BiFunction, in case you want to implement the real darts score?

@Junilu
I was overlooking the fact that if a point is within radius 1, it then is also in the radii 5 and 10. You could check to see in what particular ring a dart has landed, and value the other rings to 0. In that way, you can use findFirst, testing for a positive score. Requires to add a lower bound parameter to your ring functions, and may not be worth it.
 
Junilu Lacar
Marshal
Posts: 14052
234
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Yeah, I had to quickly remind myself of Knuth and the root of all evil when I was considering against max(). In this case, it's three values being chosen from so a difference of a couple of milliseconds at most.

Thanks for the elegance comment but I'm still on the fence about the approach. On one hand, the closure was cool and it helps meet the criteria for coding without if statements, which is an exercise I still find challenging. On the other hand, it's not code that most people can immediately understand. Without tests to back the code up, it would be more difficult to understand and maintain for more people than the straightforward imperative code.
 
Junilu Lacar
Marshal
Posts: 14052
234
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Re the use of sqrt(), I saw quite a few other community solutions that avoided it as well, opting to just square everything instead as you suggested. I thought the code made me do more double-takes because calculations related to Pythagorean theory got spread out more across the code. That is, you'd have to work with the square of the distance from center and each ring radius as well. This tended to result in wanting to use more symbolic constants, I think as a way to mitigate confusion caused by dealing with squares rather than the square root values. Remember, the squares represents the relationship whereas the square root is the actual length.

Using squares also creates inconsistency in the naming, making them kind of misleading.

These names are all lies; the values are actually squares of what the names represent. I saw this kind of code a number times in the other solutions.
 
Stephan van Hulst
Saloon Keeper
Posts: 10669
228
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Piet Souris wrote:If you add the entry: Double.MAX_VALUE, 0 to your map, you then don't need to test for null.


Good advice.

And yes, Pythagoras, but sqr(x) < rad iff x < rad*rad (x >= 0, assuming)


I'm not sure what you're trying to say with this. Maybe you can elaborate.
 
Piet Souris
Bartender
Posts: 3518
150
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
For non-negative values of x the mapping between x and sqrt(x) is 1-1, so you can avoid the costly sqrt-operation. But as Junilu rightly remarks, it makes it less clear and not worth it.

I'm wondering about this: I usually program 'to the interface', as everyone tells I should, but when it comes to TreeSets and -Maps, without a second of thought I use the implementation, while you use the interface. I wonder why I do this (because TreeMaps are the only implementation? Shorter to type?). Hmmm...
 
Marshal
Posts: 65814
250
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
. . . and why not use this method?
 
Stephan van Hulst
Saloon Keeper
Posts: 10669
228
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
D'oh, Campbell has it. That's definitely the most elegant approach in my book.

Piet, I know exactly what you mean. For the longest time I would write TreeMap if I needed a SortedMap or NavigableMap. I think it has to do with that the most common example we use when we say one should program to an interface is ArrayList vs List, so we have that one drilled into our minds, but not others. Using TreeMap and TreeSet as the formal types of variabless is just a bad habit that we have to get rid of by practicing.
 
Junilu Lacar
Marshal
Posts: 14052
234
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Stephan van Hulst wrote:D'oh, Campbell has it. That's definitely the most elegant approach in my book.


Cow-fully agree.

Solution stolen.  
 
Piet Souris
Bartender
Posts: 3518
150
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
@Stepahn
fully agree.

@Campbell
While I prefer the Point.distance, a cow from me as well for reminding us of that method!
 
Junilu Lacar
Marshal
Posts: 14052
234
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Great discussion, by the way. Good insights from everyone. Thanks!
 
Campbell Ritchie
Marshal
Posts: 65814
250
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Piet Souris wrote:. . . a cow from me as well  . . .

Thank you
 
Piet Souris
Bartender
Posts: 3518
150
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
You're welcome!    
 
Marshal
Posts: 7181
492
Mac OS X VI Editor BSD Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I came across this thread just now. I know you were talking a lot about the implementation techniques, and which solution is more clever or elegant over the other.

To me, the imperative is most simple to understand, so I'll stick to that one. However, one thing which bothered me, the responsibilities who does what.

Imperative example from the @OP's first post:

My problem with this approach is, where the calculation takes place, and the same thing bothered to me with all other's solution. To me makes more sense to have it like that:

So why like that?

Because in the darts your arrow hits the circular target, which has its Radius. How the Radius gets calculated it won't change over the time, regardless of how many times you are going to query the score or regardless how are you going to calculate radius, meaning, given the same input, radius going to be the same over the entire life, so really that perfectly fits constructor and doesn't make sense to have in the score. Because score really IS based on the Radius, but does not add somehow to the score as such.

Now the score calculation I see it only in score() method (not constructor), and not in the form of getter, but each time to calculate the score. Yes, in constructor it would calculate just once, but that's not the goal. And so the score() method's responsibility is to calculate the score based on the current its implementation requirements.

If you'd close your eyes and somebody said, you have a class which has a constructor and the score method only, go and change the implementation how the score is calculated, where would you expect such calculation's logic to find if you were have a singular attempt for that?
 
Liutauras Vilda
Marshal
Posts: 7181
492
Mac OS X VI Editor BSD Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator


And if we look to constructor, really it nicely abstracts the idea of the hit point to the Radius, hence assigns that as a field. It achieves a very clear goal, which is a principal idea of Darts game, defines the radius of the circle the arrow hit. In case appear you need point coordinate, you can store as field too to fulfil further requirements, but from the data stand point, hitCircleRadius and the hitPoint perhaps are at most what you'd ever need.
 
Piet Souris
Bartender
Posts: 3518
150
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
First of all, this is only a small one-off exercise, so why bother?

Then, I would keep the x and y values. With only keeping the distance you throw away useful information. And isn't something like 'DartsThrow' a better name than 'Darts'?

I would not code that scoring rule into the Darts class. All you need to calculate the score are the x,y values and an implementation of a ScoringRules interface that does the calculation. This way you can calculate the score, no matter if the rules are like in this exercise, or the real Darts rules, or whatever. So, something like:
 
When all four tires fall off your canoe, how many tiny ads does it take to build a doghouse?
Building a Better World in your Backyard by Paul Wheaton and Shawn Klassen-Koop
https://coderanch.com/wiki/718759/books/Building-World-Backyard-Paul-Wheaton
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!