go-linq desde la entrada hasta el combate real

¿Qué es go-linq?

Una poderosa biblioteca para integrar consultas en el lenguaje Go.

ventajas go-linq

  1. Es una biblioteca implementada en lenguaje Go, sin otras bibliotecas dependientes;
  2. Use el modo de iterador para implementar la carga diferida, es decir, ejecute un método sin usar un método y ejecútelo solo cuando se requiera una salida;
  3. seguridad de concurrencia;
  4. Las funciones comunes hacen que el código sea más conciso;
  5. Las colecciones de datos admiten múltiples tipos: rebanada, matriz, mapa, canal, colecciones personalizadas.

¿Cómo usar go-linq?

Go-linq usa una lógica de control común y llamadas a métodos encadenados para procesar recopilaciones de datos, similar al uso de ORM para operar una base de datos, refina operaciones y controles comunes, separa preocupaciones y solo necesita enfocarse en la entrada y la salida.

por ejemplo:

From(slice) .Where(predicate) .Select(selector) .Union(data)

  • De: genera un tipo especial de consulta para go-linq a partir de un conjunto de datos. Diferentes conjuntos de datos proporcionan diferentes iteradores.
  • Donde: un método de control general provisto por go-linq, lo que significa filtrar elementos de colección que cumplen con las condiciones
  • Predicado: predicado condicional, es decir, el elemento de xxxxxx
  • Seleccionar: seleccionar o transformar, selector selector o transformar
  • Unión: un método genérico proporcionado por go-linq para combinar dos conjuntos de datos

Solo tenemos que centrarnos en tres puntos: entrada -> control -> salida

Siempre que estos tres puntos se aclaren en cada paso de la operación, go-linq puede usarse bien para simplificar el código y mejorar la legibilidad del código.

ingresar

Los diversos conjuntos de datos admitidos por Go-linq generan estructuras de Consulta. Los diferentes tipos de conjuntos de datos tienen diferentes iteradores en Consulta.

type Query struct {
    Iterate func() Iterator
}

Se admiten los siguientes conjuntos de datos:

  1. Rodaja
  2. Formación
  3. Mapa
  4. Canal
  5. Colecciones personalizadas

El método para generar la estructura de consulta a partir del conjunto de datos:

// 支持任一类型的集合生成 query
func From(source interface{}) Query

// 只支持 string  类型, 生成 query
func FromString(source string) Query

// 只支持 channel 类型, 生成 query
func FromChannel(source <-chan interface{}) Query

// 支持自定义集合生成 query
func FromIterable(source Iterable) Query

// 在指定范围内, 生成集合
func Range(start, count int) Query

// 指定值和重复次数, 生成重复数据的集合
func Repeat(value interface{}, count int) Query

其中最常用的是 From(source interface{}) Query 方法,支持任一数据集类型生成 Query,甚至是自定义的集合,源码中使用反射根据类型生成 query:

From

  • 衍生方法: FromString, FromChannel, FromIterable, Range, Repeat
  • 含义: 通过数据集合生成 Query 结构,用于迭代控制

举例:

// case 1:
From("hello world")
FromString("hello world")

// case 2:
Range(1, 10)
Repeat("say 3 times", 3)

// case 3:
type user struct {
   name  string
   cards []string
}

userList := []*user{
   {name: "tom", cards: []string{"a", "b", "c"}},
   {name: "jack", cards: []string{"d", "e", "f"}},
   {name: "json", cards: []string{"g", "h", "i"}}
}

From(userList)

实战:

 /**
  * 构建人才相关搜索语句
  */
func buildTalentSearchClause(talentQuery *TalentQuery) (queries []elastic.Query) {

   linq.From(buildTalentSearchTermsQuery(talentQuery)).
      Concat(linq.From(buildTalentSearchBoolQuery(talentQuery))).
      Concat(linq.From(buildTalentSearchNestedQuery(talentQuery))).
      Concat(linq.From(buildTalentSearchCareerNestedQuery(talentQuery))).
      Concat(linq.From(buildTalentSearchEducationNestedQuery(talentQuery))).
      Concat(linq.From(buildTalentSearchRangeQuery(talentQuery))).ToSlice(&queries)

   return

}

控制

Go-linq 提供的通用数据集操作,可参考官方文档,日常开发中常用的大概有以下几类:

  • 去肥增瘦
  • 挑三拣四
  • 查漏补缺
  • 拉帮结伙

其中有三个关键字,对于理解控制方法非常重要:

  1. withT:T -> type,表示闭包的参数,指定为和数据集合元素一样的类型,避免使用时强转(常用);
  2. By:以什么为依据:比如:分组操作,可以指定以哪个参数来分组;
  3. ByT:也是以什么为依据。闭包中指定和数据集合一样的类型(常用)。

ForEach

  • 衍生方法: ForEachT, ForEachIndexed, ForEachIndexedT
  • 含义: 遍历数据集合的元素,同 for 循环

举例:

type user struct {
   name  string
   cards []string
}

userList := []*user{
   {name: "tom", cards: []string{"a", "b", "c"}},
   {name: "jack", cards: []string{"d", "e", "f"}},
   {name: "json", cards: []string{"g", "h", "i"}}
}

