في لغة Perl، كيف يمكنني إنشاء تجزئة تأتي مفاتيحها من مصفوفة معينة؟

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

  •  01-07-2019
  •  | 
  •  

سؤال

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

if($hash{X}) { ... }

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

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

المحلول

%hash = map { $_ => 1 } @array;

إنها ليست قصيرة مثل حلول "@hash{@array} = ..."، ولكن تلك الحلول تتطلب أن يتم تعريف التجزئة والمصفوفة بالفعل في مكان آخر، في حين يمكن لهذا الحل أن يأخذ مصفوفة مجهولة ويعيد تجزئة مجهولة.

ما يفعله هذا هو أخذ كل عنصر في المصفوفة وإقرانه بـ "1".عندما يتم تعيين هذه القائمة المكونة من أزواج (مفتاح، 1، مفتاح، 1، مفتاح 1) إلى تجزئة، تصبح الأرقام الفردية هي مفاتيح التجزئة، وتصبح الأرقام الزوجية هي القيم المعنية.

نصائح أخرى

 @hash{@array} = (1) x @array;

إنها شريحة تجزئة، قائمة بالقيم من التجزئة، لذا فهي تحصل على القائمة y @ في المقدمة.

من المستندات:

إذا كنت مرتبكا بشأن سبب استخدامك "@" هناك على شريحة تجزئة بدلا من ذلك من "٪" ، فكر في الأمر على هذا النحو.ال نوع القوس (مربع أو مجعد) يحكم ما إذا كان صفيفا أو أ التجزئة التي يتم النظر إليها.من ناحية أخرى اليد، الرمز البادئ ('$' أو '@') على الصفيف أو التجزئة يشير إلى ما إذا كان أنت تستعيد قيمة فردية (عددي) أو جمع واحد (قائمة).

@hash{@keys} = undef;

بناء الجملة هنا حيث تشير إلى التجزئة بـ @ هي شريحة التجزئة.نحن نقول في الأساس $hash{$keys[0]} و $hash{$keys[1]} و $hash{$keys[2]} ...هي قائمة على الجانب الأيسر من =، وهي lvalue، ونحن نقوم بتعيين تلك القائمة، والتي تدخل فعليًا في التجزئة وتحدد القيم لجميع المفاتيح المسماة.في هذه الحالة، قمت بتحديد قيمة واحدة فقط، بحيث تدخل هذه القيمة $hash{$keys[0]}, ، وإدخالات التجزئة الأخرى جميعها يتم تنشيطها تلقائيًا (تنبض بالحياة) بقيم غير محددة.[كان اقتراحي الأصلي هنا هو تعيين التعبير = 1، والذي كان سيعين هذا المفتاح على 1 والمفتاح الآخر على undef.لقد قمت بتغييرها من أجل الاتساق، ولكن كما سنرى أدناه، فإن القيم الدقيقة لا تهم.]

عندما تدرك أن lvalue، التعبير الموجود على الجانب الأيسر من =، عبارة عن قائمة مبنية على التجزئة، فسوف نبدأ في فهم سبب استخدامنا لذلك @.[إلا أنني أعتقد أن هذا سيتغير في Perl 6.]

الفكرة هنا هي أنك تستخدم التجزئة كمجموعة.ما يهم ليس القيمة التي أسندها؛إنه مجرد وجود المفاتيح.إذن ما تريد القيام به ليس شيئًا مثل:

if ($hash{$key} == 1) # then key is in the hash

بدلاً من:

if (exists $hash{$key}) # then key is in the set

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

لاحظ أنه إذا الكتابة if ( exists $hash{ key } ) لا يمثل الكثير من العمل بالنسبة لك (وهو ما أفضل استخدامه نظرًا لأن الاهتمام هو في الحقيقة وجود المفتاح وليس صدق قيمته)، فيمكنك استخدام الاختصار والحلاوة

@hash{@key} = ();

هناك افتراض مسبق هنا ، أن الطريقة الأكثر فعالية للقيام بالكثير من عمليات التحقق من "هل تحتوي المصفوفة على X؟" هي تحويل المصفوفة إلى تجزئة.تعتمد الكفاءة على الموارد النادرة، وهي في كثير من الأحيان الوقت ولكن في بعض الأحيان المساحة وأحيانا جهد المبرمج.أنت على الأقل تضاعف الذاكرة المستهلكة عن طريق الاحتفاظ بقائمة وتجزئة القائمة في وقت واحد.بالإضافة إلى أنك تكتب المزيد من التعليمات البرمجية الأصلية التي ستحتاج إلى اختبارها وتوثيقها وما إلى ذلك.

كبديل، انظر إلى وحدة List::MoreUtils، وتحديدًا الوظائف any(), none(), true() و false().إنهم جميعًا يأخذون كتلة كشرطية وقائمة كوسيطة، على غرار map() و grep():

