1

Which is the way is to achieve str.strip()-like behaviour with list, tuple and similar iterable objects in Python?

Examples:

str.strip()-like

>>> lst = ['\t', 0, 'a', ' ', 0, '\n', '\n', '', '\t']
>>> list_strip(lst)
... [0, 'a', ' ', 0]
>>> list_strip(lst, elements=(0, '\n', '', ' ', '\t'))
... ['a']

str.lstrip()-like

>>> lst = ['\t', 0, 'a', ' ', 0, '\n', '\n', '', '\t']
>>> list_lstrip(lst)
... [0, 'a', ' ', 0, '\n', '\n', '', '\t']
>>> list_lstrip(lst, elements=(0, '\n', '', ' ', '\t'))
... ['a', ' ', 0, '\n', '\n', '', '\t']

str.rstrip()-like

>>> lst = ['\t', 0, 'a', ' ', 0, '\n', '\n', '', '\t']
>>> list_rstrip(lst)
... ['\t', 0, 'a', ' ', 0]
>>> list_rstrip(lst, elements=(0, '\n', '', ' ', '\t'))
... ['\t', 0, 'a']

Prototypes of implementing functions are below

def set_force_iterable(val):
    if type(val) is str:
        return [val, ]
    else:
        try:
            iter(val)
            return val
        except TypeError:
            return [val, ]


def list_rstrip(lst, elements=None):
    '''Return a *copy* of the list or new tuple with trailing whitespace removed.

    Like :func:`str.rsrtip`.

    Parameters
    ----------
    lst : list or tuple
        List or tuple to be stripped.
    elements : iterable or None (default None)
        Elements to be stripped. Default None: strip all whitespaces.

    Returns
    -------
    list or tuple
        Return a *copy* of the list or new tuple with trailing whitespace removed.
        If elements is given and not None, remove values in elements instead.

    Examples
    --------
    >>> lst = ['\t', 0, 'a', ' ', 0, '\n', '\n', '', '\t']
    >>> list_rstrip(lst)
    ... ['\t', 0, 'a', ' ', 0]
    >>> list_rstrip(lst, elements=(0, '\n', '', ' ', '\t'))
    ... ['\t', 0, 'a']
    '''
    assert isinstance(lst, list) or isinstance(lst, tuple), '`lst` is not list or tuple'
    if elements is None:
        elements = ("", " ", "\t", "\n")
    else:
        elements = set_force_iterable(elements)

    if len(lst) == 0 or (len(lst) == 1 and lst[0] in elements):
        if isinstance(lst, list):
            return []
        else:
            return ()
    else:
        if lst[-1] not in elements:
            if isinstance(lst, list):
                return lst.copy()
            else:
                return lst
        prev_will_removed = True
        for i, el in enumerate(reversed(lst)):
            if not prev_will_removed or el not in elements:
                break
        return lst[:-i]


def list_lstrip(lst, elements=None):
    '''Return a *copy* of the list or new tuple with leading whitespace removed.

    Like :func:`str.lsrtip`.

    Parameters
    ----------
    lst : list or tuple
        List or tuple to be stripped.
    elements : iterable or None (default None)
        Elements to be stripped. Default None: strip all whitespaces.

    Returns
    -------
    list or tuple
        Return a *copy* of the list or new tuple with leading whitespace removed.
        If elements is given and not None, remove values in elements instead.

    Examples
    --------
    >>> lst = ['\t', 0, 'a', ' ', 0, '\n', '\n', '', '\t']
    >>> list_lstrip(lst)
    ... [0, 'a', ' ', 0, '\n', '\n', '', '\t']
    >>> list_lstrip(lst, elements=(0, '\n', '', ' ', '\t'))
    ... ['a', ' ', 0, '\n', '\n', '', '\t']
    '''
    assert isinstance(lst, list) or isinstance(lst, tuple), '`lst` is not list or tuple'

    if elements is None:
        elements = ("", " ", "\t", "\n")
    else:
        elements = set_force_iterable(elements)

    if len(lst) == 0 or (len(lst) == 1 and lst[0] in elements):
        if isinstance(lst, list):
            return []
        else:
            return ()
    else:
        if lst[0] not in elements:
            if isinstance(lst, list):
                return lst.copy()
            else:
                return lst
        prev_will_removed = True
        for i, el in enumerate(lst):
            if not prev_will_removed or el not in elements:
                break
        return lst[i:]


def list_strip(lst, elements=None):
    '''Return a **copy** of the list or new tuple with leading and trailing whitespace removed.

    Like :func:`str.srtip`.

    Parameters
    ----------
    lst : list or tuple
        List or tuple to be stripped.
    elements : iterable or None (default None)
        Elements to be stripped. Default None: strip all whitespaces.

    Returns
    -------
    list or tuple
        Return a **copy** of the list or new tuple with leading and trailing whitespace removed.
        If elements is given and not None, remove values in elements instead.

    Examples
    --------
    >>> lst = ['\t', 0, 'a', ' ', 0, '\n', '\n', '', '\t']
    >>> list_strip(lst)
    ... [0, 'a', ' ', 0]
    >>> list_strip(lst, elements=(0, '\n', '', ' ', '\t'))
    ... ['a']
    '''
    assert isinstance(lst, list) or isinstance(lst, tuple), '`lst` is not list or tuple'

    if elements is None:
        elements = ("", " ", "\t", "\n")
    else:
        elements = set_force_iterable(elements)

    if len(lst) == 0 or (len(lst) == 1 and lst[0] in elements):
        if isinstance(lst, list):
            return []
        else:
            return ()
    else:
        return list_lstrip(list_rstrip(lst, elements=elements), elements=elements)
