如何对具有多个排序参数的结构进行排序?

huangapple go评论97阅读模式
英文:

How to sort struct with multiple sort parameters?

问题

我有一个成员的数组/切片:

type Member struct {
    Id int
    LastName string
    FirstName string
}

var members []Member

我的问题是如何按LastName然后按FirstName对它们进行排序。

你可以使用Go语言的sort包来实现多关键字排序。以下是一个示例代码:

package main

import (
    "fmt"
    "sort"
)

type Member struct {
    Id        int
    LastName  string
    FirstName string
}

type ByLastNameFirstName []Member

func (a ByLastNameFirstName) Len() int           { return len(a) }
func (a ByLastNameFirstName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByLastNameFirstName) Less(i, j int) bool {
    if a[i].LastName == a[j].LastName {
        return a[i].FirstName < a[j].FirstName
    }
    return a[i].LastName < a[j].LastName
}

func main() {
    members := []Member{
        {Id: 1, LastName: "Doe", FirstName: "John"},
        {Id: 2, LastName: "Smith", FirstName: "Alice"},
        {Id: 3, LastName: "Doe", FirstName: "Jane"},
    }

    fmt.Println("Before sorting:")
    for _, member := range members {
        fmt.Println(member)
    }

    sort.Sort(ByLastNameFirstName(members))

    fmt.Println("\nAfter sorting:")
    for _, member := range members {
        fmt.Println(member)
    }
}

这段代码定义了一个ByLastNameFirstName类型,它实现了sort.Interface接口的LenSwapLess方法。Less方法用于比较两个成员的LastNameFirstName,并按照指定的顺序返回布尔值。然后,我们可以使用sort.Sort函数对members切片进行排序。

运行上述代码,你将看到按照LastNameFirstName排序后的结果。

更多关于Go语言排序的信息可以参考这里

英文:

I have an array/slice of members:

type Member struct {
    Id int
    LastName string
    FirstName string
}

var members []Member

My question is how to sort them by LastName and then by FirstName.

答案1

得分: 93

使用sort.Slice(自Go 1.8起可用)或sort.Sort函数对切片进行排序。

对于这两个函数,应用程序提供一个函数来测试一个切片元素是否小于另一个切片元素。要按姓氏和名字排序,先比较姓氏,然后再比较名字:

// 如果姓氏不同,则使用姓氏来确定元素i是否小于元素j。
if members[i].LastName != members[j].LastName {
    return members[i].LastName < members[j].LastName
}
// 否则,使用名字来确定元素i是否小于元素j。
return members[i].FirstName < members[j].FirstName

使用sort.Slice时,可以使用匿名函数来指定less函数:

var members []Member
sort.Slice(members, func(i, j int) bool {
    if members[i].LastName != members[j].LastName {
        return members[i].LastName < members[j].LastName
    }
    return members[i].FirstName < members[j].FirstName
})

使用sort.Sort时,可以通过interface来指定less函数:

type byLastFirst []Member

func (members byLastFirst) Len() int           { return len(members) }
func (members byLastFirst) Swap(i, j int)      { members[i], members[j] = members[j], members[i] }
func (members byLastFirst) Less(i, j int) bool { 
    if members[i].LastName != members[j].LastName {
        return members[i].LastName < members[j].LastName
    }
    return members[i].FirstName < members[j].FirstName
}

sort.Sort(byLastFirst(members))

除非性能分析显示排序是一个热点,否则请使用对应于您的应用程序最方便的函数。

英文:

Use the sort.Slice (available since Go 1.8) or the sort.Sort function to sort a slice of values.

With both functions, the application provides a function that tests if one slice element is less than another slice element. To sort by last name and then first name, compare last name and then first name:

// If last names are different, then use last
// name to determine whether element i is less than
// element j.
if members[i].LastName != members[j].LastName {
    return members[i].LastName &lt; members[j].LastName
}
// Otherwise, use first name to determine whether
// element i is less than element j. 
return members[i].FirstName &lt; members[j].FirstName

The less function is specified using an anonymous function with sort.Slice:

var members []Member
sort.Slice(members, func(i, j int) bool {
    if members[i].LastName != members[j].LastName {
        return members[i].LastName &lt; members[j].LastName
    }
    return members[i].FirstName &lt; members[j].FirstName
})

