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

Tuple: 新增 Pair 和 Triple 两种类型 #161

Closed
KirinRyuuri opened this issue Mar 6, 2023 · 8 comments
Closed

Tuple: 新增 Pair 和 Triple 两种类型 #161

KirinRyuuri opened this issue Mar 6, 2023 · 8 comments

Comments

@KirinRyuuri
Copy link
Contributor

KirinRyuuri commented Mar 6, 2023

仅限中文

使用场景

  • 需要键值对(Key, Value)的时候,但是又没必要使用 map 这种复杂结构
  • 需要一个 Key 对应多个 Value 的时候

行业分析

如果你知道有框架提供了类似功能,可以在这里描述,并且给出文档或者例子

主要参考 KotlinPython 中的 tuple

Kotlin

官方文档 PairTriple,其官方示例如下所示

val (a, b) = Pair(1, "x")
println(a) // 1
println(b) // x

val (a, b, c) = Triple(2, "x", listOf(null))
println(a) // 2
println(b) // x
println(c) // [null]

都实现了 Serializeable 接口;可以单独获取每个位置的值;支持将其转为 StringList
额外支持使用 .copy(first=3) 的方式复制并修改值

Python(3.11)

官方文档,特点如下:

  1. 元组不可变
  2. 支持一般序列的各种操作,如查找、拼接、切片、长度、最大最小值等

可行方案

如果你有设计思路或者解决方案,请在这里提供。你可以提供多个方案,并且给出自己的选择

目标

  1. 支持序列化和反序列化
  2. 支持 .copy() 来显式(复制)更改值
  3. 支持多种类型

包结构

最开始是打算第一层用 tuple 包,然后再下接 pairtriple 的,
但是发现这样存在一个很明显的问题:没必要这么设计:

不会存在 ekit.tuple(a,b,c)ekit.tuple(a,b) 混用的情况,而且这样设计也容易混淆 pairtriple

更多的值?

也就是支持列如 quadra(a,b,c,d) 的内容:
没有这个必要,如果有这么多的值的需求应该自己创建一个数组来解决

支持的类型

map 不一样,不存在需要做比较之类的操作,所以支持 any
但是因为涉及到序列化 toString()toList() ,所以需要特殊处理一些类型
直接使用 fmt 包和 []any 就可以了。所以不限制类型

关于 nil

在 Kotlin 中,Value 是可以为 null 的,但是设为 null 之后不可更改
这样可能不方便使用,故不参考

其它

任何你觉得有利于解决问题的补充说明

期待的代码示例

	pair := Pair{First: 1, Second: "one"}
	_ = pair.First                       // 1
	_ = pair.Second                      // one
	_ = pair.ToString()                  // <1,one>
	_ = pair.ToList()                    // [1,"one"]
	pair = pair.Copy(Pair{First: "two"}) // <"two","one">
	pair = pair.Copy(Pair{Second: 2})    // <"two",2>
	pair = pair.Copy(Pair{
		First:  2,
		Second: "two",
	}) // <2,"two">

	triple := Triple{
		First:  3,
		Second: "Three",
		Third:  true,
	}
	_ = triple.First                            // 3
	_ = triple.Second                           // three
	_ = triple.Third                            // true
	_ = triple.ToString()                       // <3,"three",true>
	_ = triple.ToList()                         // [3,"three", true]
	triple = triple.Copy(Triple{First: "four"}) // <"four","three",true>
	triple = triple.Copy(Triple{Second: 4})     // <"four",4,true>
	triple = triple.Copy(Triple{Third: false})  //<"four",4,false>
	triple = triple.Copy(Triple{
		First:  4,
		Second: "four",
		Third:  nil,
	}) // <4,"four",false>
@flycash flycash linked a pull request Mar 7, 2023 that will close this issue
@wangbintao1992
Copy link

pair.go

package util

import (
	"container/list"
	"encoding/json"
	"fmt"
	"github.com/ecodeclub/ekit/reflectx"
	"reflect"
)

// Pair
type Pair[F any, S any] struct {
	first     F
	second    S
	ignoreNil bool
}

type pairJson[F any, S any] struct {
	First  F `json:"first"`
	Second S `json:"second"`
}

func (p *Pair[F, S]) toJson() string {
	if byteJson, err := json.Marshal(&pairJson[F, S]{First: p.first, Second: p.second}); err == nil {
		return string(byteJson)
	}

	return ""
}
func ParsePair[F any, S any](pair *Pair[F, S], jsonStr string) error {
	p := &pairJson[F, S]{}
	err := json.Unmarshal([]byte(jsonStr), p)

	if err == nil {
		pair.SetFirst(p.First)
		pair.SetSecond(p.Second)

		return nil
	}

	return err
}

