Add DiscardElements helper transformer

In some situations users want to compare two maps where missing entries
are equal to those that have the zero value.
For example, the following maps would be considered equal:
	x := map[string]int{"foo": 12345, "zero":0}
	y := map[string]int{"foo": 12345}

To help with this, we add DiscardElements to cmpopts that transforms maps
and slices by stripping entries based on a user provided function.
To strip zero values, the user can provide:
	cmpopts.DiscardElements(func(v int) bool { return v == 0 })
diff --git a/cmp/cmpopts/sort.go b/cmp/cmpopts/sort.go
index a566d24..5ee8b01 100644
--- a/cmp/cmpopts/sort.go
+++ b/cmp/cmpopts/sort.go
@@ -95,7 +95,8 @@
 //	• Transitive: if !less(x, y) and !less(y, z), then !less(x, z)
 //	• Total: if x != y, then either less(x, y) or less(y, x)
 //
-// SortMaps can be used in conjuction with EquateEmpty.
+// SortMaps can be used in conjunction with EquateEmpty,
+// but cannot be used with DiscardElements.
 func SortMaps(less interface{}) cmp.Option {
 	vf := reflect.ValueOf(less)
 	if !function.IsType(vf.Type(), function.Less) || vf.IsNil() {
diff --git a/cmp/cmpopts/transform.go b/cmp/cmpopts/transform.go
new file mode 100644
index 0000000..cdab640
--- /dev/null
+++ b/cmp/cmpopts/transform.go
@@ -0,0 +1,90 @@
+// Copyright 2017, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package cmpopts
+
+import (
+	"fmt"
+	"reflect"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/internal/function"
+)
+
+// DiscardElements transforms slices and maps by discarding some elements.
+// The remove function must be of the form "func(T) bool" where it reports true
+// for any element that should be discarded. This transforms any slices and maps
+// of type []V or map[K]V, where type V is assignable to type T.
+//
+// As an example, zero elements in a []MyStruct can be discarded with:
+//	DiscardElements(func(v MyStruct) bool { return v == MyStruct{} })
+//
+// DiscardElements can be used in conjunction with EquateEmpty,
+// but cannot be used with SortMaps.
+func DiscardElements(rm interface{}) cmp.Option {
+	vf := reflect.ValueOf(rm)
+	if !function.IsType(vf.Type(), function.Remove) || vf.IsNil() {
+		panic(fmt.Sprintf("invalid remove function: %T", rm))
+	}
+	d := discarder{vf.Type().In(0), vf}
+	return cmp.FilterValues(d.filter, cmp.Transformer("Discard", d.discard))
+}
+
+type discarder struct {
+	in  reflect.Type  // T
+	fnc reflect.Value // func(T) bool
+}
+
+func (d discarder) filter(x, y interface{}) bool {
+	vx := reflect.ValueOf(x)
+	vy := reflect.ValueOf(y)
+	if x == nil || y == nil || vx.Type() != vy.Type() ||
+		!(vx.Kind() == reflect.Slice || vx.Kind() == reflect.Map) ||
+		!vx.Type().Elem().AssignableTo(d.in) || vx.Len()+vy.Len() == 0 {
+		return false
+	}
+	ok := d.hasDiscardable(vx) || d.hasDiscardable(vy)
+	return ok
+}
+func (d discarder) hasDiscardable(v reflect.Value) bool {
+	switch v.Kind() {
+	case reflect.Slice:
+		for i := 0; i < v.Len(); i++ {
+			if d.fnc.Call([]reflect.Value{v.Index(i)})[0].Bool() {
+				return true
+			}
+		}
+	case reflect.Map:
+		for _, k := range v.MapKeys() {
+			if d.fnc.Call([]reflect.Value{v.MapIndex(k)})[0].Bool() {
+				return true
+			}
+		}
+	}
+	return false
+}
+func (d discarder) discard(x interface{}) interface{} {
+	src := reflect.ValueOf(x)
+	switch src.Kind() {
+	case reflect.Slice:
+		dst := reflect.MakeSlice(src.Type(), 0, src.Len())
+		for i := 0; i < src.Len(); i++ {
+			v := src.Index(i)
+			if !d.fnc.Call([]reflect.Value{v})[0].Bool() {
+				dst = reflect.Append(dst, v)
+			}
+		}
+		return dst.Interface()
+	case reflect.Map:
+		dst := reflect.MakeMap(src.Type())
+		for _, k := range src.MapKeys() {
+			v := src.MapIndex(k)
+			if !d.fnc.Call([]reflect.Value{v})[0].Bool() {
+				dst.SetMapIndex(k, v)
+			}
+		}
+		return dst.Interface()
+	}
+	panic("not a slice or map") // Not possible due to FilterValues
+}
diff --git a/cmp/cmpopts/util_test.go b/cmp/cmpopts/util_test.go
index f532789..1ba9254 100644
--- a/cmp/cmpopts/util_test.go
+++ b/cmp/cmpopts/util_test.go
@@ -114,6 +114,121 @@
 		wantEqual: true,
 		reason:    "equal because EquateEmpty equates empty slices",
 	}, {
+		label: "DiscardElements",
+		x:     map[string]int{"foo": 5, "fizz": 0},
+		y:     map[string]int{"foo": 5, "buzz": 0},
+		opts: []cmp.Option{
+			DiscardElements(func(v int) bool { return v == 0 }),
+		},
+		wantEqual: true,
+		reason:    "equal because DiscardElements transforms the map to remove fizz and buzz",
+	}, {
+		label: "DiscardElements",
+		x:     map[string]MyStruct{"alice": {A: []int{1, 2, 3}}, "bob": {}},
+		y:     map[string]MyStruct{"alice": {A: []int{1, 2, 3}}, "charlie": {}},
+		opts: []cmp.Option{
+			DiscardElements(func(v MyStruct) bool { return reflect.DeepEqual(v, MyStruct{}) }),
+		},
+		wantEqual: true,
+		reason:    "equal because DiscardElements transforms maps with non-comparable types",
+	}, {
+		label: "DiscardElements",
+		x: []interface{}{
+			nil, nil, "foo",
+			[]interface{}{"a", "b", 3, nil, []interface{}{
+				map[string]interface{}{"foo": nil, "number": 5, "map": map[string]interface{}{
+					"zero":  nil,
+					"array": []interface{}{nil, 1, nil, 2, nil, 3},
+				}},
+			}, nil},
+			nil,
+		},
+		y: []interface{}{
+			"foo",
+			[]interface{}{"a", "b", 3, []interface{}{
+				map[string]interface{}{"bar": nil, "number": 5, "map": map[string]interface{}{
+					"array": []interface{}{1, 2, 3, nil, nil},
+				}},
+				nil, nil,
+			}},
+			nil, nil,
+		},
+		opts: []cmp.Option{
+			DiscardElements(func(v interface{}) bool { return v == nil }),
+		},
+		wantEqual: true,
+		reason:    "equal because DiscardElements applies recursively to sub-slices and sub-maps",
+	}, {
+		label: "DiscardElements",
+		x: map[string]MyStruct{
+			"alice": {
+				A: []int{1, 2, 3},
+				C: map[time.Time]string{
+					time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC): "birthday",
+					time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC): "",
+				},
+			},
+			"bob": {},
+		},
+		y: map[string]MyStruct{
+			"alice": {
+				A: []int{1, 2, 3},
+				C: map[time.Time]string{
+					time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC): "birthday",
+					time.Date(2020, time.November, 10, 23, 0, 0, 0, time.UTC): "",
+					time.Date(2030, time.November, 10, 23, 0, 0, 0, time.UTC): "",
+				},
+			},
+			"charlie": {},
+		},
+		opts: []cmp.Option{
+			DiscardElements(func(v MyStruct) bool { return cmp.Equal(v, MyStruct{}) }),
+			DiscardElements(func(v string) bool { return v == "" }),
+		},
+		wantEqual: true,
+		reason:    "equal because multiple DiscardElements used",
+	}, {
+		label: "DiscardElements",
+		x:     map[string]MyStruct{"alice": {A: []int{}, C: map[time.Time]string(nil)}, "bob": {}},
+		y:     map[string]MyStruct{"alice": {A: []int(nil), C: map[time.Time]string{}}, "charlie": {}},
+		opts: []cmp.Option{
+			DiscardElements(func(v MyStruct) bool { return cmp.Equal(v, MyStruct{}) }),
+			DiscardElements(func(v string) bool { return v == "" }),
+		},
+		wantEqual: false,
+		reason:    "not equal because empty slices are not the same",
+	}, {
+		label: "DiscardElements+EquateEmpty",
+		x:     map[string]MyStruct{"alice": {A: []int{}, C: map[time.Time]string(nil)}, "bob": {}},
+		y:     map[string]MyStruct{"alice": {A: []int(nil), C: map[time.Time]string{}}, "charlie": {}},
+		opts: []cmp.Option{
+			DiscardElements(func(v MyStruct) bool { return cmp.Equal(v, MyStruct{}) }),
+			DiscardElements(func(v string) bool { return v == "" }),
+			EquateEmpty(),
+		},
+		wantEqual: true,
+		reason:    "equal because zero map values and empty slices are all equal",
+	}, {
+		label: "DiscardElements+EquateEmpty",
+		x:     map[string][]int{"foo": {}},
+		y:     map[string][]int{"foo": nil},
+		opts: []cmp.Option{
+			DiscardElements(func(v []int) bool { return v == nil }),
+			EquateEmpty(),
+		},
+		wantEqual: false,
+		reason:    "not equal because DiscardElements only remove nil slice, but leaves the empty non-nil slice alone",
+	}, {
+		label: "DiscardElements+EquateEmpty",
+		x:     map[string][]int{"foo": {}},
+		y:     map[string][]int{"foo": nil},
+		opts: []cmp.Option{
+			DiscardElements(func(v []int) bool { return len(v) == 0 }),
+			EquateEmpty(),
+		},
+		wantEqual: true,
+		reason:    "equal because DiscardElements uses a more liberal definition of equal to zero",
+	}, {
 		label:     "SortSlices",
 		x:         []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
 		y:         []int{1, 0, 5, 2, 8, 9, 4, 3, 6, 7},
diff --git a/cmp/internal/function/func.go b/cmp/internal/function/func.go
index 4c35ff1..3981c6c 100644
--- a/cmp/internal/function/func.go
+++ b/cmp/internal/function/func.go
@@ -14,6 +14,7 @@
 
 	ttbFunc // func(T, T) bool
 	tibFunc // func(T, I) bool
+	tbFunc  // func(T) bool
 	trFunc  // func(T) R
 
 	Equal           = ttbFunc // func(T, T) bool
@@ -21,6 +22,7 @@
 	Transformer     = trFunc  // func(T) R
 	ValueFilter     = ttbFunc // func(T, T) bool
 	Less            = ttbFunc // func(T, T) bool
+	Remove          = tbFunc  // func(T) bool
 )
 
 var boolType = reflect.TypeOf(true)
@@ -40,6 +42,10 @@
 		if ni == 2 && no == 1 && t.In(0).AssignableTo(t.In(1)) && t.Out(0) == boolType {
 			return true
 		}
+	case tbFunc: // func(T) bool
+		if ni == 1 && no == 1 && t.Out(0) == boolType {
+			return true
+		}
 	case trFunc: // func(T) R
 		if ni == 1 && no == 1 {
 			return true