كيفية استرداد عنصر من مجموعة دون إزالته؟

StackOverflow https://stackoverflow.com/questions/59825

  •  09-06-2019
  •  | 
  •  

سؤال

افترض ما يلي:

>>> s = set([1, 2, 3])

كيف يمكنني الحصول على قيمة (أي قيمة) منها s دون أن تفعل s.pop()؟أريد ترك العنصر في المجموعة حتى أتأكد من إمكانية إزالته - وهو أمر لا يمكنني التأكد منه إلا بعد إجراء مكالمة غير متزامنة مع مضيف آخر.

سريع و قذر:

>>> elem = s.pop()
>>> s.add(elem)

لكن هل تعرف طريقة أفضل؟من الناحية المثالية في وقت ثابت.

هل كانت مفيدة؟

المحلول

خياران لا يتطلبان نسخ المجموعة بأكملها:

for e in s:
    break
# e is now an element from s

أو...

e = next(iter(s))

لكن بشكل عام، لا تدعم المجموعات الفهرسة أو التقسيم.

نصائح أخرى

الكود الأقل سيكون:

>>> s = set([1, 2, 3])
>>> list(s)[0]
1

من الواضح أن هذا سيؤدي إلى إنشاء قائمة جديدة تحتوي على كل عضو في المجموعة، لذلك ليس رائعًا إذا كانت مجموعتك كبيرة جدًا.

لتوفير بعض أرقام التوقيت وراء الأساليب المختلفة، خذ بعين الاعتبار التعليمة البرمجية التالية.إن get() هي إضافتي المخصصة إلى setobject.c في Python، كونها مجرد pop() دون إزالة العنصر.

from timeit import *

stats = ["for i in xrange(1000): iter(s).next()   ",
         "for i in xrange(1000): \n\tfor x in s: \n\t\tbreak",
         "for i in xrange(1000): s.add(s.pop())   ",
         "for i in xrange(1000): s.get()          "]

for stat in stats:
    t = Timer(stat, setup="s=set(range(100))")
    try:
        print "Time for %s:\t %f"%(stat, t.timeit(number=1000))
    except:
        t.print_exc()

الإخراج هو:

$ ./test_get.py
Time for for i in xrange(1000): iter(s).next()   :       0.433080
Time for for i in xrange(1000):
        for x in s:
                break:   0.148695
Time for for i in xrange(1000): s.add(s.pop())   :       0.317418
Time for for i in xrange(1000): s.get()          :       0.146673

وهذا يعني أن للراحة الحل هو الأسرع (أحيانًا أسرع من الحل المخصص get()).

ليرة تركية ؛ د

for first_item in muh_set: break يبقى النهج الأمثل في Python 3.x. اللعنة عليك يا جويدو.

أنت تفعل هذا

مرحبًا بكم في مجموعة أخرى من توقيتات Python 3.x، المستقرة من WR.ممتاز استجابة خاصة بـ Python 2.x.على عكس بطلإنه مفيد بنفس القدر استجابة خاصة بـ Python 3.x, ، المواعيد أدناه أيضًا الحلول الخارجية المقترحة أعلاه - بما في ذلك:

مقتطفات من التعليمات البرمجية للفرح العظيم

قم بالتشغيل والضبط والوقت:

from timeit import Timer

stats = [
    "for i in range(1000): \n\tfor x in s: \n\t\tbreak",
    "for i in range(1000): next(iter(s))",
    "for i in range(1000): s.add(s.pop())",
    "for i in range(1000): list(s)[0]",
    "for i in range(1000): random.sample(s, 1)",
]

for stat in stats:
    t = Timer(stat, setup="import random\ns=set(range(100))")
    try:
        print("Time for %s:\t %f"%(stat, t.timeit(number=1000)))
    except:
        t.print_exc()

توقيتات عفا عليها الزمن بسرعة

هوذا! مرتبة حسب المقتطفات الأسرع إلى الأبطأ:

$ ./test_get.py
Time for for i in range(1000): 
    for x in s: 
        break:   0.249871
