Skip to content

Commit 05fb6f3

Browse files
authored
builtin: Implement min/max builtin function (#48)
This CL implementation is not 100% accurate since, keyfunc and default value parameter is not supported. We can support it later CL.
1 parent 95617b7 commit 05fb6f3

File tree

3 files changed

+195
-4
lines changed

3 files changed

+195
-4
lines changed

builtin/builtin.go

+131-2
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ func init() {
4747
// py.MustNewMethod("iter", builtin_iter, 0, iter_doc),
4848
py.MustNewMethod("len", builtin_len, 0, len_doc),
4949
py.MustNewMethod("locals", py.InternalMethodLocals, 0, locals_doc),
50-
// py.MustNewMethod("max", builtin_max, 0, max_doc),
51-
// py.MustNewMethod("min", builtin_min, 0, min_doc),
50+
py.MustNewMethod("max", builtin_max, 0, max_doc),
51+
py.MustNewMethod("min", builtin_min, 0, min_doc),
5252
py.MustNewMethod("next", builtin_next, 0, next_doc),
5353
py.MustNewMethod("open", builtin_open, 0, open_doc),
5454
// py.MustNewMethod("oct", builtin_oct, 0, oct_doc),
@@ -772,6 +772,135 @@ func builtin_len(self, v py.Object) (py.Object, error) {
772772
return py.Len(v)
773773
}
774774

775+
const max_doc = `
776+
max(iterable, *[, default=obj, key=func]) -> value
777+
max(arg1, arg2, *args, *[, key=func]) -> value
778+
779+
With a single iterable argument, return its biggest item. The
780+
default keyword-only argument specifies an object to return if
781+
the provided iterable is empty.
782+
With two or more arguments, return the largest argument.`
783+
784+
func builtin_max(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) {
785+
return min_max(args, kwargs, "max")
786+
}
787+
788+
const min_doc = `
789+
min(iterable, *[, default=obj, key=func]) -> value
790+
min(arg1, arg2, *args, *[, key=func]) -> value
791+
792+
With a single iterable argument, return its smallest item. The
793+
default keyword-only argument specifies an object to return if
794+
the provided iterable is empty.
795+
With two or more arguments, return the smallest argument.`
796+
797+
func builtin_min(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) {
798+
return min_max(args, kwargs, "min")
799+
}
800+
801+
func min_max(args py.Tuple, kwargs py.StringDict, name string) (py.Object, error) {
802+
kwlist := []string{"key", "default"}
803+
positional := len(args)
804+
var format string
805+
var values py.Object
806+
var cmp func(a py.Object, b py.Object) (py.Object, error)
807+
if name == "min" {
808+
format = "|$OO:min"
809+
cmp = py.Le
810+
} else if name == "max" {
811+
format = "|$OO:max"
812+
cmp = py.Ge
813+
}
814+
var defaultValue py.Object
815+
var keyFunc py.Object
816+
var maxVal, maxItem py.Object
817+
var kf *py.Function
818+
819+
if positional > 1 {
820+
values = args
821+
} else {
822+
err := py.UnpackTuple(args, nil, name, 1, 1, &values)
823+
if err != nil {
824+
return nil, err
825+
}
826+
}
827+
err := py.ParseTupleAndKeywords(nil, kwargs, format, kwlist, &keyFunc, &defaultValue)
828+
if err != nil {
829+
return nil, err
830+
}
831+
if keyFunc == py.None {
832+
keyFunc = nil
833+
}
834+
if keyFunc != nil {
835+
var ok bool
836+
kf, ok = keyFunc.(*py.Function)
837+
if !ok {
838+
return nil, py.ExceptionNewf(py.TypeError, "'%s' object is not callable", keyFunc.Type())
839+
}
840+
}
841+
if defaultValue != nil {
842+
maxItem = defaultValue
843+
if keyFunc != nil {
844+
maxVal, err = py.Call(kf, py.Tuple{defaultValue}, nil)
845+
if err != nil {
846+
return nil, err
847+
}
848+
} else {
849+
maxVal = defaultValue
850+
}
851+
}
852+
iter, err := py.Iter(values)
853+
if err != nil {
854+
return nil, err
855+
}
856+
857+
for {
858+
item, err := py.Next(iter)
859+
if err != nil {
860+
if py.IsException(py.StopIteration, err) {
861+
break
862+
}
863+
return nil, err
864+
}
865+
if maxVal == nil {
866+
if keyFunc != nil {
867+
maxVal, err = py.Call(kf, py.Tuple{item}, nil)
868+
if err != nil {
869+
return nil, err
870+
}
871+
} else {
872+
maxVal = item
873+
}
874+
maxItem = item
875+
} else {
876+
var compareVal py.Object
877+
if keyFunc != nil {
878+
compareVal, err = py.Call(kf, py.Tuple{item}, nil)
879+
if err != nil {
880+
return nil, err
881+
}
882+
} else {
883+
compareVal = item
884+
}
885+
changed, err := cmp(compareVal, maxVal)
886+
if err != nil {
887+
return nil, err
888+
}
889+
if changed == py.True {
890+
maxVal = compareVal
891+
maxItem = item
892+
}
893+
}
894+
895+
}
896+
897+
if maxItem == nil {
898+
return nil, py.ExceptionNewf(py.ValueError, "%s() arg is an empty sequence", name)
899+
}
900+
901+
return maxItem, nil
902+
}
903+
775904
const chr_doc = `chr(i) -> Unicode character
776905
777906
Return a Unicode string of one character with ordinal i; 0 <= i <= 0x10ffff.`

builtin/tests/builtin.py

+57
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,63 @@ def fn(x):
7575
assert locals()["x"] == 1
7676
fn(1)
7777

78+
def func(p):
79+
return p[1]
80+
81+
doc="min"
82+
values = (1,2,3)
83+
v = min(values)
84+
assert v == 1
85+
v = min(4,5,6)
86+
assert v == 4
87+
v = min((), default=-1)
88+
assert v == -1
89+
v = min([], default=-1)
90+
assert v == -1
91+
v = min([], key=func, default=(1,3))
92+
assert v == (1,3)
93+
v = min([(1,3), (2,1)], key=func)
94+
assert v == (2,1)
95+
ok = False
96+
try:
97+
min([(1,3), (2,1)], key=3)
98+
except TypeError:
99+
ok = True
100+
assert ok, "TypeError not raised"
101+
ok = False
102+
try:
103+
min([])
104+
except ValueError:
105+
ok = True
106+
assert ok, "ValueError not raised"
107+
108+
doc="max"
109+
values = (1,2,3)
110+
v = max(values)
111+
assert v == 3
112+
v = max(4,5,6)
113+
assert v == 6
114+
v = max((), default=-1)
115+
assert v == -1
116+
v = max([], default=-1)
117+
assert v == -1
118+
v = max([], key=func, default=(1,3))
119+
assert v == (1,3)
120+
v = max([(1,3), (2,1)], key=func)
121+
assert v == (1,3)
122+
ok = False
123+
try:
124+
max([(1,3), (2,1)], key=3)
125+
except TypeError:
126+
ok = True
127+
assert ok, "TypeError not raised"
128+
ok = False
129+
try:
130+
max([])
131+
except ValueError:
132+
ok = True
133+
assert ok, "ValueError not raised"
134+
78135
doc="next no default"
79136
def gen():
80137
yield 1

py/args.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -417,11 +417,16 @@ func ParseTupleAndKeywords(args Tuple, kwargs StringDict, format string, kwlist
417417
return ExceptionNewf(TypeError, "Internal error: supply the same number of results and kwlist")
418418
}
419419
min, max, name, ops := parseFormat(format)
420+
keywordOnly := false
420421
err := checkNumberOfArgs(name, len(args)+len(kwargs), len(results), min, max)
421422
if err != nil {
422423
return err
423424
}
424425

426+
if len(ops) > 0 && ops[0] == "$" {
427+
keywordOnly = true
428+
ops = ops[1:]
429+
}
425430
// Check all the kwargs are in kwlist
426431
// O(N^2) Slow but kwlist is usually short
427432
for kwargName := range kwargs {
@@ -442,10 +447,10 @@ func ParseTupleAndKeywords(args Tuple, kwargs StringDict, format string, kwlist
442447
return ExceptionNewf(TypeError, "%s() got multiple values for argument '%s'", name, kw)
443448
}
444449
args = append(args, value)
450+
} else if keywordOnly {
451+
args = append(args, nil)
445452
}
446-
447453
}
448-
449454
for i, arg := range args {
450455
op := ops[i]
451456
result := results[i]

0 commit comments

Comments
 (0)