#!/usr/bin/env python
# cardinal_pythonlib/lists.py
"""
===============================================================================
Original code copyright (C) 2009-2022 Rudolf Cardinal (rudolf@pobox.com).
This file is part of cardinal_pythonlib.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
===============================================================================
**Functions for dealing with lists (and generators/iterables).**
"""
from collections import Counter
from operator import itemgetter
from typing import Any, Callable, Iterable, List, Sequence, Tuple
# =============================================================================
# Lists and similar
# =============================================================================
[docs]def contains_duplicates(values: Iterable[Any]) -> bool:
"""
Does the iterable contain any duplicate values?
"""
for v in Counter(values).values():
if v > 1:
return True
return False
[docs]def index_list_for_sort_order(
x: List[Any], key: Callable[[Any], Any] = None, reverse: bool = False
) -> List[int]:
"""
Returns a list of indexes of ``x``, IF ``x`` WERE TO BE SORTED.
Args:
x: data
key: function to be applied to the data to generate a sort key; this
function is passed as the ``key=`` parameter to :func:`sorted`;
the default is ``itemgetter(1)``
reverse: reverse the sort order?
Returns:
list of integer index values
Example:
.. code-block:: python
z = ["a", "c", "b"]
index_list_for_sort_order(z) # [0, 2, 1]
index_list_for_sort_order(z, reverse=True) # [1, 2, 0]
q = [("a", 9), ("b", 8), ("c", 7)]
index_list_for_sort_order(q, key=itemgetter(1))
"""
def key_with_user_func(idx_val: Tuple[int, Any]):
return key(idx_val[1])
if key:
sort_key = key_with_user_func
# see the simpler version below
else:
sort_key = itemgetter(1)
# enumerate, below, will return tuples of (index, value), so
# itemgetter(1) means sort by the value
index_value_list = sorted(enumerate(x), key=sort_key, reverse=reverse)
return [i for i, _ in index_value_list]
[docs]def sort_list_by_index_list(x: List[Any], indexes: List[int]) -> None:
"""
Re-orders ``x`` by the list of ``indexes`` of ``x``, in place.
Example:
.. code-block:: python
from cardinal_pythonlib.lists import sort_list_by_index_list
z = ["a", "b", "c", "d", "e"]
sort_list_by_index_list(z, [4, 0, 1, 2, 3])
z # ["e", "a", "b", "c", "d"]
"""
x[:] = [x[i] for i in indexes]
[docs]def flatten_list(x: List[Any]) -> List[Any]:
"""
Converts a list of lists into a flat list.
Args:
x: list of lists
Returns:
flat list
As per
https://stackoverflow.com/questions/952914/making-a-flat-list-out-of-list-of-lists-in-python
"""
return [item for sublist in x for item in sublist]
[docs]def unique_list(seq: Iterable[Any]) -> List[Any]:
"""
Returns a list of all the unique elements in the input list.
Args:
seq: input list
Returns:
list of unique elements
As per
https://stackoverflow.com/questions/480214/how-do-you-remove-duplicates-from-a-list-in-whilst-preserving-order
"""
seen = set()
seen_add = seen.add
return [x for x in seq if not (x in seen or seen_add(x))]
[docs]def filter_unique(seq: Iterable[Any]) -> Iterable[Any]:
"""
Filters the input sequence, yielding only unique values.
"""
seen = set()
seen_add = seen.add # fractionally faster to look it up once
for x in seq:
if x not in seen:
seen_add(x)
yield x
[docs]def chunks(x: List[Any], n: int) -> Iterable[List[Any]]:
"""
Yield successive ``n``-sized chunks from the list ``x``.
Args:
x: input list
n: chunk size
Yields:
successive chunks of size ``n``
"""
for i in range(0, len(x), n):
yield x[i : i + n]
[docs]def count_bool(blist: Iterable[Any]) -> int:
"""
Counts the number of "truthy" members of the input list.
Args:
blist: list of booleans or other truthy/falsy things
Returns:
number of truthy items
"""
return sum([1 if x else 0 for x in blist])
[docs]def delete_elements_by_index(x: List[Any], indices: Sequence[int]) -> None:
"""
Deletes (in place) objects from a list, by (zero-based) index values.
Args:
x:
list to modify
indices:
zero-based index values at which to delete elements of x
Note:
- If you pass duplicate values, unexpected results will occur.
- If you pass out-of-bound indices, :exc:`IndexError` will be raised.
After:
- https://thispointer.com/python-remove-elements-from-list-by-index/
- https://stackoverflow.com/questions/11520492/difference-between-del-remove-and-pop-on-lists
- https://stackoverflow.com/questions/627435/how-to-remove-an-element-from-a-list-by-index
""" # noqa: E501
for i in sorted(indices, reverse=True): # work from high to low
del x[i]