Skip to content

Commit bfd31c4

Browse files
feat: add list.contains (#2942)
--------- Co-authored-by: Marco Edward Gorelli <[email protected]>
1 parent c9ca0ff commit bfd31c4

File tree

14 files changed

+155
-3
lines changed

14 files changed

+155
-3
lines changed

docs/api-reference/expr_list.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
handler: python
55
options:
66
members:
7+
- contains
78
- len
89
- unique
910
show_source: false

docs/api-reference/series_list.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
handler: python
55
options:
66
members:
7+
- contains
78
- len
89
- unique
910
show_source: false

narwhals/_arrow/series_list.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ def len(self) -> ArrowSeries:
1717
return self.with_native(pc.list_value_length(self.native).cast(pa.uint32()))
1818

1919
unique = not_implemented()
20+
contains = not_implemented()

narwhals/_compliant/any_namespace.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
if TYPE_CHECKING:
1010
from typing import Callable
1111

12-
from narwhals.typing import TimeUnit
12+
from narwhals.typing import NonNestedLiteral, TimeUnit
1313

1414
__all__ = [
1515
"CatNamespace",
@@ -54,6 +54,7 @@ def offset_by(self, by: str) -> CompliantT_co: ...
5454
class ListNamespace(_StoresCompliant[CompliantT_co], Protocol[CompliantT_co]):
5555
def len(self) -> CompliantT_co: ...
5656
def unique(self) -> CompliantT_co: ...
57+
def contains(self, item: NonNestedLiteral) -> CompliantT_co: ...
5758

5859

5960
class NameNamespace(_StoresCompliant[CompliantT_co], Protocol[CompliantT_co]):

narwhals/_compliant/expr.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,9 @@ def len(self) -> EagerExprT:
10621062
def unique(self) -> EagerExprT:
10631063
return self.compliant._reuse_series_namespace("list", "unique")
10641064

1065+
def contains(self, item: NonNestedLiteral) -> EagerExprT:
1066+
return self.compliant._reuse_series_namespace("list", "contains", item=item)
1067+
10651068

10661069
class CompliantExprNameNamespace( # type: ignore[misc]
10671070
_ExprNamespace[CompliantExprT_co],

narwhals/_duckdb/expr_list.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from duckdb import Expression
1111

1212
from narwhals._duckdb.expr import DuckDBExpr
13+
from narwhals.typing import NonNestedLiteral
1314

1415

1516
class DuckDBExprListNamespace(
@@ -27,3 +28,8 @@ def func(expr: Expression) -> Expression:
2728
).otherwise(expr_distinct)
2829

2930
return self.compliant._with_callable(func)
31+
32+
def contains(self, item: NonNestedLiteral) -> DuckDBExpr:
33+
return self.compliant._with_elementwise(
34+
lambda expr: F("list_contains", expr, lit(item))
35+
)

narwhals/_ibis/expr_list.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
if TYPE_CHECKING:
99
from narwhals._ibis.expr import IbisExpr
10+
from narwhals.typing import NonNestedLiteral
1011

1112

1213
class IbisExprListNamespace(LazyExprNamespace["IbisExpr"], ListNamespace["IbisExpr"]):
@@ -15,3 +16,6 @@ def len(self) -> IbisExpr:
1516

1617
def unique(self) -> IbisExpr:
1718
return self.compliant._with_callable(lambda expr: expr.unique())
19+
20+
def contains(self, item: NonNestedLiteral) -> IbisExpr:
21+
return self.compliant._with_callable(lambda expr: expr.contains(item))

narwhals/_pandas_like/series_list.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,4 @@ def len(self) -> PandasLikeSeries:
3333
return self.with_native(result.astype(dtype)).alias(self.native.name)
3434

3535
unique = not_implemented()
36+
contains = not_implemented()

narwhals/_polars/expr.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,15 @@ def len(self) -> PolarsExpr:
415415

416416
return self.compliant._with_native(native_result)
417417

418+
def contains(self, item: Any) -> PolarsExpr:
419+
if self.compliant._backend_version < (1, 28):
420+
result: pl.Expr = pl.when(self.native.is_not_null()).then(
421+
self.native.list.contains(item)
422+
)
423+
else:
424+
result = self.native.list.contains(item)
425+
return self.compliant._with_native(result)
426+
418427

419428
class PolarsExprStructNamespace(
420429
PolarsExprNamespace, PolarsStructNamespace[PolarsExpr, pl.Expr]

narwhals/_polars/series.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,13 @@
3636
from narwhals._utils import Version, _LimitedContext
3737
from narwhals.dtypes import DType
3838
from narwhals.series import Series
39-
from narwhals.typing import Into1DArray, IntoDType, MultiIndexSelector, _1DArray
39+
from narwhals.typing import (
40+
Into1DArray,
41+
IntoDType,
42+
MultiIndexSelector,
43+
NonNestedLiteral,
44+
_1DArray,
45+
)
4046

4147
T = TypeVar("T")
4248
IncludeBreakpoint: TypeAlias = Literal[False, True]
@@ -737,6 +743,11 @@ def len(self) -> PolarsSeries:
737743
ns = self.__narwhals_namespace__()
738744
return self.to_frame().select(ns.col(name).list.len()).get_column(name)
739745

746+
def contains(self, item: NonNestedLiteral) -> PolarsSeries:
747+
name = self.name
748+
ns = self.__narwhals_namespace__()
749+
return self.to_frame().select(ns.col(name).list.contains(item)).get_column(name)
750+
740751

741752
class PolarsSeriesStructNamespace(
742753
PolarsSeriesNamespace, PolarsStructNamespace[PolarsSeries, pl.Series]

0 commit comments

Comments
 (0)