-
-
Notifications
You must be signed in to change notification settings - Fork 682
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
bookstore : add to track #310
Conversation
Renamed to make clear the status of this PR. @Twisti, when you're ready for a review, please rename it back to |
Ready for a review :) |
break; | ||
case 5: | ||
discountPercentage = 25; | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see a pretty clear relationship between groupSize
and discountPercentage
. Might there be a more concise, but equally readable, way to capture this?
Also wondering, is this really configuration data rather than business logic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't the the relationship between groupSize
and discountPercentage
, can you tell me more about this?
Also I didn't understand your second question, can you develop and explain it ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just checked the php exemple of the exercise and saw a different version about the discount percentage :
$discount = [0, 0, 0.05, 0.1, 0.2, 0.25];
$price = 8;
return $price * $count * (1 - $discount[$count]);
So in Java, it will be :
double[] discountPercentage = {0, 0, 0.05, 0.1, 0.2, 0.25};
return 8 * groupSize * (1 - discountPercentage[groupSize]);
It's this kind of relationship you're talking about ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or even simpler. Perhaps take it in two steps:
- Check for invalid group size and throw and exception if invalid
- return for the discounted group price:
8 * (groupSize - 1) * 0.05d;
Even better, avoid the use of magic numbers. Name the book price and discount increment, EG DISCOUNT_INCREMENT
, BOOK_PRICE
, define them in the class, and use:
BOOK_PRICE * (groupSize - 1) * DISCOUNT_INCREMENT;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as my "configuration vs logic" comment, it's not hard to imagine that the price of a book or the discount increment would be a business rule of the bookstore. In a production application, you would want to separate setting those values from the logic. You might even imagine a case in which you would have a class that is responsible for calculating the discount that is separate from the one that applies it to the user's purchase.
In this example, I feel like calling these values out by defining them at the class level with clear names is probably enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
8 * (groupSize - 1) * 0.05d;
Are you sure that this work ? Or maybe I'm missing something ?
My method looks like this :
private double CostPerGroup(int groupSize) throws Exception{
if(groupSize > maxGroupSize || groupSize <= 0 )
throw new Exception("Invalide group size : " + groupSize );
double discountForOneBook = bookPrice * (groupSize - 1) * discountIncrement;
return (bookPrice * groupSize ) - (groupSize * discountForOneBook);
}
But by running gradle for the test, I have 13 tests completed, 5 failed :
BookStoreTest > TwoCopiesOfEachBook FAILED
java.lang.AssertionError: expected:<60.0> but was:<64.0>
at org.junit.Assert.fail(Assert.java:88)
at org.junit.Assert.failNotEquals(Assert.java:834)
at org.junit.Assert.assertEquals(Assert.java:553)
at org.junit.Assert.assertEquals(Assert.java:683)
at BookStoreTest.TwoCopiesOfEachBook(BookStoreTest.java:106)
BookStoreTest > TwoEachOfFirst4BooksAnd1CopyEachOfRest FAILED
java.lang.AssertionError: expected:<55.6> but was:<59.2>
at org.junit.Assert.fail(Assert.java:88)
at org.junit.Assert.failNotEquals(Assert.java:834)
at org.junit.Assert.assertEquals(Assert.java:553)
at org.junit.Assert.assertEquals(Assert.java:683)
at BookStoreTest.TwoEachOfFirst4BooksAnd1CopyEachOfRest(BookStoreTest.java:97)
BookStoreTest > ThreeEachOFirst2BooksAnd2EachOfRemainingBooks FAILED
java.lang.AssertionError: expected:<75.2> but was:<79.2>
at org.junit.Assert.fail(Assert.java:88)
at org.junit.Assert.failNotEquals(Assert.java:834)
at org.junit.Assert.assertEquals(Assert.java:553)
at org.junit.Assert.assertEquals(Assert.java:683)
at BookStoreTest.ThreeEachOFirst2BooksAnd2EachOfRemainingBooks(BookStoreTest.java:124)
BookStoreTest > ThreeCopiesOfFirstBookAnd2EachOfRemaining FAILED
java.lang.AssertionError: expected:<68.0> but was:<72.0>
at org.junit.Assert.fail(Assert.java:88)
at org.junit.Assert.failNotEquals(Assert.java:834)
at org.junit.Assert.assertEquals(Assert.java:553)
at org.junit.Assert.assertEquals(Assert.java:683)
at BookStoreTest.ThreeCopiesOfFirstBookAnd2EachOfRemaining(BookStoreTest.java:115)
BookStoreTest > TwoGroupsOfFourIsCheaperThanGroupOfFivePlusGroupOfThree FAILED
java.lang.AssertionError: expected:<51.2> but was:<53.6>
at org.junit.Assert.fail(Assert.java:88)
at org.junit.Assert.failNotEquals(Assert.java:834)
at org.junit.Assert.assertEquals(Assert.java:553)
at org.junit.Assert.assertEquals(Assert.java:683)
at BookStoreTest.TwoGroupsOfFourIsCheaperThanGroupOfFivePlusGroupOfThree(BookStoreTest.java:79)
So, I prefer to upload this modifications and don't push now.
BookStore_Java_Twisti.tar.gz
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I made an error! I thought the discount tiers were linear by groupSize
, and they are not. My proposal and the file you uploaded both have a problem because of that. However, a solution like the PHP one you mentioned would work nicely:
private static double[] DISCOUNT_TIERS = {0,5,10,20,25}; //declare at class level
private double CostPerGroup(int groupSize) throws Exception{
if (groupSize < 1 || groupSize > 5){
throw new Exception("Invalid group size : " + groupSize );
}
return bookPrice * groupSize * (100 - DISCOUNT_TIERS[groupSize-1])/100;
}
assertEquals(30,BookStore.CalculateTotalCost(books),2); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like from this point on, the test suite diverges from the canonical test suite for this exercise. Is that intentional?
All the remaining tests are extra, and these tests from the canonical spec are missing:
{
"description": "Two groups of four is cheaper than group of five plus group of three",
"basket": [1,1,2,2,3,3,4,5],
"targetgrouping": [[1,2,3,4],[1,2,3,5]],
"expected": 51.20
},
{
"description": "Group of four plus group of two is cheaper than two groups of three",
"basket": [1,1,2,2,3,4],
"targetgrouping": [[1,2,3,4],[1,2]],
"expected": 40.8
},
{
"description": "Two each of first 4 books and 1 copy each of rest",
"basket": [1,1,2,2,3,3,4,4,5],
"targetgrouping": [[1,2,3,4,5],[1,2,3,4]],
"expected": 55.60
},
{
"description": "Two copies of each book",
"basket": [1,1,2,2,3,3,4,4,5,5],
"targetgrouping": [[1,2,3,4,5],[1,2,3,4,5]],
"expected": 60.00
},
{
"description": "Three copies of first book and 2 each of remaining",
"basket": [1,1,2,2,3,3,4,4,5,5,1],
"targetgrouping": [[1,2,3,4,5],[1,2,3,4,5],[1]],
"expected": 68.00
},
{
"description": "Three each of first 2 books and 2 each of remaining books",
"basket": [1,1,2,2,3,3,4,4,5,5,1,2],
"targetgrouping": [[1,2,3,4,5],[1,2,3,4,5],[1,2]],
"expected": 75.20
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I followed the test from the C# version, so this test is just a translation from C# to Java.
I will update my tests to add tests you have linked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I see that!
Thanks for adding the additional tests. Along the way, before we merge your changes, we'll need you to add the annotation @Ignore
before all but the first test in the suite. That way users can focus on getting one test at a time to pass.
@Twisti thanks for doing this! In the spirit of challenging each other to produce the best possible product, I have two questions for you. Please let me know your thoughts. Also, we merged some older, pending PRs this morning and that created a merge conflict with your |
public class BookStoreTest{ | ||
|
||
@Test | ||
public void Only_a_single_book(){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Standard convention in Java is to use camelCase
for method and variable names. Save the snake_case
for Python!
@Test | ||
public void Only_a_single_book(){ | ||
Integer[] p = {1}; | ||
ArrayList<Integer> books = new ArrayList<>(Arrays.asList(p)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Twisti thanks for your additional efforts on this one. I have made some specific comments in response to your questions, and called out a few more things that should be updated to conform to Java 'best practices.' Finally, I must draw your attention to a conversation that some of the maintainers had around another exercise. Specifically, it was about preferring the use of instance methods rather than class methods. If you are comfortable making this change in your PR, I think it would be good. Please let me know if you would like help with any of these things, I'd be pleased to assist. |
As you advised for me to do, I updated my code :
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Twisti this is really coming along nicely!
Please consider the feedback I have given you for the discount method, but know that it is optional.
The Java style changes would be appreciated, as they are the standard here on exercism/xjava
.
I've made a few comments about capitalization that apply in several places, but I didn't think it necessary to mention them all. They will need to be addressed in both the example code and the test code please.
Please let me know if I can assist you in any way.
double minPrice = Double.MAX_VALUE; | ||
|
||
|
||
if(books.size() == 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stylistically we would prefer if you use braces, even for simple cases like this. EG:
if (books.size() ==0){
return priceSoFar;
}
this.bookPrice = 8; | ||
} | ||
|
||
public double CalculateTotalCost(){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Method names should be camelCase
, but not CapitalWords
in Java. Save the CapitalWords
for class names.
import java.util.List; | ||
import java.util.stream.Collectors; | ||
|
||
public class BookStore{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In English we would say Bookstore
rather than BookStore
. Also, the folder name should likewise be one word bookstore
without the hypen.
if(books.size() == 0) | ||
return priceSoFar; | ||
|
||
List<Integer> groups = (ArrayList<Integer>) books.stream().distinct().collect(Collectors.toList()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Beautiful!
discountPercentage = 25; | ||
break; | ||
default: | ||
throw new Exception("Invalide group size : " + groupSize ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll want to change the spelling to Invalid
here.
|
||
import org.junit.Test; | ||
|
||
public class BookStoreTest{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BookstoreTest
please.
public class BookStoreTest{ | ||
|
||
@Test | ||
public void OnlyASingleBook(){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The camelCase
Java rule would apply here.
@Test | ||
public void OnlyASingleBook(){ | ||
Integer[] p = {1}; | ||
List<Integer> books = new ArrayList<>(Arrays.asList(p)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can just pass the integers to Arrays.asList()
. No need to declare and assign beforehand. EG:
List<Integer> books = new ArrayList<>(Arrays.asList(1));
or
List<Integer> books = new ArrayList<>(Arrays.asList(1,2,3));
break; | ||
case 5: | ||
discountPercentage = 25; | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I made an error! I thought the discount tiers were linear by groupSize
, and they are not. My proposal and the file you uploaded both have a problem because of that. However, a solution like the PHP one you mentioned would work nicely:
private static double[] DISCOUNT_TIERS = {0,5,10,20,25}; //declare at class level
private double CostPerGroup(int groupSize) throws Exception{
if (groupSize < 1 || groupSize > 5){
throw new Exception("Invalid group size : " + groupSize );
}
return bookPrice * groupSize * (100 - DISCOUNT_TIERS[groupSize-1])/100;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Twisti this is really looking terrific. There are two small changes you should make to adhere to Java conventions and track style.
Also we will need to resolve the merge conflict in config.json
that was caused my merging another PR earlier. If you need help with this please let me know.
After that this will be ready to merge!
private int bookPrice, maxGroupSize; | ||
private double discountIncrement; | ||
private List<Integer> books; | ||
private static double[] discount_tiers = {0,5,10,20,25}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normally a constant would be ALL_CAPITALS
like this. Most editors will recognize them as such and make them easier to spot in the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added static
to constants BOOK_PRICE
, MAX_GROUP_SIZE
.
Also, I deleted the discountIncrement
because it's useless now.
|
||
|
||
if(books.size() == 0) | ||
return priceSoFar; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curly braces here please.
Beautiful! Thank you so much 🥇 |
I'll work on the bookstore exercise using C# version
https://github.com/exercism/x-common/tree/master/exercises/book-store