Quick actions

cmd+k|ctrl+k

Navigation

Languages

StructureDescriber

Snippet info

Language

Python

Visibility

public

Author

rajivm1991

Created

2024-11-27T03:39:24.613958Z

Updated

2024-11-27T05:30:03.856556Z

import json
from collections import defaultdict
from typing import Any, Dict, List, Optional, Union


def remove_null_values(data: Union[Dict, List, str]) -> Optional[Union[Dict, List, str]]:
    if isinstance(data, dict):
        return {k: v for k, v in ((k, remove_null_values(v)) for k, v in data.items()) if v is not None}
    elif isinstance(data, list):
        return [v for v in (remove_null_values(v) for v in data) if v]
    elif isinstance(data, str):
        return data.strip() or None
    return data


def find_n_grams(input_list: List[Any], n: int = 2):
    return zip(*[input_list[i:] for i in range(n)])


class StructureDescriber:
    """
    Usage:
        s = StructureDescriber()
        s.insert(<python obj: list or dict>)
        s.describe()
    """

    def __init__(self, scan_depth: int = 4, collect_data: Optional[List[str]] = None):
        self.STRUCTURE = defaultdict(lambda: {'type': defaultdict(int)})
        self.collect_data = set(collect_data) if collect_data else set()
        self.scan_depth = scan_depth

    @staticmethod
    def get_type(data: Any) -> str:
        type_of_data = type(data).__name__
        if not data and type_of_data not in ('bool', 'int', 'float'):
            type_of_data = f'empty_{type_of_data}'
        return type_of_data

    def insert(self, data: Any, level: int = 0, prefix: Optional[str] = None):
        if level > self.scan_depth:
            return

        type_of_data = self.get_type(data)

        if prefix:
            self.STRUCTURE[prefix]['type'][type_of_data] += 1
            if prefix in self.collect_data:
                self.STRUCTURE[prefix].setdefault('data', {}).setdefault(data, 0)
                self.STRUCTURE[prefix]['data'][data] += 1

        if type_of_data == 'dict':
            for k, v in data.items():
                self.insert(data=v, level=level + 1, prefix=f'{prefix}.{k}' if prefix else k)
        elif type_of_data == 'list':
            for v in data:
                self.insert(data=v, level=level, prefix=f'{prefix}.*' if prefix else '*')

    def describe(self):
        print("__root__:")
        for key, structure_data in sorted(self.STRUCTURE.items()):
            if not key.endswith('*'):
                data_types = dict(structure_data['type'])
                if 'list' in data_types:
                    for t in self.STRUCTURE[f'{key}.*']['type']:
                        data_types[f'list_of_{t}'] = data_types['list']
                    data_types.pop('list')
                
                # indent = (key.count('.') - key.count('*')) * '│   '
                # print(f"{indent}├── {key} {data_types}")
                # if key in self.collect_data:
                #     for d, c in sorted(structure_data['data'].items(), key=lambda item: item[1], reverse=True):
                #         print(f"│   {indent}├── {d} {c}")
                
                indent = (key.count('.') - key.count('*')) * '    '
                print(f'    {indent}{key}:', [f'{dt} {co}' for dt, co in data_types.items()])
                if key in self.collect_data:
                    for d, c in sorted(structure_data['data'].items(), key=lambda item: item[1], reverse=True):
                        print(f'        {indent}- {d} {c}')



if __name__ == '__main__':
    data = input()
    data = json.loads(data)

    collect_data = input()
    collect_data = collect_data.split(',')

    s = StructureDescriber(collect_data=collect_data)
    for d in data:
        s.insert(d)
    s.describe()
INFO