Skip to content

builtin: Implement min/max builtin function #48

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

Merged
merged 1 commit into from
Feb 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 131 additions & 2 deletions builtin/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ func init() {
// py.MustNewMethod("iter", builtin_iter, 0, iter_doc),
py.MustNewMethod("len", builtin_len, 0, len_doc),
py.MustNewMethod("locals", py.InternalMethodLocals, 0, locals_doc),
// py.MustNewMethod("max", builtin_max, 0, max_doc),
// py.MustNewMethod("min", builtin_min, 0, min_doc),
py.MustNewMethod("max", builtin_max, 0, max_doc),
py.MustNewMethod("min", builtin_min, 0, min_doc),
py.MustNewMethod("next", builtin_next, 0, next_doc),
py.MustNewMethod("open", builtin_open, 0, open_doc),
// py.MustNewMethod("oct", builtin_oct, 0, oct_doc),
Expand Down Expand Up @@ -772,6 +772,135 @@ func builtin_len(self, v py.Object) (py.Object, error) {
return py.Len(v)
}

const max_doc = `
max(iterable, *[, default=obj, key=func]) -> value
max(arg1, arg2, *args, *[, key=func]) -> value

With a single iterable argument, return its biggest item. The
default keyword-only argument specifies an object to return if
the provided iterable is empty.
With two or more arguments, return the largest argument.`

func builtin_max(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) {
return min_max(args, kwargs, "max")
}

const min_doc = `
min(iterable, *[, default=obj, key=func]) -> value
min(arg1, arg2, *args, *[, key=func]) -> value

With a single iterable argument, return its smallest item. The
default keyword-only argument specifies an object to return if
the provided iterable is empty.
With two or more arguments, return the smallest argument.`

func builtin_min(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) {
return min_max(args, kwargs, "min")
}

func min_max(args py.Tuple, kwargs py.StringDict, name string) (py.Object, error) {
kwlist := []string{"key", "default"}
positional := len(args)
var format string
var values py.Object
var cmp func(a py.Object, b py.Object) (py.Object, error)
if name == "min" {
format = "|$OO:min"
cmp = py.Le
} else if name == "max" {
format = "|$OO:max"
cmp = py.Ge
}
var defaultValue py.Object
var keyFunc py.Object
var maxVal, maxItem py.Object
var kf *py.Function

if positional > 1 {
values = args
} else {
err := py.UnpackTuple(args, nil, name, 1, 1, &values)
if err != nil {
return nil, err
}
}
err := py.ParseTupleAndKeywords(nil, kwargs, format, kwlist, &keyFunc, &defaultValue)
if err != nil {
return nil, err
}
if keyFunc == py.None {
keyFunc = nil
}
if keyFunc != nil {
var ok bool
kf, ok = keyFunc.(*py.Function)
if !ok {
return nil, py.ExceptionNewf(py.TypeError, "'%s' object is not callable", keyFunc.Type())
}
}
if defaultValue != nil {
maxItem = defaultValue
if keyFunc != nil {
maxVal, err = py.Call(kf, py.Tuple{defaultValue}, nil)
if err != nil {
return nil, err
}
} else {
maxVal = defaultValue
}
}
iter, err := py.Iter(values)
if err != nil {
return nil, err
}

for {
item, err := py.Next(iter)
if err != nil {
if py.IsException(py.StopIteration, err) {
break
}
return nil, err
}
if maxVal == nil {
if keyFunc != nil {
maxVal, err = py.Call(kf, py.Tuple{item}, nil)
if err != nil {
return nil, err
}
} else {
maxVal = item
}
maxItem = item
} else {
var compareVal py.Object
if keyFunc != nil {
compareVal, err = py.Call(kf, py.Tuple{item}, nil)
if err != nil {
return nil, err
}
} else {
compareVal = item
}
changed, err := cmp(compareVal, maxVal)
if err != nil {
return nil, err
}
if changed == py.True {
maxVal = compareVal
maxItem = item
}
}

}

if maxItem == nil {
return nil, py.ExceptionNewf(py.ValueError, "%s() arg is an empty sequence", name)
}

return maxItem, nil
}

const chr_doc = `chr(i) -> Unicode character

Return a Unicode string of one character with ordinal i; 0 <= i <= 0x10ffff.`
Expand Down
57 changes: 57 additions & 0 deletions builtin/tests/builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,63 @@ def fn(x):
assert locals()["x"] == 1
fn(1)

def func(p):
return p[1]

doc="min"
values = (1,2,3)
v = min(values)
assert v == 1
v = min(4,5,6)
assert v == 4
v = min((), default=-1)
assert v == -1
v = min([], default=-1)
assert v == -1
v = min([], key=func, default=(1,3))
assert v == (1,3)
v = min([(1,3), (2,1)], key=func)
assert v == (2,1)
ok = False
try:
min([(1,3), (2,1)], key=3)
except TypeError:
ok = True
assert ok, "TypeError not raised"
ok = False
try:
min([])
except ValueError:
ok = True
assert ok, "ValueError not raised"

doc="max"
values = (1,2,3)
v = max(values)
assert v == 3
v = max(4,5,6)
assert v == 6
v = max((), default=-1)
assert v == -1
v = max([], default=-1)
assert v == -1
v = max([], key=func, default=(1,3))
assert v == (1,3)
v = max([(1,3), (2,1)], key=func)
assert v == (1,3)
ok = False
try:
max([(1,3), (2,1)], key=3)
except TypeError:
ok = True
assert ok, "TypeError not raised"
ok = False
try:
max([])
except ValueError:
ok = True
assert ok, "ValueError not raised"

doc="next no default"
def gen():
yield 1
Expand Down
9 changes: 7 additions & 2 deletions py/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,11 +417,16 @@ func ParseTupleAndKeywords(args Tuple, kwargs StringDict, format string, kwlist
return ExceptionNewf(TypeError, "Internal error: supply the same number of results and kwlist")
}
min, max, name, ops := parseFormat(format)
keywordOnly := false
err := checkNumberOfArgs(name, len(args)+len(kwargs), len(results), min, max)
if err != nil {
return err
}

if len(ops) > 0 && ops[0] == "$" {
keywordOnly = true
ops = ops[1:]
}
// Check all the kwargs are in kwlist
// O(N^2) Slow but kwlist is usually short
for kwargName := range kwargs {
Expand All @@ -442,10 +447,10 @@ func ParseTupleAndKeywords(args Tuple, kwargs StringDict, format string, kwlist
return ExceptionNewf(TypeError, "%s() got multiple values for argument '%s'", name, kw)
}
args = append(args, value)
} else if keywordOnly {
args = append(args, nil)
}

}

for i, arg := range args {
op := ops[i]
result := results[i]
Expand Down