From(userList).ForEachT(func(user *user) {
   fmt.Printf("name:%s, cards:%v\n", user.name, userList)
})

output:
name:tom, cards:[0xc000076180 0xc0000761e0 0xc000076240]
name:jack, cards:[0xc000076180 0xc0000761e0 0xc000076240]
name:json, cards:[0xc000076180 0xc0000761e0 0xc000076240]

实战:

 /**
  * 各个评估结论的个数-映射关系
  */
func (self *evaluationSummaryService) getConclusionCount(evaluationList []*domain.Evaluation) (result conclusionCount) {

   result = make(conclusionCount)

   linq.From(evaluationList).ForEachT(
      func(evaluation *domain.Evaluation) {
         if *evaluation.CommitStatus == constants.CommitStatus_UnCommitted {
            result[constants.Conclusion_NoConclusion] += 1
         } else if evaluation.Conclusion != nil {
            result[*evaluation.Conclusion] += 1
         }
      },
   )
   
   return
}

Where

  • 衍生方法: WhereT, WhereIndexed, WhereIndexedT
  • 含义: 筛选出符合条件的集合元素

举例:

numList := []int64{1, 2, 3, 5, 8, 13}
query := From(numList).Where(func(i interface{}) bool {
   return i.(int64) > 8
})

fmt.Println(query.Results())

output:
[13]

实战:

 /**
  * 获取除了"已入职"以外的所有阶段
  */
func (self *evaluationSearchProdService) listAllStageIDWithoutHired(ctx context.Context) (result []string) {

   allStageData := facade.ApplicationFacade.ListAllStageWithCache(ctx)
   linq.From(allStageData).WhereT(func(item *aInf.StageInfo) bool {
      return *item.TypeA1 != aconstants.StageType_Hired
   }).SelectT(func(item *aInf.StageInfo) string {
      return *item.ID
   }).ToSlice(&result)

   return
}

Any

  • 衍生方法: AnyWith, AnyWithT
  • 含义: 任意一个,with / withT 可指定条件,即满足条件的任意一个

举例:

numList := []int64{1, 2, 3, 5, 8, 13}

query := From(numList).AnyWithT(func(i int64) bool {
   return i > 8
})

fmt.Println(query)

output:
true

实战:

 /**
  * 判断是否需要扩充阶段
  */
func (self *evaluationSearchProdService) needExtendStage(stageIDList []string) bool {
   return linq.From(stageIDList).AnyWithT(func(item string) bool {
      return item == constant.ApplicationStatusGroupInProgress
   })

}

Append / Prepend

  • 衍生方法:
  • 含义: 集合中插入元素,后插/前插,类似于列表的 append 方法

举例:

numList := []int64{1, 2, 3, 5, 8, 13}
query := From(numList).Append(100)
fmt.Println(query.Results())

output:
[1 2 3 5 8 13 100]

numList := []int64{1, 2, 3, 5, 8, 13}
query := From(numList).prepend(100)
fmt.Println(query.Results())

output:
[100 1 2 3 5 8 13]

实战: 暂无

Select

  • 衍生方法: SelectT, SelectMany, SelectManyBy, SelectManyByT, SelectIndexed, SelectIndexedT, SelectManyIndexedT, SelectManyIndexed, SelectManyByIndexed, SelectManyByIndexedT
  • 含义: 挑选出集合中的元素,可进行转化后组成新的集合,这是最常用的控制方法,效果同 flatmap 操作

举例:

// case 1:
numList := []int64{1, 2, 3, 5, 8, 13}
query := From(numList).SelectT(func(i int64) string {
   return fmt.Sprintf("num: %d\n", i)
})
fmt.Println(query.Results())

output:
[
    num: 1 
    num: 2 
    num: 3 
    num: 5 
    num: 8 
    num: 13
]

// case 2:
num1 := []int64{1, 2, 3}
num2 := []int64{4, 5, 6}
num3 := []int64{7, 8, 9}

numList := [][]int64{
   num1,
   num2,
   num3,
}

query := From(numList).SelectManyT(func(num []int64) Query {
   return From(num).SelectT(func(i int64) string {
      return fmt.Sprintf("num: %d", i)
   })
})

fmt.Println(query.Results())

output:
[
    num: 1 
    num: 2 
    num: 3 
    num: 4 
    num: 5 
    num: 6 
    num: 7 
    num: 8 
    num: 9
]

// case 3:
num1 := []int64{1, 2, 3}
num2 := []int64{4, 5, 6}
num3 := []int64{7, 8, 9}

numList := [][]int64{
   num1,
   num2,
   num3,
}

query := From(numList).SelectManyByT(
   func(num []int64) Query { return From(num) },
   func(item int64, num []int64) string { return fmt.Sprintf("num:%v, numList:%v\n", num, numList) },
)

fmt.Println(query.Results())

output:
[
 num:1, numList:[1 2 3]
 num:2, numList:[1 2 3]
 num:3, numList:[1 2 3]
 num:4, numList:[4 5 6]
 num:5, numList:[4 5 6]
 num:6, numList:[4 5 6]
 num:7, numList:[7 8 9]
 num:8, numList:[7 8 9]
 num:9, numList:[7 8 9]
]

