Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making a partial array in Perl6

Tags:

arrays

raku

Is there a nice way to get a partial Array derived from another Array?

Example: given an array with x number of elements, and an Int $index within the range of elements of that Array, I would like to run a function from $index + 1 to number of elements - 1.

Essentially I would like to compare all elements within the Array. Compare being a function of the element itself. (Note I am aware of the function eqv, though this is not suitable for this particular situation)

I was thinking of something in the likes of:

for $!my-array.kv -> $index, $element
{
    for $!my-array[$index+1 .. .elems-1] -> $another-element
    {
        $element.compare($another-element)
    }
}
like image 324
Mikkel Avatar asked Jun 25 '18 11:06

Mikkel


2 Answers

.elems - 1 calls a method on $_, which is probably wrong.
If you are going to write it that way $!my-array.elems - 1, or you could give it a lambda *.elems - 1. The usual lambda is * - 1 (an Array numifies to the number of elements).
In this case I would just use a Whatever *.

$!my-array[$index + 1 .. *]

To get for to return a result, prefix it with do.

my @result = do for $!my-array.kv -> $index, $element
{

    do for $!my-array[$index + 1 .. *] -> $another-element
    {
        $element.compare($another-element)
    }
}

It might be clearer to use map

$!my-array.kv.map: -> $index, $element {

  $!my-array[$index + 1 .. *].map: -> $another-element {

    $element.compare($another-element)
  }
}

Since you mention that eqv isn't suitable, that leads me to believe that .compare returns True when they are equivalent, and False otherwise.
So then eqv is actually suitable. You just have to adjust it first.

class Foo {
    method compare (Foo:D: Foo:D $other ) {…}
    …
}

# this gets mixed into the existing &infix:«eqv»
# mark it for export so that it is available in other code units
multi sub infix:«eqv» ( Foo:D \left, Foo:D \right ) is export {

  left.compare(right)

}
# must import it everywhere you want the special cased &infix:«eqv»
# as operators are lexically scoped
use Foo;

$!my-array.kv.map: -> $index, $element {
    # cross using &infix:«eqv»
    $element X[eqv] $!my-array[$index + 1 .. *]
}

You could also use «eqv« (<<eqv<<).


What you wrote happens to coincide fairly closely with combinations.

my @result = $!my-array.combinations(2).map(
  -> ( $a, $b ) { $a eqv $b }
)

This produces a flat list.

< a b c d e >.combinations(2).map: -> ($element, $another-element){
    $element cmp $another-element
}
# (Less,Less,Less,Less,Less,Less,Less,Less,Less,Less)

While the previous code produces a list of lists. (with an extra empty one)

my @a = < a b c d e >;
say @a.kv.map: -> $index, $value {
  @a[$index ^.. *].map: $value cmp *
}
# (
#   (Less,Less,Less,Less),
#   (Less,Less,Less),
#   (Less,Less),
#   (Less,),
#   (),
# )

Note that you can spell the name of the infix operator eqv in several ways

&infix:«  eqv  »
&infix:<  eqv  >
&infix:[ 'eqv' ]
&[eqv]

All but the last one can be used when declaring a new multi sub implementation.
(after removing the &)


P.S. I think a method named .compare should return an Order (Less,Same,More).
So then the correct operator to adjust would be &infix:«cmp».

like image 61
Brad Gilbert Avatar answered Nov 15 '22 04:11

Brad Gilbert


You can avoid the double for loop by use combinations. Assuming you do not want to record the indices, it can be as simple as:

for @($!my-array).combinations: 2 -> ( $element, $another-element ) {
    $element.compare($another-element);
}
like image 24
Håkon Hægland Avatar answered Nov 15 '22 04:11

Håkon Hægland