【问题标题】:Golang: update slice in loop for empty interfaceGolang:在循环中更新切片以获取空接口
【发布时间】:2020-09-24 02:06:22
【问题描述】:

例如,我们有 3 个 CSV 文件,常见的是电子邮件列。在第一个文件中是名称和电子邮件,在另一个文件中是电子邮件(加上不同的信息)并且没有名称字段。所以,如果我需要根据第一个文件中的名称和Еmail的对应关系填写2和3个文件字段名称,而不是......我写了这样的代码:

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "path/filepath"
    "strings"

    "github.com/jszwec/csvutil"
)

type User struct {
    Name  string `csv:"name"`
    Email string `csv:"email"`
}

type Good struct {
    User
    Dt string `csv:"details"`
}

type Strange struct {
    User
    St string `csv:"status"`
    Dt string `csv:"details"`
}

var lst map[string]string

func readCSV(fn string, dat interface{}) error {
    raw, err := ioutil.ReadFile(fn)
    if err != nil {
        return fmt.Errorf("Cannot read CSV: %w", err)
    }

    if err := csvutil.Unmarshal(raw, dat); err != nil {
        return fmt.Errorf("Cannot unmarshal CSV: %w", err)
    }
    return nil
}

func fixNames(fl string, in interface{}) error {
    if err := readCSV(fl, in); err != nil {
        return fmt.Errorf("CSV: %w", err)
    }
    switch in.(type) {
    case *[]Good:
        var vals []Good
        for _, v := range *in.(*[]Good) {
            v.Name = lst[strings.TrimSpace(strings.ToLower(v.Email))]
            vals = append(vals, v)
        }
        in = vals
    case *[]Strange:
        var vals []Strange
        for _, v := range *in.(*[]Strange) {
            v.Name = lst[strings.TrimSpace(strings.ToLower(v.Email))]
            vals = append(vals, v)
        }
        in = vals
    }

    b, err := csvutil.Marshal(in)
    if err != nil {
        return fmt.Errorf("Cannot marshal CSV: %w", err)
    }
    ext := filepath.Ext(fl)
    bas := filepath.Base(fl)
    err = ioutil.WriteFile(bas[:len(bas)-len(ext)]+"-XIAOSE"+ext, b, 0644)
    if err != nil {
        return fmt.Errorf("Cannot save CSV: %w", err)
    }
    return nil
}

func main() {
    var users []User
    if err := readCSV("./Guitar_Contacts.csv", &users); err != nil {
        log.Fatalf("CSV: %s", err)
    }
    lst = make(map[string]string)
    for _, v := range users {
        lst[strings.TrimSpace(strings.ToLower(v.Email))] = v.Name
    }

    var usersGood []Good
    if err := fixNames("./Guitar-Good.csv", &usersGood); err != nil {
        log.Fatalf("fix: %s", err)
    }

    var usersStrange []Strange
    if err := fixNames("./Guitar-Uknown.csv", &usersStrange); err != nil {
        log.Fatalf("fix: %s", err)
    }

    fmt.Println("OK")
}

在这段代码中,我不喜欢 func fixNames 中的一部分:开关在哪里:

switch in.(type) {
    case *[]Good:
        var vals []Good
        for _, v := range *in.(*[]Good) {
            v.Name = lst[strings.TrimSpace(strings.ToLower(v.Email))]
            vals = append(vals, v)
        }
        in = vals
    case *[]Strange:
        var vals []Strange
        for _, v := range *in.(*[]Strange) {
            v.Name = lst[strings.TrimSpace(strings.ToLower(v.Email))]
            vals = append(vals, v)
        }
        in = vals
    }

因为我只是在 *in.(SOME_TYPE) 的部分重复代码。我想要一个循环和一个动作用于不同类型、名称和电子邮件字段的结构......

用反射来做这件事也是一个想法。像这样:

v := reflect.ValueOf(in)
v = v.Elem()
for i := 0; i < v.Len(); i++ {
    fmt.Println(v.Index(i))
}

但我不知道下一步该怎么做,如何为 Name 添加 v

【问题讨论】:

    标签: go interface range reflect


    【解决方案1】:

    对于这种特殊情况,您不需要反思。您可以通过意识到您只处理结构的User 部分来清理代码,并且您可以简化类型切换:

    fix:=func(in *User) {
      in.Name = lst[strings.TrimSpace(strings.ToLower(in.Email))]
    }
    switch k:=in.(type) {
      case *[]Good:
         for i := range *k {
             fix( &(*k)[i].User )
         }
      case *[]Strange:
         for i := range *k {
             fix( &(*k)[i].User )
         }
    }
    

    您必须重复 for 循环,但上面的代码会进行适当的更正。

    您可以通过不传递对切片的引用来进行更多清理。

    【讨论】:

    • 还有:for i := range *t { fix(&(*t)[i].User) } 在2个地方,完全一样。如果我添加一个新结构怎么办?像 []Good2、[]Strange666 等?再次,新案例,新复制粘贴。您提出的解决方案非常漂亮的代码,但不是太多,抱歉
    • 这些是不同的类型,没有泛型支持,这是一种满足您需要的高效方式。通过更改设计,您可能会做得更好。反射不会那么干净或高效。
    • Ок,您认为,必须如何改变设计?
    • 去掉类型开关,为两个不同的结构写两个循环,不要使用interface{}。您必须编写两个几乎相同的 for 循环而无需反射,它类型安全且性能良好。反射速度较慢,并且涉及此类场景的反射的代码不能很好地老化。
    【解决方案2】:

    使用反射包,您可以这样做。

    func fixNames(fl string, in interface{}) error {
        //other code
    
        v := reflect.ValueOf(in)
        if v.Kind() == reflect.Ptr {
            arr := v.Elem()
            fmt.Println(arr.Len())
            if arr.Kind() == reflect.Slice || arr.Kind() == reflect.Array {
                for i := 0; i < arr.Len(); i++ {
                    elem := arr.Index(i)
                    f := elem.FieldByName("Name")
                    f.SetString("NameOfUser")
                }
            }
        }
    
        // other code
    }
    

    还有游乐场示例:https://play.golang.org/p/KrGvLVprslH

    【讨论】:

      猜你喜欢
      • 2018-06-18
      • 1970-01-01
      • 2019-07-21
      • 1970-01-01
      • 2021-12-20
      • 2012-09-27
      • 2017-08-29
      • 2013-10-23
      • 1970-01-01
      相关资源
      最近更新 更多