The less function is specified with through an interface with the sort.Sort function:

type byLastFirst []Member

func (members byLastFirst) Len() int           { return len(members) }
func (members byLastFirst) Swap(i, j int)      { members[i], members[j] = members[j], members[i] }
func (members byLastFirst) Less(i, j int) bool { 
    if members[i].LastName != members[j].LastName {
        return members[i].LastName &lt; members[j].LastName
    }
    return members[i].FirstName &lt; members[j].FirstName    }

sort.Sort(byLastFirst(members))

Unless performance analysis shows that sorting is a hot spot, use the function that's most convenient for your application.

答案2

得分: 32

使用较新的sort.Slice函数,如下所示:

sort.Slice(members, func(i, j int) bool {
	switch strings.Compare(members[i].FirstName, members[j].FirstName) {
	case -1:
		return true
	case 1:
		return false
	}
	return members[i].LastName > members[j].LastName
})

或者类似这样的代码。

英文:

Use the newer sort.Slice function as such:

sort.Slice(members, func(i, j int) bool {
	switch strings.Compare(members[i].FirstName, members[j].FirstName) {
	case -1:
		return true
	case 1:
		return false
	}
	return members[i].LastName &gt; members[j].LastName
})

or something like that.

答案3

得分: 22

另一种模式,我觉得稍微更清晰一些:

如果 members[i].LastName 不等于 members[j].LastName {
    返回 members[i].LastName < members[j].LastName
}

返回 members[i].FirstName < members[j].FirstName
英文:

Another pattern, which I find slightly cleaner:

if members[i].LastName != members[j].LastName {
    return members[i].LastName &lt; members[j].LastName
}

return members[i].FirstName &lt; members[j].FirstName

答案4

得分: 8

我为您翻译了以下内容:

我设法编写的最短且仍然可理解的代码如下:

package main

import (
	"fmt"
	"sort"
)

type Member struct {
	Id        int
	LastName  string
	FirstName string
}

func sortByLastNameAndFirstName(members []Member) {
	sort.SliceStable(members, func(i, j int) bool {
		mi, mj := members[i], members[j]
		switch {
		case mi.LastName != mj.LastName:
			return mi.LastName < mj.LastName
		default:
			return mi.FirstName < mj.FirstName
		}
	})
}

func main() {
	members := []Member{
		{0, "The", "quick"},
		{1, "brown", "fox"},
		{2, "jumps", "over"},
		{3, "brown", "grass"},
		{4, "brown", "grass"},
		{5, "brown", "grass"},
		{6, "brown", "grass"},
		{7, "brown", "grass"},
		{8, "brown", "grass"},
		{9, "brown", "grass"},
		{10, "brown", "grass"},
		{11, "brown", "grass"},
	}

	sortByLastNameAndFirstName(members)

	for _, member := range members {
		fmt.Println(member)
	}
}

完全不同的想法,但使用相同的 API

如果您想避免多次提及 LastNameFirstName 字段,并且想避免混淆 ij(这可能一直发生),我进行了一些尝试,基本思想是:

func sortByLastNameAndFirstNameFunctional(members []Member) {
	NewSorter().
		AddStr(member -> member.LastName).
		AddStr(member -> member.FirstName).
		AddInt(member -> member.Id).
		SortStable(members)
}

由于 Go 不支持用于创建匿名函数的 -> 运算符,也没有像 Java 那样的泛型,因此需要一些语法开销:

func sortByLastNameAndFirstNameFunctional(members []Member) {
	NewSorter().
		AddStr(func(i interface{}) string { return i.(Member).LastName }).
		AddStr(func(i interface{}) string { return i.(Member).FirstName }).
		AddInt(func(i interface{}) int { return i.(Member).Id}).
		SortStable(members)
}

使用 interface{} 和反射,实现和 API 都有点丑陋,但它只提及每个字段一次,并且应用代码没有任何机会意外混淆索引 ij,因为它不涉及它们。

我设计这个 API 的灵感来自于 Java 的 Comparator.comparing

上述排序器的基础设施代码如下:

type Sorter struct{ keys []Key }

func NewSorter() *Sorter { return new(Sorter) }

func (l *Sorter) AddStr(key StringKey) *Sorter { l.keys = append(l.keys, key); return l }
func (l *Sorter) AddInt(key IntKey) *Sorter    { l.keys = append(l.keys, key); return l }

