patterngoMinor
How many of a particular item of clothing do I need?
Viewed 0 times
itemclothinghowneedmanyparticular
Problem
I am learning Go and want to do things the go-way, please have a look this code suggest how to make it better Go.
This is a simulation to determine how items of a particular type of clothing you need. Rules are as follows:
Assuming there is a washing day every Monday, how many items of clothing do you need to never run out of clean ones?
There are some extra rules (I have made them flags):
Code in the go-playground at: https://play.golang.org/p/3jawv55Y3p
```
package main
import (
"flag"
"fmt"
"time"
)
func even(number int) bool {
return number%2 == 0
}
func isWashingDay(today time.Time, thursday bool) bool {
if today.Format("Mon") == "Mon" {
fmt.Println("Monday!")
return true
} else if thursday && today.Format("Mon") == "Thu" {
_, week := today.ISOWeek()
if even(week) {
fmt.Println("Thursday!")
return true
}
}
return false
}
func main() {
numberOftshirtsPtr := flag.Int("shirts", 9, "a int number of tshirts")
thursdayPtr := flag.Bool("thursday", true, "a bool use Thursday")
dryingPtr := flag.Bool("drying", true, "a bool use drying")
flag.Parse()
var clean, minClean = numberOftshirtsPtr, numberOftshirtsPtr
var dirty, washing, drying int
today, _ := time.Parse(time.RFC3339, "2017-02-06T00:00:00+00:00")
fmt.Println("Date
This is a simulation to determine how items of a particular type of clothing you need. Rules are as follows:
- Simulation starts with all the clothes in the clean pile
- Every morning you take one item from the clean pile and add it to the dirty pile (assuming an item of clothing becomes dirty the moment you put it on)
- On wash days - before you put on a new item - all the items in the dirty pile go into washing pile
- Every morning all items in the washing pile are put into the clean pile
Assuming there is a washing day every Monday, how many items of clothing do you need to never run out of clean ones?
There are some extra rules (I have made them flags):
- Every second Thursday is also a washing day
- There is an extra step in the washing process - drying. After an item is washed, the next day it is put into the drying pile, and only after drying does it go into the clean pile.
Code in the go-playground at: https://play.golang.org/p/3jawv55Y3p
```
package main
import (
"flag"
"fmt"
"time"
)
func even(number int) bool {
return number%2 == 0
}
func isWashingDay(today time.Time, thursday bool) bool {
if today.Format("Mon") == "Mon" {
fmt.Println("Monday!")
return true
} else if thursday && today.Format("Mon") == "Thu" {
_, week := today.ISOWeek()
if even(week) {
fmt.Println("Thursday!")
return true
}
}
return false
}
func main() {
numberOftshirtsPtr := flag.Int("shirts", 9, "a int number of tshirts")
thursdayPtr := flag.Bool("thursday", true, "a bool use Thursday")
dryingPtr := flag.Bool("drying", true, "a bool use drying")
flag.Parse()
var clean, minClean = numberOftshirtsPtr, numberOftshirtsPtr
var dirty, washing, drying int
today, _ := time.Parse(time.RFC3339, "2017-02-06T00:00:00+00:00")
fmt.Println("Date
Solution
For a beginner to go your program is good. You have obviously read up on
Flags
So, having said you have a good grasp on those concepts, I am going to recommend that you change the flag handling (and as a consequence, you remove the pointers).
In addition, you should be putting in the number of days to simulate, and perhaps the starting date, as flags too.
So,
I would write like:
By taking the address of the var and giving it to flag, we can then use the variables as-is later without having to keep the pointer handling at all, so code like:
becomes:
Note that, by convention, using hungarian notation in go is not good code-style. You should not suffix pointer variables with
OK, so the above code changes the flag handling to be Var-based, and it reads easier, and removes all pointer references later.
I would also add flags for the starting date, and number of days to simulate.
Washing Day
Your washingday function is a good idea, but you are doing string processing in places where the
Note that I no longer have any raw text in there (it's all
Main
Your program is essentially contained inside the main-method. This makes the main method bulky, and the code is not reusable. Even in example progams and learning exercises, you should try to break your code in to functions that can be run, and tested independently. Go has a strong toolset related to unit-tests and benchmarks, and you should get in the habit of creating small, independent functions that are easy to process in the testing systems too.
One way to improve the bulkiness of the main method is to declare a
In addition, to separate out your printing of the state, I would create a bit of a string helper function... I'll explain it later, but show you here, now:
With this function, we can add a
Now we can use the
Further, I would take the state-tansition logic and make it a method on the struct too:
```
func (cs ClothesState) Advance(doDrying, doThursday
flag handling, and you've got a grasp on the pointers. I have come to like the flag processing in go (though having previously learned/understood getopt and written my own library in Java, I feel like go should allow more powerful commandline handling).Flags
So, having said you have a good grasp on those concepts, I am going to recommend that you change the flag handling (and as a consequence, you remove the pointers).
In addition, you should be putting in the number of days to simulate, and perhaps the starting date, as flags too.
So,
flag functions include both ...Ptr and ...Var variants. I recommend using the ...Var variants where you can. Your code:numberOftshirtsPtr := flag.Int("shirts", 9, "a int number of tshirts")
thursdayPtr := flag.Bool("thursday", true, "a bool use Thursday")
dryingPtr := flag.Bool("drying", true, "a bool use drying")
flag.Parse()I would write like:
numberOftshirts := 9
doThursdays := true
doDrying := true
flag.IntVar(&numberOftshirts, "shirts", numberOftshirts, "the number of tshirts")
flag.BoolVar(&doThursdays, "thursday", doThursdays, "use Thursday")
flag.BoolVar(&doDrying, "drying", doDrying, "use drying")
flag.Parse()By taking the address of the var and giving it to flag, we can then use the variables as-is later without having to keep the pointer handling at all, so code like:
var clean, minClean = *numberOftshirtsPtr, *numberOftshirtsPtrbecomes:
var clean, minClean = numberOftshirts, numberOftshirtsNote that, by convention, using hungarian notation in go is not good code-style. You should not suffix pointer variables with
Ptr. A pointer to an int containing the number of T-Shirts is still numberOftshirts and not numberOftshirtsPtrOK, so the above code changes the flag handling to be Var-based, and it reads easier, and removes all pointer references later.
I would also add flags for the starting date, and number of days to simulate.
Washing Day
Your washingday function is a good idea, but you are doing string processing in places where the
time library has better options to offer. Note that time has constantes for the days-of-week, and those constants are declared as a Weekday type, and that type has a String() function available: https://golang.org/pkg/time/#Weekday - what this means is that you can avoid the string-conversions in the function. I personally would probably use the String() option too to print the days. Actually, I would remove the println from the function because it is making the function do too much - (computation and presentation). I would have your function as:func isWashingDay(today time.Time, doThursday bool) bool {
if today.Weekday() == time.Monday {
//fmt.Printf("%v!\n", time.Monday)
return true
}
if doThursday && today.Weekday() == time.Thursday {
_, week := today.ISOWeek()
if even(week) {
//fmt.Printf("%v!\n", time.Thursday)
return true
}
}
return false
}Note that I no longer have any raw text in there (it's all
time variables, etc), and also note that I no longer have if ... else ... statements. When the if part of a condition always has a return in it, there's no need for the else at all.Main
Your program is essentially contained inside the main-method. This makes the main method bulky, and the code is not reusable. Even in example progams and learning exercises, you should try to break your code in to functions that can be run, and tested independently. Go has a strong toolset related to unit-tests and benchmarks, and you should get in the habit of creating small, independent functions that are easy to process in the testing systems too.
One way to improve the bulkiness of the main method is to declare a
struct to contain the state of a given day. You can get clever with some function processing too. I would create a state struct, and use it for a bunch of the logic....type ClothesState struct {
day time.Time
clean int
dirty int
washing int
drying int
}In addition, to separate out your printing of the state, I would create a bit of a string helper function... I'll explain it later, but show you here, now:
func format(day, clean, dirty, washing, drying interface{}) string {
return fmt.Sprintf("%-11v| %-2v| %-2v| %-2v| %2-v", day, clean, dirty, washing, drying)
}With this function, we can add a
String() method to our state too that uses it:func (cs ClothesState) String() string {
return format(cs.day.Format("2006-01-02"), cs.clean, cs.dirty, cs.washing, cs.drying)
}Now we can use the
%v (or %s) style fmt to print the state:fmt.Printf("%v\n", state)Further, I would take the state-tansition logic and make it a method on the struct too:
```
func (cs ClothesState) Advance(doDrying, doThursday
Code Snippets
numberOftshirtsPtr := flag.Int("shirts", 9, "a int number of tshirts")
thursdayPtr := flag.Bool("thursday", true, "a bool use Thursday")
dryingPtr := flag.Bool("drying", true, "a bool use drying")
flag.Parse()numberOftshirts := 9
doThursdays := true
doDrying := true
flag.IntVar(&numberOftshirts, "shirts", numberOftshirts, "the number of tshirts")
flag.BoolVar(&doThursdays, "thursday", doThursdays, "use Thursday")
flag.BoolVar(&doDrying, "drying", doDrying, "use drying")
flag.Parse()var clean, minClean = *numberOftshirtsPtr, *numberOftshirtsPtrvar clean, minClean = numberOftshirts, numberOftshirtsfunc isWashingDay(today time.Time, doThursday bool) bool {
if today.Weekday() == time.Monday {
//fmt.Printf("%v!\n", time.Monday)
return true
}
if doThursday && today.Weekday() == time.Thursday {
_, week := today.ISOWeek()
if even(week) {
//fmt.Printf("%v!\n", time.Thursday)
return true
}
}
return false
}Context
StackExchange Code Review Q#153828, answer score: 2
Revisions (0)
No revisions yet.