Python a un dictionnaire ordonné . Et un ensemble commandé?
collections.Counter
est le sac de Python.
Python a un dictionnaire ordonné . Et un ensemble commandé?
collections.Counter
est le sac de Python.
Réponses:
Il existe une recette d' ensemble ordonné ( nouveau lien possible ) pour cela, à laquelle se réfère la documentation Python 2 . Cela fonctionne sur Py2.6 ou version ultérieure et 3.0 ou version ultérieure sans aucune modification. L'interface est presque exactement la même qu'un ensemble normal, sauf que l'initialisation doit être effectuée avec une liste.
OrderedSet([1, 2, 3])
Il s'agit d'un MutableSet, donc la signature de .union
ne correspond pas à celle de set, mais comme il inclut __or__
quelque chose de similaire, il peut facilement être ajouté:
@staticmethod
def union(*sets):
union = OrderedSet()
union.union(*sets)
return union
def union(self, *sets):
for set in sets:
self |= set
update
, union
, intersection
.
union
dans la même classe. Le dernier "gagnera" et le premier n'existera pas lors de l'exécution. C'est parce que OrderedSet.union
(pas de parens) doit se référer à un seul objet.
Les clés d'un dictionnaire sont uniques. Ainsi, si l'on ne tient pas compte des valeurs dans un dictionnaire ordonné (par exemple en les affectant None
), alors on a essentiellement un ensemble ordonné.
Depuis Python 3.1 il y en a collections.OrderedDict
. Voici un exemple d'implémentation d'un OrderedSet. (Notez que seules quelques méthodes doivent être définies ou remplacées: collections.OrderedDict
et collections.MutableSet
faites le gros du travail.)
import collections
class OrderedSet(collections.OrderedDict, collections.MutableSet):
def update(self, *args, **kwargs):
if kwargs:
raise TypeError("update() takes no keyword arguments")
for s in args:
for e in s:
self.add(e)
def add(self, elem):
self[elem] = None
def discard(self, elem):
self.pop(elem, None)
def __le__(self, other):
return all(e in other for e in self)
def __lt__(self, other):
return self <= other and self != other
def __ge__(self, other):
return all(e in self for e in other)
def __gt__(self, other):
return self >= other and self != other
def __repr__(self):
return 'OrderedSet([%s])' % (', '.join(map(repr, self.keys())))
def __str__(self):
return '{%s}' % (', '.join(map(repr, self.keys())))
difference = __sub__
difference_update = __isub__
intersection = __and__
intersection_update = __iand__
issubset = __le__
issuperset = __ge__
symmetric_difference = __xor__
symmetric_difference_update = __ixor__
union = __or__
OrderedSet
qui sous OrderedDict
- classe abc.Set
et puis définir __len__
, __iter__
et __contains__
.
collections
, mais sinon une bonne suggestion
OrderedSet([1,2,3])
déclenche une TypeError. Comment fonctionne même le constructeur? Exemple d'utilisation manquant.
La réponse est non, mais vous pouvez utiliser collections.OrderedDict
la bibliothèque standard Python avec juste des clés (et des valeurs comme None
) dans le même but.
Mise à jour : à partir de Python 3.7 (et CPython 3.6), le standard dict
est garanti pour préserver l'ordre et est plus performant que OrderedDict
. (Pour des raisons de compatibilité descendante et surtout de lisibilité, cependant, vous pouvez continuer à utiliser OrderedDict
.)
Voici un exemple d'utilisation à utiliser dict
comme ensemble ordonné pour filtrer les éléments en double tout en préservant l'ordre, émulant ainsi un ensemble ordonné. Utilisez la dict
méthode de classe fromkeys()
pour créer un dict, puis demandez simplement le keys()
verso.
>>> keywords = ['foo', 'bar', 'bar', 'foo', 'baz', 'foo']
>>> list(dict.fromkeys(keywords))
['foo', 'bar', 'baz']
dict.fromkeys()
. Mais dans ce cas, l'ordre des clés n'est conservé que dans les implémentations de CPython 3.6+, donc OrderedDict
c'est une solution plus portable lorsque l'ordre est important.
keys = (1,2,3,1,2,1)
list(OrderedDict.fromkeys(keys).keys())
-> [1, 2, 3]
, python-3.7. Ça marche.
dict
, set
dans Python 3.7+, malheureusement, ne conserve pas l'ordre.
Je peux vous faire mieux qu'un OrderedSet: boltons a un IndexedSet
type compatible Python pur, 2/3 qui n'est pas seulement un ensemble ordonné, mais prend également en charge l'indexation (comme avec les listes).
Simplement pip install boltons
(ou copiez setutils.py
dans votre base de code), importez le IndexedSet
et:
>>> from boltons.setutils import IndexedSet
>>> x = IndexedSet(list(range(4)) + list(range(8)))
>>> x
IndexedSet([0, 1, 2, 3, 4, 5, 6, 7])
>>> x - set(range(2))
IndexedSet([2, 3, 4, 5, 6, 7])
>>> x[-1]
7
>>> fcr = IndexedSet('freecreditreport.com')
>>> ''.join(fcr[:fcr.index('.')])
'frecditpo'
Tout est unique et conservé dans l'ordre. Divulgation complète: j'ai écrit le IndexedSet
, mais cela signifie également que vous pouvez me bogue s'il y a des problèmes . :)
Alors que d'autres ont souligné qu'il n'y a pas encore d'implémentation intégrée d'un ensemble de préservation de l'ordre d'insertion en Python, j'ai le sentiment qu'il manque une réponse à cette question qui indique ce qu'il y a à trouver sur PyPI .
Il y a les packages:
Certaines de ces implémentations sont basées sur la recette publiée par Raymond Hettinger sur ActiveState, qui est également mentionnée dans d'autres réponses ici.
my_set[5]
)remove(item)
Les deux implémentations ont O (1) pour add(item)
et __contains__(item)
( item in my_set
).
set.union
ne fonctionnent pas dessus, même si elles héritent collections.abc.Set
.
Si vous utilisez l'ensemble ordonné pour maintenir un ordre trié, envisagez d'utiliser une implémentation d'ensemble trié de PyPI. Le module sortedcontainers fournit un SortedSet à cet effet. Quelques avantages: pure Python, implémentations rapides comme C, couverture de tests unitaires à 100%, heures de tests de stress.
L'installation à partir de PyPI est facile avec pip:
pip install sortedcontainers
Notez que si vous ne le pouvez pas pip install
, déroulez simplement les fichiers sortedlist.py et sortedset.py du référentiel open-source .
Une fois installé, vous pouvez simplement:
from sortedcontainers import SortedSet
help(SortedSet)
Le module sortedcontainers maintient également une comparaison des performances avec plusieurs implémentations alternatives.
Pour le commentaire qui a posé une question sur le type de données de sac de Python, il existe également un type de données SortedList qui peut être utilisé pour implémenter efficacement un sac.
SortedSet
classe exige que les membres soient comparables et lavables.
set
et frozenset
nécessitent également que les éléments soient lavables. La contrainte comparable est l'addition pour SortedSet
, mais c'est aussi une contrainte évidente.
Si vous utilisez déjà des pandas dans votre code, son Index
objet se comporte à peu près comme un ensemble ordonné, comme indiqué dans cet article .
Exemples tirés de l'article:
indA = pd.Index([1, 3, 5, 7, 9])
indB = pd.Index([2, 3, 5, 7, 11])
indA & indB # intersection
indA | indB # union
indA - indB # difference
indA ^ indB # symmetric difference
indA.difference(indB)
, le signe moins effectue une soustraction standard
Un peu tard pour le jeu, mais je l' ai écrit une classe setlist
dans le cadre de collections-extended
ce que met en œuvre à la fois pleinement Sequence
etSet
>>> from collections_extended import setlist
>>> sl = setlist('abracadabra')
>>> sl
setlist(('a', 'b', 'r', 'c', 'd'))
>>> sl[3]
'c'
>>> sl[-1]
'd'
>>> 'r' in sl # testing for inclusion is fast
True
>>> sl.index('d') # so is finding the index of an element
4
>>> sl.insert(1, 'd') # inserting an element already in raises a ValueError
ValueError
>>> sl.index('d')
4
GitHub: https://github.com/mlenzen/collections-extended
Documentation: http://collections-extended.lenzm.net/en/latest/
Il n'y OrderedSet
en a pas dans la bibliothèque officielle. Je fais une feuille de triche exhaustive de toute la structure de données pour votre référence.
DataStructure = {
'Collections': {
'Map': [
('dict', 'OrderDict', 'defaultdict'),
('chainmap', 'types.MappingProxyType')
],
'Set': [('set', 'frozenset'), {'multiset': 'collection.Counter'}]
},
'Sequence': {
'Basic': ['list', 'tuple', 'iterator']
},
'Algorithm': {
'Priority': ['heapq', 'queue.PriorityQueue'],
'Queue': ['queue.Queue', 'multiprocessing.Queue'],
'Stack': ['collection.deque', 'queue.LifeQueue']
},
'text_sequence': ['str', 'byte', 'bytearray']
}
Le package ParallelRegression fournit une classe d'ensemble ordonnée setList () qui est plus complète en termes de méthode que les options basées sur la recette ActiveState. Il prend en charge toutes les méthodes disponibles pour les listes et la plupart sinon toutes les méthodes disponibles pour les ensembles.
Comme d'autres réponses le mentionnent, comme pour python 3.7+, le dict est ordonné par définition. Au lieu de sous-classer, OrderedDict
nous pouvons sous abc.collections.MutableSet
- classer ou typing.MutableSet
utiliser les clés du dict pour stocker nos valeurs.
class OrderedSet(typing.MutableSet[T]):
"""A set that preserves insertion order by internally using a dict."""
def __init__(self, iterable: t.Iterator[T]):
self._d = dict.fromkeys(iterable)
def add(self, x: T) -> None:
self._d[x] = None
def discard(self, x: T) -> None:
self._d.pop(x)
def __contains__(self, x: object) -> bool:
return self._d.__contains__(x)
def __len__(self) -> int:
return self._d.__len__()
def __iter__(self) -> t.Iterator[T]:
return self._d.__iter__()
Alors juste:
x = OrderedSet([1, 2, -1, "bar"])
x.add(0)
assert list(x) == [1, 2, -1, "bar", 0]
J'ai mis ce code dans une petite bibliothèque , donc tout le monde peut le faire pip install
.
À de nombreuses fins, un simple appel trié suffit. Par exemple
>>> s = set([0, 1, 2, 99, 4, 40, 3, 20, 24, 100, 60])
>>> sorted(s)
[0, 1, 2, 3, 4, 20, 24, 40, 60, 99, 100]
Si vous comptez utiliser ceci à plusieurs reprises, il y aura des frais généraux encourus en appelant la fonction triée afin que vous souhaitiez peut-être enregistrer la liste résultante, tant que vous avez terminé de modifier l'ensemble. Si vous devez conserver des éléments uniques et triés, je suis d'accord avec la suggestion d'utiliser OrderedDict à partir de collections avec une valeur arbitraire telle que None.
J'ai donc également eu une petite liste où j'avais clairement la possibilité d'introduire des valeurs non uniques.
J'ai cherché l'existence d'une liste unique, mais j'ai réalisé que tester l'existence de l'élément avant de l'ajouter fonctionne très bien.
if(not new_element in my_list):
my_list.append(new_element)
Je ne sais pas s'il y a des mises en garde à cette approche simple, mais cela résout mon problème.