func (l *Sorter) SortStable(slice interface{}) {
	value := reflect.ValueOf(slice)
	sort.SliceStable(slice, func(i, j int) bool {
		si := value.Index(i).Interface()
		sj := value.Index(j).Interface()
		for _, key := range l.keys {
			if key.Less(si, sj) {
				return true
			}
			if key.Less(sj, si) {
				return false
			}
		}
		return false
	})
}

type Key interface {
	Less(a, b interface{}) bool
}

type StringKey func(interface{}) string

func (k StringKey) Less(a, b interface{}) bool  { return k(a) < k(b) }

type IntKey func(interface{}) int

func (k IntKey) Less(a, b interface{}) bool  { return k(a) < k(b) }

希望对您有所帮助!

英文:

The shortest and still comprehensible code I managed to write for this is:

package main

import (
	&quot;fmt&quot;
	&quot;sort&quot;
)

type Member struct {
	Id        int
	LastName  string
	FirstName string
}

func sortByLastNameAndFirstName(members []Member) {
	sort.SliceStable(members, func(i, j int) bool {
		mi, mj := members[i], members[j]
		switch {
		case mi.LastName != mj.LastName:
			return mi.LastName &lt; mj.LastName
		default:
			return mi.FirstName &lt; mj.FirstName
		}
	})
}

The pattern using the switch statement easily extends to more than two sorting criteria and is still short enough to be read.

Here's the rest of the program:

func main() {
	members := []Member{
		{0, &quot;The&quot;, &quot;quick&quot;},
		{1, &quot;brown&quot;, &quot;fox&quot;},
		{2, &quot;jumps&quot;, &quot;over&quot;},
		{3, &quot;brown&quot;, &quot;grass&quot;},
		{4, &quot;brown&quot;, &quot;grass&quot;},
		{5, &quot;brown&quot;, &quot;grass&quot;},
		{6, &quot;brown&quot;, &quot;grass&quot;},
		{7, &quot;brown&quot;, &quot;grass&quot;},
		{8, &quot;brown&quot;, &quot;grass&quot;},
		{9, &quot;brown&quot;, &quot;grass&quot;},
		{10, &quot;brown&quot;, &quot;grass&quot;},
		{11, &quot;brown&quot;, &quot;grass&quot;},
	}

	sortByLastNameAndFirstNameFunctional(members)

	for _, member := range members {
		fmt.Println(member)
	}
}

A completely different idea, but same API

If you want to avoid mentioning the fields LastName and FirstName multiple times and if you want to avoid mixing i and j (which can happen all the way), I played around a bit and the basic idea is:

func sortByLastNameAndFirstNameFunctional(members []Member) {
	NewSorter().
		AddStr(member -&gt; member.LastName).
		AddStr(member -&gt; member.FirstName).
        AddInt(member -&gt; member.Id).
		SortStable(members)
}

Since Go doesn't support the -&gt; operator for creating anonymous functions and also doesn't have generics like Java, a bit of syntactical overhead is needed:

func sortByLastNameAndFirstNameFunctional(members []Member) {
	NewSorter().
		AddStr(func(i interface{}) string { return i.(Member).LastName }).
		AddStr(func(i interface{}) string { return i.(Member).FirstName }).
		AddInt(func(i interface{}) int { return i.(Member).Id}).
		SortStable(members)
}

The implementation as well as the API is a bit ugly using interface{} and reflection, but it only mentions each field once, and the application code does not have a single chance to accidentally mix the indexes i and j since it doesn't deal with them.

I designed this API in the spirit of Java's Comparator.comparing.

The infrastructure code for the above sorter is:

type Sorter struct{ keys []Key }

func NewSorter() *Sorter { return new(Sorter) }

func (l *Sorter) AddStr(key StringKey) *Sorter { l.keys = append(l.keys, key); return l }
func (l *Sorter) AddInt(key IntKey) *Sorter    { l.keys = append(l.keys, key); return l }

func (l *Sorter) SortStable(slice interface{}) {
	value := reflect.ValueOf(slice)
	sort.SliceStable(slice, func(i, j int) bool {
		si := value.Index(i).Interface()
		sj := value.Index(j).Interface()
		for _, key := range l.keys {
			if key.Less(si, sj) {
				return true
			}
			if key.Less(sj, si) {
				return false
			}
		}
		return false
	})
}

