Skip to content
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

Closed
wants to merge 1 commit into from

Conversation

prashantv
Copy link
Contributor

When comparing runes, don't skip equal runes if they are digits. This avoids issues when comparing values like 100 vs 11 since dropping the 1 causes a comparison of 00 vs 1.

Fixes #194

@prashantv prashantv force-pushed the num_order branch 2 times, most recently from 314ff01 to 368c61d Compare August 23, 2016 03:00
@prashantv
Copy link
Contributor Author

Hi @niemeyer, would you be able to look at this change? I'd love to get this merged in and avoid maintaining a fork!

@geofffranks
Copy link

I would love to see this merged as well

Copy link
Contributor

@rogpeppe rogpeppe left a 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
Copy link
Contributor

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.

Copy link

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

Copy link

@aviadl aviadl Aug 9, 2017

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
Copy link

Hey @rogpeppe,

We are also running into issues with the non-stable ordering in maps. Is there a timeline for when this or another fix may be merged?

Best,

@jfmyers9 && @tjvman

@niemeyer
Copy link
Contributor

@jfmyers9 I'll fix it this week, somehow.

@cppforlife
Copy link

@niemeyer any updates?

niemeyer added a commit that referenced this pull request Mar 25, 2018
@niemeyer
Copy link
Contributor

This is fixed in dcd83b3, without significant changes to the algorithm overall behavior.

Please let me know how it goes.

@niemeyer niemeyer closed this Mar 25, 2018
@HeavyWombat
Copy link

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))
		}
	}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants