kcott has asked for the wisdom of the Perl Monks concerning the following question:

See "leetcode perl solutions" for background. See https://leetcode.com/problems/two-sum/ for the problem.

Here's a quick solution I put together. Feel free to improve or discuss. Use of features from more recent Perls is fine (do indicate the version required). Use modules if you want. Golfing solutions are acceptable.

Update: I received feedback from ++LanX#11140763 and ++NetWallah#11140770 regarding issues with my original code. I made changes accordingly. I also noted that the issue identified by NetWallah, was also present in the next OUTER if $input->[$i] > $target; statement: I've removed that line and added two more tests (which also show that zero is a valid target). The new code follows. The original code can be found at the end in the spoiler.

#!/usr/bin/env perl use strict; use warnings; use constant { INPUT => 0, TARGET => 1, EXPECTED => 2, }; use Test::More; my @tests = ( [[2,7,11,15], 9, [0,1]], [[2,7,11,15], 9, [1,0]], [[3,2,4], 6, [1,2]], [[3,2,4], 6, [2,1]], [[3,3], 6, [0,1]], [[3,3], 6, [1,0]], [[-5,-3,1,4,7], 1, [1,3]], [[-5,-3,1,4,7], 1, [3,1]], [[1,-1], 0, [0,1]], [[1,-1], 0, [1,0]], ); plan tests => 0+@tests; for my $test (@tests) { is_deeply sort_arrayref(two_sum($test->[INPUT], $test->[TARGET])), sort_arrayref($test->[EXPECTED]); } sub two_sum { my ($input, $target) = @_; my $got; OUTER: for my $i (0 .. $#$input - 1) { for my $j ($i + 1 .. $#$input) { if ($input->[$i] + $input->[$j] == $target) { $got = [$i, $j]; last OUTER; } } } return $got; } sub sort_arrayref { my ($aref) = @_; return [ sort { $a <=> $b } @$aref ]; }

New output:

1..8 ok 1 ok 2 ok 3 ok 4 ok 5 ok 6 ok 7 ok 8 ok 9 ok 10

The original code and its output are in the spoiler. Note that this is code on which others' comments were based.

— Ken