type Key interface {
	Less(a, b interface{}) bool
}

type StringKey func(interface{}) string

func (k StringKey) Less(a, b interface{}) bool  { return k(a) &lt; k(b) }

type IntKey func(interface{}) int

func (k IntKey) Less(a, b interface{}) bool  { return k(a) &lt; k(b) }

答案5

得分: 5

一个比当前接受的答案稍微更清晰的解决方案是像这样做:

sort.Slice(members, func(i, j int) bool {
	if members[i].FirstName != members[j].FirstName {
		return members[i].FirstName < members[j].FirstName
	}
	return members[i].LastName < members[j].LastName
})

这种方式的好处在于,如果你添加了一个新的字段Age(或其他字段),你可以很清楚地扩展它:

sort.Slice(members, func(i, j int) bool {
	if members[i].FirstName != members[j].FirstName {
		return members[i].FirstName < members[j].FirstName
	}
	if members[i].Age != members[j].Age {
		return members[i].Age < members[j].Age
	}
	return members[i].LastName < members[j].LastName
})

所以模式是,你为所有属性添加一个if X[i] != X[j]语句,直到最后一个属性,最后一个属性则正常比较。

英文:

A slightly cleaner solution than the currently-accepted answer is to do something more like this:

sort.Slice(members, func(i, j int) bool {
	if members[i].FirstName != members[j].FirstName {
		return members[i].FirstName &lt; members[j].FirstName
	}
	return members[i].LastName &lt; members[j].LastName
})

The nice thing about doing it this way is that it's much clearer how you would extend it if you added a new field Age (or something):

sort.Slice(members, func(i, j int) bool {
	if members[i].FirstName != members[j].FirstName {
		return members[i].FirstName &lt; members[j].FirstName
	}
	if members[i].Age != members[j].Age {
		return members[i].Age &lt; members[j].Age
	}
	return members[i].LastName &lt; members[j].LastName
})

So the pattern is that you add an if X[i] != X[j] statement for all properties until the last one, which is just compared normally.

答案6

得分: 3

这是我通常的做法:

// 您可以编辑此代码!
// 单击此处开始输入。
package main

import (
	"fmt"
	"sort"
	"strings"
)

type User struct {
	Name string
	Age  int
}

func main() {
	users := []User{
		{"A", 10},
		{"B", 10},
		{"C", 20},
		{"D", 5},
		{"A", 100},
	}
	sort.Slice(users, func(i, j int) bool {
		lhs, rhs := users[i], users[j]
		byAge := lhs.Age - rhs.Age
		byName := strings.Compare(lhs.Name, rhs.Name) // 如果相等返回0,如果lhs小于rhs返回-1,如果lhs大于rhs返回1
		
		// +号不是必需的,但可以增加清晰度,表示值增加,即升序。
		// sortBy(+byAge, +byName) // 按年龄升序,按姓名升序排序
		// sortBy(-byAge, +byName) // 按年龄降序,按姓名升序排序
		// sortBy(+byName, +byAge) // 按姓名升序,按年龄升序排序
		return sortBy(-byAge, -byName) // 按年龄降序,按姓名降序排序
	})
	fmt.Println(users)
}

func sortBy(sc ...int) bool {
	for _, c := range sc {
		if c != 0 {
			return c < 0
		}
	}
	return sc[len(sc)-1] < 0
}
英文:

This is how I usually do it:

// You can edit this code!
// Click here and start typing.
package main

import (
	&quot;fmt&quot;
	&quot;sort&quot;
	&quot;strings&quot;
)

type User struct {
	Name string
	Age  int
}

func main() {
	users := []User{
		{&quot;A&quot;, 10},
		{&quot;B&quot;, 10},
		{&quot;C&quot;, 20},
		{&quot;D&quot;, 5},
		{&quot;A&quot;, 100},
	}
	sort.Slice(users, func(i, j int) bool {
		lhs, rhs := users[i], users[j]
		byAge := lhs.Age - rhs.Age
		byName := strings.Compare(lhs.Name, rhs.Name) // Returns 0 if equal, -1 if lhs is less than rhs, and 1 if lhs is greater than rhs
		
		// The + sign is not necessary, but adds clarity as it means increasing in value, aka ascending.
		// sortBy(+byAge, +byName) // Sort by age asc, by name asc
		// sortBy(-byAge, +byName) // Sort by age desc, by name asc
		// sortBy(+byName, +byAge) // Sort by name asc, by age asc
		return sortBy(-byAge, -byName) // Sort by age desc, by name desc
	})
	fmt.Println(users)
}

