loading...

Daily Challenge #266 - Who Likes It?

thepracticaldev profile image dev.to staff ・1 min read

You probably know the "like" system from Facebook and other pages. People can "like" blog posts, pictures or other items. We want to create the text that should be displayed next to such an item.

Implement a function likes :: [String] -> String, which must take in input array, containing the names of people who like an item. It must return the display text as shown in these examples:

likes {} // must be "no one likes this"
likes {"Peter"} // must be "Peter likes this"
likes {"Jacob", "Alex"} // must be "Jacob and Alex like this"
likes {"Max", "John", "Mark"} // must be "Max, John and Mark like this"
likes {"Alex", "Jacob", "Mark", "Max"} // must be "Alex, Jacob and 2 others like this"

For 4 or more names, the number in and 2 others simply increases.

Good luck!


This challenge comes from BattleRattle 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
Collapse
willsmart profile image
willsmart

This'd be a good use for template literals. Here's a quicky in javascript:

function likesStringForNames(names) {
  const [first, second, third] = names;
  switch (names.length) {
    case 0: return "no one likes this";
    case 1: return `${first} likes this`;
    case 2: return `${first} and ${second} like this`;
    case 3: return `${first}, ${second} and ${third} like this`;
    default: return `${first}, ${second} and ${names.length - 2} others like this`;
  }
}

Sanity check:

[
  [],
  ["Peter"],
  ["Jacob", "Alex"],
  ["Max", "John", "Mark"],
  ["Alex", "Jacob", "Mark", "Max"],
  ["Alex", "Mark", ...Array(10000)]
].map(likesStringForNames)
-->
[
  "no one likes this",
  "Peter likes this",
  "Jacob and Alex like this",
  "Max, John and Mark like this",
  "Alex, Jacob and 2 others like this",
  "Alex, Mark and 10000 others like this"
]
Collapse
peter279k profile image
peter279k

Here is the code snippets with PHP:

function likes( $names ) {
    $namesCount = count($names);
    if ($namesCount === 0) {
        return "no one likes this";
    }

    if ($namesCount === 1) {
        return sprintf("%s likes this", $names[0]);
    }

    if ($namesCount === 2) {
        return sprintf("%s and %s like this", $names[0], $names[1]);
    }

    if ($namesCount === 3) {
        return sprintf("%s, %s and %s like this", $names[0], $names[1], $names[2]);
    }

    if ($namesCount >= 4) {
        $likesStringFormat = "%s, %s and %d others like this";

        $otherNamesCount = $namesCount - 2;

        return sprintf($likesStringFormat, $names[0], $names[1], $otherNamesCount);
    }
}
Collapse
edisonnpebojot profile image
Edison Pebojot(πŸ‘¨β€πŸ’»)

For cleverness, we can solve it like this in JavaScript:

var likes = (n, l = n.length) => l == 0 ? "no one likes this" : l == 1 ? n[0] + " likes this" : l == 2 ? n[0] + " and " + n[1] + " like this" : l == 3 ? n[0] + ", " + n[1] + " and " + n[2] + " like this" : l == 4 ? n[0] + ", " + n[1] + " and " + (l - 2) + " others like this" :false console.log(likes([])) console.log(likes(["Peter"])) console.log(likes(["Jacob", "Alex"])) console.log(likes(["Max", "John", "Mark"])) console.log(likes(["Alex", "Jacob", "Mark", "Max"]))

Bonus

For best practice and cleverness, we can do this in JavaScript:

function likes (names) { var templates = [ 'no one likes this', '{name} likes this', '{name} and {name} like this', '{name}, {name} and {name} like this', '{name}, {name} and {n} others like this' ]; var idx = Math.min(names.length, 4); return templates[idx].replace(/{name}|{n}/g, function (val) { return val === '{name}' ? names.shift() : names.length; }); } console.log(likes([])) console.log(likes(["Peter"])) console.log(likes(["Jacob", "Alex"])) console.log(likes(["Max", "John", "Mark"])) console.log(likes(["Alex", "Jacob", "Mark", "Max"]))

And for the most best practice and cleverness, we can do this in JavaScript:

