loading...

Daily Challenge #110 - Love VS. Friendship

thepracticaldev profile image dev.to staff ・1 min read

If a = 1, b = 2, c = 3 ... z = 26

Then l + o + v + e = 54

and f + r + i + e + n + d + s + h + i + p = 108

So friendship is twice as strong as love :-)

Write a function to find the "strength" of each of these values.

Test cases:
wordsToMarks(attitude)
wordsToMarks(friends)
wordsToMarks(family)
wordsToMarks(selflessness)
wordsToMarks(knowledge)

Good luck!


This challenge comes from J or nor J on CodeWars. Thank you to CodeWars, who has licensed redistribution of this challenge under the 2-Clause BSD License!

Want to propose a challenge idea for a future post? Email yo+challenge@dev.to with your suggestions!

Discussion

pic
Editor guide
 

A bit like @nicolas Hervé, using the reduce method:

f=a=>[...a].reduce((t,l)=>t+l.charCodeAt``-96,0)
  • [...a] transforms the string to an array with every character (["l", "o", "v", "e"])
  • The reduce methods will loop through every character, get its charCode, subtract 96 to it (the charCode associated with "a" + 1), and sum everything
 

Thanks to @ynndvn for showing me reduce method, it exist too in Swift language and it's awesome !!!

This is my solution in Swift :

func wordsToMarks(word: String) -> Int {
    return word.compactMap { $0 }.reduce(0) {
       $0 + Int($1.asciiValue ?? 0) - 96
    }
}

wordsToMarks(word:"attitude")
wordsToMarks(word:"friends")
wordsToMarks(word:"family")
wordsToMarks(word:"selflessness")
wordsToMarks(word:"knowledge")
 

Why the compactMap, you are mapping each letter to itself, works fine without that too.

Also when I tried your solution with the input string "love🐈" it returned -42 since the way you handle the Optional results in non-ASCII characters having a value of -96. The following version simply ignores such characters instead:

func wordsToMarks(word: String) -> Int {
    return word.reduce(0) {
       $0 + Int($1.asciiValue.map { $0 - 96 } ?? 0)
    }
}
 

Oh nice! That's better !

I forgot String are a table of Character so you right compactMap is useless.
I have totally ignored emojis because the challenger treat only letter a to z

So thank you for your feedback, your optimisation is truly better 👌

I have totally ignored emojis because the challenger treat only letter a to z

True, but you did add some code to deal with the case were a character may not have an asciiValue, I just offered an alternative to that :-)

 

I started picking up ruby for rails, so here is my shining gem.

Try it out here

word.rb

class Word
  @@alphabet = ('a'..'z').to_a

  def self.to_mark(word)
    word.chars.sum { |char| @@alphabet.index(char.downcase) + 1 }
  end
end

word_test.rb

require 'minitest/autorun'
require_relative 'word'

class TestWord < Minitest::Test
  def setup
    @cases = [
      { input: 'love', expected: 54 },
      { input: 'friendship', expected: 108 },
      { input: 'attitude', expected: 100 },
      { input: 'friends', expected: 75 },
      { input: 'family', expected: 66 },
      { input: 'selflessness', expected: 154 },
      { input: 'knowledge', expected: 96 }
    ]
  end

  def test_to_mark
    @cases.each { |test| assert_equal test[:expected], Word.to_mark(test[:input]) }
  end
end
 

Just a friendly tip: alphabet really shouldn't be a class variable @@, they are very seldom what you want in Ruby:

railstips.org/blog/archives/2006/1...

In this specific case a constant would be the most common:

ALPHABET = ('a'..'z').to_a
 

Ah okay gotcha, thank ya for the tip!

 
def wordsToMarks(word)
  alphabet = ("a".."z").to_a
  mark = 0
  word.each_char do |char|
    mark += alphabet.index(char.downcase) + 1
  end
  mark
end
 

("a".."z").to_a is nifty!

 

Well, my first attempt had me iterating calling .index on ("a".."z") only to find out that you can't call index on a range, so in trying to work around that I realized I could just convert the range into an array... :)

 

Ruby:

def words_to_marks(word)
  word.chars.sum { |c| c.ord - 96 }
end

For fun a second version that builds a lookup table with an endless range (introduced in Ruby 2.6):

