如何在反序列化期间按照先进先出(FIFO)顺序对 Map 进行排序?

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

How to FIFO Order a Map during Unmarshalling

问题

我知道从阅读中得知,在Go语言中,映射(Maps)是有意无序的,但它们提供了许多我想在这个问题上使用的好处。我的问题是如何按照FIFO(先进先出)的方式对映射进行排序?是否值得尝试实现这个功能?具体来说,我希望能够将其反序列化为一组结构体,最好是通过接口。

我有以下代码:

type Package struct {
    Account   string
    Jobs      []*Jobs
    Libraries map[string]string
}

type Jobs struct {
    // Job的名称
    JobName string `mapstructure:"name" json:"name" yaml:"name" toml:"name"`
    // Job的类型,应该是下面job结构中列出的字符串之一
    Job *Job `mapstructure:"job" json:"job" yaml:"job" toml:"job"`
    // 不进行序列化
    JobResult string
    // 多个值
    JobVars []*Variable
}

type Job struct {
    // 设置/重置要使用的主要账户
    Account *Account `mapstructure:"account" json:"account" yaml:"account" toml:"account"`
    // 设置一个任意值
    Set *Set `mapstructure:"set" json:"set" yaml:"set" toml:"set"`
    // 编译合约并发送到链上
    Deploy *Deploy `mapstructure:"deploy" json:"deploy" yaml:"deploy" toml:"deploy"`
    // 从一个账户发送代币到另一个账户
    Send *Send `mapstructure:"send" json:"send" yaml:"send" toml:"send"`
    // 利用eris:db的本地名称注册表注册一个名称
    RegisterName *RegisterName `mapstructure:"register" json:"register" yaml:"register" toml:"register"`
    // 发送一个将更新账户权限的交易。必须从在区块链上具有根权限的账户发送(由genesis.json或后续交易中设置)
    Permission *Permission `mapstructure:"permission" json:"permission" yaml:"permission" toml:"permission"`
    // 发送一个bond交易
    Bond *Bond `mapstructure:"bond" json:"bond" yaml:"bond" toml:"bond"`
    // 发送一个unbond交易
    Unbond *Unbond `mapstructure:"unbond" json:"unbond" yaml:"unbond" toml:"unbond"`
    // 发送一个rebond交易
    Rebond *Rebond `mapstructure:"rebond" json:"rebond" yaml:"rebond" toml:"rebond"`
    // 发送一个合约交易。将在底层使用eris-abi执行所有繁重的工作
    Call *Call `mapstructure:"call" json:"call" yaml:"call" toml:"call"`
    // mintdump dump的包装器。WIP
    DumpState *DumpState `mapstructure:"dump-state" json:"dump-state" yaml:"dump-state" toml:"dump-state"`
    // mintdum restore的包装器。WIP
    RestoreState *RestoreState `mapstructure:"restore-state" json:"restore-state" yaml:"restore-state" toml:"restore-state"`
    // 向合约发送一个“模拟调用”。主要用于合约中的访问器函数(合约内的“Getter”)
    QueryContract *QueryContract `mapstructure:"query-contract" json:"query-contract" yaml:"query-contract" toml:"query-contract"`
    // 查询账户的信息
    QueryAccount *QueryAccount `mapstructure:"query-account" json:"query-account" yaml:"query-account" toml:"query-account"`
    // 查询eris:db的本地名称注册表中注册的名称的信息
    QueryName *QueryName `mapstructure:"query-name" json:"query-name" yaml:"query-name" toml:"query-name"`
    // 查询验证者集合的信息
    QueryVals *QueryVals `mapstructure:"query-vals" json:"query-vals" yaml:"query-vals" toml:"query-vals"`
    // 进行断言(用于测试目的)
    Assert *Assert `mapstructure:"assert" json:"assert" yaml:"assert" toml:"assert"`
}

我想做的是让Jobs包含一个从字符串到Job的映射,并消除job字段,同时保持它们在配置文件中的顺序(目前使用viper)。欢迎任何关于如何实现这一点的建议。

英文:

I know from reading around that Maps are intentionally unordered in Go, but they offer a lot of benefits that I would like to use for this problem I'm working on. My question is how might I order a map FIFO style? Is it even worth trying to make this happen? Specifically I am looking to make it so that I can unmarshal into a set of structures hopefully off of an interface.

I have:

type Package struct {
Account   string
Jobs      []*Jobs
Libraries map[string]string
}
type Jobs struct {
// Name of the job
JobName string `mapstructure:"name" json:"name" yaml:"name" toml:"name"`
// Type of the job. should be one of the strings outlined in the job struct (below)
Job *Job `mapstructure:"job" json:"job" yaml:"job" toml:"job"`
// Not marshalled
JobResult string
// For multiple values
JobVars []*Variable
}
type Job struct {
// Sets/Resets the primary account to use
Account *Account `mapstructure:"account" json:"account" yaml:"account" toml:"account"`
// Set an arbitrary value
Set *Set `mapstructure:"set" json:"set" yaml:"set" toml:"set"`
// Contract compile and send to the chain functions
Deploy *Deploy `mapstructure:"deploy" json:"deploy" yaml:"deploy" toml:"deploy"`
// Send tokens from one account to another
Send *Send `mapstructure:"send" json:"send" yaml:"send" toml:"send"`
// Utilize eris:db's native name registry to register a name
RegisterName *RegisterName `mapstructure:"register" json:"register" yaml:"register" toml:"register"`
// Sends a transaction which will update the permissions of an account. Must be sent from an account which
// has root permissions on the blockchain (as set by either the genesis.json or in a subsequence transaction)
Permission *Permission `mapstructure:"permission" json:"permission" yaml:"permission" toml:"permission"`
// Sends a bond transaction
Bond *Bond `mapstructure:"bond" json:"bond" yaml:"bond" toml:"bond"`
// Sends an unbond transaction
Unbond *Unbond `mapstructure:"unbond" json:"unbond" yaml:"unbond" toml:"unbond"`
// Sends a rebond transaction
Rebond *Rebond `mapstructure:"rebond" json:"rebond" yaml:"rebond" toml:"rebond"`
// Sends a transaction to a contract. Will utilize eris-abi under the hood to perform all of the heavy lifting
Call *Call `mapstructure:"call" json:"call" yaml:"call" toml:"call"`
// Wrapper for mintdump dump. WIP
DumpState *DumpState `mapstructure:"dump-state" json:"dump-state" yaml:"dump-state" toml:"dump-state"`
// Wrapper for mintdum restore. WIP
RestoreState *RestoreState `mapstructure:"restore-state" json:"restore-state" yaml:"restore-state" toml:"restore-state"`
// Sends a "simulated call" to a contract. Predominantly used for accessor functions ("Getters" within contracts)
QueryContract *QueryContract `mapstructure:"query-contract" json:"query-contract" yaml:"query-contract" toml:"query-contract"`
// Queries information from an account.
QueryAccount *QueryAccount `mapstructure:"query-account" json:"query-account" yaml:"query-account" toml:"query-account"`
// Queries information about a name registered with eris:db's native name registry
QueryName *QueryName `mapstructure:"query-name" json:"query-name" yaml:"query-name" toml:"query-name"`
// Queries information about the validator set
QueryVals *QueryVals `mapstructure:"query-vals" json:"query-vals" yaml:"query-vals" toml:"query-vals"`
// Makes and assertion (useful for testing purposes)
Assert *Assert `mapstructure:"assert" json:"assert" yaml:"assert" toml:"assert"`
}

What I would like to do is to have jobs contain a map of string to Job and eliminate the job field, while maintaining order in which they were placed in from the config file. (Currently using viper). Any and all suggestions for how to achieve this are welcome.

答案1

得分: 4

你需要将键存储在一个单独的切片中,并与之一起操作。

type fifoJob struct {
    m      map[string]*Job
    order  []string
    result []string
    // 不确定 JobVars 将放在哪里。
}

func (str *fifoJob) Enqueue(key string, val *Job) {
    str.m[key] = val
    str.order = append(str.order, key)
}

func (str *fifoJob) Dequeue() {
    if len(str.order) > 0 {
        delete(str.m, str.order[0])
        str.order = str.order[1:]
    }
}

无论如何,如果你正在使用 viper,你可以使用上面定义的 fifoJob 结构体。还要注意,我在这里做了一些假设。

type Package struct {
    Account   string
    Jobs      *fifoJob
    Libraries map[string]string
}

var config Package
config.Jobs = fifoJob{}
config.Jobs.m = map[string]*Job{}

// 你的配置文件需要将顺序存储在一个数组中。
// 如果 viper 有一个返回 []interface{} 的 getSlice 方法,那就更容易了。
config.Jobs.order = viper.GetStringSlice("package.jobs.order")

for k, v := range viper.GetStringMap("package.jobs.jobmap") {
    if job, ok := v.(Job); ok {
        config.Jobs.m[k] = &job
    }
}

PS:你在问题中提供了太多无关的细节。我要求提供一个 MCVE

英文:

You would need to hold the keys in a separate slice and work with that.

type fifoJob struct {
m map[string]*Job
order []string
result []string
// Not sure where JobVars will go. 
}
func (str *fifoJob) Enqueue(key string, val *Job) {
str.m[key] = val
str.order = append(str.order, key)
}
func (str *fifoJob) Dequeue() {
if len(str.order) > 0 {
delete(str.m, str.order[0])
str.order = str.order[1:]
}
}

Anyways if you're using viper you can use something like the fifoJob struct defined above. Also note that I'm making a few assumptions here.

type Package struct {
Account   string
Jobs      *fifoJob
Libraries map[string]string
}
var config Package
config.Jobs = fifoJob{}
config.Jobs.m = map[string]*Job{}
// Your config file would need to store the order in an array.
// Would've been easy if viper had a getSlice method returning []interface{}
config.Jobs.order = viper.GetStringSlice("package.jobs.order") 
for k,v := range viper.GetStringMap("package.jobs.jobmap") {
if job, ok := v.(Job); ok {
config.Jobs.m[k] = &job
}
}

for

PS: You're giving too many irrelevant details in your question. I was asking for a MCVE.

答案2

得分: 0

地图本质上是无序的,但你可以用键填充一个切片。然后你可以遍历切片并按照你喜欢的方式进行排序。你可以使用[i]来提取切片中的特定元素。

请查看以下页面的第170、203或204页,其中有一些很好的示例:

《Go编程》

英文:

Maps are by nature unordered but you can fill up a slice instead with your keys. Then you can range over your slice and sort it however you like. You can pull out specific elements in your slice with [i].

Check out pages 170, 203, or 204 of some great examples of this:

Programming in Go

huangapple
  • 本文由 发表于 2016年11月4日 07:31:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/40412953.html
匿名

发表评论

匿名网友

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

确定