function likes(names) { names = names || []; switch(names.length){ case 0: return 'no one likes this'; case 1: return names[0] + ' likes this'; case 2: return names[0] + ' and ' + names[1] + ' like this'; case 3: return names[0] + ', ' + names[1] + ' and ' + names[2] + ' like this'; default: return names[0] + ', ' + names[1] + ' and ' + (names.length - 2) + ' others like this'; } } console.log(likes([])) console.log(likes(["Peter"])) console.log(likes(["Jacob", "Alex"])) console.log(likes(["Max", "John", "Mark"])) console.log(likes(["Alex", "Jacob", "Mark", "Max"]))
Collapse
jpantunes profile image
JP Antunes

I think you don't need the break after the return, it would be unreachable.

const likes = (arr = []) => {
    switch (arr.length) {
        case 0:
            return "no one likes this";
        case 1:
            return `${arr[0]} likes this`;
        case 2:
            return `${arr[0]} and ${arr[1]} like this`;
        case 3:
            return `${arr[0]}, ${arr[1]} and ${arr[2]} like this`;
        default:
            return `${arr[0]}, ${arr[1]} and ${arr.length - 2} others like this`;
    }
}
Collapse
edisonnpebojot profile image
Edison Pebojot(πŸ‘¨β€πŸ’»)

Thank you for pointing out that! πŸ‘πŸ‘

Collapse
functional_js profile image
Functional Javascript

This is a case where the switch block is probably the simplest idiom with the least cognitive load.

Having to use Math.min, conditional Regex's, replace callbacks, and array shifts, would be unnecessary complexity.

Good work showing the variants to outline the pros and cons though.

Collapse
functional_js profile image
Functional Javascript

The exception would be if the more "complex" solution was more performant.

For verification, I ran a perf test on the two funcs, and the switch idiom won out. (see screenshot below).

If another idiom were to beat out the switch idiom, I would swap out the implementation for the fastest idiom that uses the least cycles.
Wrap it in a function, document it, and include a resource link to the performance notes, tests and reasoning of the chosen implementation.

Alt Text

Collapse
jericho22034104 profile image
Jericho Reyes

The last part is, in my opinion , the best part I've ever seen

Collapse
dry profile image
Hayden Mankin
let likes = ([a, b, ...c]) => c.length > 1 ? `${a}, ${b} and ${c.length} others like this` : c.length ? `${a}, ${b} and ${c[0]} like this` : b ? `${a} and ${b} like this` : a ? `${a} likes this` : `no one likes this`;

console.log(likes([])); // no one likes this
console.log(likes(["Peter"])); // Peter likes this
console.log(likes(["Jacob", "Alex"])); // Jacob and Alex like this
console.log(likes(["Max", "John", "Mark"])); // Max, John and Mark like this
console.log(likes(["Alex", "Jacob", "Mark", "Max"])); // Alex, Jacob and 2 others like this
Collapse
cipharius profile image
Valts Liepiņő

Very declarative Haskell solution:

likes :: [String] -> String
likes []     = "no one likes this"
likes [name] = name <> " likes this"
likes names  = showMultiple names <> " like this"

