リストをセットに変換すると要素の順序が変わる


119

最近、a listset要素の順序に変換すると、文字で並べ替えられ、変更されることに気付きました。

この例を考えてみましょう:

x=[1,2,20,6,210]
print x 
# [1, 2, 20, 6, 210] # the order is same as initial order

set(x)
# set([1, 2, 20, 210, 6]) # in the set(x) output order is sorted

私の質問は-

  1. なぜこうなった?
  2. 最初の順序を失うことなく、どのようにしてセット演算(特にSet Difference)を実行できますか?

8
特に集合演算をしている場合は特に、最初の順序を失いたくないのですか?「順序」は、Pythonだけでなく数学においても、セットにとって意味のない概念です。
Karl Knechtel 2012年

131
@KarlKnechtel-はい、「順序はセットの無意味な概念です...数学では」ですが、実際の問題があります:)
d.putto

CPython 3.6以降unique = list(dict.fromkeys([1, 2, 1]).keys())。これは、dictsが挿入順序を保持するため機能します。
ボリス

回答:


105
  1. A setは順序付けされていないデータ構造であるため、挿入順序は保持されません。

  2. これは要件によって異なります。通常のリストがあり、リストの順序を維持しながら要素のセットを削除したい場合は、リスト内包を使用してこれを行うことができます。

    >>> a = [1, 2, 20, 6, 210]
    >>> b = set([6, 20, 1])
    >>> [x for x in a if x not in b]
    [2, 210]

    高速なメンバーシップテスト挿入順序の保持の両方をサポートするデータ構造が必要な場合は、Python 3.7から挿入順序の保持が保証されているPython辞書のキーを使用できます。

    >>> a = dict.fromkeys([1, 2, 20, 6, 210])
    >>> b = dict.fromkeys([6, 20, 1])
    >>> dict.fromkeys(x for x in a if x not in b)
    {2: None, 210: None}

    b実際にここで注文する必要はありません–を使用することもできますseta.keys() - b.keys()はセットの違いをとして返すsetため、挿入順序は保持されないことに注意してください。

    古いバージョンのPythonでは、collections.OrderedDict代わりに次を使用できます。

    >>> a = collections.OrderedDict.fromkeys([1, 2, 20, 6, 210])
    >>> b = collections.OrderedDict.fromkeys([6, 20, 1])
    >>> collections.OrderedDict.fromkeys(x for x in a if x not in b)
    OrderedDict([(2, None), (210, None)])

3
どのオブジェクトも16バイトのコストはかかりません。デフォルトのOrderedSet()がある場合のみ。:(
Sean

2
@ショーンいいえ、彼らはしません。None言語保証シングルトンです。CPythonでは、実際のコストは単なるポインタです(ただし、そのコストは常にそこにありますが、dictの場合、ほとんどの場合None、他のシングルトンまたは共有参照は「無料」と見なすことができます)。 。しかし、ええ、それはセットがそうであることができるほどスペース効率が良くありません。
juanpa.arrivillaga

2
CPython 3.6以降ではdict.fromkeys([1, 2, 1]).keys()、通常dictのsも順序を保持するため、これを行うことができます。
ボリス

@Borisこれは、Python 3.7以降の言語仕様の一部にすぎません。CPython実装はバージョン3.6で挿入順序を既に保持していますが、これは他のPython実装が従わない実装の詳細見なされます。
Sven Marnach

@Sven私はCPythonと言いました。私はこれをどこにでも投稿していますが、「CPython 3.6またはPython 3.7で始まるその他の実装」を書くのに飽きてきました。それは問題ではありません、誰もがCPythonを使用しています
ボリス

52

Pythonの3.6で、set()すべき秩序を保つが、 Pythonの2と3のための別の解決策があります:

>>> x = [1, 2, 20, 6, 210]
>>> sorted(set(x), key=x.index)
[1, 2, 20, 6, 210]

8
順序の保存に関する2つの注意事項:Python 3.6以降で、それでも実装の詳細と見なされているため、これに依存しないでください。それとは別に、x.index呼び出されるたびに線形検索が実行されるため、コードは非常に非効率的です。2次の複雑さで問題がなければ、そもそもaを使用する理由はありませんset
Thijs van Dien、

27
@ThijsvanDienこれは間違っています。Python3.6でset()注文されておらず、実装の詳細としても注文されていません。あなたはdicts について考えています
Chris_Rands

8
時々そう見えるもののので@ThijsvanDienありません彼らは、並べ替えられていないint自分自身への、多くの場合、ハッシュはstackoverflow.com/questions/45581901/...
Chris_Rands

3
x=[1,2,-1,20,6,210]それをセットにしてみてください。Python 3.6でテストされているため、注文されていないことがわかります。
GabrielChu 2018

3
なぜこの回答に多くの賛成票があるのか​​理解できません。挿入順序が維持されず、セットも返されません。
Igor Rodriguez

20

最初の質問に答えると、セットはセット操作用に最適化されたデータ構造です。数学セットのように、要素の特定の順序を強制または維持しません。セットの抽象的な概念は順序を強制しないため、実装は必須ではありません。リストからセットを作成すると、Pythonは、セットに使用する内部実装のニーズに合わせて要素の順序を変更することができ、セットの操作を効率的に実行できます。



8

数学には、セット順序付きセット(オセット)があります。

  • set:一意の要素の順序付けされていないコンテナ(実装)
  • oset:順序付けされた一意の要素のコンテナ(NotImplemented)

Pythonでは、セットのみが直接実装されます。通常のdictキーでosetsをエミュレートできます(3.7+)で。

与えられた

a = [1, 2, 20, 6, 210, 2, 1]
b = {2, 6}

コード

oset = dict.fromkeys(a).keys()
# dict_keys([1, 2, 20, 6, 210])

デモ

複製は削除され、挿入順序は保持されます。

list(oset)
# [1, 2, 20, 6, 210]

dictキーに対するセットのような操作。

oset - b
# {1, 20, 210}

oset | b
# {1, 2, 5, 6, 20, 210}

oset & b
# {2, 6}

oset ^ b
# {1, 5, 20, 210}

細部

注:順序付けられていない構造は、順序付けられた要素を排除しません。むしろ、維持された順序は保証されません。例:

assert {1, 2, 3} == {2, 3, 1}                    # sets (order is ignored)

assert [1, 2, 3] != [2, 3, 1]                    # lists (order is guaranteed)

リストマルチセット(mset)がさらに魅力的な数学的データ構造であることを発見して喜ばれるかもしれません。

  • リスト:複製を許可する要素の順序付きコンテナ(実装)
  • mset:複製を許可する要素の順序付けされていないコンテナー(NotImplemented)*

概要

Container | Ordered | Unique | Implemented
----------|---------|--------|------------
set       |    n    |    y   |     y
oset      |    y    |    y   |     n
list      |    y    |    n   |     y
mset      |    n    |    n   |     n*  

* multisetはcollections.Counter()multiplicities(counts)のdictのようなマッピングで間接的にエミュレートできます。


4

他の回答で示されているように、セットは要素の順序を保持しないデータ構造(および数学的概念)です-

ただし、セットと辞書の組み合わせを使用することで、希望するwevereverを実現できる可能性があります。以下のスニペットを使用してみてください。

# save the element order in a dict:
x_dict = dict(x,y for y, x in enumerate(my_list) )
x_set = set(my_list)
#perform desired set operations
...
#retrieve ordered list from the set:
new_list = [None] * len(new_set)
for element in new_set:
   new_list[x_dict[element]] = element

1

Svenの答えに基づいて、collections.OrderedDictを使用しているのを見つけたので、あなたが望むものを達成するのに役立ち、さらにdictに項目を追加できるようにしました。

import collections

x=[1,2,20,6,210]
z=collections.OrderedDict.fromkeys(x)
z
OrderedDict([(1, None), (2, None), (20, None), (6, None), (210, None)])

アイテムを追加したいが、それをセットのように扱う場合は、次のようにします。

z['nextitem']=None

そして、あなたはdictでz.keys()のような操作を実行し、セットを取得することができます:

z.keys()
[1, 2, 20, 6, 210]

list(z.keys())リストの出力を取得する必要があります。
jxn

Python 3では、はい。Python 2ではありませんが、指定する必要がありました。
jimh

0

リストに戻す、上記の最高スコアの概念の実装:

def SetOfListInOrder(incominglist):
    from collections import OrderedDict
    outtemp = OrderedDict()
    for item in incominglist:
        outtemp[item] = None
    return(list(outtemp))

Python 3.6およびPython 2.7で(簡単に)テストされています。


0

2つの初期リストに少数の要素があり、その上で差分演算を実行したい場合collections.OrderedDict、実装を複雑にして読みにくくする代わりに、次のように使用できます。

# initial lists on which you want to do set difference
>>> nums = [1,2,2,3,3,4,4,5]
>>> evens = [2,4,4,6]
>>> evens_set = set(evens)
>>> result = []
>>> for n in nums:
...   if not n in evens_set and not n in result:
...     result.append(n)
... 
>>> result
[1, 3, 5]

その時間の複雑さはそれほど良くありませんが、きちんとしていて読みやすいです。


0

人々が常に「現実世界の問題」を使って理論科学の定義を冗談にするのは興味深いことです。

セットに順序がある場合、最初に次の問題を理解する必要があります。リストに重複する要素がある場合、リストをセットにすると、順序はどうなりますか?2つのセットを結合すると、順序はどうなりますか?同じ要素で異なる順序の2つのセットを交差させる場合の順序は何ですか?

さらに、setは特定のキーの検索がはるかに高速で、sets操作に非常に適しています(そのため、リストは必要ありませんが、リストは必要ありません)。

インデックスが本当に重要な場合は、リストとして保持してください。それでも多くのリストの要素に対してset操作を実行する場合、最も簡単な方法は、元のリストのキーのすべてのインデックスを含むリストの値とともに、セット内の同じキーを持つリストごとに辞書を作成することです。

def indx_dic(l):
    dic = {}
    for i in range(len(l)):
        if l[i] in dic:
            dic.get(l[i]).append(i)
        else:
            dic[l[i]] = [i]
    return(dic)

a = [1,2,3,4,5,1,3,2]
set_a  = set(a)
dic_a = indx_dic(a)

print(dic_a)
# {1: [0, 5], 2: [1, 7], 3: [2, 6], 4: [3], 5: [4]}
print(set_a)
# {1, 2, 3, 4, 5}

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.