func sortBy(sc ...int) bool {
	for _, c := range sc {
		if c != 0 {
			return c &lt; 0
		}
	}
	return sc[len(sc)-1] &lt; 0
}

答案7

得分: 1

你可以创建一个函数切片:

package main

type (
   mFunc func(a, b member) bool
   member struct { lastName, firstName string }
)

var members = []member{
   {"Mullen", "Larry"},
   {"Howard", "James"},
   {"Clayton", "Adam"},
   {"Howard", "Ben"},
}

var mFuncs = []mFunc{
   func(a, b member) bool { return a.lastName < b.lastName },
   func(a, b member) bool { return a.firstName < b.firstName },
}

然后通过这些函数进行迭代,直到其中一个函数找到差异:

package main

import (
   "fmt"
   "sort"
)

func main() {
   sort.Slice(members, func(a, b int) bool {
      ma, mb := members[a], members[b]
      for _, mf := range mFuncs {
         if mf(ma, mb) {
            return true
         }
         if mf(mb, ma) {
            break
         }
      }
      return false
   })
   fmt.Println(members) // [{Clayton Adam} {Howard Ben} {Howard James} {Mullen Larry}]
}

参考链接:https://golang.org/pkg/sort#example__sortMultiKeys

英文:

You can create a slice of functions:

package main
type (
mFunc func(a, b member) bool
member struct { lastName, firstName string }
)
var members = []member{
{&quot;Mullen&quot;, &quot;Larry&quot;},
{&quot;Howard&quot;, &quot;James&quot;},
{&quot;Clayton&quot;, &quot;Adam&quot;},
{&quot;Howard&quot;, &quot;Ben&quot;},
}
var mFuncs = []mFunc{
func(a, b member) bool { return a.lastName &lt; b.lastName },
func(a, b member) bool { return a.firstName &lt; b.firstName },
}

and then iterate through the functions, until one of the functions finds a
difference:

package main
import (
&quot;fmt&quot;
&quot;sort&quot;
)
func main() {
sort.Slice(members, func(a, b int) bool {
ma, mb := members[a], members[b]
for _, mf := range mFuncs {
if mf(ma, mb) {
return true
}
if mf(mb, ma) {
break
}
}
return false
})
fmt.Println(members) // [{Clayton Adam} {Howard Ben} {Howard James} {Mullen Larry}]
}

<https://golang.org/pkg/sort#example__sortMultiKeys>

答案8

得分: 0

这非常有帮助。我需要对一组结构体进行排序,并在这里找到了答案。我实际上将其扩展为三重排序。尽管对于运行时来说,这么多排序可能不是最优的,但在某些情况下很有用,特别是当替代方案导致的代码难以维护或修改,并且更快的运行时并不关键时。

这段代码首先按照 code 进行排序,然后按照 group 进行排序,最后按照 IDGroup 进行排序。

英文:

This has been very helpful. I needed to sort a slice of structs, and found my answer here. I actually extended it to triply-sorting. Although sorting this much is not optimal for runtime purposes, it's useful in some circumstances, especially when the alternative leads to code that is hard to maintain or modify and where faster runtimes are not crucial.

sort.Slice(servers, func(i, j int) bool {
if servers[i].code &lt; servers[j].code {
return true
} else if servers[i].code &gt; servers[j].code {
return false
} else {
if servers[i].group &lt; servers[j].group {
return true
} else {
if servers[i].group &gt; servers[j].group {
return false
}
}
}
return servers[i].IDGroup &lt; servers[j]. IDGroup
})

This code sorts first by code, then by group, then by IDGroup.

答案9

得分: 0

刚刚创建了一个Go项目来实现它:https://github.com/itroot/keysort

你只需要返回一个要排序的字段列表:

package main

import (
    "fmt"

    "github.com/itroot/keysort"
)

type Data struct {
    A int
    B string
    C bool
}