ALPHABET = ('a'..'z').zip(1..).to_h

def words_to_marks(word)
  word.chars.sum { |c| ALPHABET.fetch(c, 0) }
end
 

Here is the simple solution with Python:

def words_to_marks(s):
    alphabet = {
        'a': 1,
        'b': 2,
        'c': 3,
        'd': 4,
        'e': 5,
        'f': 6,
        'g': 7,
        'h': 8,
        'i': 9,
        'j': 10,
        'k': 11,
        'l': 12,
        'm': 13,
        'n': 14,
        'o': 15,
        'p': 16,
        'q': 17,
        'r': 18,
        's': 19,
        't': 20,
        'u': 21,
        'v': 22,
        'w': 23,
        'x': 24,
        'y': 25,
        'z': 26,
    }

    sum_length = 0
    for char in s:
        sum_length += alphabet[char]

    return sum_length
 
const wordsToMarks = (word) => word.split("").reduce((acc, curr) => acc + curr.charCodeAt(0) - "a".charCodeAt(0) + 1, 0);
 

A little solution for Ruby :) this was fun!

def wordsToMarks(word)
  strength = 0
  alphabet = ('a'..'z').to_a
  word.split('').each do |i|
    strength = strength + (alphabet.index(i) + 1)
  end
  return strength
end
 
words_to_marks("",0).
words_to_marks(S,Mark) :-
    string_codes(S,SC),
    length(SC, SCL),
    sum_list(SC,SCS),
    Mark is SCS - SCL * 96.
?- words_to_marks(“love”, V).
% V = 54

?- words_to_marks(“declarative”, V).
% V = 100
 

Nice to see some Prolog :-)

 
#include <stdio.h>

int main(int argc, char *argv[]) 
{
    if (argc != 2)
    {
        printf("Usage: ./strength string\n");
        return 1;
    }
    int sum = 0;
    char *c = argv[1];
    while(*c != '\0')
    {
        if (*c < 'a' || *c > 'z')
        {
            printf("Invalid argument\n");
            return 1;
        }
        sum += *c - 'a' + 1;
        c++;
    }
    printf("%i\n", sum);
    return 0;
}
 

In PHP


$alpha = function (string $string) {
    $al = array_flip(range('a', 'z'));

    $points = 0;
    foreach (str_split($string) as $s) {
        $points += $al[$s] + 1;
    }
    return $points;
};

echo $alpha('friendship');

EDIT:

array_map(function (string $string) {
    $al = array_flip(range('a', 'z'));

    $points = 0;
    foreach (str_split($string) as $s) {
        $points += $al[$s] + 1;
    }
    echo "$string: $points<br>";
}, ['attitude', 'friends', 'family', 'selflessness', 'knowledge']);

attitude: 100
friends: 75
family: 66
selflessness: 154
knowledge: 96
 

Haskell

import Data.Char (ord)

wordsToMarks :: String -> Int
wordsToMarks = sum . map ((flip (-) 96) . ord)

Playground

Here.

 

The prelude defines subtract, so you don't need flip here:

wordsToMarks = sum . map (subtract 96 . ord)

Another relatively common workaround is to use a right-section that adds a negative number, i.e.

wordsToMarks = sum . map ((+(-96)) . ord)
 

Thanks for your insight. I didn't know about subtract and when the second solution is kind of obscure to me it seems very cool and short.

Since - is both subtraction and unary minus one can’t use a right-section like (-1), but considering that subtracting is the same as adding a negative number the slightly obscure form makes sense. I’d still use subtract though.

 

My solution to the challenge using map and reduce in Python. If anyone has any suggestions on how to improve it, I would love to hear! :)

from functools import reduce 
def wordsToMarks(word):
    print(reduce((lambda x,y: x+y), map((lambda x: ord(x) - 96), list(word))))
 

Here is my fun solution using Kotlin:

fun wordsToMarks(word: String) = word.toCharArray()
        .map { it + 1 - 'a' }
        .sum()
 

c#, one liner using linq, ignoring non-letters chars:

int WordsToMarks(string word)
{
    return word.Where(c => char.IsLetter(c)).Sum(c => char.ToUpperInvariant(c) - 64);
}
 

My Python Algorithm:

words_to_marks = lambda words: sum(
    map(ord, words.lower()),
    -96 * len(words))