Python a un dictionnaire ordonné . Et un ensemble commandé?
collections.Counterest le sac de Python.
Python a un dictionnaire ordonné . Et un ensemble commandé?
collections.Counterest 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 .unionne 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.
uniondans 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.OrderedDictet collections.MutableSetfaites 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__
OrderedSetqui sous OrderedDict- classe abc.Setet 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.OrderedDictla 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 dictest 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 dictcomme ensemble ordonné pour filtrer les éléments en double tout en préservant l'ordre, émulant ainsi un ensemble ordonné. Utilisez la dictmé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 OrderedDictc'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, setdans Python 3.7+, malheureusement, ne conserve pas l'ordre.
Je peux vous faire mieux qu'un OrderedSet: boltons a un IndexedSettype 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.pydans votre base de code), importez le IndexedSetet:
>>> 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.unionne 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.
SortedSetclasse exige que les membres soient comparables et lavables.
setet frozensetné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 Indexobjet 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 setlistdans le cadre de collections-extendedce que met en œuvre à la fois pleinement SequenceetSet
>>> 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 OrderedSeten 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, OrderedDictnous pouvons sous abc.collections.MutableSet- classer ou typing.MutableSetutiliser 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.