/now
projects
ramblings
smol projects

balanced word

15.11.2023 3 min read

We can assign a value to each character in a word, based on their position in the alphabet (a = 1, b = 2, … , z = 26). A balanced word is one where the sum of values on the left-hand side of the word equals the sum of values on the right-hand side. For odd length words, the middle character (balance point) is ignored.

Write a function that returns true if the word is balanced, and false if it’s not.

Crystal:

def split_word(w : String) : Array(String)
  is_even = w.size % 2 == 0
  middle = (w.size / 2).to_i
  if is_even
    return [w[0...middle], w[middle..]]
  end
  [w[0...middle], w[middle + 1..]]
end

def calc_value(w : String)
  chars = "a".."z"
  w.split("").map { |ch| chars.index!(ch) + 1 }.sum
end

def balanced(w : String)
  return true if w.reverse == w
  head, tail = split_word(w)
  return calc_value(head) == calc_value(tail)
end

Nothing special here. Perhaps of note: .. and ... represent different ranges in Crystal. .. is inclusive, while ... is exclusive. E.g. "a"..."z" would actually only give us “a” to “y”, so it’s something to look out for.

import std/[strutils, sequtils, math, algorithm]

type WordArr = array[2, string]

proc splitWord(w: string): WordArr =
    let isEven = w.len mod 2 == 0
    let mid = w.len div 2
    if isEven: return [w[0..<mid], w[mid..^1]]
    return [w[0..<mid], w[mid+1..^1]]

proc calcScore(a: string): int =
    let lc = LowercaseLetters.toSeq
    let chars = a.toSeq
    chars.mapIt(lc.find(it) + 1).sum

func balanced(w: string): bool =
    if w.toSeq.reversed == w.toSeq: return true
    let 
        split = splitWord(w)
        head = split[0]
        tail = split[^1]
    return calcScore(head) == calcScore(tail)

Nim gives us some nice ways to index arrays / sequences. ..< means exclusive, while .. is inclusive (I like this better than Crystal’s version - it’s easier to remember). We’ve got ^1, which is shorthand for arr.len - 1. Instead of creating a range, we use LowercaseLetters from the standard lib’s strutils. It provides us with a set, which we then convert into a sequence so that we can lookup indexes.

For this kata, we had to import quite a few modules from the standard library. I’m not too sure how to feel about that - on the one hand, I suppose it’s a good thing that we import something explicitly when we need it - on the other, I suppose you would need some degree of familiarity with the stlib, or at least know where to look.

Raku:

sub split-word(Str $w) {
    my $mid = $w.comb.elems div 2;
    return $w.substr(0..^$mid), $w.substr($mid + 1) if $w.comb.elems mod 2;
    return $w.substr(0..^$mid), $w.substr($mid);
}

sub calc-score(Str $w) {
    my $map = "a".."z";
    [+] $w.comb.map({$map.first($_, :k) + 1});
}

sub balanced(Str $w) {
    return True if $w.comb.reverse ~~ $w.comb;
    my ($head, $tail) = split-word($w);
    return calc-score($head) == calc-score($tail);
}

In Raku, we can use the ^ to denote an exclusive range, but otherwise, the implementation is quite similar.

Last one, Javascript:

function splitWord(w: string) {
  const mid = Math.floor(w.length / 2);
  const isEven = w.length % 2 == 0;
  if (isEven) return [w.slice(0, mid), w.slice(mid)];
  return [w.slice(0, mid), w.slice(mid + 1)];
}

function calcScore(w: string) {
  const chars = "abcdefghijklmnopqrstuvwxyz";
  return w
    .split("")
    .map((c) => chars.indexOf(c) + 1)
    .reduce((acc, curr) => acc + curr);
}

function balanced(w: string) {
  if (w.split("").reverse().join("") === w) return true;
  const [head, tail] = splitWord(w);
  return calcScore(head) == calcScore(tail);
}
Built with Astro and Tailwind 🚀