实战:

 /**
  * 获取除了"已入职"以外的所有阶段
  */
func (self *evaluationSearchProdService) listAllStageIDWithoutHired(ctx context.Context) (result []string) {
   allStageData := facade.ApplicationFacade.ListAllStageWithCache(ctx)

   linq.From(allStageData).WhereT(func(item *aInf.StageInfo) bool {
      return *item.TypeA1 != aconstants.StageType_Hired
   }).SelectT(func(item *aInf.StageInfo) string {
      return *item.ID
   }).ToSlice(&result)

   return
}

Group

  • 衍生方法: GroupBy, GroupByT, GroupJoin, GroupJoinT
  • 含义: 根据字段来分组

注意:分组后产生的是一个新的数据结构 Group

type Group struct {
   Key   interface{}
   Group []interface{}
}

举例:

// case 1:
type Person struct {
   Name string
   Age  int64
}

personList := []*Person{
   {Name: "Jack", Age: 18},
   {Name: "Tom", Age: 23},
   {Name: "John", Age: 23},
   {Name: "Susan", Age: 38},
}

query := From(personList).GroupByT(
   func(person *Person) int64 { return person.Age },
   func(person *Person) string { return person.Name },
)

fmt.Println(query.Results())

output:
[{18 [Jack]} {23 [Tom John]} {38 [Susan]}]

实战:

 /**
  * 查询笔试阅卷数据, key: examID, value: examMarking list
  */
func queryExamMarkingMap(ctx context.Context, examList []*domain.Exam) (result map[string][]*domain.ExamMarking) {

   markingList := ExamMarkingService.QueryByExamIDList(ctx, util.ExtractStringFromSlice(examList, "ID"))
   result = make(map[string][]*domain.ExamMarking, len(markingList))

   // convert list to map, key: examID, value: examMarking list
   linq.From(markingList).
      WhereT(func(marking *domain.ExamMarking) bool {
         return marking != nil && util.IsNotStrBlankPtr(marking.ExamID)
      }).
      GroupByT(
         func(marking *domain.ExamMarking) string { return *marking.ExamID },
         func(marking *domain.ExamMarking) *domain.ExamMarking { return marking },
      ).
      ToMapByT(
         &result,
         func(g linq.Group) string { return g.Key.(string) },
         func(g linq.Group) (examMarkingList []*domain.ExamMarking) {
            linq.From(g.Group).SelectT(
               func(gVal interface{}) *domain.ExamMarking { return gVal.(*domain.ExamMarking) },
            ).ToSlice(&examMarkingList)
            return
         },
      )

   return
}

Join

  • 衍生方法: JoinT
  • 含义: 基于相同的键,将两个集合的元素进行关联,跟 sql 的 join 使用类似

举例:

// case1: 

fruits := []string{
   "apple",
   "banana",
   "apricot",
   "cherry",
   "clementine",
}

query := Range(1, 10).
   JoinT(From(fruits),
      func(num int) int { return num },
      func(fruit string) int { return len(fruit) },
      func(num int, fruit string) KeyValue {
         return KeyValue{Key: num, Value: fruit}
      })

fmt.Println(query.Results())

output:
[{5 apple} {6 banana} {6 cherry} {7 apricot} {10 clementine}]

// case 2:
type Person struct {
   Name string
   Age  int64
}

type Pet struct {
   Name  string
   Owner Person
}

magnus := Person{Name: "Hedlund, Magnus"}
terry := Person{Name: "Adams, Terry"}
charlotte := Person{Name: "Weiss, Charlotte"}

barley := Pet{Name: "Barley", Owner: Person{Name: "Adams, Terry"}}
boots := Pet{Name: "Boots", Owner: Person{Name: "Adams, Terry"}}
whiskers := Pet{Name: "Whiskers", Owner: Person{Name: "Weiss, Charlotte"}}
daisy := Pet{Name: "Daisy", Owner: Person{Name: "Hedlund, Magnus"}}

people := []Person{magnus, terry, charlotte}
pets := []Pet{barley, boots, whiskers, daisy}

query := From(people).
   JoinT(From(pets),
      func(person Person) Person { return person },
      func(pet Pet) Person { return pet.Owner },
      func(person Person, pet Pet) string {
         return fmt.Sprintf("%s - %s\n", person.Name, pet.Name)
      },
   )

fmt.Println(query.Results())

output:
[
 Hedlund, Magnus - Daisy
 Adams, Terry - Barley
 Adams, Terry - Boots
 Weiss, Charlotte - Whiskers
]

实战:

func (self *interviewerLarkCalendarLimiter) check(in AppointmentTimeSlice) bool {

   userIsBusyMap := make(map[string]bool, len(in))
   userBusyTimeMap := self.queryUserBusyTimeMap(in)

   From(in).
      JoinT(
         From(userBusyTimeMap),
         func(iter *AppointmentTime) interface{} { return iter.UserID },
         func(kv KeyValue) interface{} { return kv.Key },
         func(src *AppointmentTime, kv KeyValue) KeyValue {
            idle := src.RecommendTimeList.Reject(kv.Value.(utils.IntervalMixSlice))
            return KeyValue{Key: src.UserID, Value: src.RecommendTimeList.Equals(idle)}
         },
      ).
      ToMap(&userIsBusyMap)

   for _, isBusy := range userIsBusyMap {
      if isBusy {
         return false
      }
   }

   return true

}

