from __future__ import annotations from math import floor from typing import Generic, Protocol, Callable, TypeVar, Union, \ Iterable, cast, Optional, Any T = TypeVar( 'T' ) S = TypeVar( 'S', covariant = True ) class EachProto( Protocol, Generic[ S ] ): def each( self, __f: Callable[ [ S ], object ] ) -> None: ... Each = Union[ Iterable[ T ], EachProto[ T ] ] def each( f: Callable[ [ T ], object ], data: Each[ T ] ) -> None: if hasattr( data, "each" ): cast( EachProto[ T ], data ).each( f ) else: for x in cast( Iterable[ T ], data ): f( x ) def each_len( data: Each[ T ] ) -> int: counter = 0 def inc( _: T ) -> None: nonlocal counter counter += 1 each( inc, data ) return counter def each_sum( data: Each[ int ] ) -> int: sum_ = 0 def add( x: int ) -> None: nonlocal sum_ sum_ += x each( add, data ) return sum_ def each_avg( data: Each[ int ] ) -> float: items = 0 sum_ = 0 def add( x: int ) -> None: nonlocal items, sum_ items += 1 sum_ += x each( add, data ) return sum_ / items def each_median( data: Each[ int ] ) -> Optional[ int ]: items = [] def add( x: int ) -> None: items.append( x ) each( add, data ) if not items: return None len_ = len( items ) return sorted( items )[ len_ // 2 - ((len_ + 1) % 2) ] import run_tests