DEV Community

Daily Challenge #266 - Who Likes It?

dev.to staff on July 08, 2020

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 tex...
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
 
jpantunes profile image
JP Antunes • Edited

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
 
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
 
aminnairi profile image
Amin • Edited

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
 
astagi profile image
Andrea Stagi • Edited

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 • Edited

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
 
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
 
mxldevs profile image
MxL Devs • Edited

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
 
nombrekeff profile image
Keff

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
Keff

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

Collapse
 
celyes profile image
Ilyes Chouia • Edited

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
 
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
 
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
 
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
 
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
 
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
bb4L
Collapse
 
djjensen profile image
David Jensen • Edited

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
 
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