Skip to content

How to Group List to Dictionary by Prefix in Python

Learn how to transform flat lists with prefix headers into structured dictionaries in Python using stateful iteration, custom conditions, and practical examples for logs, configs, and more.

When data arrives as a flat stream where headers are followed by values (common in log files, legacy exports, or configuration formats), you need stateful iteration to group items correctly.

This guide covers robust patterns for transforming flat lists into structured dictionaries.

Track the current group key as you iterate:

raw_data = [
    "GROUP-A", 10, 20,
    "GROUP-B", 5,
    "GROUP-C", 99, 100, 101
]

def group_by_prefix(data, prefix="GROUP-"):
    result = {}
    current_key = None
    
    for item in data:
        if isinstance(item, str) and item.startswith(prefix):
            current_key = item
            result[current_key] = []
        elif current_key is not None:
            result[current_key].append(item)
    
    return result

print(group_by_prefix(raw_data))

Handling Values Before First Header

def group_by_prefix_safe(data, prefix="GROUP-", orphan_key="_orphaned"):
    """Handle items that appear before any header."""
    result = {orphan_key: []}
    current_key = orphan_key
    
    for item in data:
        if isinstance(item, str) and item.startswith(prefix):
            current_key = item
            result[current_key] = []
        else:
            result[current_key].append(item)
    
    if not result[orphan_key]:
        del result[orphan_key]
    
    return result

data = [1, 2, "GROUP-A", 10, 20, "GROUP-B", 30]
print(group_by_prefix_safe(data))

Using a Callable Header Detector

Make the grouping logic more flexible:

def group_by_condition(data, is_header):
    """Group items based on a custom header detection function."""
    result = {}
    current_key = None
    
    for item in data:
        if is_header(item):
            current_key = item
            result[current_key] = []
        elif current_key is not None:
            result[current_key].append(item)
    
    return result

data = ["SECTION_A", 1, 2, 3, "SECTION_B", 4, 5]
result = group_by_condition(data, lambda x: isinstance(x, str) and x.isupper())
print(result)

data = ["# Config", "key=value", "# Database", "host=localhost"]
result = group_by_condition(data, lambda x: x.startswith("#"))
print(result)

Output:

{'SECTION_A': [1, 2, 3], 'SECTION_B': [4, 5]}
{'# Config': ['key=value'], '# Database': ['host=localhost']}

Using itertools.groupby

For data with clear header/value markers:

from itertools import groupby

data = [
    ("H", "Users"),
    ("V", "Alice"),
    ("V", "Bob"),
    ("H", "Admins"),
    ("V", "Charlie")
]

def group_with_markers(data):
    result = {}
    current_key = None
    
    for is_header, group in groupby(data, key=lambda x: x[0] == "H"):
        items = list(group)
        if is_header:
            current_key = items[0][1]
            result[current_key] = []
        else:
            result[current_key].extend(item[1] for item in items)
    
    return result

print(group_with_markers(data))

Practical Examples

Log File Parsing

log_lines = [
    "[ERROR] Connection failed",
    "  Timeout after 30s",
    "  Retry count: 3",
    "[WARNING] Disk space low",
    "  Available: 500MB",
    "[ERROR] Authentication failed",
    "  User: admin",
    "  IP: 192.168.1.100"
]

def parse_log_entries(lines):
    entries = []
    current_entry = None
    
    for line in lines:
        if line.startswith("["):
            if current_entry:
                entries.append(current_entry)
            bracket_end = line.index("]")
            level = line[1:bracket_end]
            message = line[bracket_end + 2:]
            current_entry = {"level": level, "message": message, "details": []}
        elif current_entry:
            current_entry["details"].append(line.strip())
    
    if current_entry:
        entries.append(current_entry)
    
    return entries

parsed = parse_log_entries(log_lines)
for entry in parsed:
    print(entry)

Output:

{'level': 'ERROR', 'message': 'Connection failed', 'details': ['Timeout after 30s', 'Retry count: 3']}
{'level': 'WARNING', 'message': 'Disk space low', 'details': ['Available: 500MB']}
{'level': 'ERROR', 'message': 'Authentication failed', 'details': ['User: admin', 'IP: 192.168.1.100']}

Configuration Section Grouping

config_data = [
    "[database]",
    "host=localhost",
    "port=5432",
    "[cache]",
    "enabled=true",
    "ttl=3600",
    "[logging]",
    "level=debug"
]

def parse_ini_style(lines):
    sections = {}
    current_section = None
    
    for line in lines:
        if line.startswith("[") and line.endswith("]"):
            current_section = line[1:-1]
            sections[current_section] = {}
        elif current_section and "=" in line:
            key, value = line.split("=", 1)
            sections[current_section][key] = value
    
    return sections

print(parse_ini_style(config_data))

Output:

{'database': {'host': 'localhost', 'port': '5432'}, 'cache': {'enabled': 'true', 'ttl': '3600'}, 'logging': {'level': 'debug'}}

Numbered Section Grouping

data = [
    "1.", "First item", "First detail",
    "2.", "Second item",
    "3.", "Third item", "Detail A", "Detail B"
]

import re

def group_numbered_sections(items):
    result = {}
    current = None
    
    for item in items:
        if re.match(r"^\d+\.$", item):
            current = item
            result[current] = []
        elif current:
            result[current].append(item)
    
    return result

print(group_numbered_sections(data))

Output:

{'1.': ['First item', 'First detail'], '2.': ['Second item'], '3.': ['Third item', 'Detail A', 'Detail B']}

Using defaultdict for Cleaner Code

from collections import defaultdict

def group_by_prefix_defaultdict(data, prefix):
    result = defaultdict(list)
    current_key = None
    
    for item in data:
        if isinstance(item, str) and item.startswith(prefix):
            current_key = item
        elif current_key:
            result[current_key].append(item)
    
    return dict(result)

data = ["CAT-A", 1, 2, "CAT-B", 3, 4, 5]
print(group_by_prefix_defaultdict(data, "CAT-"))

Output:

{'CAT-A': [1, 2], 'CAT-B': [3, 4, 5]}

Transforming Values During Grouping

def group_and_transform(data, is_header, transform=None):
    """Group with optional value transformation."""
    result = {}
    current_key = None
    
    for item in data:
        if is_header(item):
            current_key = item
            result[current_key] = []
        elif current_key is not None:
            value = transform(item) if transform else item
            result[current_key].append(value)
    
    return result

data = ["GROUP-A", "10", "20", "GROUP-B", "30"]
result = group_and_transform(
    data,
    is_header=lambda x: x.startswith("GROUP-"),
    transform=int
)
print(result)

Output:

{'GROUP-A': [10, 20], 'GROUP-B': [30]}

Summary

Method Readability Flexibility Best For
State loop High High General-purpose, messy data
With callable High Very high Configurable header detection
groupby Medium Medium Clean, predictable formats
defaultdict High Medium Cleaner initialization
Navigation