Replies are listed 'Best First'.
Re: LeetCode Problem #1 - Two Sum - Improve and/or discuss.
by NetWallah (Canon) on Jan 24, 2022 at 03:35 UTC
    I did not benchmark this yet - but this should be faster (it passes your tests):
    sub twosum_NetWallah{ my ($inp, $target)=@_; my %targ_idx = map { $inp->[$_] => $_ } 0..$#$inp; for (0..$#$inp){ next unless defined ( my $other=$targ_idx{ $target - $inp->[$_] } + ); next if $other == $_; return [ $other > $_ ? ($_, $other) : ($other, $_) ]; } return undef; } # Test NetWallah.. for my $test (@tests) { is_deeply sort_arrayref(twosum_NetWallah ($test->[INPUT], $test->[TARGET +])), sort_arrayref($test->[EXPECTED]); }
    UPDATE 1: removed buggy " -1" from (0..$#$inp -1); deleted unnecessary variable @solutions.

    UPDATE 2: I did try to benchmark the code - kcott's runs ~ 40% faster, so I was surprised!
    However- After adding this test case:

    [[-5,-3,1,4,7], 1, [1,3]],
    kcott's code fails that test case, but mine passes.
    Note - that new test case does meet the problem definition (array of integers), which can have negative numbers.
    Also - kcott's code assumes and takes advantage of the input being sorted - input order is NOT specified in the problem definition.

    BTW - thanks for setting up this challenge!

                    "If you had better tools, you could more effectively demonstrate your total incompetence."

      G'day NetWallah,

      "After adding this test case: [[-5,-3,1,4,7], 1, [1,3]], kcott's code fails that test case, but mine passes."

      Well spotted and thanks for reporting the bug.

      I had added

      next INNER if $input->[$j] > $target;

      on the basis that a single comparison was less processing than the addition plus comparison in

      if ($input->[$i] + $input->[$j] == $target) {

      I thought that was a handy logic short-circuit; however, I hadn't taken into account negative numbers. I've removed that line and the INNER label: all tests now pass.

      "Also - kcott's code assumes and takes advantage of the input being sorted - input order is NOT specified in the problem definition."

      I don't know where you got that idea from. I neither made that assumption nor wrote any code taking advantage of such sorting. Note [[3,2,4], 6, [1,2]] which has unsorted input.

      — Ken

      This is O(N) instead of O(N^2), so it will scale better.

Re: LeetCode Problem #1 - Two Sum - Improve and/or discuss.
by syphilis (Archbishop) on Jan 24, 2022 at 06:02 UTC
    use strict; use warnings; my @tests = ( [[2,7,11,15], 9, 0, 1], [[2,7,11,15], 9, 1, 0], [[3,2,4], 6, 1, 2], [[3,2,4], 6, 2, 1], [[3,3], 6, 0, 1], [[3,3], 6, 1, 0], [[3,3,3,3,3,1,3,2,3,3,3,3], 3, 5, 7], [[3,3,3,3,3,1,3,2,3,3,3,3], 3, 7, 5], [[7,7,9,2,8,7,4,2], 13, 2, 6], [[7,7,9,2,8,7,4,2], 13, 6, 2], ); my $count = 1; for(@tests) { my $ret = two_sum($_->[0], $_->[1]); #check the result for correctness. if( ($$ret[0] == $_->[2] && $$ret[1] == $_->[3]) || ($$ret[1] == $_->[2] && $$ret[0] == $_->[3]) ){ print "ok $count +\n" } else { print "@$ret\n"; print "not ok $count\n"; } $count++; } sub two_sum { my ($input, $target) = (shift, shift); # keep track of the original indices of the values, # prior to sorting the values. my %h; for(my $i = 0; $i < @$input; $i++) { # If the same value occurs twice, then # either they both form the solution, or # neither is part of the solution. # Any value that occurs more than twice # cannot be part of the solution. if(exists $h{$$input[$i]} ) { return [$h{$$input[$i]}, $i] if $target == $$input[$i] * 2; next; # not part of the solution and therefore # no need to test that value any further. } $h{$$input[$i]} = $i; } # Now let's sort the values. my @in = sort {$a <=> $b} keys(%h); my ($lo, $hi) = (0, @in - 1); for(;;) { # eliminate one value at each iteration my $cmp = ($in[$lo] + $in[$hi]) <=> $target; if ($cmp < 0) { $lo++ } elsif($cmp > 0) { $hi-- } else { # return the indices of the given values. return [ $h{$in[$lo]}, $h{$in[$hi]} ]; } # Safeguard against non-conforming inputs: die "Bad input array" if $lo >= $hi; } }
    Cheers,
    Rob

    Update:
    When I first looked at this challenge, I noticed that the values in the first example were ordered from lowest to highest - which enables the opportunity to halve the maximum number of trial additions that will be needed. (This could be quite a saving on lengthy arrays of values.)
    So I went ahead and coded up a procedure that worked quite nicely on sorted arrays.
    Then I noticed that there was no guarantee that the input arrays would be pre-sorted.

    Ok ... I'll just have to sort the input array first. (And don't worry that the cost of doing that probably outweighs the advantage of reduced additions.)
    And then I woke up to the fact that I had to return the indices of the 2 values, not the 2 actual values.
    Ok ... I can pre-process the original input array so that I keep track of the indices of the values, and then return the appropriate index values.
    While doing this, I can remove duplicates from the input array - so it's not all additional cost.

    By the time I finished, I felt like the only man to not Escape from Stalag Luft 112B.
    But I posted anyway - as testament to the dangers of obstinacy and determination ;-)

      O(N log N), O(N) if already sorted.

      Rather than sorting the inputs, I would sort the indexes. That way, you wouldn't need an extra hash an array, just an array.

      G'day Rob,

      ++ Perhaps consider it in the positive light of professionalism and perseverance.

      I loved those Ripping Yarns shows when they came out in the mid to late '70s. From your link, I found others; further searching located the remainder. It looks like I've lined up several hours of Australia Day entertainment. :-)

      — Ken

        It looks like I've lined up several hours of Australia Day entertainment. :-)

        That sure is a lot more appealing than waving flags ;-)

        Cheers,
        Rob
