loading...

Daily Challenge #48 - Facebook Likes

thepracticaldev profile image dev.to staff ・1 min read

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!

Posted on by:

thepracticaldev profile

dev.to staff

@thepracticaldev

The hardworking team behind dev.to ❤️

Discussion

pic
Editor guide
 

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:

 

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

 

Bloody brilliant!

 

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)

 

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"
 

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"
 
 

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
 

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

lol great minds think alike

 

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`;
}
 

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
        ),
    }
}
 

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!

 

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

 

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;
 

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

 

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

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)
 

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

 

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

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.

 

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

 

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),
    }
}
 
    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";   
        }


`

 

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.

 

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
 

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"
 

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);
    }
}
 

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

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") 
 
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';
}
 

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)

 

A bit of functional JS

48-js-code

 

Ooh, a ternary chain. Nice 👍