print "At least one value undefined" if any { !defined($_) } @list;

لقد أجريت اختبارًا سريعًا، وقمت بتحميل نصف /usr/share/dict/words إلى مصفوفة (25000 كلمة)، ثم بحثت عن إحدى عشرة كلمة مختارة من القاموس بأكمله (كل 5000 كلمة) في المصفوفة، باستخدام كل من المصفوفة -طريقة التجزئة و any() وظيفة من القائمة::MoreUtils.

في الإصدار Perl 5.8.8 المبني من المصدر، تعمل طريقة المصفوفة إلى التجزئة بشكل أسرع بنحو 1100 مرة من الطريقة any() الطريقة (أسرع بمقدار 1300 مرة ضمن حزمة Perl 5.8.7 المعبأة في Ubuntu 6.06.)

ومع ذلك، هذه ليست القصة الكاملة - يستغرق التحويل من المصفوفة إلى التجزئة حوالي 0.04 ثانية وهو ما يقتل في هذه الحالة كفاءة الوقت لطريقة المصفوفة إلى التجزئة إلى 1.5x-2x أسرع من الطريقة any() طريقة.لا تزال جيدة، ولكن ليس بنفس الدرجة من النجومية.

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

اعتقدت ذلك دائما

foreach my $item (@array) { $hash{$item} = 1 }

كان على الأقل لطيفًا وقابلاً للقراءة/قابل للصيانة.

في الإصدار 5.10 من Perl، يوجد عامل التشغيل القريب من السحر ~~:

sub invite_in {
    my $vampires = [ qw(Angel Darla Spike Drusilla) ];
    return ($_[0] ~~ $vampires) ? 0 : 1 ;
}

انظر هنا: http://dev.perl.org/perl5/news/2007/perl-5.10.0.html

تجدر الإشارة أيضًا إلى الاكتمال، وهي طريقتي المعتادة للقيام بذلك باستخدام صفيفتين متساويتين في الطول @keys و @vals الذي تفضله كان تجزئة ...

my %hash = map { $keys[$_] => $vals[$_] } (0..@keys-1);

يمكن تشديد حل رالدي إلى هذا (ليس من الضروري استخدام '=>' من النص الأصلي):

my %hash = map { $_,1 } @array;

يمكن أيضًا استخدام هذه التقنية لتحويل قوائم النص إلى تجزئات:

my %hash = map { $_,1 } split(",",$line)

بالإضافة إلى ذلك، إذا كان لديك سطر من القيم مثل هذا:"foo=1,bar=2,baz=3" يمكنك القيام بذلك:

my %hash = map { split("=",$_) } split(",",$line);

[تعديل ليشمل]


الحل الآخر المقدم (والذي يأخذ سطرين) هو:

my %hash;
#The values in %hash can only be accessed by doing exists($hash{$key})
#The assignment only works with '= undef;' and will not work properly with '= 1;'
#if you do '= 1;' only the hash key of $array[0] will be set to 1;
@hash{@array} = undef;

يمكنك أيضًا استخدام بيرل6::تقاطع.

use Perl6::Junction qw'any';

my @arr = ( 1, 2, 3 );

if( any(@arr) == 1 ){ ... }

إذا قمت بالكثير من العمليات النظرية - يمكنك أيضًا استخدامها مجموعة::العددية أو وحدة مماثلة.ثم $s = Set::Scalar->new( @array ) سوف نقوم بإنشاء المجموعة لك - ويمكنك الاستعلام عنها باستخدام: $s->contains($m).

يمكنك وضع التعليمات البرمجية في روتين فرعي، إذا كنت لا تريد تلويث مساحة الاسم الخاصة بك.

my $hash_ref =
  sub{
    my %hash;
    @hash{ @{[ qw'one two three' ]} } = undef;
    return \%hash;
  }->();

أو حتى أفضل:

sub keylist(@){
  my %hash;
  @hash{@_} = undef;
  return \%hash;
}

my $hash_ref = keylist qw'one two three';

# or

my @key_list = qw'one two three';
my $hash_ref = keylist @key_list;

إذا كنت تريد حقًا تمرير مرجع صفيف:

sub keylist(\@){
  my %hash;
  @hash{ @{$_[0]} } = undef if @_;
  return \%hash;
}

my @key_list = qw'one two three';
my $hash_ref = keylist @key_list;

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

#!/usr/bin/perl -w

use strict;
use Data::Dumper;

my @a = qw(5 8 2 5 4 8 9);
my @b = qw(7 6 5 4 3 2 1);
my $h = {};

@{$h}{@a} = @b;

print Dumper($h);

يعطي (لاحظ أن المفاتيح المتكررة تحصل على القيمة في الموضع الأكبر في المصفوفة - أي 8->2 وليس 6)

$VAR1 = {
          '8' => '2',
          '4' => '3',
          '9' => '1',
          '2' => '5',
          '5' => '4'
        };
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top