Skip to content

Commit

Permalink
* moved nil to utils
Browse files Browse the repository at this point in the history
* improvements for get
* improvements for apply
* improvements for some
* improvements for merge
* improvements for merge_with
* improvements for get
* improvements for Protocol
* improvements for Dict
* improvements for List
* improvements for Iterable
* improvements for Set
* improvements for _reflect
* alias for recursive_dict_merge -> deep_merge
* improvements for pop
* added popv
* improved partition
* improved complement
* added interleave
* deprecated windows
  • Loading branch information
jjtolton committed Feb 22, 2018
1 parent ab3e5b8 commit f973aaf
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 48 deletions.
135 changes: 88 additions & 47 deletions naga/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,14 @@
import itertools
import types
from functools import reduce, partial
from warnings import warn

from naga.utils import Namespaced
from naga import nil
from naga.utils import Namespaced, decorator

seq_types = list, tuple, str


class nil:
def __bool__(self):
return False


def reductions(fn, seq, default=nil):
"""generator version of reduce that returns 1 item at a time"""

Expand Down Expand Up @@ -57,21 +54,26 @@ def get_in(d, ks, not_found=None):
if len(ks) == 1:
return get(d, first(ks), not_found)
else:
return get(get(d, first(ks)), rest(ks), not_found)
return get_in(get(d, first(ks)), rest(ks), not_found)


def apply(fn, x):
def apply(fn, *x):
"""Applies fn to the argument list formed by prepending intervening arguments to args.
apply(fn, x) --> fn(*x)"""
return fn(*x)
if len(x) > 0 and isinstance(x[-1], (tuple, list)):
return apply(fn, *(x[:-1] + tuple(x[-1])))
else:
return fn(*x)


def some(fn, seq=None):
def some(fn, seq=nil):
"""Returns first truthy value or False. Can accept a predicate as first argument."""

if not isinstance(fn, (types.FunctionType, types.MethodType)):
return some(lambda x: x, seq=fn)
if seq is nil:
seq = fn
fn = identity
return some(fn, seq)

for e in seq:
if fn(e):
Expand Down Expand Up @@ -319,7 +321,7 @@ def merge(*ds):
"""Returns a dict that consists of the rest of the dicts merged onto
the first. If a key occurs in more than one dict, the mapping from
the latter (left-to-right) will be the mapping in the result."""
return dict(itertools.chain(*map(lambda _d: _d.items(), ds)))
return dict(itertools.chain(*(d.items() for d in ds)))


def assoc(m, k, v):
Expand All @@ -343,9 +345,7 @@ def merge_with(fn, *ds):
from the latter (left-to-right) will be combined with the mapping in
the result by calling fn(val-in-result, val-in-latter)."""
return reduce(
lambda d, x: dict(itertools.chain(
d.items(),
dict(((k, v) if k not in d else (k, fn(d[k], x[k])) for k, v in x.items())).items())),
lambda d, x: merge(d, dict(((k, v) if k not in d else (k, fn(d[k], x[k])) for k, v in x.items()))),
ds)


Expand Down Expand Up @@ -381,7 +381,7 @@ def terminal_dicts(*ds):
return all(map(terminal_dict, ds))


def get(x, k, not_found=None):
def get(x, k=nil, not_found=None):
"""Get key "k" from collection "x".
>>> get({1: 2}, 1)
Expand Down Expand Up @@ -420,7 +420,10 @@ def get(d, k, not_found=None):
raise NotImplementedError

def first(d):
return next(iter(d))
try:
return next(iter(d))
except StopIteration:
return []

def rest(d):
raise NotImplementedError
Expand All @@ -434,11 +437,6 @@ def dissoc(d, *ks):

# noinspection PyMethodParameters
class Dict(Protocol):
def rest(d):
res = iter(d)
next(res)
return res

def first(d):
return next(iter(k for k in d))

Expand All @@ -462,6 +460,11 @@ def dissoc(d, *ks):
# noinspection PyMethodParameters
class List(Protocol):
def get(d, k, not_found=None):
if not 0 <= k <= len(d):
if not_found is nil:
return None
return not_found

return d[k]

def update(d, k, fn, *args, **kwargs):
Expand Down Expand Up @@ -525,16 +528,19 @@ def last(d):
return None

def first(d):
return next(iter(d))
try:
return next(iter(d))
except StopIteration:
return None

def rest(d):
try:
next(d)
return d
except StopIteration:
return None
return []

def get(x, k, not_found=None):
def get(d, k, not_found=None):
if k < 0 or not isinstance(k, int):
raise Exception("\"k\" must be an index value greater than one, not {k}(type(k))")
x = iter(x)
Expand All @@ -551,9 +557,6 @@ def constantly(x):