Intersect

  • 衍生方法: IntersectBy, IntersectBy
  • 含义: 两个数据集合取交集,带 by 关键字的表示以字段为依据取交集

举例:

// case1: 值类型的基本数据类型
id1 := []int{44, 26, 92, 30, 71, 38}
id2 := []int{39, 59, 83, 47, 26, 4, 30}

query := From(id1).Intersect(From(id2))
fmt.Println(query.Results())

output:
[26 30]

// case1: 值类型的struct  类型
type Person struct {
   Name string
   Age  int64
}

p1 := Person{Name: "Jack", Age: 23}
p2 := Person{Name: "Jon", Age: 18}
p3 := Person{Name: "Tom", Age: 23}

aList := []Person{p1, p2}
bList := []Person{p2, p3}

var both []Person
From(aList).Intersect(From(bList)).ToSlice(&both)
fmt.Println(both)

output:
[{Jon 18}]

// case 3: 引用类型
type Person struct {
   Name string
   Age  int64
}

p1 := &Person{Name: "Jack", Age: 23}
p2 := &Person{Name: "Jon", Age: 18}
p3 := &Person{Name: "Tom", Age: 23}
p4 := &Person{Name: "Tom", Age: 23}

aList := []*Person{p1, p2, p4}
bList := []*Person{p2, p3}

var both []*Person
From(aList).Intersect(From(bList)).ToSlice(&both)

for _, p := range both {
   fmt.Printf("%p, %s, %d", p, p.Name, p.Age)
}

output:
0xc00000c0a0, Jon, 18

// case 4: 指定求交集的依据
type Person struct {
   Name string
   Age  int64
}

p1 := &Person{Name: "Jack", Age: 23}
p2 := &Person{Name: "Jon", Age: 18}
p3 := &Person{Name: "Tom", Age: 23}
p4 := &Person{Name: "Tom", Age: 23}

aList := []*Person{p1, p2, p4}
bList := []*Person{p2, p3}

var both []*Person
From(aList).
   IntersectByT(From(bList), func(i *Person) int64 {
      return i.Age
   }).
   ToSlice(&both)

for _, p := range both {
   fmt.Printf("%p, %s, %d\n", p, p.Name, p.Age)
}

output:
0xc00000c080, Jack, 23
0xc00000c0a0, Jon, 18

实战:

func selectAvailableInterviewerTime(ctx context.Context, appointmentTask *domain.InterviewAppointmentTask,
   filteredUserTimeList []*domain.InterviewAppointmentInterviewerTime) (result []*domain.InterviewAppointmentInterviewerTime) {

   From(appointmentTask.InterviewerTimeList).
      IntersectByT(
         From(filteredUserTimeList),
         func(interviewerTime *domain.InterviewAppointmentInterviewerTime) string { return *interviewerTime.UserID },
      ).
      DistinctByT(func(interviewerTime *domain.InterviewAppointmentInterviewerTime) int64 {
         return *interviewerTime.EndTime - *interviewerTime.BeginTime
      }).
      ToSlice(&result)

   log.Info(ctx, "[selectAvailableInterviewerTime] result: %v", data_tk.Show(result))

   return
}

Except

  • 衍生方法: ExceptBy, ExceptByT
  • 含义: 两个数据集合取差集,带 by 关键字的表示以字段为依据取差集

举例:

// case 1

type Person struct {
   Name string
   Age  int64
}

p1 := Person{Name: "Jack", Age: 23}
p2 := Person{Name: "Jon", Age: 18}
p3 := Person{Name: "Tom", Age: 23}

aList := []Person{p1, p2}
bList := []Person{p2, p3}

var both []Person
From(aList).Except(From(bList)).ToSlice(&both)
fmt.Println(both)

output:
[{Jack 23}]

实战: 暂无

Union

  • 衍生方法:
  • 含义: 两个集合的并集,相同的元素去重

举例:

// case1
id1 := []int{1, 2, 3, 4}
id2 := []int{3, 4, 5, 6}

query := From(id1).Union(From(id2))
fmt.Println(query.Results())

output:
[1 2 3 4 5 6]

实战: 暂无

Concat

  • 衍生方法:
  • 含义: 两个集合的并集,相同的元素不去重

举例:

// case1
id1 := []int{1, 2, 3, 4}
id2 := []int{3, 4, 5, 6}

query := From(id1).Concat(From(id2))
fmt.Println(query.Results())

output:
[1 2 3 4 3 4 5 6]

实战:

