DEV Community

loading...

Daily Challenge #48 - Facebook Likes

dev.to staff on August 23, 2019

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 examp...
pic
Editor guide
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
jitheshkt profile image
Jithesh. KT

Bloody brilliant!

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
avalander profile image
Avalander

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

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

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

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

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

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
dak425 profile image
Donald Feury

More Go with tests:

likes.go

package likes

import "fmt"

// Likes takes a slice of people who like a post and returns a string indicating who likes it
func Likes(list []string) string {
    likeLen := len(list)

    switch likeLen {
    case 0:
        return "no one likes this"
    case 1:
        return fmt.Sprintf("%s likes this", list[0])
    case 2:
        return fmt.Sprintf("%s and %s like this", list[0], list[1])
    case 3:
        return fmt.Sprintf("%s, %s, and %s like this", list[0], list[1], list[2])
    default:
        return fmt.Sprintf("%s, %s, and %d others like this", list[0], list[1], likeLen-2)
    }
}

likes_test.go

package likes

import (
    "testing"
)

var testCases = []struct {
    description string
    input       []string
    expected    string
}{
    {
        "no likes",
        []string{},
        "no one likes this",
    },
    {
        "one like",
        []string{"Mark"},
        "Mark likes this",
    },
    {
        "two likes",
        []string{"Mark", "Jeff"},
        "Mark and Jeff like this",
    },
    {
        "three likes",
        []string{"Mark", "Jeff", "Bob"},
        "Mark, Jeff, and Bob like this",
    },
    {
        "many likes",
        []string{"Mark", "Jeff", "Bob", "Alice", "Susan"},
        "Mark, Jeff, and 3 others like this",
    },
}

func TestLikes(t *testing.T) {
    for _, test := range testCases {
        if result := Likes(test.input); result != test.expected {
            t.Fatalf("FAIL: %s - Likes(%v): %s, expected: %s \n", test.description, test.input, result, test.expected)
        }
        t.Logf("PASS: %s \n", test.description)
    }
}
Collapse
choroba profile image
E. Choroba

Perl solution. The special array @_ contains the arguments of a subroutine, but in scalar context it returns the number of them.

#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };

sub likes {
    my $count = @_ - 2;
    sprintf((('no one',
              '%s',
              '%s and %s',
              '%s, %s, and %s',
    )[scalar @_] || "%s, %s, and $count others")
    . ' like%s this', @_[0 .. (@_ > 3 ? 1 : $#_)], 's' x (@_ < 2))
}

use Test::More tests => 5;

is likes(), 'no one likes this';
is likes('Peter'), 'Peter likes this';
is likes('Jacob', 'Alex'), 'Jacob and Alex like this';
is likes('Max', 'John', 'Mark'), 'Max, John, and Mark like this';
is likes('Alex', 'Jacob', 'Mark', 'Max'), 'Alex, Jacob, and 2 others like this';

0xford comma included.

Collapse
roninjosue profile image
Reynaldo Josué Cano Bárcenas

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
brightone profile image
Oleksii Filonenko

Rust:

pub fn likes(names: &[&str]) -> String {
    match names {
        [] => String::from("no one likes this"),
        [a] => format!("{} likes this", a),
        [a, b] => format!("{} and {} like this", a, b),
        [a, b, c] => format!("{}, {} and {} like this", a, b, c),
        [a, b, ..] => format!("{}, {} and {} others like this", a, b, names.len() - 2),
    }
}
Collapse
vivek97 profile image
Vivek97
    public String message(List <String> list)
    { 
        switch(list.toArray().length){
        case 0:
            return "no one likes this";
        case 1: 
             return list.get(0)+" likes this";
        case 2: 
             return list.get(0)+" and "+list.get(1)+" like this";
        case 3: 
             return list.get(0)+", "+list.get(1)+" and "+list.get(2)+" like this";
        default: 
             return list.get(0)+","+list.get(1)+" and "+(list.toArray().length-2)+" other like this";   
        }


`

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
citizen428 profile image
Michael Kohl

F#:

let likes names =
    match names with
    | [] -> "no one likes this"
    | [ a ] -> sprintf "%s likes this" a
    | [ a; b ] -> sprintf "%s and %s like this" a b
    | [ a; b; c ] -> sprintf "%s, %s and %s like this" a b c
    | a :: b :: rest -> sprintf "%s, %s and %d others like this" a b rest.Length

Alternatively a point-free version without explicit argument, though it's considered bad style for exported functions:

let likes =
    function
    | [] -> "no one likes this"
    | [ a ] -> sprintf "%s likes this" a
    | [ a; b ] -> sprintf "%s and %s like this" a b
    | [ a; b; c ] -> sprintf "%s, %s and %s like this" a b c
    | a :: b :: rest -> sprintf "%s, %s and %d others like this" a b rest.Length
Collapse
thepeoplesbourgeois profile image
Josh

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"
Collapse
peter279k profile image
peter279k

Here is my simple solution 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
idanarye profile image
Idan Arye

Also Python, but using a different approach:

def likes(users):
    for fmt in [
        'no one likes this',
        '%s likes this',
        '%s and %s like this',
        '%s, %s and %s like this',
    ]:
        try:
            return fmt % tuple(users)
        except TypeError:
            pass
    u1, u2, *rest = users
    return '%s, %s and %s others like this' % (u1, u2, len(rest))
Collapse
b2aff6009 profile image
b2aff6009

Python solution using a dict as switch case. Sadly I didn't had a cool idea about the "default" case wiithout using a default dict (didn't wanted to import anything)

def createString(persons): 
    defFunc = lambda x : "{}, {} and {} others like this".format(x[0], x[1], len(x)-2) 
    converter = {0 : lambda x : "{} likes this".format("no one"), 
                 1 : lambda x : "{} likes this".format(x[0]), 
                 2 : lambda x : "{} and {} like this".format(x[0], x[1]), 
                 3 : lambda x : "{}, {} and {} like this".format(x[0], x[1], x[2])} 
    return converter.get(len(persons), defFunc)(persons) 

assert(createString([]) == "no one likes this") 
assert(createString(["Peter"]) == "Peter likes this") 
assert(createString(["Jacob", "Alex"]) == "Jacob and Alex like this") 
assert(createString(["Max", "John", "Mark"]) == "Max, John and Mark like this") 
assert(createString(["Alex", "Jacob", "Mark", "Max"]) == "Alex, Jacob and 2 others like this") 
Collapse
themisir profile image
Misir Jafarov
function x(likes) {
  return (function (){
    if (likes.length == 0) return 'no one';
    if (likes.length == 1) return likes[0];
    if (likes.length < 4) {
        var b = likes.pop();
            return likes.join(', ') + ' and ' + b;
    }
    return `${likes[0]}, ${likes[1]} and ${likes.length - 2} others`;   
  })() + ' likes this';
}
Collapse
hectorpascual profile image
Héctor Pascual

Python goes there :

def likes(*args):
    return {
        1: f'{args} likes this.',
        2: f'{args[0]} and {args[1]} likes this.',
        3: f'{args[0]}, {args[1]} and {args[2]} likes this.',
        4: f'{args[0]}, {args[1]} and {len(args)-2} others likes this.',
    }.get(len(args) if len(args) < 4 else 4)

Collapse
gnsp profile image
Ganesh Prasad

A bit of functional JS

48-js-code

Collapse
thepeoplesbourgeois profile image
Josh

Ooh, a ternary chain. Nice 👍

Collapse
devparkk profile image