func (p *Pair[F, S]) ToList() *list.List {
	return p.toListCallBack(nil)
}

func (p *Pair[F, S]) ToArray() []any {
	return p.toArrayCallBack(nil)
}

func (p *Pair[F, S]) toArrayCallBack(cb func() any) []any {
	var array []interface{}
	array = append(array, p.first)
	array = append(array, p.second)

	if cb != nil {
		array = append(array, cb())
	}

	return array
}

func (p *Pair[F, S]) toListCallBack(cb func() any) *list.List {
	list := list.New()
	list.PushBack(p.first)
	list.PushBack(p.second)

	if cb != nil {

		list.PushBack(cb())
	}

	return list
}
func (p *Pair[F, S]) Copy(value *Pair[F, S]) *Pair[F, S] {
	if p.ignoreNil {
		if !reflectx.IsNilValue(reflect.ValueOf(value.first)) {
			p.first = value.first
		}

		if !reflectx.IsNilValue(reflect.ValueOf(value.second)) {
			p.second = value.second
		}
	} else {
		p.first = value.first
		p.second = value.second
	}

	return p
}

func NewPair[F any, S any](first F, second S) *Pair[F, S] {
	return &Pair[F, S]{first: first, second: second}
}

func NewPairIgnoreNil[F any, S any](first F, second S) *Pair[F, S] {
	return &Pair[F, S]{first: first, second: second, ignoreNil: true}
}

func (p *Pair[F, S]) ToString() string {
	return p.toStringCallBack(nil)
}

func (p *Pair[F, S]) toStringCallBack(cb func() string) string {
	if cb != nil {
		return "<" + fmt.Sprintf("%#v", p.first) + "," + fmt.Sprintf("%#v", p.second) + "," + cb() + ">"
	}

	return "<" + fmt.Sprintf("%#v", p.first) + "," + fmt.Sprintf("%#v", p.second) + ">"
}

func (p *Pair[F, S]) IgnoreNil() bool {
	return p.ignoreNil
}

func (p *Pair[F, S]) SetIgnoreNil(ignoreNil bool) {
	p.ignoreNil = ignoreNil
}

func (p *Pair[F, S]) First() F {
	return p.first
}

func (p *Pair[F, S]) SetFirst(first F) {
	p.first = first
}

func (p *Pair[F, S]) Second() S {
	return p.second
}

func (p *Pair[F, S]) SetSecond(second S) {
	p.second = second
}

// Triple
type Triple[F any, S any, T any] struct {
	*Pair[F, S]
	third T
}

type tripleJson[F any, S any, T any] struct {
	First  F `json:"first"`
	Second S `json:"second"`
	Third  T `json:"third"`
}

func (t *Triple[F, S, T]) Third() T {
	return t.third
}

func (t *Triple[F, S, T]) SetThird(third T) {
	t.third = third
}

func (t *Triple[F, S, T]) ToList() *list.List {
	return t.Pair.toListCallBack(func() any {
		return t.third
	})
}

func (t *Triple[F, S, T]) ToString() string {
	return t.Pair.toStringCallBack(func() string {
		return fmt.Sprintf("%#v", t.third)
	})
}

func (t *Triple[F, S, T]) ToArray() []any {
	return t.Pair.toArrayCallBack(func() any {
		return t.third
	})
}

func (t *Triple[F, S, T]) toJson() string {

	if byteJson, err := json.Marshal(&tripleJson[F, S, T]{First: t.first, Second: t.second, Third: t.third}); err == nil {
		return string(byteJson)
	}

	return ""
}

func ParseTriple[F any, S any, T any](triple *Triple[F, S, T], jsonStr string) error {
	t := &tripleJson[F, S, T]{}
	err := json.Unmarshal([]byte(jsonStr), t)

	if err == nil {
		triple.Pair = NewPair(t.First, t.Second)
		triple.SetThird(t.Third)

		return nil
	}

	return err
}

func (t *Triple[F, S, T]) Copy(value *Triple[F, S, T]) *Triple[F, S, T] {
	t.Pair.Copy(value.Pair)

	if t.ignoreNil {
		if !reflectx.IsNilValue(reflect.ValueOf(value.third)) {
			t.third = value.third
		}
	} else {
		t.third = value.third
	}

	return t
}

pair_test.go

package util

import (
	"fmt"
	"reflect"
	"testing"
	"time"
)

type Test struct {
	Name string
	Age  int
	Date time.Time
}