dinya
  • 1,563
  • 1
  • 16
  • 30
  • 4
    *"Most pythonic"* is going to be opinion-based. Is there a *problem* you're looking to solve with your current implementation? – jonrsharpe Aug 21 '20 at 10:35
  • Pythonic is in the eye of the beholder. In Python 3.8, I'd just use `lst = [stripped for value in lst if value and (stripped := value.strip())]`, or use `stripped = (value.strip() for value in lst if value)`, `lst = [value in stripped if value]`. – Martijn Pieters Aug 21 '20 at 10:39
  • first use `join()` on ietrable then use `strip()` like this `','.join(map(str, lst)).lstrip().lstrip(',').split(',')` – deadshot Aug 21 '20 at 10:40
  • @deadshot: you missed the point, sorry. That won't give the expected output. – Martijn Pieters Aug 21 '20 at 10:40
  • Note that a list that mixes integers and strings is, in my view, not well designed. Try to avoid mixing scalar types to that extent. – Martijn Pieters Aug 21 '20 at 10:41
  • The description says "iterable objects", but the code shows and mentions just tuples and lists. This problem is significantly easer when it does not have to deal with arbitrary iterables. What exactly do you have to support? – MisterMiyagi Aug 21 '20 at 10:42
  • @jonrsharpe, The code is just a prototype and may look ugly, but it works as expected. I didn't find the "classic" way, so I asked this question. You can read "most pythonic" as "classic solution" :-). – dinya Aug 21 '20 at 10:45
  • This might be more appropriate for [CodeReview](https://codereview.stackexchange.com). – MisterMiyagi Aug 21 '20 at 10:46
  • 2
    I'm not sure there is a "classic way", this is a specific task and there are many different ways to approach it. I'd probably start with something like `l[next(index for index, value in enumerate(l) if value):]`, then start adding behaviour (e.g. handling input *without* any truth-y values, applying some transformation to the values, ...) from there. – jonrsharpe Aug 21 '20 at 10:47
  • @MartijnPieters I'm not sure your solutions work- they want to remove only the starting/trailing elements of the list that satisfy the condition. Anyway, if you remove the word 'pythonic' from this question, I feel it is a valid question for SO – Chris_Rands Aug 21 '20 at 10:47
  • 2
    I think, you can apply `itertools.dropwhile()`. – Olvin Roght Aug 21 '20 at 10:48
  • 1
    I was thinking `itertools.groupy()` – Chris_Rands Aug 21 '20 at 10:50
  • @Chris_Rands I edited the question following your advice. – dinya Aug 21 '20 at 11:01
  • Actually, question is pretty clear and well-formatted, I have no idea why it have been closed. Voted for reopen. – Olvin Roght Aug 21 '20 at 11:02
  • 2
    It's still not clear what the *actual* question is. The question already includes working solutions for the problem statement. If they do not work, the question should clarify in how far they do not work, including actual and desired output for a given input. If they do work, the question should clarify what *else* a solution must provide, to distinguish from the solution presented in the question. – MisterMiyagi Aug 21 '20 at 11:06
  • @MisterMiyagi If the OP completely reworded the question then it could possibly be on-topic on Code Review. dinya if you were to post this on Code Review as is then it could be closed for either; asking for code or, depending on how people take "Prototypes of", not being real code. If you want to post on Code Review follow their [How do I ask a good question?](https://codereview.stackexchange.com/help/how-to-ask) page and completely reform your question to "how can I improve my code" rather than "give me the most Pythonic code". – Peilonrayz Aug 21 '20 at 11:37
  • 2
    @Chris_Rands: I disagree. This is more a code review question than a well-scoped focused coding issue. – Martijn Pieters Aug 21 '20 at 11:39

1 Answers1

1

As a simple alternative you can use list slicing:

def _first_index(lst):
    return next(i for i, s in enumerate(lst) if not isinstance(s, str) or (s and not s.isspace()))

def list_strip(lst):
    return lst[_first_index(lst):-_first_index(reversed(lst))]

def list_lstrip(lst):
    return lst[_first_index(lst):]

def list_rstrip(lst):
    return lst[:-_first_index(reversed(lst))]

Usage:

lst = ['\t', 0, 'a', ' ', 0, '\n', '\n', '', '\t']
print(lst)
print(list_strip(lst))
print(list_lstrip(lst))
print(list_rstrip(lst))

Output:

['\t', 0, 'a', ' ', 0, '\n', '\n', '', '\t']
[0, 'a', ' ', 0]
[0, 'a', ' ', 0, '\n', '\n', '', '\t']
['\t', 0, 'a', ' ', 0]

If you want to set elements to filter manually, you need to change condition in _find_index() and add additional argument to all functions:

_fitered_chars = {"", " ", "\t", "\n"}
def _first_index(lst, elements=_fitered_chars):
    return next(i for i, s in enumerate(lst) if s not in elements)

def list_strip(lst, elements=_fitered_chars):
    return lst[_first_index(lst, elements):-_first_index(reversed(lst), elements)]

def list_lstrip(lst, elements=_fitered_chars):
    return lst[_first_index(lst, elements):]

def list_rstrip(lst, elements=_fitered_chars):
    return lst[:-_first_index(reversed(lst), elements)]

P.S. Code is pretty clear, but if you need any explanation, feel free to ask in comments.

Olvin Roght
  • 7,677
  • 2
  • 16
  • 35