showMultiple :: [String] -> String
showMultiple xs = case xs of
  []          -> ""
  [x]         -> x
  [x,x']      -> x <> " and " <> x'
  [x,x',x'']  -> x <> ", " <> showMultiple [x',x'']
  (x:x':rest) -> x <> ", " <> x' <> " and " <> (show $ length rest) <> " others"
Collapse
moufeed_m profile image
Mofid Jobakji
 const likes = (names) => {
   const map = new Map([
     [0, 'no one likes this'],
     [1, '{name} likes this'],
     [2, '{name} and {name} like this'],
     [3, '{name}, {name} and {name} like this'],
     [4, '{name}, {name} and {n} others like this'],
   ]);

   return map
   .get(names.length < 4 ? names.length : 4)
     .replace(/{name}|{n}/g, (val) =>
       val === '{name}' ? names.shift() : names.length
     );
 };

Collapse
nombrekeff profile image
Manolo Edge

Cool I did this one 3 years ago over at CodeWars, here is the answer I posted:

function likes(names) {
  switch(names.length){
    case 0:  return `no one likes this`; break;
    case 1:  return `${names[0]} likes this`; break;
    case 2:  return `${names[0]} and ${names[1]} like this`; break;
    case 3:  return `${names[0]}, ${names[1]} and ${names[2]} like this`; break;
    default: return `${names[0]}, ${names[1]} and ${names.length - 2} others like this`;
  }
}
Collapse
nombrekeff profile image
Manolo Edge

Looking at it now,break is not needed, as return breaks out of the switch statement.

Collapse
celyes profile image
Ilyes Chouia

The solution in PHP

function likes(array $names): string
{
  $likeWord = "like";
  $string = "";
  switch(count($names)){
    case 0:
      $string = sprintf("No one %s this", $likeWord."s");
      break;
    case 1:
      $string = sprintf("%s %s this", $names[0],$likeWord."s");
      break;
    case 2:
      $string = sprintf("%s and %s %s this", $names[0], $names[1], $likeWord);
      break;
    case 3:
      $string = sprintf("%s, %s and %s %s this", $names[0], $names[1], $names[2], $likeWord);
      break;
    default:
      $string = sprintf("%s, %s and %s others %s this", $names[0], $names[1], count($names) - 2 , $likeWord);
  }
  return $string;
}
Collapse
savagepixie profile image
SavagePixie

Elixir

There's probably a much simpler way to solve it, but hey, I basically know how to do pattern matching and recursion, so there we go:

defmodule Like do
  def who_likes(list) do
    count = Enum.count(list)
    _who_likes(list, count - 2)
    |> _add_likes(count)
    |> IO.puts
  end

  defp _add_likes(x, n) when (n < 2), do: "#{x} likes this"
  defp _add_likes(x, _), do: "#{x} like this"

  defp _who_likes([], _), do: "no one"
  defp _who_likes([ a | [] ], _), do: a
  defp _who_likes([ a, b | [] ], _), do: "#{a} and #{b}"
  defp _who_likes([ a, b, c | [] ], _), do: "#{a}, #{b} and #{c}"
  defp _who_likes([ a, b | _tail], count), do: "#{a}, #{b} and #{count} others"
end
Collapse
aminnairi profile image
Amin

TypeScript

const likes = ([a, b, c, ...xs]: string[]): string =>
    a === undefined
        ? "No one likes this."
        : b === undefined
            ? `${a} likes this.`
            :  c === undefined
                ? `${a} and ${b} like this.`
                : xs.length === 0
                    ? `${a}, ${b} and ${c} like this.`
                    : `${a}, ${b} and ${[c, ...xs].length} others like this.`;
Collapse
sam_ferree profile image
Sam Ferree

C# switch expressions to the rescue!

public static class Format
{
  public static string LikeText(string[] names) => names.Length switch
  {
    0 => "no one likes this",
    1 => $"{names[0]} likes this",
    2 => $"{names[0]} and {names[1}} like this",
    3 => $"{names[0]}, {names[1]}, and {names[2]} like this",
    _ => $"{names[0]}, {names[1]}, and {names.Length-2} others  like this",
  };
}
Collapse
markmead profile image
Mark

This is what I got with Ruby, not a lover of it though - any Ruby devs got some alternatives?

def like_text(names = [])
  count = names.count

  string = case count
           when 0
            "no one likes this"
           when 1
            "#{names.first} likes this"
           when 2
            "#{names.join(' and ')} like this"
           when 3
            "#{names.first(2).join(', ')} and #{names.last} like this"
           else
            "#{names.first(2).join(', ')} and #{names.count - 2} others like this"
           end
end

With Rails, I'm fairly certain two of these can be solved with to_sentence πŸ™Œ

Collapse
dimitrilahaye profile image
Dimitri Lahaye

Here is my proposition in JS:

function whoLike(names) {
  const l = names.length;
  if (!l) {
    return 'No one likes this';
  } else if (l === 1) {
    return `${names[0]} likes this.`;
  } else if (l < 4) {
    return `${names.slice(0, l - 1).join(', ')} and ${names[l - 1]} like this.`;
  }
  return  `${names.slice(0, 2).join(', ')} and ${l - 2} others like this.`;
}
Collapse
benaryorg profile image
#benaryorg

Strings in Rust are a bit tedious since you usually want to avoid extra allocations and there's ownership concerns, so this code has a lot more refs than I'd like, and the function signature is a classic, but otherwise I think it's kind of pretty.
What I like most is that there's no -2 for the length, because Rust pattern matching lets me capture the elements into another slice right there.

fn like_text<S: AsRef<str>>(likes: &[S]) -> String
{
    match likes
    {
        &[] => "no one likes this".to_string(),
        &[ref one] => format!("{} likes this", one.as_ref()),
        &[ref one, ref two] => format!("{} and {} like this", one.as_ref(), two.as_ref()),
        &[ref one, ref two, ref three] => format!("{}, {} and {} like this", one.as_ref(), two.as_ref(), three.as_ref()),
        &[ref one, ref two, ref rest@..] => format!("{}, {} and {} others like this", one.as_ref(), two.as_ref(), rest.len())
    }
}

fn main()
{
    let likes = ["foo", "bar", "baz", "quux"];

    println!("{}", like_text(&likes))
}

Permalink to the Rust Playground: play.rust-lang.org/?version=stable...

Collapse
mxldevs profile image
MxL Devs

Ruby string-formatting takes an array of arguments, so if my function takes an array of names, then I just need to feed it into some pre-formatted "templates". Also the use of splat-array which I don't completely understand actually.

def likes(names)
  num_people = names.size
  case num_people
  when 0
    "no one likes this"
  when 1
    "%s likes this" %names
  when 2
    "%s and %s like this" %names
  when 3
    "%s, %s and %s like this" %names
  else
    "%s, %s and %d others like this" %[*names[0..1], num_people - 2] 
  end
end

p likes [] # must be "no one likes this"
p likes ["Peter"] # must be "Peter likes this"
p likes ["Jacob", "Alex"] # must be "Jacob and Alex like this"
p likes ["Max", "John", "Mark"] # must be "Max, John and Mark like this"
p likes ["Alex", "Jacob", "Mark", "Max"] # must be "Alex, Jacob and 2 others like this"
Collapse
astagi profile image
Andrea Stagi

For Python


def who_likes_this(names: list) -> str:
    who = 'no one'
    number_of_likes = len(names)
    if number_of_likes > 0 and number_of_likes < 3 :
        who = ' and '.join(names)
    elif number_of_likes >= 3:
        who = ', '.join(names[:2])
        who += ' and ' + (names[2] if number_of_likes == 3 else f' and {len(names[2:])} others')
    return f'{who} like{"" if number_of_likes > 1 else "s" } this'

Test it:

print (who_likes_this([]))
print (who_likes_this(["Peter"]))
print (who_likes_this(["Jacob", "Alex"]))
print (who_likes_this(["Max", "John", "Mark"]))
print (who_likes_this(["Alex", "Jacob", "Mark", "Max"]))
print (who_likes_this(["Alex", "Jacob", "Mark", "Max", "Peter"]))
Collapse
rafaacioly profile image
Rafael Acioly

Here's my version :)

from typing import List


def likes(names: List[str]) -> str:
  messages = {
    0: 'no one likes this',
    1: '%s likes this',
    2: '%s and %s like this',
    3: '%s, %s and %s like this'
  }

  message = messages.get(len(names))
  if not message:
    return '%s, %s and %d others like this' % (*names[:2], len(names) - 2)

  return message % tuple(names)
Collapse
djjensen profile image
David Jensen

Javascript one-liner.
The cutoff can be changed.

let summarizeNamesAtNumNames = 4;
const likes = (names) =>
  [
    names.length === 0
      ? "no one"
      : names
          .slice(0, summarizeNamesAtNumNames - 1)
          .reduce(
            (acc, name, index) =>
              acc +
              (index === 0
                ? ""
                : index ===
                  Math.min(names.length - 1, summarizeNamesAtNumNames - 2)
                ? " and "
                : ", ") +
              (names.length >= summarizeNamesAtNumNames &&
              index === summarizeNamesAtNumNames - 2
                ? names.length - (summarizeNamesAtNumNames - 2) + " others"
                : name),
            ""
          ),
    "like" + (names.length <= 1 ? "s" : ""),
    "this",
  ].join(" ");

for (let cut = 2; cut < 7; cut++) {
  summarizeNamesAtNumNames = cut;
  console.log(likes([]));
  console.log(likes(["Peter"]));
  console.log(likes(["Jacob", "Alex"]));
  console.log(likes(["Max", "John", "Mark"]));
  console.log(likes(["Alex", "Jacob", "Mark", "Max"]));
  console.log(likes(["Alex", "Jacob", "Mark", "Max", "Joey", "Joe Joe"]));
  console.log(
    likes(["Alex", "Jacob", "Mark", "Max", "Joey", "Joe Joe", "Shabadoo"])
  );
}
Collapse
alvaromontoro profile image
Alvaro Montoro

Wasn't this in a previous challenge? I recall building this in HTML+CSS...

Collapse
rafaacioly profile image
Rafael Acioly

I think so Alvaro, i've also done this in python...

Collapse
bb4l profile image