func (self *evaluationSearchProdService) supplyApplicationStageAndStatus(ctx context.Context,
   stageAndStatusIDList []string) (result []string) {

   if len(stageAndStatusIDList) == 0 {
      return
   }
   
   // 判断是否需要扩展阶段
   existExtend := self.needExtendStage(stageAndStatusIDList)
   if !existExtend {
      return stageAndStatusIDList
   }

   // 主要构成两部分: 1.前端传的阶段, 去掉"进行中"; 2.补充除了"已入职"的所有阶段
   // 即: "进行中"阶段 == 除了"已入职"阶段的阶段
   linq.From(
      self.filterStageIDWithoutInProgress(stageAndStatusIDList),
   ).Concat(
      linq.From(self.listAllStageIDWithoutHired(ctx)),
   ).SelectT(func(item string) string {
      return item
   }).ToSlice(&result)

   log.Info(ctx, "[supplyApplicationStageAndStatus] stage_id_list:%v", result)

   return

}

Distinct

  • 衍生方法: DistinctBy, DistinctByT
  • 含义: 数据集合去重,union() == concat().distinct()

举例:

// case1
id1 := []int{1, 2, 3, 4}
id2 := []int{3, 4, 5, 6}

query := From(id1).Concat(From(id2)).Distinct()
fmt.Println(query.Results())

output:
[1 2 3 4 5 6]

实战:

 /**

 * 补充投递来源

 */

func (self *evaluationSearchProdService) supplyApplicationSource(ctx context.Context, sourceIDList []string) (
   result []string) {

   if len(sourceIDList) == 0 {
      return
   }

   // 查询投递来源
   resumeSourceList := facade.TalentFacade.ListResumeSourceByIDList(ctx,tconstants.ResumeSourceUsagePtr(tconstants.ResumeSourceUsage_Application),
      util.BoolPtr(true),
      sourceIDList,
   ).ResumeSourceInfoList

   // 查找投递来源 ID, concat 前端传入的 applicationSource, 防止 talent 服务返回的 resumeSourceList 为空
   linq.From(resumeSourceList).
      SelectManyT(func(item *tInf.ResumeSourceInfo) linq.Query {
         return linq.From(builder.ExtractResumeSourceIDList(item))
      }).
      Concat(linq.From(sourceIDList)).
      Distinct().
      ToSlice(&result)

   return
}

输出

Last

  • 衍生方法: LastWith, LastWithT
  • 含义: 取数据集中的最后一个元素,带 with 关键字,表示以什么条件取最后一个元素

举例:

// case1
id1 := []int{1, 2, 3, 4}
query := From(id1).Last()
fmt.Println(query)

output:
4

实战: 暂无

First

  • 衍生方法: FirstWith, FirstWithT
  • 含义: 取数据集中的第一个元素,带 with 关键字,表示以什么条件取第一个元素

举例:

// case 1
id1 := []int{1, 2, 3, 4}

query := From(id1).FirstWithT(func(i int) bool {
   return i > 3
})
fmt.Println(query)

output:
4

实战:

 /**

 * 批量更新评估的 evaluation_status

 */

func (self *evaluationStatusService) batchUpdateEvaluationStatus(ctx context.Context, db *db.DB,
   evaluationList []*domain.Evaluation, evaluationStatus evalCts.ActivityStatus) {

   if len(evaluationList) == 0 {
      log.Info(ctx, "[evaluationStatusService][batchUpdateEvaluationStatus] evaluation list is empty")
      return
   }

   evaluationIDList := util.ExtractStringFromSlice(evaluationList, "ID")
   evaluationExtList := repo.EvaluationExtRepo.QueryByEvaluationIDList(ctx, db, evaluationIDList)
   if len(evaluationExtList) == 0 {
      log.Info(ctx, "[evaluationStatusService][batchUpdateEvaluationStatus] "+
         "evaluation ext list is empty by evaluation_id_list: [%v]", evaluationIDList)
      return
   }

   evaluationExtIDList := util.ExtractStringFromSlice(evaluationExtList, "ID")
   // 更新评估状态
   tempEvaluationExt := linq.From(evaluationExtList).First().(*domain.EvaluationExt)
   tempEvaluationExt.EvaluationStatus = evalCts.ActivityStatusPtr(evaluationStatus)
   tempEvaluationExt.BizModifyTime = util.Int64Ptr(util.GetMilliTimestamp())
   repo.EvaluationExtRepo.BatchUpdateByIDList(ctx, db, evaluationExtIDList, tempEvaluationExt)

   log.Info(ctx, "[evaluationStatusService][batchUpdateEvaluationStatus] "+
      "update evaluation_status [%v] for evaluation_id_list:[%v]", evaluationStatus, evaluationIDList)
}

Min

  • 衍生方法:
  • 含义: 取数据集中的最小的元素

举例:

// case 1
id1 := []int{1, 2, 3, 4}
query := From(id1).Min()
fmt.Println(query)

output:
1

实战:

 /**

 * 根据场次获取面试官日历范围

 */