Time for for i in range(1000): next(iter(s)):    0.526266
Time for for i in range(1000): s.add(s.pop()):   0.658832
Time for for i in range(1000): list(s)[0]:   4.117106
Time for for i in range(1000): random.sample(s, 1):  21.851104

زراعة الوجه لجميع أفراد الأسرة

بشكل غير مفاجئ، يظل التكرار اليدوي أسرع مرتين على الأقل باعتباره الحل الأسرع التالي.على الرغم من أن الفجوة قد انخفضت مقارنة بأيام Bad Old Python 2.x (حيث كان التكرار اليدوي أسرع بأربعة أضعاف على الأقل)، إلا أنها كانت مخيبة للآمال بيب 20 متعصب في داخلي أن الحل الأكثر تفصيلاً هو الأفضل.على الأقل تحويل مجموعة إلى قائمة فقط لاستخراج العنصر الأول من المجموعة أمر فظيع كما هو متوقع. أشكر جويدو، نرجو أن يستمر نوره في إرشادنا.

والمثير للدهشة أن الحل القائم على RNG أمر فظيع للغاية. تحويل القائمة أمر سيء، ولكن random حقًا يأخذ كعكة الصلصة الفظيعة.الكثير من أجل رقم عشوائي ان شاء الله.

أتمنى فقط أن يقوم الأشخاص غير المتبلورين برفع مستوى PEP set.get_first() طريقة بالنسبة لنا بالفعل.إذا كنت تقرأ هذا، فهم:"لو سمحت.قم بعمل ما."

نظرًا لأنك تريد عنصرًا عشوائيًا، فسيعمل هذا أيضًا:

>>> import random
>>> s = set([1,2,3])
>>> random.sample(s, 1)
[2]

لا يبدو أن الوثائق تشير إلى أداء random.sample.من اختبار تجريبي سريع حقًا مع قائمة ضخمة ومجموعة ضخمة، يبدو أن الوقت ثابت للقائمة ولكن ليس للمجموعة.كما أن التكرار على مجموعة ليس عشوائيًا؛الترتيب غير محدد ولكن يمكن التنبؤ به:

>>> list(set(range(10))) == range(10)
True 

إذا كانت العشوائية مهمة وتحتاج إلى مجموعة من العناصر في وقت ثابت (مجموعات كبيرة)، سأستخدمها random.sample وقم بالتحويل إلى القائمة أولاً:

>>> lst = list(s) # once, O(len(s))?
...
>>> e = random.sample(lst, 1)[0] # constant time

تساءلت عن كيفية أداء الوظائف لمجموعات مختلفة، لذلك قمت بإجراء اختبار مرجعي:

from random import sample

def ForLoop(s):
    for e in s:
        break
    return e

def IterNext(s):
    return next(iter(s))

def ListIndex(s):
    return list(s)[0]

def PopAdd(s):
    e = s.pop()
    s.add(e)
    return e

def RandomSample(s):
    return sample(s, 1)

def SetUnpacking(s):
    e, *_ = s
    return e

from simple_benchmark import benchmark

b = benchmark([ForLoop, IterNext, ListIndex, PopAdd, RandomSample, SetUnpacking],
              {2**i: set(range(2**i)) for i in range(1, 20)},
              argument_name='set size',
              function_aliases={first: 'First'})

b.plot()

enter image description here

تظهر هذه المؤامرة بوضوح أن بعض الأساليب (RandomSample, SetUnpacking و ListIndex) يعتمد على حجم المجموعة ويجب تجنبه في الحالة العامة (على الأقل إذا كان الأداء قد كن مهما).كما هو موضح بالفعل من خلال الإجابات الأخرى فإن أسرع طريقة هي ForLoop.

ومع ذلك، طالما تم استخدام أحد أساليب الوقت الثابت، فسيكون فرق الأداء ضئيلًا.


iteration_utilities (تنصل:أنا المؤلف) يحتوي على وظيفة ملائمة لحالة الاستخدام هذه: first:

>>> from iteration_utilities import first
>>> first({1,2,3,4})
1

