/now
projects
ramblings
smol projects

number of boomerangs

10.11.2023 3 min read

A boomerang is a V-shaped sequence that is either upright or upside down. Specifically, a boomerang can be defined as: sub-array of length 3, with the first and last digits being the same and the middle digit being different.

Some boomerang examples:

[3, 7, 3], [1, -1, 1], [5, 6, 5]

Be aware that boomerangs can overlap, like so:

[1, 7, 1, 7, 1, 7, 1] // 5 boomerangs (from left to right): [1, 7, 1], [7, 1, 7], [1, 7, 1], [7, 1, 7], and [1, 7, 1]

Slices. We need slices.

crystal:

def number_of_boomerangs(arr : Array(Int32)) : Int32
  count = 0
  arr.each_with_index do |_, i|
    slice = arr[i..i + 2]
    next if slice.size != 3
    a, b, c = slice
    count += 1 if a == c && a != b
  end
  return count
end

crystal lets us create slices even if it goes out of bounds. Not so with nim:

proc numberOfBoomerangs(s: seq[int]): int =
    for i in 0 ..< s.len:
        if i + 2 > s.len - 1: break
        let slice = s[i .. i + 2]
        if slice[0] == slice[2] and slice[0] != slice[1]: result.inc

We have to be a little more careful with indexing - trying to access something that doesn’t exist will throw an indexDefect in nim. We also don’t have nice destructuring syntax built-in, unlike crystal.

raku:

sub number-of-boomerangs(@a) {
    my $c = 0;
    for @a.kv -> $i, $_ {
        last if Any ~~ any(@a[$i..$i+2]);
        my @s = @a[$i..$i+2];
        $c++ if @s.head == @s.tail && @s.head != @s[1];
    }
    $c;
}

More of the same… but different? We could check what i is, and break out of the loop - instead, we check if any of the elements in the slice are of type Any. While raku doesn’t complain about out of bounds errors, it does something unexpected - it goes ahead and creates the slice, but fills it with Any when there are no more elements left to index.

js:

function numberOfBoomerangs(arr: number[]): number {
  let count = 0;

  for (let i = 0; i <= arr.length - 1; i++) {
    const slice = arr.slice(i, i + 3);
    if (slice.length !== 3) break;
    const [a, b, c] = slice;
    if (a === c && a !== b) count++;
  }

  return count;
}

Here, we have to use a c-style loop. While I don’t like these very much (I am very much prone to off-by-one errors), .forEach doesn’t allow breaking out of the loop.

Anyway, that’s about it. While the core logic for each is largely the same - it’s interesting to see how each language has its own set of idiosyncrasies.

Built with Astro and Tailwind 🚀