func (self *calendarProdService) getCalendarRangeFromSession(ctx context.Context,
   jFSession *domain.JFSession) (startTime, endTime *int64) {

   if len(jFSession.SubSessionList) == 0 {
      return
   }

   var subSessionStartTimeMSList []int64
   // 取 SubSession startTime 列表
   linq.From(jFSession.SubSessionList).
      WhereT(func(item *domain.JFSubSession) bool {
         return item != nil && item.StartTime != nil
      }).
      SelectT(func(item *domain.JFSubSession) int64 {
         return *item.StartTime
      }).
      ToSlice(&subSessionStartTimeMSList)

   if linq.From(subSessionStartTimeMSList).Count() == 0 {
      return
   }

   start := linq.From(subSessionStartTimeMSList).Min().(int64)
   end := linq.From(subSessionStartTimeMSList).Max().(int64)

   // 判断 SubSession 最大的 startTime 是否超过 session 的 endTime
   if end > *jFSession.EndTime {
      end = *jFSession.EndTime
   }

   log.Info(ctx, "[getCalendarRangeFromSession] calendar start_time:%d, end_time:%d", start, end)

   return util.Int64Ptr(start), util.Int64Ptr(end)
}

Max

  • 衍生方法:
  • 含义: 取数据集中的最大的元素

举例:

// case 1
id1 := []int{1, 2, 3, 4, 4, 4}
query := From(id1).Max()
fmt.Println(query)

output:
4

实战:

 /**

 * 根据场次获取面试官日历范围

 */

func (self *calendarProdService) getCalendarRangeFromSession(ctx context.Context,
   jFSession *domain.JFSession) (startTime, endTime *int64) {
   
   if len(jFSession.SubSessionList) == 0 {
      return
   }

   var subSessionStartTimeMSList []int64
   // 取 SubSession startTime 列表
   linq.From(jFSession.SubSessionList).
      WhereT(func(item *domain.JFSubSession) bool {
         return item != nil && item.StartTime != nil
      }).
      SelectT(func(item *domain.JFSubSession) int64 {
         return *item.StartTime
      }).
      ToSlice(&subSessionStartTimeMSList)

   if linq.From(subSessionStartTimeMSList).Count() == 0 {
      return
   }



   start := linq.From(subSessionStartTimeMSList).Min().(int64)
   end := linq.From(subSessionStartTimeMSList).Max().(int64)

   // 判断 SubSession 最大的 startTime 是否超过 session 的 endTime
   if end > *jFSession.EndTime {
      end = *jFSession.EndTime
   }

   log.Info(ctx, "[getCalendarRangeFromSession] calendar start_time:%d, end_time:%d", start, end)

   return util.Int64Ptr(start), util.Int64Ptr(end)
}

Single

  • 相关方法: SingleWith, SingleWithT
  • 含义: 返回集合中的唯一元素,如果集合中的元素不唯一返回 nil

举例:

// case 1:
numList := []int64{1, 2, 3, 5, 8, 13}
query := From(numList).Single()
fmt.Println(query)

output:
nil

// case 2:
numList := []int64{1}
query := From(numList).Single()
fmt.Println(query)

output:
1

// case 3:
numList := []int64{1, 2, 3, 5, 8, 13}
query := From(numList).SingleWithT(func(i int64) bool {
   return i > 8
})
fmt.Println(query)

output:
13

实战: 暂无

ToSlice

  • 衍生方法:
  • 含义: 将数据集 query 的值输出到指定 slice 中,这是最常用的方法之一

举例:

// case1
type Person struct {
   Name string
   Age  int64
}

p1 := Person{Name: "Jack", Age: 23}
p2 := Person{Name: "Jon", Age: 18}
p3 := Person{Name: "Tom", Age: 23}

aList := []Person{p1, p2}
bList := []Person{p2, p3}

var both []Person
From(aList).Except(From(bList)).ToSlice(&both)
fmt.Println(both)

output:
[{Jack 23}]

实战:

func (self *evaluationSearchProdService) supplyApplicationStageAndStatus(ctx context.Context,
   stageAndStatusIDList []string) (result []string) {

   if len(stageAndStatusIDList) == 0 {
      return
   }

   // 判断是否需要扩展阶段
   existExtend := self.needExtendStage(stageAndStatusIDList)
   if !existExtend {
      return stageAndStatusIDList
   }
   
   // 主要构成两部分: 1.前端传的阶段, 去掉"进行中"; 2.补充除了"已入职"的所有阶段
   // 即: "进行中"阶段 == 除了"已入职"阶段的阶段
   linq.From(
      self.filterStageIDWithoutInProgress(stageAndStatusIDList),
   ).Concat(
      linq.From(self.listAllStageIDWithoutHired(ctx)),
   ).SelectT(func(item string) string {
      return item
   }).ToSlice(&result)

   log.Info(ctx, "[supplyApplicationStageAndStatus] stage_id_list:%v", result)

   return
}

ToMap

  • 衍生方法: ToMapBy, ToMapByT
  • 含义: 将数据集 query 的值输出到指定 map 中,这是最常用的方法之一

注意:要提前 alloc map

举例:

type Person struct {
   Name string
   Age  int64
}

p1 := Person{Name: "Jack", Age: 23}
p2 := Person{Name: "Jon", Age: 18}
p3 := Person{Name: "Tom", Age: 23}

aList := []Person{p1, p2}
bList := []Person{p2, p3}