لقد أدرجته أيضًا في المعيار أعلاه.يمكنه التنافس مع الحلين "السريعين" الآخرين ولكن الفرق ليس كثيرًا في كلا الاتجاهين.

أستخدم وظيفة الأداة المساعدة التي كتبتها.اسمه مضلل إلى حد ما لأنه يشير إلى أنه قد يكون عنصرًا عشوائيًا أو شيء من هذا القبيل.

def anyitem(iterable):
    try:
        return iter(iterable).next()
    except StopIteration:
        return None

على ما يبدو الأكثر إحكاما (6 رموز) بالرغم من ذلك بطيء جدا طريقة للحصول على عنصر محدد (أصبح ممكنًا بواسطة بيب 3132):

e,*_=s

مع Python 3.5+، يمكنك أيضًا استخدام هذا التعبير المكون من 7 رموز (بفضل بيب 448):

[*s][0]

كلا الخيارين أبطأ بحوالي 1000 مرة على جهازي من طريقة for-loop.

متابعة @wr.بعد النشر، أحصل على نتائج مماثلة (لـ Python3.5)

from timeit import *

stats = ["for i in range(1000): next(iter(s))",
         "for i in range(1000): \n\tfor x in s: \n\t\tbreak",
         "for i in range(1000): s.add(s.pop())"]

for stat in stats:
    t = Timer(stat, setup="s=set(range(100000))")
    try:
        print("Time for %s:\t %f"%(stat, t.timeit(number=1000)))
    except:
        t.print_exc()

انتاج:

Time for for i in range(1000): next(iter(s)):    0.205888
Time for for i in range(1000): 
    for x in s: 
        break:                                   0.083397
Time for for i in range(1000): s.add(s.pop()):   0.226570

ومع ذلك، عند تغيير المجموعة الأساسية (على سبيل المثال.دعوة ل remove()) الأمور تسير بشكل سيء بالنسبة للأمثلة القابلة للتكرار (for, iter):

from timeit import *

stats = ["while s:\n\ta = next(iter(s))\n\ts.remove(a)",
         "while s:\n\tfor x in s: break\n\ts.remove(x)",
         "while s:\n\tx=s.pop()\n\ts.add(x)\n\ts.remove(x)"]

for stat in stats:
    t = Timer(stat, setup="s=set(range(100000))")
    try:
        print("Time for %s:\t %f"%(stat, t.timeit(number=1000)))
    except:
        t.print_exc()

النتائج في:

Time for while s:
    a = next(iter(s))
    s.remove(a):             2.938494
Time for while s:
    for x in s: break
    s.remove(x):             2.728367
Time for while s:
    x=s.pop()
    s.add(x)
    s.remove(x):             0.030272

ماذا عن s.copy().pop()؟لم أقم بتحديد توقيت لذلك، ولكن يجب أن يعمل وهو بسيط.إنه يعمل بشكل أفضل مع المجموعات الصغيرة، لأنه ينسخ المجموعة بأكملها.

هناك خيار آخر وهو استخدام قاموس يحتوي على قيم لا تهمك.على سبيل المثال،


poor_man_set = {}
poor_man_set[1] = None
poor_man_set[2] = None
poor_man_set[3] = None
...

يمكنك التعامل مع المفاتيح كمجموعة باستثناء أنها مجرد مصفوفة:


keys = poor_man_set.keys()
print "Some key = %s" % keys[0]

أحد الآثار الجانبية لهذا الاختيار هو أن الكود الخاص بك سيكون متوافقًا مع الإصدارات القديمة والسابقةset إصدارات بايثون.ربما لا تكون هذه هي الإجابة الأفضل ولكنها خيار آخر.

يحرر:يمكنك أيضًا القيام بشيء كهذا لإخفاء حقيقة أنك استخدمت إملاءًا بدلاً من مصفوفة أو مجموعة:


poor_man_set = {}
poor_man_set[1] = None
poor_man_set[2] = None
poor_man_set[3] = None
poor_man_set = poor_man_set.keys()
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top