DEV Community

dev.to staff
dev.to staff

Posted on

Daily Challenge #48 - Facebook Likes

In today's challenge, you are asked to create a like system from Facebook; a text which displays the names of people who liked an item.

For example:

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"

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


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

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

Top comments (35)

Collapse
 
alvaromontoro profile image
Alvaro Montoro

CSS

This is a "don't do this at home" type of solution. Will it work? Yes. Is is worth it? We can all agree that CSS is always worth it (:P), but in this case, it may end up with a ton of unnecessary HTML code.

The idea is to use a ul/ol to represent the list of people, and with counters keep track of the number of likes (aka people in the list). Then with pseudo-elements display the appropriate message or connector.

ul, ol { 
  display: block;
  list-style: none;
  padding-left: 0;
  clear: both;
  height: 40px;
  line-height: 40px;
  counter-reset: people -2;
}

li {
  display: inline-block;
  float: left;
  font-size: 1rem;
  counter-increment: people;
}

/* empty list */
ul:empty::before,
ol:empty::before {
  content: "No one likes this post.";
}

/* one element only */
li:first-child:last-child::after {
  content: " likes this post.";
}

/* separate all names by commas */
li:not(:first-child)::before {
  content: ", ";
}

/* the last name (or from the third one) will end the list */
li:nth-child(n + 3)::before,
li:last-child:not(:first-child)::before {
  content: "\00a0 and ";
}

/* add message for multiple names */
li:nth-child(n+3)::after,
li:last-child::after {
  content: " like this post.";
}

/* from the 4th element forth, they must be hidden */
li:nth-child(n+3):not(:last-child) {
  font-size: 0;
}

/* the last element in a list of 4 or more is special */
li:nth-child(n+4):last-child::before {
  font-size: 1rem;
  content: "\00a0 and " counter(people) " others like this post.";
}
li:nth-child(n+4):last-child {
  font-size: 0;
}
li:nth-child(n+4):last-child::after {
  content: "";
}

And here is a demo on codepen:

Collapse
 
alvaromontoro profile image
Alvaro Montoro

Thanks to this challenge, I learnt that CSS counters don't increment in elements with display: none. Which is nice.

Collapse
 
alvaromontoro profile image
Alvaro Montoro

Also, I'm more of an Oxford-comma type of person, but the challenge didn't have it. (That is my excuse and I'll stick to it)

Collapse
 
jitheshkt profile image
Jithesh. KT

Bloody brilliant!

Collapse
 
avalander profile image
Avalander • Edited

Some simple pattern matching with Haskell.

likes :: [String] -> String
likes []        = "no one likes this"
likes [a]       = a ++ " likes this"
likes [a, b]    = a ++ " and " ++ b ++ " like this"
likes [a, b, c] = a ++ ", " ++ b ++ " and " ++ c ++ " like this"
likes (a:b:xs)  = a ++ ", " ++ b ++ " and " ++ (show $ length xs) ++ " others like this"
Collapse
 
hanachin profile image
Seiei Miyagi • Edited

ruby <3

def likes(ls)
  case ls
  in []
    "no one likes this"
  in [a]
    format "%s likes this", a
  in [a, b]
    format "%s and %s like this", a, b
  in [a, b, c]
    format "%s, %s and %s like this", a, b, c
  in [a, b, *rest]
    format "%s, %s and %d others like this", a, b, rest.size
  end
end

p likes([]) # => "no one likes this"
p likes(["Peter"]) # => "Peter likes this"
p likes(["Jacob", "Alex"]) # => "Jacob and Alex like this"
p likes(["Max", "John", "Mark"]) # => "Max, John and Mark like this"
p likes(["Alex", "Jacob", "Mark", "Max"]) # => "Alex, Jacob and 2 others like this"
Collapse
 
ben profile image
Ben Halpern

Nice!

Collapse
 
hanachin profile image
Seiei Miyagi

I can't wait ruby 2.7 release!

git clone git@github.com:ruby/ruby.git
cd ruby
autoconf
./configure optflags="-O0" debugflags="-g3" --prefix="$HOME/.rbenv/versions/master"
make && make install
rbenv global master
Collapse
 
chrisachard profile image
Chris Achard

JS, with each case defined. I went for straightforward, though there's probably room to condense this somehow given the repetition:

const likes = 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`
  }
}
Collapse
 
jasman7799 profile image
Jarod Smith

lol great minds think alike

Collapse
 
andre000 profile image
André Adriano • Edited

Using destructuring with JS

const likeText = (likes = []) => {
    if(!likes.length) return 'no one likes this';

    const [first, second, third, ...rest] = likes
    if(!second) return `${first} likes this`;
    if(!third) return `${first} and ${second} like this`;
    if(!rest.length) return `${first}, ${second} and ${third} like this`;
    return `${first}, ${second} and ${rest.length + 1} others like this`;
}
Collapse
 
jay profile image
Jay

Rust Playground

fn fb_likes(names: &[&str]) -> String {
    match names.len() {
        0 => "no one likes this".to_string(),
        1 => format!("{} likes this", names[0]),
        2 => format!("{} and {} like this", names[0], names[1]),
        3 => format!("{}, {} and {} like this", names[0], names[1], names[2]),
        _ => format!(
            "{}, {} and {} others like this",
            names[0],
            names[1],
            names.len() - 2
        ),
    }
}
Collapse
 
ynndvn profile image
La blatte

And a bit of golf with the Intl.ListFormat tool!

f=(n)=>(b=n.length<2,a=new Intl.ListFormat`en-GB`,(n.length>3?a.format([n[0],n[1],n.length-2+' others']):a.format(n.length?n:['no one']))+` like${b?'s':''} this`)

Here is the output:

f([])
"no one likes this"
f(["Peter"])
"Peter likes this"
f(["Jacob", "Alex"])
"Jacob and Alex like this"
f(["Max", "John", "Mark"])
"Max, John and Mark like this"
f(["Alex", "Jacob", "Mark", "Max"])
"Alex, Jacob and 2 others like this"

Basically, with a bit of wibbly wobbly trickery, depending on the input length, we either call the format function with ["no one"], the complete input, or the two first elements followed by the remaining quantity of items. Finally, we add (or not) an s to the like, and return the built string!

Collapse
 
fennecdjay profile image
Jérémie Astor

Trying with Gwion

fun void likes(string array[]) {
  array.size() => const int sz;
  switch(sz) {
    case 0:
      <<< "no one", " likes this" >>>;
      break;
    case 1:
      <<< array[0], " likes this" >>>;
      break;
    case 2:
      <<< array[0], " and ", array[1], " like this" >>>;
      break;
    case 3:
      <<< array[0], ", ", array[1], " and ", array[2], " like this" >>>;
      break;
    default:
      <<< array[0], ", ", array[1], " and ", sz -2, " others", " like this" >>>;
      break;
  }
}

[ "Peter" ] => likes;
[ "Jacob", "Alex" ] => likes;
[ "Max", "John", "Mark" ] => likes;
[ "Alex", "Jacob", "Mark", "Max" ] => likes;
Collapse
 
jasman7799 profile image
Jarod Smith
function likes(people) {
  switch (people.length) {
    case 0:
      return 'no one likes this';
    case 1:
      return `${people[0]} likes this`;
    case 2:
      return `${people[0]} and ${people[1]} like this`;
    case 3:
      return `${people[0]}, ${people[1]} and ${people[2]} like this`;
    default:
      return `${people[0]}, ${people[1]} and ${people.length - 2} others like this`;
  }
}

Thank god for template strings.

Collapse
 
celyes profile image
Ilyes Chouia • Edited

PHP

$likes = ["Alex", "John", "Jeremiah", "David", "Travis"];
$message = "";
switch ($likes){
    case (!isset($likes)):
        $message = "no one likes this";
    break;
    case (sizeof($likes) == 1):
        $message = "$likes[0] likes this";
    break;
    case (sizeof($likes) == 2):
        $message = "$likes[0] and $likes[1] like this";
    break;
    case (sizeof($likes) == 3):
        $message = "$likes[0], $likes[1] and $likes[2] like this";
    break;
    case (sizeof($likes) > 3):
        $message = "$likes[0], $likes[1] and ". ( sizeof($likes) - 2 ) . " others like this";
    break;
    default: 
        $message = "no one likes this";
}
echo $message;
Collapse
 
kvharish profile image
K.V.Harish • Edited

Late to the party and I can see people have already posted a similar solution but just going to post anyway. Sometimes keeping it simple is OK :)

My simple solution in js


const displayLikes = (likes) => {
  let msg;
  switch(likes.length) {
    case 0:
      msg = `no one likes this`
      break;
    case 1:
      msg = `${likes[0]} likes this`;
      break;
    case 2:
      msg = `${likes[0]} and ${likes[1]} likes this`;
      break;
    case 3:
      msg = `${likes[0]}, ${likes[1]} and ${likes[2]} likes this`;
      break;
    default:
      msg = `${likes[0]}, ${likes[1]} and ${likes.length - 2} others likes this`;
      break;
  }
  return msg;
};

Collapse
 
rafaacioly profile image
Rafael Acioly • Edited

At first i've used a lot of if statement then i got excited and tried another point

Python version (using as fewer ifs as possible):

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

  likes_quantity = len(persons)
  content = messages.get(likes_quantity) or messages[4]

  if likes_quantity > 3:
    return content % (persons[0], persons[1], len(persons[2:]))

  return content % (tuple(persons))
Collapse
 
idanarye profile image
Idan Arye

Possible improvements: instead of

content = messages.get(likes_quantity) or messages[4]

You can write:

content = messages.get(likes_quantity, messages[4])

Or - if you want to get fancy:

content = messages[min(likes_quantity, 4)]

Or even:

from collections import defaultdict

messages = defaultdict(
    lambda: '%s, %s and %d others like this',
  {
    0: 'no one likes this',
    1: '%s like this',
    2: '%s and %s like this',
    3: '%s, %s and %s like this',
  })

likes_quantity = len(persons)
content = messages[likes_quantity]

But of course - all of this is redundant, since you already check for likes_quantity > 3. So how about this:

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

    likes_quantity = len(persons)

    if likes_quantity > 3:
        return '%s, %s and %d others like this' % (
            persons[0], persons[1], len(persons[2:]))

    return messages[likes_quantity] % tuple(persons)
Collapse
 
rafaacioly profile image
Rafael Acioly

Nice, it was in fact redundant getting the message content, thanks for sharing :D

Collapse
 
roninjosue profile image
Reynaldo Josué Cano Bárcenas • Edited

My code in JS

let peoples = [];

let people_likes = (p) => {
let tam = p.length;
if(tam === 0){
console.log("no one likes this");
}
else {
if(tam < 4){
console.log(${p.join(", ")} likes this);
} else {
console.log(${p.slice(0, 2).join(", ")} and ${tam - 2} others like this);
}
}
};

people_likes(peoples);
peoples.push("Peter");
people_likes(peoples);
peoples.push("Jacob");
people_likes(peoples);
peoples.push("Mark");
people_likes(peoples);
peoples.push("Alex");
people_likes(peoples);
peoples.push("Reynaldo");
people_likes(peoples);

Collapse
 
aminnairi profile image
Amin

My take at the challenge written in Elm.

Source-code

module Main exposing (main)

import Html exposing (Html, p, text, div)

likes : List String -> String
likes names =
    case names of
        [] ->
            "no one likes this"

        name :: [] ->
            name ++ " likes this"

        first :: second :: [] ->
            first ++ " and " ++ second ++ " like this"

        first :: second :: third :: [] ->
            first ++ ", " ++ second ++ " and " ++ third ++ " like this"

        first :: second :: rest ->
            first ++ ", " ++ second ++ " and " ++ (String.fromInt <| List.length rest) ++ " others like this"

main : Html actions
main =
    div [] [ p [] [ text <| likes [] ] -- no one likes this
           , p [] [ text <| likes [ "Peter" ] ] -- Peter likes this
           , p [] [ text <| likes [ "Jacob", "Alex" ] ] -- Jacob and Alex like this
           , p [] [ text <| likes [ "Max", "John", "Mark" ] ] -- Max, John and Mark like this
           , p [] [ text <| likes [ "Alex", "Jacob", "Mark", "Max" ] ] -- Alex, Jacob and 2 others like this
           ]

Try it online

On Ellie App.

Collapse
 
thepeoplesbourgeois profile image
Josh • Edited

I question the validity of this challenge. No Oxford comma??? 🧐

But on with it...

defmodule Liked do
  def by([]), do: "no one likes this"
  def by([friend]), do: "#{friend} likes this"
  def by([friend_1, friend_2]), do: "#{friend_1} and #{friend_2} like this"
  def by([f1, f2, f3]), do: "#{f1}, #{f2} and #{f3} like this"
  def by([friend_1, friend_2 | friends]), do: "#{friend_1}, #{friend_2} and #{length(friends)} others like this"

end

Liked.by([])
# "no one likes this"
Liked.by(["the strippers"])
# "the strippers like this"
Liked.by(["the strippers", "JFK"])
#"the strippers and JFK like this"
Liked.by(["the strippers", "JFK", "Stalin"])
# "the strippers, JFK and Stalin like this
america = List.duplicate("american", 300000000) # p expensive, don't actually make this large of a list
Liked.by(["the strippers", "JFK", "Stalin" | america])
# "the strippers, JFK and 300000001 others like this"