func TestTriple(t *testing.T) {
	triple := NewTriple("kobe", 2, time.Now())

	fmt.Println("first :", triple.First())
	fmt.Println("first type:", reflect.TypeOf(triple.First()))

	fmt.Println("second :", triple.Second())
	fmt.Println("second type:", reflect.TypeOf(triple.Second()))

	fmt.Println("third :", triple.Third())
	fmt.Println("third type:", reflect.TypeOf(triple.Third()))

	fmt.Println("toList:", triple.ToList())
	fmt.Println("toString:", triple.ToString())
	fmt.Println("json:", triple.toJson())

	m := make(map[int]string)
	m[1] = "new one"
	m[2] = "new two"

	fmt.Println("copy:", triple.Copy(NewTriple("james", 99, time.Now())).ToString())

	jsonStr := "{\"first\":\"jordan\",\"second\":5}"

	tripleJson := &Triple[string, int, time.Time]{}
	ParseTriple(tripleJson, jsonStr)
	fmt.Println("json first:", tripleJson.First())
	fmt.Println("json second:", tripleJson.Second())
	fmt.Println("json third:", tripleJson.Third())
}

func NewTriple[F any, S any, T any](first F, second S, third T) *Triple[F, S, T] {
	return &Triple[F, S, T]{Pair: NewPair(first, second), third: third}
}

func TestPair(t *testing.T) {
	m := make(map[string]int)
	m["A"] = 1
	m["B"] = 2
	pair2 := NewPair(m, 1)

	fmt.Println("first:", pair2.First())
	fmt.Println("first type:", reflect.TypeOf(pair2.First()))
	fmt.Println("second:", pair2.Second())
	fmt.Println("second type:", reflect.TypeOf(pair2.Second()))
	fmt.Println("tostring:", pair2.ToString())
	fmt.Println("toList:", pair2.ToList())
	fmt.Println("toArray:", pair2.ToArray())
	fmt.Println("copy:", pair2.Copy(NewPair(make(map[string]int), 2)))
	fmt.Println("json:", pair2.toJson())

	jsonStr := "{\"first\":{\"C\":3,\"D\":4},\"second\":9}"
	p := &Pair[map[string]int, int]{}
	ParsePair(p, jsonStr)

	fmt.Println("json first:", p.first)
	fmt.Println("json second:", p.second)
}

func TestPairIgnoreNil(t *testing.T) {
	newPair := NewPairIgnoreNil(&Test{Name: "tester", Age: 99, Date: time.Now()}, float64(99))

	copyValue := &Pair[*Test, float64]{first: nil, second: 88}

	fmt.Println("first:", newPair.First())
	fmt.Println("first:", newPair.first.Name, newPair.first.Age, newPair.first.Date.String())
	fmt.Println("first type:", reflect.TypeOf(newPair.First()))
	fmt.Println("second:", newPair.Second())
	fmt.Println("second type:", reflect.TypeOf(newPair.Second()))
	fmt.Println("toString:", newPair.ToString())
	fmt.Println("toList:", newPair.ToList())
	fmt.Println("toArray:", newPair.ToArray())
	fmt.Println("copy:", newPair.Copy(copyValue))

	fmt.Println("json:", newPair.toJson())
}
image image

@longyue0521
Copy link
Collaborator

@wangbintao1992

当前实现中toList/toArray的使用场景是什么? First,Second,Third本身不就表示了逻辑上的“list/array”有顺序的概念了吗?

  1. 参考 Tuple: 增加 Pair 和 Triple 两种类型 #164 中的讨论及本issue 自查一下你当前的实现
  2. 觉得没问题 参考 https://ekit.gocn.vip/contribution/ 提一个独立的PR, 我们在PR中进行针对性讨论

@flycash
Copy link
Contributor

flycash commented Jul 6, 2023

可以直接提合并请求,这样我们比较好 review 代码 😄

@wangbintao1992
Copy link

没勇气提pr啊....

@KirinRyuuri
Copy link
Contributor Author

没勇气提pr啊....

怕什么,大不了被拒 [滑稽]
而且你写的也不差,勇敢一点

@flycash
Copy link
Contributor

flycash commented Jul 14, 2023

还是直接发 PR,我们比较好管理

@dxyinme
Copy link
Contributor

dxyinme commented Jan 1, 2024

感觉对于Pair的函数定义来说,Copy应该还是用于复制自身,然后需要一个MergeFrom来更新会比较好

@flycash
Copy link
Contributor

flycash commented Jan 8, 2024

Pair 已经解决了,等后面有需要的时候,再来解决 tuple 的问题

@flycash flycash closed this as completed Jan 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

5 participants