Re: LeetCode Problem #1 - Two Sum - Improve and/or discuss.
by choroba (Cardinal) on Jan 24, 2022 at 15:11 UTC
    I'd solve it this way:
    sub two_sum_choroba { my ($input, $target) = @_; my %seen; for my $i (0 .. $#$input) { my $x = $input->[$i]; return [$i, $seen{ $target - $x }] if exists $seen{ $target - $x }; $seen{$x} = $i; } }

    It passes all the tests presented in the thread so far and benchmarks show it's also the fastest.

    Translated to Python 3 (Dona eis requiem, smack), it was accepted by LeetCode as

    Runtime: 48 ms, faster than 99.40% of Python3 online submissions for T +wo Sum. Memory Usage: 15.4 MB, less than 42.44% of Python3 online submissions +for Two Sum.

    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
Re: LeetCode Problem #1 - Two Sum - Improve and/or discuss.
by tybalt89 (Monsignor) on Jan 24, 2022 at 13:24 UTC
    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11140759 use warnings; use constant { INPUT => 0, TARGET => 1, EXPECTED => 2, }; use Test::More; my @tests = ( [[2,7,11,15], 9, [0, 1]], [[15,11,7,2], 9, [2, 3]], [[-15,-11,-7,-2], -9, [2, 3]], [[3,2,4], 6, [1,2]], [[3,3], 6, [0,1]], [[-3,-3], -6, [0,1]], [[(1,6)x1995,3,5], 8, [3990,3991]], # for O(n**2) solutions [[-1,4, -4], 0, [1,2]], ); plan tests => 0+@tests; for my $test (@tests) { is_deeply two_sum($test->[INPUT], $test->[TARGET]), $test->[EXPECTED]; } sub two_sum # O(n) { my ($input, $target) = @_; my (%h, $second); push @{ $h{$input->[$_]} }, $_ for 0 .. $#$input; $second = $h{$target - $_} and ($_ != $target - $_ or @$second > 1) and return [ $h{$_}[0], $second->[-1] ] for @$input; }
Re: LeetCode Problem #1 - Two Sum - Improve and/or discuss.
by jdporter (Paladin) on Jan 25, 2022 at 13:36 UTC

    I believe this runs in O(n) time. But that's on the presumption that the input list of numbers is already sorted. If it's not, then this solution would have to add a sorting step, which will make it O(n log n).

    sub TwoSum { my( $target, @nums ) = @_; # assumption: @nums are already sorted. # caveat: the nums in the list might not be uniq!!! my %num; @num{@nums} = (); # for existence tests. for my $i2 ( reverse( 1 .. $#nums ) ) { my $d = $target - $nums[$i2]; if ( exists $num{$d} ) { # gotta find the _first_ occurrence of this number in the li +st: my($i1) = grep { $nums[$_] == $d } 0 .. $i2-1; return( $i1, $i2 ); } } die }
    I reckon we are the only monastery ever to have a dungeon staffed with 16,000 zombies.

      G'day jdporter,

      I liked the general thinking behind this; however, I see two issues.

      "# assumption: @nums are already sorted."

      There is no requirement for the input list to be sorted. As an example, see "1. Two Sum" Example 2:

      Input: nums = [3,2,4], target = 6 Output: [1,2]

      If you did "add a sorting step", you could potentially change the result:

      Input: nums = [2,3,4], target = 6 Output: [0,2]
      "# caveat: the nums in the list might not be uniq!!!"

      Again, there's no requirement for this uniqueness. See Example 3:

      Input: nums = [3,3], target = 6 Output: [0,1]

      As far as I can tell (by inspection) your code produces a correct result with this input. An input of [3,1,1,1,3] would produce a correct result: [0,4]. An input of [3,3,3] would be invalid from the outset, because there's more than one possible solution: [0,1], [0,2] and [1,2] — whatever code you (or anyone) wrote would be immaterial for this input.

      — Ken

Re: LeetCode Problem #1 - Two Sum - Improve and/or discuss.
by Discipulus (Canon) on Jan 24, 2022 at 08:08 UTC
    Hello kcott and thanks for this amusement

    The test is more complex than the code if you want to accept both [0,1] and [1,0] better and easier just rely on the expected sum. Anyway..

    sub two_sum_Discipulus { my ($input, $target) = @_; foreach my $index ( 0 .. $#$input ){ ( $_ != $index and $input->[$_] + $input->[$index] == $target +) ? return [$index, $_] + : 0 for 0 .. $#$input; } }

    PS added newlines for a minimal readability enhancement

    L*

    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
Re: LeetCode Problem #1 - Two Sum - Improve and/or discuss.
by tybalt89 (Monsignor) on Jan 26, 2022 at 00:01 UTC

    Both concise and weird. And no, it's not really golfed. I just wanted to see if this would work.

    sub two_sum # O(n) { my ($i, $input, $target, %h) = (0, @_); return [ $h{$_} // ($h{$target - $_} = $i++, next), $i ] for @$input +; }
Re: LeetCode Problem #1 - Two Sum - Improve and/or discuss.
by LanX (Saint) on Jan 24, 2022 at 01:19 UTC
    nitpick:

    I think

    • You can return the answer in any order.

    means you have to also allow the reverse order in your tests.

    edit

    this can be fixed by sorting the result first.

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

      That's a fair enough comment: any order was specified in the problem spec. I added to @tests:

      my @tests = ( [[2,7,11,15], 9, [0,1]], [[2,7,11,15], 9, [1,0]], [[3,2,4], 6, [1,2]], [[3,2,4], 6, [2,1]], [[3,3], 6, [0,1]], [[3,3], 6, [1,0]], );

      Modified is_deeply:

      for my $test (@tests) { is_deeply sort_arrayref(two_sum($test->[INPUT], $test->[TARGET])), sort_arrayref($test->[EXPECTED]); }

      Appended a new subroutine:

      sub sort_arrayref { my ($aref) = @_; return [ sort { $a <=> $b } @$aref ]; }

      Output now:

      1..6 ok 1 ok 2 ok 3 ok 4 ok 5 ok 6

      — Ken

        not sure why you are duplicating the tests ... (?)

        one sort_arrayref(two_sum(...)) is enough if you only keep tests with already sorted expectations.

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery