ما هي الطريقة الأكثر أمانًا للتكرار من خلال مفاتيح تجزئة Perl؟

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

  •  08-06-2019
  •  | 
  •  

سؤال

إذا كان لدي تجزئة Perl مع مجموعة من أزواج (المفتاح، القيمة)، ما هي الطريقة المفضلة للتكرار عبر جميع المفاتيح؟لقد سمعت أن استخدام each قد يكون لها بطريقة ما آثار جانبية غير مقصودة.فهل هذا صحيح، وهل إحدى الطريقتين التاليتين هي الأفضل أم أن هناك طريقة أفضل؟

# Method 1
while (my ($key, $value) = each(%hash)) {
    # Something
}

# Method 2
foreach my $key (keys(%hash)) {
    # Something
}
هل كانت مفيدة؟

المحلول

القاعدة الأساسية هي استخدام الوظيفة الأكثر ملاءمة لاحتياجاتك.

إذا كنت تريد المفاتيح فقط ولا تخطط لذلك أبدًا يقرأ أي من القيم، استخدم المفاتيح ():

foreach my $key (keys %hash) { ... }

إذا كنت تريد القيم فقط، فاستخدم القيم ():

foreach my $val (values %hash) { ... }

إذا كنت بحاجة إلى المفاتيح و القيم، استخدم كل ():

keys %hash; # reset the internal iterator so a prior each() doesn't affect the loop
while(my($k, $v) = each %hash) { ... }

إذا كنت تخطط لتغيير مفاتيح التجزئة بأي شكل من الأشكال يستثني لحذف المفتاح الحالي أثناء التكرار، يجب ألا تستخدم كل ().على سبيل المثال، هذا الرمز لإنشاء مجموعة جديدة من المفاتيح الكبيرة ذات القيم المضاعفة يعمل بشكل جيد باستخدام المفاتيح ():

%h = (a => 1, b => 2);

foreach my $k (keys %h)
{
  $h{uc $k} = $h{$k} * 2;
}

إنتاج التجزئة الناتجة المتوقعة:

(a => 1, A => 2, b => 2, B => 4)

ولكن باستخدام every() للقيام بنفس الشيء:

%h = (a => 1, b => 2);

keys %h;
while(my($k, $v) = each %h)
{
  $h{uc $k} = $h{$k} * 2; # BAD IDEA!
}

يؤدي إلى نتائج غير صحيحة بطرق يصعب التنبؤ بها.على سبيل المثال:

(a => 1, A => 2, b => 2, B => 8)

لكن هذا آمن:

keys %h;
while(my($k, $v) = each %h)
{
  if(...)
  {
    delete $h{$k}; # This is safe
  }
}

كل هذا موصوف في وثائق بيرل:

% perldoc -f keys
% perldoc -f each

نصائح أخرى

شيء واحد يجب أن تكون على دراية به عند الاستخدام each هو أن لها تأثير جانبي لإضافة "حالة" إلى تجزئة (تجزئة يجب أن يتذكر ما هو المفتاح "التالي").عند استخدام رمز مثل المقتطفات المنشورة أعلاه ، والتي تكرر على التجزئة بأكملها في واحدة ، فإن هذه عادة لا تكون مشكلة.ومع ذلك ، سوف تصادفك إلى تعقب المشكلات (أتحدث من التجربة ؛) ، عند استخدام each جنبا إلى جنب مع تصريحات مثلlast أو return للخروج من while ... each حلقة قبل معالجة جميع المفاتيح.

في هذه الحالة ، سوف يتذكر التجزئة المفاتيح التي عادت بالفعل ، وعندما تستخدم each عليها في المرة القادمة (ربما في قطعة رمز غير ذات صلة تمامًا) ، ستستمر في هذا الموقف.

مثال:

my %hash = ( foo => 1, bar => 2, baz => 3, quux => 4 );

# find key 'baz'
while ( my ($k, $v) = each %hash ) {
    print "found key $k\n";
    last if $k eq 'baz'; # found it!
}

# later ...

print "the hash contains:\n";

# iterate over all keys:
while ( my ($k, $v) = each %hash ) {
    print "$k => $v\n";
}

هذا يطبع:

found key bar
found key baz
the hash contains:
quux => 4
foo => 1

ماذا حدث للمفاتيح "bar" وbaz"؟ما زالوا هناك ، لكن الثانية each يبدأ من حيث توقفت الأولى، ويتوقف عندما يصل إلى نهاية التجزئة، لذلك لا نراها أبدًا في الحلقة الثانية.

المكان each يمكن أن يسبب لك مشاكل هو أنه مكرر حقيقي وغير محدد النطاق.على سبيل المثال:

while ( my ($key,$val) = each %a_hash ) {
    print "$key => $val\n";
    last if $val; #exits loop when $val is true
}

