Weekly Challenge #085 Task #1 :: (Raku) 
Tags: challenge, perlweeklychallenge, raku
Published: 2020-11-02 10:40:34

## [**TASK #1 › Triplet Sum**](https://perlweeklychallenge.org/blog/perl-weekly-challenge-085/#TASK1)
Submitted by: Mohammad S Anwar

You are given an array of real numbers greater than zero.

Write a script to find if there exists a triplet (a,b,c) such that 1 < a+b+c < 2. Print 1 if you succeed otherwise 0.
```
Example 1:

Input: @R = (1.2, 0.4, 0.1, 2.5)
Output: 1 as 1 < 1a.2 + 0.4 + 0.1 < 2

Example 2:

Input: @R = (0.2, 1.5, 0.9, 1.1)
Output: 0

Example 3:

Input: @R = (0.5, 1.1, 0.3, 0.7)
Output: 1 as 1 < 0.5 + 1.1 + 0.3 < 2
```

### **Floating vs Rational Number**
In Raku, It seems that there are two types to save floating point values. actually we can express most of floating point as rational number like ...
```
1.7 -> 10 / 17
```

1. [Rat](https://docs.raku.org/type/Rat)
2. [FatRat](https://docs.raku.org/type/FatRat)

And document said...
> Since, unlike Rat, FatRat arithmetics do not fall back Num at some point, there is a risk that repeated arithmetic operations generate pathologically large numerators and denominators.

So I guess Rat is enough for this task. and making a Rat is straightforward. Just add point even if the number can be integer value.
```perl
> (1.0).WHAT
(Rat)
> (1.7).numerator
17
> (1.7).denominator
10
> 1.Rat # or you can declare explicitly
```
### **A Sample Made Easy**
So we need triplet of rational number next code will generate random numbers suitable for the task.
* I add more zeroes to generate more 0.* values *
```perl
> (10..99).pick(10).map({ (|(0 xx 10), |(0..3)).pick + $_/100 })
(3.24 0.75 0.83 0.74 0.58 0.85 1.68 0.81 0.59 0.12)
```

### **Combinations of Triplet**
Maybe I'm too obsessed with combinations 😂
```perl
> my @r = (10..99).pick(10).map({ (|(0 xx 10), |(0..3)).pick + $_/100 })
[0.23 0.1 0.13 0.56 0.81 0.71 1.4 0.79 0.16 0.52]
> @r.combinations(3).head(3) # head() can take an argment
((0.23 0.1 0.13) (0.23 0.1 0.56) (0.23 0.1 0.81))
```

### **Sum of Triplet**
Our desired value is between 1 and 2 (or 1.0 and 2.0 in clearer way to compare)
```perl
> @r.combinations(3).grep( -> \t { 1.0 < t.sum < 2.0 } );
((0.23 0.1 0.81) (0.23 0.1 0.71) (0.23 0.1 1.4) (0.23 0.1 0.79) ...
```
Okay.. those results are too many, probably. 🤪
### **Finding Only One 💖**
Human is too greedy. We are searching for more and leave the processor suffering even though all we need is one possible answer.

```perl
> @r.combinations(3).grep( -> \t { 1.0 < t.sum < 2.0 } ).head;
((0.23 0.1 0.81))
```
and maybe flattening to get only triplet itself.
```perl
> @r.combinations(3).grep( -> \t { 1.0 < t.sum < 2.0 } ).head.flat;
(0.23 0.1 0.81)
```

hang on... but we have 📔[*first()*](https://docs.raku.org/routine/first)
### **First() is NOT [Head](https://docs.raku.org/routine/head)**
if we simply replace head() with first() ...
```perl
> @r.combinations(3).grep( -> \t { 1.0 < t.sum < 2.0 } ).first;
(0.23 0.1 0.81)
```
It might look similar but don't need to *flat()* and also first() gives us the way to searching an object what we are to get.
```perl
> @r.combinations(3).first( -> \t { 1.0 < t.sum < 2.0 } );
```
Good. Probably it does the similar thing like below code.
```perl
> @r.combinations(3).grep( -> \t { 1.0 < t.sum < 2.0 } ).lazy.first;
```
Because grep() wants every cases made from combinations, and won't finish until check all of them. AND even worse, if you talk to your partner about his or her bad habit all at once, He/She will leave you before you are finished, right? I meant it is exhausting.
But by appending .lazy() it will evaluated every time when we want to eager to get the value.

I guess that it is possible that combinations() is also try to get every possible combinations before calling grep() so we can append .lazy() to combinations(). *Even though the list is short it will only makes whole programme a bit slower*, it is not painful so I added.

### **Final Code**
```perl
#!/usr/bin/env raku

unit sub MAIN ( *@r where { @r.all ~~ Rat and @r.all > 0.0 } );

@r.
grep(* < 2.0).
sort.
combinations(3).
lazy.
first( 1.0 < *.sum < 2.0 )
andthen say("1.0 < "
           ~ "({join(' + ', $_.List)})"
           ~ " < 2.0")
orelse say "0";
 
```
Above code is the second solution of mine and I'll leave first solution for comparison. I took second one because it looks more raku-ish.

```perl
#!/usr/bin/env raku

unit sub MAIN ( *@a where { @a.all ~~ Rat and @a.all > 0 } );

my $triplet-it = @a.combinations(3).iterator;

my $found = False;
loop ( my $t := $triplet-it.pull-one;
    ($t =:= IterationEnd).not
    ; $t := $triplet-it.pull-one ) {
    if 1.0 < $t.sum < 2.0 {
        $found = True;
        say("1.0 < "
           ~ "({join(' + ', $t.List)})"
           ~ " < 2.0");
        last;
    }
}

say 0 unless $found;
```
Not bad. Isn't it? It looks more likely imperative or OOP approach.
Okay, that's all.
Thank you for reading !!
Please check out other challenges in [🐪PWC🦋](https://perlweeklychallenge.org/challenges/)