-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Fix unstable map ordering for specific keys with numbers #195
Conversation
314ff01
to
368c61d
Compare
Hi @niemeyer, would you be able to look at this change? I'd love to get this merged in and avoid maintaining a fork! |
I would love to see this merged as well |
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.
Thanks for this, but I'll need to think some more before submitting it. The keyList.Less method looks problematic already, in fact, as it looks like it's assuming that unicode.IsDigit implies that the rune value is between ASCII '0' and '9'.
@@ -63,6 +65,9 @@ func (l keyList) Less(i, j int) bool { | |||
if ai != bi { | |||
return ai < bi | |||
} | |||
if ar[i] == br[i] { | |||
continue |
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 suspect this may not be quite right. The original code always returned after comparing numbers, but this now means that we can carry on instead. At the least, this introduces quadratic behaviour which might be a problem for long keys with many decimal digits in. I'll need to think some more about this.
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.
@rogpeppe is there a chance that this would be fixed anytime soon, it really hurts us on some scenarios
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.
Can i offer to simple do a string comparison on the last case:
So the less function would look like this:
func (l keyList) Less(i, j int) bool {
a := l[i]
b := l[j]
ak := a.Kind()
bk := b.Kind()
for (ak == reflect.Interface || ak == reflect.Ptr) && !a.IsNil() {
a = a.Elem()
ak = a.Kind()
}
for (bk == reflect.Interface || bk == reflect.Ptr) && !b.IsNil() {
b = b.Elem()
bk = b.Kind()
}
af, aok := keyFloat(a)
bf, bok := keyFloat(b)
if aok && bok {
if af != bf {
return af < bf
}
if ak != bk {
return ak < bk
}
return numLess(a, b)
}
if ak != reflect.String || bk != reflect.String {
return ak < bk
}
ar, br := a.String(), b.String()
return ar < br
}
My mine use case is that the output would be consistent.
I don't really care about the order of map, as long as it is consitent
@jfmyers9 I'll fix it this week, somehow. |
@niemeyer any updates? |
This is fixed in dcd83b3, without significant changes to the algorithm overall behavior. Please let me know how it goes. |
Just in case someone comes here like me and thinks this is completely solved, I can still reproduce unstable map ordering with a use case I have. Run the following code a couple of times and you will observe different orders: package main
import (
"fmt"
"gopkg.in/yaml.v2"
)
const example = `
properties:
cc:
quota_definitions:
q256MB:
non_basic_services_allowed: true
q2GB:
non_basic_services_allowed: true
q4GB:
non_basic_services_allowed: true
`
func main() {
var data interface{}
if err := yaml.Unmarshal([]byte(example), &data); err == nil {
if out, err := yaml.Marshal(data); err == nil {
fmt.Println(string(out))
}
}
} |
When comparing runes, don't skip equal runes if they are digits. This avoids issues when comparing values like
100
vs11
since dropping the1
causes a comparison of00
vs1
.Fixes #194