pMap := make(map[string]int64)
From(aList).
   Except(From(bList)).
   ToMapByT(&pMap,
      func(p Person) string { return p.Name },
      func(p Person) int64 { return p.Age },
   )

fmt.Println(pMap)

output:
map[Jack:23]

实战:

func (self *interviewerLarkCalendarLimiter) queryUserBusyTimeMap(in AppointmentTimeSlice) (
   result map[string]utils.IntervalMixSlice) {

   startUTCDateTimestamp, endUTCDateTimestamp := in.getTotalDurationIgnoreTimezone(self.ctx)
   busyTimes := UserBusyPeriods(self.ctx, self.userList, startUTCDateTimestamp,
      endUTCDateTimestamp)

   result = make(map[string]utils.IntervalMixSlice, len(busyTimes))
   From(busyTimes).ToMapByT(
      &result,
      func(iter *AppointmentTime) interface{} { return iter.UserID },
      func(iter *AppointmentTime) utils.IntervalMixSlice { return iter.RecommendTimeList },
   )

   return
}

Contains

  • 衍生方法:
  • 含义: 数据集合中是否包含某个元素,不用 for 循环去遍历集合再判断

举例:

// case1
type Person struct {
   Name string
   Age  int64
}

p1 := Person{Name: "Jack", Age: 23}
p2 := Person{Name: "Jon", Age: 18}
p3 := Person{Name: "Tom", Age: 23}

aList := []Person{p1, p2}
bList := []Person{p2, p3}

isContains := From(aList).
   Except(From(bList)).
   Contains(p1)
   
fmt.Println(isContains)

output:
true

实战:

func (self *interviewAppointmentTaskProdService) checkAppointmentLinkExpiryAndStatus(appointment *domain.InterviewAppointmentTask) (result func() error) {

   return func() error {
      // 约面任务是否存在
       if appointment == nil {
           return bizerr.AppointmentTaskNotExit
       }
   
       // 约面链接是否失效
       if *appointment.TerminateTime < util.GetMilliTimestamp() {
           return bizerr.AppointmentLinkNotAvailable
       }
   
       // 约面任务是否可以确认/拒绝
       acceptStatusList := []intCts.AppointmentStatus{
           intCts.AppointmentStatus_Dating,
           intCts.AppointmentStatus_Selecting,
       }

       if !From(acceptStatusList).Contains(*appointment.AppointmentStatus) {
           return bizerr.AppointmentTaskIsCanceled

       }
   
       return nil
   }

}

综合实战

原来的逻辑:

 // 投递状态V2版本需要扩展, 为树形结构
if len(request.StageIDList) > 0 {
   existExtend := false
   expandIDList := make([]string, 0)
   for _, stageEnum := range request.StageIDList {
      if stageEnum == constant.ApplicationStatusGroupInProgress {
         existExtend = true
         break
      }
   }

   if existExtend {
      allStageData := facade.ApplicationFacade.ListAllStageWithCache(ctx)
      for _, v := range request.StageIDList {
         if v == constant.ApplicationStatusGroupInProgress {
            for _, stageData := range allStageData {
               if *stageData.TypeA1 != aconstants.StageType_Hired {
                  expandIDList = append(expandIDList, *stageData.ID)
               }
            }
         } else {
            expandIDList = append(expandIDList, v)
         }
      }
      request.StageIDList = expandIDList
   }

   log.Info(ctx, "Evaluation application_status expand %s", data_tk.Show(expandIDList))
}

使用 go-linq 重构后的逻辑:

// 补充投递状态
func (self *evaluationSearchProdService) supplyApplicationStageAndStatus(ctx context.Context,
   stageAndStatusIDList []string) (result []string) {

   if len(stageAndStatusIDList) == 0 {
      return
   }

   // 判断是否需要扩展阶段
   existExtend := self.needExtendStage(stageAndStatusIDList)
   if !existExtend {
      return stageAndStatusIDList
   }

   // 主要构成两部分: 1.前端传的阶段, 去掉"进行中"; 2.补充除了"已入职"的所有阶段
   // 即: "进行中"阶段 == 除了"已入职"阶段的阶段
   linq.From(
      self.filterStageIDWithoutInProgress(stageAndStatusIDList),
   ).Concat(
      linq.From(self.listAllStageIDWithoutHired(ctx)),
   ).SelectT(func(item string) string {
      return item
   }).ToSlice(&result)

   log.Info(ctx, "[supplyApplicationStageAndStatus] stage_id_list:%v", result)

   return
}

 /**
  * 判断是否需要扩充阶段
  */

func (self *evaluationSearchProdService) needExtendStage(stageIDList []string) bool {
   return linq.From(stageIDList).AnyWithT(func(item string) bool {
      return item == constant.ApplicationStatusGroupInProgress
   })
}

 /**
  * 过滤掉"进行中"阶段
  */
func (self *evaluationSearchProdService) filterStageIDWithoutInProgress(stageIDList []string) (result []string) {

   linq.From(stageIDList).WhereT(func(item string) bool {
      return item != constant.ApplicationStatusGroupInProgress
   }).ToSlice(&result)

   return
}



 /**
  * 获取除了"已入职"以外的所有阶段
  */
