def __ngrams(s, n=3):
    """ Raw n-grams from a sequence

        If the sequence is a string, it will return char-level n-grams.
        If the sequence is a list of words, it will return word-level n-grams.

        Note: it treats space (' ') and punctuation like any other character.

            >>> ngrams('This is not a test!')
            [('T', 'h', 'i'), ('h', 'i', 's'), ('i', 's', ' '), ('s', ' ', 'i'),
            (' ', 'i', 's'), ('i', 's', ' '), ('s', ' ', 'n'), (' ', 'n', 'o'),
            ('n', 'o', 't'), ('o', 't', ' '), ('t', ' ', 'a'), (' ', 'a', ' '),
            ('a', ' ', 't'), (' ', 't', 'e'), ('t', 'e', 's'), ('e', 's', 't'),
            ('s', 't', '!')]
            >>> ngrams(["This", "is", "not", "a", "test!"])
            [('This', 'is', 'not'), ('is', 'not', 'a'), ('not', 'a', 'test!')]

        Args:
            s: a string or a list of strings
            n: an int for the n in n-gram

        Returns:
            list: tuples of char-level or word-level n-grams
    """
    return list(zip(*[s[i:] for i in range(n)]))
Raw n-grams from a sequence

        If the sequence is a string, it will return char-level n-grams.
        If the sequence is a list of words, it will return word-level n-grams.

        Note: it treats space (' ') and punctuation like any other character.

            >>> ngrams('This is not a test!')
            [('T', 'h', 'i'), ('h', 'i', 's'), ('i', 's', ' '), ('s', ' ', 'i'),
            (' ', 'i', 's'), ('i', 's', ' '), ('s', ' ', 'n'), (' ', 'n', 'o'),
            ('n', 'o', 't'), ('o', 't', ' '), ('t', ' ', 'a'), (' ', 'a', ' '),
            ('a', ' ', 't'), (' ', 't', 'e'), ('t', 'e', 's'), ('e', 's', 't'),
            ('s', 't', '!')]
            >>> ngrams(["This", "is", "not", "a", "test!"])
            [('This', 'is', 'not'), ('is', 'not', 'a'), ('not', 'a', 'test!')]

        Args:
            s: a string or a list of strings
            n: an int for the n in n-gram

        Returns:
            list: tuples of char-level or word-level n-grams