# noinspection PyMethodParameters
class Set(Protocol):
def assoc(s, x):
return s | {x}

def dissoc(s, x):
return s - {x}

Expand Down Expand Up @@ -586,20 +589,30 @@ def update_in(d, key_list, fn, *args, **kwargs):
return update(d, first(key_list), update_in, rest(key_list), fn, *args, **kwargs)


def _reflect(x):
return ({dict: Dict, list: List, str: String, tuple: Tuple, set: Set, types.GeneratorType: Iterable,
conj: Iterable}
.get(type(x), cond(x,
fpartial(isinstance, collections.MutableMapping), dict,
fpartial(isinstance, types.GeneratorType), Iterable,
constantly(True), x)))
class _Reflect:
vals = {dict: Dict, list: List, str: String, tuple: Tuple, set: Set, types.GeneratorType: Iterable}

@classmethod
def reflect(cls, x):
datatype = cls.vals.get(type(x))
if datatype is None:
datatype = cond(x,
fpartial(isinstance, collections.MutableMapping), Dict,
fpartial(isinstance, types.GeneratorType), Iterable,
constantly(True), x)
return datatype


_reflect = _Reflect.reflect


def recursive_dict_merge(*ds):
"""Recursively merge dictionaries"""
return merge_with(lambda a, b: recursive_dict_merge(a, b) if not terminal_dicts(a, b) else merge(a, b), *ds)


deep_merge = recursive_dict_merge

def keys2dict(val, *ks):
"""Convert a value and a list of keys to a nested dictionary with the value at the leaf"""
v_in = reduce(lambda x, y: {y: x}, (list(ks) + [val])[::-1])
Expand Down Expand Up @@ -677,23 +690,28 @@ def append(*seqs):


def pop(iterable):
_, seq = itertools.tee(iterable)
return itertools.islice(0, len(seq) - 1)
it = iter(iterable)
val = first(iterable)
while val:
try:
yield val
val = next(it)
except StopIteration:
pass


def windows(n, seq):
def popv(iterable):
return list(pop(iterable))


def partition(n, seq):
"""Returns a lazy sequence of lists of n items each"""
if 'zip_longest' in dir(itertools):
return itertools.zip_longest(*(seq[i::n] for i in range(n)))
else:
return itertools.izip_longest(*(seq[i::n] for i in range(n)))


def partition(n, seq):
"""Returns a lazy sequence of lists of n items each"""
return windows(n, seq)


def conj(x, *args):
return append(x, args)

Expand All @@ -714,8 +732,12 @@ def nonep(x):
return x is None


def complement(x):
return not x
@decorator
def complement(f):
def _complement(*args, **kwargs):
return not f(*args, **kwargs)

return _complement


def somep(x):
Expand All @@ -740,7 +762,15 @@ def case(x, *forms):
def remove(f, x):
""">>> list(remove(lambda x: x > 2, range(10)))
[0, 1]"""
return filter(comp(), x)
return filter(complement(f), x)


def interleave(*xs):
return (a for b in zip(*xs) for a in b)


def intereleavev(*xs):
return list(interleave(*xs))


##############
Expand All @@ -750,10 +780,21 @@ def remove(f, x):
def rreduce(fn, seq, default=None):
"""'readable reduce' - More readable version of reduce with arrity-based dispatch; passes keyword arguments
to functools.reduce"""
from warnings import warn
warn(DeprecationWarning("rreduce is deprecated and will be removed in future versions"))

# if two arguments
if default is None:
return reduce(fn, seq)

# if three arguments
return reduce(fn, seq, default)


def windows(n, seq):
"""Returns a lazy sequence of lists of n items each"""
warn(DeprecationWarning("windows is deprecated and will be removed in future versions"))
if 'zip_longest' in dir(itertools):
return itertools.zip_longest(*(seq[i::n] for i in range(n)))
else:
return itertools.izip_longest(*(seq[i::n] for i in range(n)))
8 changes: 8 additions & 0 deletions naga/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,11 @@ def evenrecurse(n):


print(oddrecurse(10))


class nil:
def __bool__(self):
return False

def __repr__(self):
return 'nil'
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
setup(
name='naga',
packages=['naga'], # this must be the same as the name above
version='0.3.2.4',
version='0.3.2.5',
description='A powerful Clojure-inspired Python/Lisp-hybrid-monster!',
author='James J. Tolton',
author_email='jjtolton@gmail.com',
Expand Down

0 comments on commit f973aaf

Please sign in to comment.