func (self *evaluationSearchProdService) listAllStageIDWithoutHired(ctx context.Context) (result []string) {

   allStageData := facade.ApplicationFacade.ListAllStageWithCache(ctx)
   
   linq.From(allStageData).WhereT(func(item *aInf.StageInfo) bool {
      return *item.TypeA1 != aconstants.StageType_Hired
   }).SelectT(func(item *aInf.StageInfo) string {
      return *item.ID
   }).ToSlice(&result)

   return
}

进阶

数据集合输入可以指定自定义的集合,只要实现对应的接口,实现自己的迭代器即可使用 go-linq 提供的控制方法和输出方法。

Iterate 和 CompareTo

示例:

数组的数组,进行求交集/差集/并集,目前自动计算推荐面试时间可用到,类似于下面的时间切片数组

  • timeList1: [ [10, 11], [12, 15], [16, 30] ]
  • timeList2: [ [15, 16], [16, 20] ]

对时间切片数组中的元素取交集/差集/并集

type MixTime [2]int64

func (f MixTime) CompareTo(c Comparable) int {
   a, b := f, c.(MixTime)
   if a[0] > b[0] {
      return 1
   } else if a[0] < b[0] {
      return -1
   }

   return 0
}



type MixTimeArray []MixTime

func (MixTimeArray) Iterate() Iterator {
   return func() (item interface{}, ok bool) {
      return
   }
}

func TestIterable() {
   q1 := MixTimeArray{
      {1, 2},
      {3, 4},
      {5, 6},
   }
   
   q2 := MixTimeArray{
      {1, 200},
      {3, 4},
      {7, 8},
   }

   res1 := From(q1).Intersect(From(q2)).Results()
   fmt.Printf("intersect: %v\n", res1)

   res2 := From(q1).Except(From(q2)).Results()
   fmt.Printf("Except: %v\n", res2)

   res3 := From(q1).Union(From(q2)).Results()
   fmt.Printf("Union: %v\n", res3)

   q3 := MixTimeArray{
      {1, 2},
      {3, 4},
      {5, 6},
   }

   res4 := From(q1).SequenceEqual(From(q2))
   res5 := From(q1).SequenceEqual(From(q3))
   fmt.Printf("Equal: q1 & q2 %v; q1 & q3 %v\n", res4, res5)
}

output:
intersect: [[3 4]]
Except: [[1 2] [5 6]]
Union: [[1 2] [3 4] [5 6] [1 200] [7 8]]
Equal: q1 & q2 false; q1 & q3 true

前车之鉴

使用 go-linq 的目的不是为了解决圈复杂度,而是为了让代码更简洁,更具有可读性,带来的好处之一就是圈复杂度降低了,从侧面也说明代码复杂度降低了。

  1. 分离关注点,明确输入和输出,再选择合适的控制方法;
  2. 使用带 withT 的关键字时,需要注意数据源的元素类型和闭包中指定的类型要一致,否则运行时 panic;
  3. 使用 groupBy 后的输出要注意 []interface{} 的类型转换,可以使用 selecT 进行单独转换,不能强制类型转换 []interface {} -> []type;
  4. 在使用 go-linq 时,尽量将复杂的闭包封装成小函数,好处:
    • 可读性更高
    • 小函数职责单一,可复用
    • 分支控制和逻辑处理独立
  5. 弄清楚每个 go-linq 方法的场景和特性,比较模糊的时候可以写 demo 测试一下:
    • 比如:takeWith,skipWith 理解和具体使用有混淆的地方;
    • Otro ejemplo: establecer operaciones como la intersección/diferencia/unión de dos conjuntos, algunos tipos de conjuntos de datos pueden no ser posibles y el tipo debe ser compatible con hashable, etc.
  6. Cuando se encuentre con una conversión más compleja, puede pensar de atrás hacia adelante, qué método se puede usar para obtener la salida, puede pensar en qué tipo de entrada y luego examinarla capa por capa;
  7. Al igual que los patrones de diseño, go-linq no es una panacea. Recuerda no abusar de él. Necesitas entender cuál es el método de control para resolver;
  8. Excepto por la introducción en el sitio web oficial, casi no hay información en Internet, y pueden discutir entre ustedes para lograr las mejores prácticas.

Únete a nosotros

Somos de Lark Business Applications de ByteDance Feishu Actualmente, hemos establecido áreas de oficina en Beijing, Shenzhen, Shanghai, Wuhan, Hangzhou, Chengdu, Guangzhou y Sanya. Las áreas de productos en las que nos enfocamos son principalmente software de gestión de experiencia empresarial, incluidos Feishu OKR, desempeño de Feishu, reclutamiento de Feishu, personal de Feishu y otros sistemas HCM, así como sistemas de aprobación, OA, asuntos legales, finanzas, compras, viajes y reembolso de Feishu. Bienvenido a unirse a nosotros.

WeChat escanea el código para encontrar trabajos y enviar currículums

Entrega en el sitio web oficial: job.toutiao.com/s/FyL7DRg

Supongo que te gusta

Origin juejin.im/post/7122403456417021989
Recomendado
Clasificación