# but "each" hasn't reset!!
while ( my ($key,$val) = each %a_hash ) {
    # continues where the last loop left off
    print "$key => $val\n";
}

إذا كنت بحاجة للتأكد من ذلك each يحصل على جميع المفاتيح والقيم، التي تحتاج إلى التأكد من استخدامها keys أو values أولاً (حيث يؤدي ذلك إلى إعادة تعيين المكرر).انظر وثائق لكل.

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

لذا، فإن المكان الوحيد الذي يكون فيه "كل" مفيدًا حقًا هو عندما تكون التجزئة كبيرة جدًا (مقارنة بالذاكرة المتوفرة).من المحتمل أن يحدث هذا فقط عندما لا تعيش التجزئة نفسها في الذاكرة نفسها إلا إذا كنت تقوم ببرمجة جهاز جمع بيانات محمول أو شيء ذو ذاكرة صغيرة.

إذا لم تكن الذاكرة مشكلة، فعادةً ما يكون نموذج الخريطة أو المفاتيح هو النموذج الأكثر انتشارًا والأسهل في القراءة.

بعض الأفكار المتنوعة حول هذا الموضوع:

  1. لا يوجد شيء غير آمن بشأن أي من تكرارات التجزئة نفسها.ما هو غير آمن هو تعديل مفاتيح التجزئة أثناء تكرارها.(إن تعديل القيم آمن تمامًا.) التأثير الجانبي الوحيد المحتمل الذي يمكنني التفكير فيه هو ذلك values إرجاع الأسماء المستعارة مما يعني أن تعديلها سيؤدي إلى تعديل محتويات التجزئة.هذا حسب التصميم ولكنه قد لا يكون ما تريده في بعض الظروف.
  2. جون إجابة مقبولة جيد مع استثناء واحد:توضح الوثائق أنه ليس من الآمن إضافة مفاتيح أثناء التكرار عبر التجزئة.قد يعمل مع بعض مجموعات البيانات ولكنه سيفشل مع مجموعات أخرى اعتمادًا على ترتيب التجزئة.
  3. كما ذكرنا من قبل، من الآمن حذف المفتاح الأخير الذي تم إرجاعه each.هذا هو لا صحيح ل keys مثل each هو مكرر بينما keys إرجاع قائمة.

أنا دائمًا أستخدم الطريقة الثانية أيضًا.الفائدة الوحيدة من استخدام كل منها هي أنك إذا كنت تقرأ فقط (بدلاً من إعادة تعيين) قيمة إدخال التجزئة، فإنك لا تقوم بإلغاء الإشارة إلى التجزئة باستمرار.

قد أتعرض للعض من هذا ولكن أعتقد أنه تفضيل شخصي.لا يمكنني العثور على أي مرجع في المستندات لكل () مختلف عن المفاتيح () أو القيم () (بخلاف الإجابة الواضحة "إنهم يعيدون أشياء مختلفة".في الواقع، تنص المستندات على استخدام نفس المُكرِّر وأنهم جميعًا يُرجعون قيم القائمة الفعلية بدلاً من نسخ منها، وأن تعديل التجزئة أثناء التكرار عليها باستخدام أي استدعاء أمر سيء.

بعد كل ما قيل، أستخدم المفاتيح () دائمًا تقريبًا لأنه بالنسبة لي عادةً ما يكون التوثيق الذاتي أكثر للوصول إلى قيمة المفتاح عبر التجزئة نفسها.أحيانًا أستخدم value() عندما تكون القيمة مرجعًا لبنية كبيرة وكان مفتاح التجزئة مخزنًا بالفعل في البنية، وعند هذه النقطة يكون المفتاح زائدًا عن الحاجة ولا أحتاج إليه.أعتقد أنني استخدمت every() مرتين خلال 10 سنوات من برمجة Perl وربما كان الاختيار الخاطئ في المرتين =)

أنا عادة استخدام keys ولا أستطيع أن أفكر في آخر مرة استخدمت فيها أو قرأت استخدامًا لـ each.

لا تنسى map, ، اعتمادًا على ما تفعله في الحلقة!

map { print "$_ => $hash{$_}\n" } keys %hash;

أود أن أقول:

  1. استخدم ما هو أسهل في القراءة/الفهم بالنسبة لمعظم الأشخاص (لذلك عادةً ما أزعم أن المفاتيح)
  2. استخدم ما تقرره باستمرار من خلال قاعدة التعليمات البرمجية بأكملها.

وهذا يعطي ميزتين رئيسيتين:

  1. من الأسهل اكتشاف الكود "الشائع" حتى تتمكن من إعادة التعامل معه في الوظائف/الأساليب.
  2. من الأسهل على المطورين المستقبليين الحفاظ عليه.

لا أعتقد أن استخدام المفاتيح فوق كل منها أكثر تكلفة، لذلك لا حاجة إلى بنيتين مختلفتين لنفس الشيء في التعليمات البرمجية الخاصة بك.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top