func main() {
    slice := []Data{{1, "2", false}, {2, "2", false}, {2, "1", true}, {2, "1", false}}
    keysort.Sort(slice, func(i int) keysort.Sortable {
        e := slice[i]
        return keysort.Sequence{e.A, e.B, e.C}
    })
    fmt.Println(slice)
}

Playground链接:https://play.golang.org/p/reEDcoXNiwh

英文:

Just created go project to implement it: https://github.com/itroot/keysort

You can just return a list of fields to sort:

package main
import (
&quot;fmt&quot;
&quot;github.com/itroot/keysort&quot;
)
type Data struct {
A int
B string
C bool
}
func main() {
slice := []Data{{1, &quot;2&quot;, false}, {2, &quot;2&quot;, false}, {2, &quot;1&quot;, true}, {2, &quot;1&quot;, false}}
keysort.Sort(slice, func(i int) keysort.Sortable {
e := slice[i]
return keysort.Sequence{e.A, e.B, e.C}
})
fmt.Println(slice)
}

Playground link: https://play.golang.org/p/reEDcoXNiwh

答案10

得分: 0

只需阅读官方Go文档中的SortMultiKeys示例代码,并将其调整为适用于你的Member结构。

为了保持这个答案的最新状态,我不会在这里复制和粘贴整个示例代码,但你最终可以按照以下方式编写它:

OrderedBy(lastName, firstName).Sort(members)

如果要求还要按年龄排序,只需添加age闭包即可:

OrderBy(lastName, firstName, age).Sort(members)
英文:

Just read the code of the SortMultiKeys example in the official Go documentation at https://pkg.go.dev/sort and adapt it to your Member structure.

To keep this answer up to date I won't copy and paste the whole example here, but eventually you'll be able to write it as follows:

OrderedBy(lastName, firstName).Sort(members)

If the requirement changed to sort also by age, you'd simply add the age closure:

OrderBy(lastName, firstName, age).Sort(members)

答案11

得分: -1

所有的好答案,
如果你不想编写任何配置,你可以使用外部包来进行排序。

go get -d github.com/raunakjodhawat/multisort

然后像这样调用函数:

sortedSlice, err := multisort.MultiSorted(inputSlice, inputKeys, SortOrders)

要查看一个具体的例子,请访问:
https://github.com/raunakjodhawat/multisort

英文:

All good answers,
If you don't want to write any configuration. You can use the external package to perform sorting.

go get -d github.com/raunakjodhawat/multisort

And call the function like so:

sortedSlice, err := multisort.MultiSorted(inputSlice, inputKeys, SortOrders)

To look at a concrete example, go to:
https://github.com/raunakjodhawat/multisort

答案12

得分: -4

这是更简洁的实现方式:

package main

import (
	"fmt"
	"sort"
)

type Member struct {
	FirstName string
	LastName  string
}

func main() {
	members := []Member{
		{"John", "Doe"},
		{"Jane", "Doe"},
		{"Mary", "Contrary"},
	}

	fmt.Println(members)

	sort.Slice(members, func(i, j int) bool {
		return members[i].LastName < members[j].LastName || members[i].FirstName < members[j].FirstName
	})

	fmt.Println(members)
}

运行后将输出以下结果:

[{John Doe} {Jane Doe} {Mary Contrary}]
[{Mary Contrary} {Jane Doe} {John Doe}]

这是预期的结果:成员按姓氏升序打印,如果姓氏相同,则按名字升序打印。

英文:

Here is a slightly more concise implementation of the accepted answer:

package main
import (
&quot;fmt&quot;
&quot;sort&quot;
)
type Member struct {
FirstName string
LastName  string
}
func main() {
members := []Member{
{&quot;John&quot;, &quot;Doe&quot;},
{&quot;Jane&quot;, &quot;Doe&quot;},
{&quot;Mary&quot;, &quot;Contrary&quot;},
}
fmt.Println(members)
sort.Slice(members, func(i, j int) bool {
return members[i].LastName &lt; members[j].LastName || members[i].FirstName &lt; members[j].FirstName
})
fmt.Println(members)
}

Running will print the following output:

[{John Doe} {Jane Doe} {Mary Contrary}]
[{Mary Contrary} {Jane Doe} {John Doe}]

This is as expected: the members are printed in order of ascending last name, where in the case of a tie, they are printed in order of first name.

huangapple
  • 本文由 发表于 2016年3月21日 11:26:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/36122668.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定