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

Parse Run Length Encoded file for cellular automaton data

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

Problem

I wanted to be able to parse Run Length Encoded (*.rle) files into something usable for a cellular automaton simulator (e.g. Conway's Game of Life and such).

Here is my main method which has the code I used for manual testing. The example file is taken from the above linked wiki.

def main():
    sample_rle = \
"""#N Gosper glider gun
#C This was the first gun discovered.
#C As its name suggests, it was discovered by Bill Gosper.
#O Bill Gosper Nov. 1970
x = 36, y = 9, rule = B3/S23
24bo$22bobo$12b2o6b2o12b2o$11bo3bo4b2o12b2o$2o8bo5bo3b2o$2o8bo3bob2o4b
obo$10bo5bo7bo$11bo3bo$12b2o! """

    rle_parser = RunLengthEncodedParser(sample_rle)
    print("name:", rle_parser.get_name())
    print("comments:")
    pprint.pprint(rle_parser.get_comments())
    print("author:", rle_parser.get_author())
    print("size_x:", rle_parser.get_size_x())
    print("size_y:", rle_parser.get_size_y())
    print("rule_birth:", rle_parser.get_rule_birth())
    print("rule_survival:", rle_parser.get_rule_survival())
    print("pattern_raw:", rle_parser.get_pattern_raw())
    #print("pattern_2d_array:")
    #print(rle_parser.get_pattern_2d_array())
    print("human_friendly_pattern:")
    print(rle_parser.get_human_friendly_pattern())


The console output of the file is as follows. I left out the output from print(rle_parser.get_pattern_2d_array()) as it is just a 2D list of b (dead) and o (alive) cells. For the sake of human readability, I replaced b dead cells with dots in the return from get_human_friendly_pattern.

```
name: Gosper glider gun
comments:
['This was the first gun discovered.',
'As its name suggests, it was discovered by Bill Gosper.']
author: Bill Gosper Nov. 1970
size_x: 36
size_y: 9
rule_birth: [3]
rule_survival: [2, 3]
pattern_raw: 24bo$22bobo$12b2o6b2o12b2o$11bo3bo4b2o12b2o$2o8bo5bo3b2o$2o8bo3bob2o4bobo$10bo5bo7bo$11bo3bo$12b2o!
human_friendly_pattern:
........................o...........
......................o.o...........
............oo......

Solution

Apart from @Graipher's great answer (my answer is based on his), I'd use the following when it comes to multi-line styling:

from pprint import pformat

class RunLengthEncodedParser:
    """
    Parser for Run Length Encode (RLE) strings / files.
    More information: http://www.conwaylife.com/w/index.php?title=Run_Length_Encoded
    """
    def __init__(self, rle_string):
        self.rle_string = rle_string
        self.name = ""
        self._comments = []    # Note underscore
        self.author = ""
        self.size_x = 0
        self.size_y = 0
        self.rule_birth = []
        self.rule_survival = []
        self.pattern_raw = ""

        # Fill in instance attributes by parsing the raw strings
        self.populate_attributes(self.rle_string.strip().splitlines())
        self.pattern_2d_array = self.populate_pattern(self.pattern_raw, self.size_x, self.size_y)

    def populate_attributes(self, lines):
        ...

    def populate_pattern(self, pattern_raw, size_x, size_y, default_cell='b'):
        ...

    def isdigit(self, c):
        """Returns True is the character is a digit"""
        return '0' <= c <= '9'

    def __str__(self):
        return self.rle_string

    def __format__(self, fmt):
        return 'name: {self.name}\n' \
               'comments: {self.comments}\n' \
               'author: {self.author}\n' \
               'size_x: {self.size_x}\n' \
               'size_y: {self.size_y}\n' \
               'rule_birth: {self.rule_birth}\n' \
               'rule_survival: {self.rule_survivor}\n' \
               'pattern_raw: {self.pattern_raw}\n' \
               'human_friendly_pattern: {self.human_friendly_pattern}\n'.format(self=self)

    @property
    def human_friendly_pattern(self):
        pattern_str = ""
        for row in self.pattern_2d_array:
            row_str = ""
            for c in row:
                if c == 'b':
                    row_str += '.'
                else:
                    row_str += c
            pattern_str += row_str + '\n'
        return pattern_str

    @property
    def comments(self):
        return pformat(self._comments)

def main():
    sample_rle = '#N Gosper glider gun\n' \
                 '#C This was the first gun discovered.\n' \
                 '#C This was the first gun discovered.\n' \
                 '#C As its name suggests, it was discovered by Bill Gosper.\n' \
                 '#O Bill Gosper Nov. 1970\n' \
                 'x = 36, y = 9, rule = B3/S23\n' \
                 '24bo$22bobo$12b2o6b2o12b2o$11bo3bo4b2o12b2o$2o8bo5bo3b2o$2o8bo3bob2o4b\n' \
                 'obo$10bo5bo7bo$11bo3bo$12b2o!\n'

    rle_parser = RunLengthEncodedParser(sample_rle)
    print(format(rle_parser))
if __name__ == '__main__':
    main()


For me, the trade-off of using \n and \ to gain the correct indentation makes sense + makes those snippets look clear.

I also added the from pprint import pformat at the top of the code.

Code Snippets

from pprint import pformat


class RunLengthEncodedParser:
    """
    Parser for Run Length Encode (RLE) strings / files.
    More information: http://www.conwaylife.com/w/index.php?title=Run_Length_Encoded
    """
    def __init__(self, rle_string):
        self.rle_string = rle_string
        self.name = ""
        self._comments = []    # Note underscore
        self.author = ""
        self.size_x = 0
        self.size_y = 0
        self.rule_birth = []
        self.rule_survival = []
        self.pattern_raw = ""

        # Fill in instance attributes by parsing the raw strings
        self.populate_attributes(self.rle_string.strip().splitlines())
        self.pattern_2d_array = self.populate_pattern(self.pattern_raw, self.size_x, self.size_y)

    def populate_attributes(self, lines):
        ...

    def populate_pattern(self, pattern_raw, size_x, size_y, default_cell='b'):
        ...

    def isdigit(self, c):
        """Returns True is the character is a digit"""
        return '0' <= c <= '9'

    def __str__(self):
        return self.rle_string

    def __format__(self, fmt):
        return 'name: {self.name}\n' \
               'comments: {self.comments}\n' \
               'author: {self.author}\n' \
               'size_x: {self.size_x}\n' \
               'size_y: {self.size_y}\n' \
               'rule_birth: {self.rule_birth}\n' \
               'rule_survival: {self.rule_survivor}\n' \
               'pattern_raw: {self.pattern_raw}\n' \
               'human_friendly_pattern: {self.human_friendly_pattern}\n'.format(self=self)

    @property
    def human_friendly_pattern(self):
        pattern_str = ""
        for row in self.pattern_2d_array:
            row_str = ""
            for c in row:
                if c == 'b':
                    row_str += '.'
                else:
                    row_str += c
            pattern_str += row_str + '\n'
        return pattern_str

    @property
    def comments(self):
        return pformat(self._comments)


def main():
    sample_rle = '#N Gosper glider gun\n' \
                 '#C This was the first gun discovered.\n' \
                 '#C This was the first gun discovered.\n' \
                 '#C As its name suggests, it was discovered by Bill Gosper.\n' \
                 '#O Bill Gosper Nov. 1970\n' \
                 'x = 36, y = 9, rule = B3/S23\n' \
                 '24bo$22bobo$12b2o6b2o12b2o$11bo3bo4b2o12b2o$2o8bo5bo3b2o$2o8bo3bob2o4b\n' \
                 'obo$10bo5bo7bo$11bo3bo$12b2o!\n'

    rle_parser = RunLengthEncodedParser(sample_rle)
    print(format(rle_parser))
if __name__ == '__main__':
    main()

Context

StackExchange Code Review Q#149068, answer score: 5

Revisions (0)

No revisions yet.