def run(self, head_rows, tail_rows=None, n_rows=None):
        """
        Run the intuition process
        :param head_rows: A list of rows from the start of the file. Should have at least 30 rows
        :param tail_rows: A list of rows from the end of the file. Optional, but should have at least 30 rows
        :param n_rows: Total number of rows, if a subset was provided in head_rows
        :return:
        """

        from .exceptions import RowIntuitError

        header_rows = []
        found_header = False
        MIN_SKIP_ROWS = 30

        try:
            data_pattern_skip_rows = min(MIN_SKIP_ROWS, len(head_rows) - 8)

        except TypeError:
            # Hopefully b/c head_rows is a generator, not a sequence
            raise RowIntuitError("Head_rows must be a sequence, not a generator or iterator")


        try:
            data_pattern, self.data_pattern_source, n_cols = self.data_pattern(head_rows[data_pattern_skip_rows:])
        except Exception as e:
            logger.debug("Failed to find data pattern")
            raise

        patterns = ([('D', data_pattern),
                     # More than 25% strings in row is header, if it isn't matched as data
                     ('H', re.compile(r'X{{{},{}}}'.format(max(3, n_cols/8),max(3,n_cols/4)))),
                     ] +
                    list(self.patterns))

        if self.debug:

            logger.debug("--- Patterns")
            for e in patterns:
                logger.debug("    {} {}".format(e[0], e[1].pattern))

        for i, row in enumerate(head_rows):

            picture = self.picture(row)

            label = self.match_picture(picture, patterns)

            try:
                # If a header or data has more than half of the line is a continuous nulls,
                # it's probably a comment.
                if label != 'B' and len(re.search('_+', picture).group(0)) > len(row)/2:
                    label = 'C'
            except AttributeError:
                pass  # re not matched

            if not found_header and label == 'H':
                found_header = True

            if label is False:

                if found_header:
                    label = 'D'
                else:
                    # Could be a really wacky header
                    found_header = True
                    label = 'H'

            if self.debug:
                logger.debug("HEAD: {:<5} {} {} {}".format(i, label, picture, row))

            if label == 'C':
                self.comment_lines.append(i)

            elif label == 'H':
                self.header_lines.append(i)
                header_rows.append(row)

            elif label == 'D':
                self.start_line = i
                self.headers = self.coalesce_headers(header_rows)
                break

        if tail_rows:
            from itertools import takewhile, islice

            for i, row in enumerate(islice(reversed(tail_rows), 0, 10)):
                picture = self.picture(row)
                label = self.match_picture(picture, patterns)
                logger.debug("TAIL: {:<5} {} {} {}".format(i, label, picture, row))

            # Compute the data label for the end line, then reverse them.
            labels = reversed(list(self.match_picture(self.picture(row), patterns) for row in tail_rows))

            # Count the number of lines, from the end, that are either comment or blank
            end_line = len(list(takewhile(lambda x: x == 'C' or x == 'B' or x == 'H', labels)))

            if end_line:
                self.end_line = n_rows-end_line-1

        return self