HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Resolving multiple "paths" in nested attributes

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
pathsnestedattributesmultipleresolving

Problem

I need to resolve "multiple paths" specified like so:

p1 = 'item.element.[compact|fullsize].documents.[note|invoice]'


A list like this is expected:

['item.element.compact.documents.note', 'item.element.fullsize.documents.note',
 'item.element.compact.documents.invoice', 'item.element.fullsize.documents.invoice']


Code:

def resolve_paths(path):
    parts = path.split('.')
    depth = len(parts)
    new_paths = []
    for level, part in enumerate(parts):
        mult_branches = re.findall(r'\[(\w+)(?:\|(\w+))*\]', part)
        if mult_branches:
            mult_branches = flatten_iterable(mult_branches)
            for branch in mult_branches:
                interm_path = '.'.join(parts[:level] + [branch] + parts[level+1:])
                new_paths.extend(resolve_paths(interm_path))
            return new_paths
        elif level == depth - 1:
            new_paths.append(path)
    return new_paths


Several tests I wrote for this function pass, but I'm not entirely happy with this solution, it's somewhat convoluted. Better solutions? Simplifications?

Solution

What do you think this will return for part = '[compact|fullsize|x|y]'

re.findall(r'\[(\w+)(?:\|(\w+))*\]', part)


It will give:

[('compact', 'y')]


Because, when a group matches multiple times, the last match overwrites previous matches, as per the docs.
If you want to support multiple patterns, I'm afraid you will have to do in two steps: matching and then splitting.

The part I don't like about this is that you split path to parts,
then when you need to recurse,
you re-join the parts again,
and in the recursion step it will be split again, and so on.
That's a lot of splitting and joining and iterations.
The recursive logic can be also tricky to understand.

Here's an alternative, without using recursion and unnecessary splitting, joining, iteration:

from collections import deque

def resolve_paths(path):
    parts = deque(path.split('.'))
    paths = [[]]

    while parts:
        part = parts.popleft()
        branches = re.findall(r'\[(\w+)(?:\|(\w+))*\]', part)
        if branches:
            orig_paths = paths[:]
            paths = []
            for branch in branches[0]:
                for path in orig_paths:
                    paths.append(path + [branch])
        else:
            for path in paths:
                path.append(part)

    return ['.'.join(path) for path in paths]

Code Snippets

re.findall(r'\[(\w+)(?:\|(\w+))*\]', part)
[('compact', 'y')]
from collections import deque


def resolve_paths(path):
    parts = deque(path.split('.'))
    paths = [[]]

    while parts:
        part = parts.popleft()
        branches = re.findall(r'\[(\w+)(?:\|(\w+))*\]', part)
        if branches:
            orig_paths = paths[:]
            paths = []
            for branch in branches[0]:
                for path in orig_paths:
                    paths.append(path + [branch])
        else:
            for path in paths:
                path.append(part)

    return ['.'.join(path) for path in paths]

Context

StackExchange Code Review Q#98697, answer score: 5

Revisions (0)

No revisions yet.