"""Handy general use functions.""" from collections import Counter from distutils.util import strtobool from itertools import zip_longest import json import os def zip_dict(keys, values): """Return a dict of two lists.""" return dict(zip(keys, values)) def split_zip_dict(s1, s2): """Return a dict of two comma-separated strings.""" keys = s1.split(',') values = s2.split(',') return zip_dict(keys, values) def var_to_bool(s): """Convert various variable types to bool.""" if type(s) is bool: return s if (type(s) is not str and type(s) is not int) or s is None: raise ValueError('Cannot covert {} to a bool'.format(s)) else: if (type(s) is int and int(s) == 0) or not strtobool(s): return_var = False elif (type(s) is int and int(s) == 1) or strtobool(s): return_var = True else: raise ValueError('Cannot covert {} to a bool'.format(s)) return return_var def primitive_dict(obj): """Create a dict from an iterable.""" return {f: k for f, k in vars(obj).items() if ((type(k) is str or type(k) is int or type(k) is bool or type(k) is list or type(k) is dict or type(k) is set or type(k) is float or type(k) is None) and '__' not in f[:2])} def grouper(iterable, n, fillvalue=None): """Collect data into fixed-length list_chunks or blocks.""" # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx" args = [iter(iterable)] * n return zip_longest(*args, fillvalue=fillvalue) def list_chunks(l, n): """Yield successive n-sized list_chunks from l.""" l2 = list(l) for i in range(0, len(l2), n): yield l2[i:i + n] def dedupe_list(seq): # OrderedDict is faster as of python 3.5 """Dedupe a list while preserving order.""" seen = set() seen_add = seen.add return [x for x in seq if not (x in seen or seen_add(x))] # TODO: add args, etc. to docstring def sanitize_json_dumps(s): """Perform an aggressive clean of a string json data.""" try: clean_json = json.dumps(s, ensure_ascii=False) clean_json = repr(clean_json).replace('\\', '\\')[1:-1] except Exception as e: return json.dumps({'error': str(e)}, ensure_ascii=False) return clean_json # TODO: add args, etc. to docstring def merge_two_dicts(x, y): """Merge two dicts. 2nd overwrites first when keys match.""" z = x.copy() # start with x's keys and values z.update(y) # modifies z with y's keys and values & returns None return z # TODO: add args, etc. to docstring def represents_int(s): """Check if a variable of unknown type can represent an int.""" try: int(s) return True except ValueError: return False def escape_bad_chars(s): """Escape characters which cause problems in str ingestion.""" s = s.replace('\'', '\\\'') return s def which(program): """Determines if an application is installed on the system.""" def is_exe(file_path): return os.path.isfile(file_path) and os.access(file_path, os.X_OK) fpath, fname = os.path.split(program) if fpath: if is_exe(program): return program else: for path in os.environ["PATH"].split(os.pathsep): exe_file = os.path.join(path, program) if is_exe(exe_file): return exe_file return None def check_for_dupes(elements): """Check if given list contains any duplicates """ setOfElems = set() for elem in elements: if elem in setOfElems: return True else: setOfElems.add(elem) return False # Slower Version of above # def check_for_dupes(list): # """Find and report on duplicates.""" # has_dupes = False # deduped_list = dedupe_list(list) # # if len(list) != len(deduped_list): # has_dupes = True # # return has_dupes def get_duplicates_in_list(list): """Report the duplicates in a list of items""" # Using Counter in case of complex hashable objects. dupe_cnt = Counter() for x in list: dupe_cnt[x] += 1 dupe_dict = dict() for dupe, cnt in dupe_cnt.items(): if cnt > 1: dupe_dict[dupe] = cnt return dupe_dict def compare_lists(list_1, list_2, case_sensitive=False): """Generates a dict report of the difference and intersection of two lists. Args: list_1: (list) a list of elements. list_2: (list) a second list of elements. case_sensitive: (bool) Determines if comparisons will be case sensitive. Returns: dict: A dictionary of comparisons, containing 3 lists, stored in the keys 'list_1_only', 'list_2_only', and 'both' """ comparisons = dict() comparisons['list_1_only'] = list() comparisons['list_2_only'] = list() comparisons['both'] = list() list_1_orig = list_1.copy() list_2_orig = list_2.copy() if not case_sensitive: list_1 = [x.lower() for x in list_1] list_2 = [x.lower() for x in list_2] for pos, el in enumerate(list_1): if el in list_2: comparisons['both'].append(list_1_orig[pos]) else: comparisons['list_1_only'].append(list_1_orig[pos]) comparisons_both_orig = comparisons['both'].copy() if not case_sensitive: comparisons['both'] = [x.lower() for x in comparisons['both']] for pos, el in enumerate(list_2): if el not in comparisons['both']: comparisons['list_2_only'].append(list_2_orig[pos]) comparisons['both'] = comparisons_both_orig return comparisons def check_consecutive(elements): elements = [int(x) for x in elements] return sorted(elements) == list(range(min(elements), max(elements) + 1))