DEV Community

dev.to staff
dev.to staff

Posted on

Daily Challenge #44 - Mexican Wave

The wave (known as the Mexican wave in the English-speaking world outside North America) is an example of metachronal rhythm typically achieved in a packed stadium. Spectators will start a cheer in one corner and then roll it around the arena, with each section rising from its seat as it yells.

Today's challenge is to write a function that turns a string into a Mexican Wave. You will be passed a string and you must return that string in an array where an uppercase letter is a person standing up. The input string will always start lower-case. If the character in the string is whitespace then pass over it.

Ex.
wave("hello") => []string{"Hello", "hEllo", "heLlo", "helLo", "hellO"}


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

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

Top comments (47)

Collapse
 
chrisachard profile image
Chris Achard

Got it to one line of js :)
It's a bit unreadable but... you know 🤣

const wave = str => [...str].map((c, i) => `${str.slice(0, i)}${c.toUpperCase()}${str.slice(i + 1, str.length)}`)
Collapse
 
alfredosalzillo profile image
Alfredo Salzillo

Without use slice

const wave = str => [...str].filter(c => c !== ' ').map((c, i) => Object.values({ ...str, [i]: c.toUpperCase()}).join(''));
Collapse
 
chrisachard profile image
Chris Achard

oh, haha - nice!

Collapse
 
chrisachard profile image
Chris Achard

In my quest for a single line function, I missed the part about skipping the character if it's whitespace... so here's that tacked on too (though I added the filter to the end in order to keep the whole thing to one line) :)

const wave = str => [...str].map((c, i) => `${str.slice(0, i)}${c.toUpperCase()}${str.slice(i +  1, str.length)}`).filter(s => /[A-Z]/.test(s))
Collapse
 
jeddevs profile image
Theo

Always amazes me when people get it into one line. 👍

Thread Thread
 
chrisachard profile image
Chris Achard

map, filter, and reduce are your friend! Whenever I'm dealing with translating one string, array, or object into another one, there's probably a way to do it by just chaining those three methods together.

Thread Thread
 
jeddevs profile image
Theo

Nice, I'm a python man myself and will be sure to check it out! 👍
(I believe map,filter and reduce are available in python as well....)

Thread Thread
 
moopet profile image
Ben Sinclair

Minimising your javascript will get everything onto one line. I'm not sure about why you'd want to do that when showing your code to anyone though.

Collapse
 
moopet profile image
Ben Sinclair

It's only "one line" because you have no whitespace restriction in javascript.

Collapse
 
thepeoplesbourgeois profile image
Josh • Edited

It is also a single method chain. The result of each call flows immediately into the next subsequent call, and the variable space doesn't become overcomplicated, accidentally overwritten, or unnecessarily populated, as a result.

I'm curious which languages hit upon a "whitespace restriction" that would force the answer onto multiple lines? AFAIK it would also be possible to create such a method chain in any of the languages people have implemented their answers in so far. Well, maybe not C++, I have no idea whether arrays there have map, filter, and reduce 🤔

Personally, I wish more people would notice my tight Elixir guard clauses and Erlang VM optimization tricks, but hey, you win some, you lose some ¯\_(ツ)_/¯

Thread Thread
 
moopet profile image
Ben Sinclair

Python?

Mostly I just mean that this is a "daily challenge" to produce a solution to a problem, not a code golf challenge, but answers like this are basically unreadable.

Thread Thread
 
thepeoplesbourgeois profile image
Josh • Edited

Python's got map, filter, and reduce 😶 It's even got comprehensions... If anything, it's easier to do this as a one-liner there than in JS.

And you're still missing the fundamental point... For starters, nobody is looking at strings in modern languages and saying, "Man, you know what's a hard problem that needs solving? Capitalizing exactly one letter at a time within "I am the very model of a modern, major general." If we only could find an engineer who knew how, all of this company's problems would vanish overnight."

And beyond that, the one-line solution can still be broken out into multiple "lines" while retaining the fact that it is one chain of methods, acting upon a stream of data, in a way that allows us to obviate the need for loops, potentially-overwritten variables, and layers of cyclomatic complexity. It's literally just a matter of adding newlines, at any of the method boundaries, in any language you're working in. Chris can rewrite this, trivially, as

const wave = str => [...str]
  .map(...)
  .filter(...) // can't seem to copy-pasta the code from the comment in iOS, might file a bug when the sun is up, in the meanwhile just gonna say the gist is good enough.

All he would need to do — all anybody who felt like this (definitely code-golf challenge) were unreadable would need to do — would be to place their cursor at a spot where they felt would make sense to break the method chain out onto another line, find the appropriate button on their keyboard for creating a line break, and press it. As far as scrolling left and right goes, I've dealt with much, much bigger pains in code than the readability of this, and it's usually been around an actual bug that's manifested from some contrived double for-loop logic, or, you know, the complexities involved in modeling real-world systems within code.

Thread Thread
 
moopet profile image
Ben Sinclair

Chris can rewrite this, trivially, as...

Yes, and that's my point. It can be written in an easy-to-read-and-understand way, if you wanted to write it in a way that people could learn from or maintain.

If it was an interview question, would you obfuscate your answer? It's not, but I bet you wouldn't.

This isn't a golf challenge. It's to see what different people's approaches to the problem might be.

As far as scrolling left and right goes, I've dealt with much, much bigger pains in code than the readability of this

I'm sure we all have, but just because you can come up with some examples of something that's worse doesn't make a good argument for anything. I mean, I don't like squid, but I'm not going to pick it off the menu because my date points out that anchovies are worse when there're whole other pages of options.

Thread Thread
 
chrisachard profile image
Chris Achard

I kind of joked about it being unreadable - but in real-life code, I would have done something very similar. Yes, I would have put the map and filter on different lines, and I probably would have filtered the whitespace first, instead of at the end like I did... but the actual solution would have been pretty close to the same.

I think there are a huge variety of coding styles, and I happen to like using map/filter/reduce combos when I can. I don't think they're unreadable - * as long as everyone on the team is ok with them *. So I think that's a good conversation to have with your team about code style.

Thread Thread
 
thepeoplesbourgeois profile image
Josh • Edited

Okay. Well... maybe you could lighten up, as I'm now just asking you, right out, to do that? However you regard these daily challenges, I promise you that some people are actually doing them to just have a little fun, and some of them are going to be trying to solve them — as some do with golf — in as few strokes as possible.

The nitpicking of other people's code is giving me huge Dwight Schrute vibes, and it's turning daily puzzles into a joyless trial, and there doesn't seem to be any reason for you to need to. Unless we're secretly all in The Last Starfighter r/n.

 
thepeoplesbourgeois profile image
Josh • Edited

EDIT: replying to the wrong comment, sorry 😅

Thread Thread
 
chrisachard profile image
Chris Achard

😅

Collapse
 
chrisachard profile image
Chris Achard

Yep, very true

Collapse
 
alvaromontoro profile image
Alvaro Montoro

CSS

This is not exactly what is requested in the challenge, but close (at least for CSS). The letters need to be wrapped on their own span, and then add "wave" to the parent element. An animation is added that transform one letter at a time into uppercase (not exactly an array, sorry, and it heavily depends on length):

@keyframes mexicanWaveText {
  0%, 20%, 100% { text-transform: lowercase; }
  10% { text-transform: uppercase; }
}

.wave span {
  text-transform: lowercase;
  animation: mexicanWaveText 11s infinite;
}

.wave span:nth-child(1n) { animation-delay: -10s; }
.wave span:nth-child(2n) { animation-delay: -9s; }
.wave span:nth-child(3n) { animation-delay: -8s; }
.wave span:nth-child(4n) { animation-delay: -7s; }
.wave span:nth-child(5n) { animation-delay: -6s; }
.wave span:nth-child(6n) { animation-delay: -5s; }
.wave span:nth-child(7n) { animation-delay: -4s; }
.wave span:nth-child(8n) { animation-delay: -3s; }
.wave span:nth-child(9n) { animation-delay: -2s; }
.wave span:nth-child(10n) { animation-delay: -1s; }
.wave span:nth-child(11n) { animation-delay: -0s; }

Here is a demo (with some other animations too):

Collapse
 
fanfan1609 profile image
Dat Vo

CSS solution is amazing :D

Collapse
 
necrotechno profile image
NecroTechno

In Rust, although there is almost certainly a more efficient way of doing this.

fn main() {
    let wave = wave("hello");
    for (_i, a) in wave.into_iter().enumerate() {
        println!("{}", a)
    }
}

fn wave(input: &str) -> Vec<String> {
    let mut result = Vec::new();
    for (i, _c) in input.chars().enumerate() {
        let mut subresult = Vec::new();
        for (ind, cha) in input.chars().enumerate() {
            if i == ind {
                subresult.push(cha.to_uppercase().to_string());
            } else {
                subresult.push(cha.to_lowercase().to_string());
            }
        }
        result.push(subresult.into_iter().collect::<String>());
    }
    return result
}

Playground link here.

Collapse
 
serbuvlad profile image
Șerbu Vlad Gabriel • Edited

You can use concatenation to avoid a character by character copy.

...
    for (i, c) in input.chars().enumerate() {
        let upper = c.to_uppercase().to_string();
        let subresult = input[..i].to_string() + &upper + &input[i+upper.len()..];
        result.push(subresult);
    }
...

I don't know the first thing about Rust, so this is probably not optimal either.

Playgrund link here

Collapse
 
necrotechno profile image
NecroTechno

Nice!

Collapse
 
lordapple profile image
LordApple • Edited

c++

#include <iostream>
#include <vector>

std::vector<std::string> wave(std::string str){
    std::vector<std::string> returnVal;

    for(unsigned long i = 0; i < str.size(); ++i){
        if(str[i] < 'A' || str[i] > 'z'){
            continue;
        }
        std::string copy = str;
        copy[i] -= 32;
        returnVal.push_back(copy);
    }

    return returnVal;
}

int main(){
    for(const auto& word : wave("hello world")){
        std::cout << word << std::endl;
    }

    return 0;
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
lainofthewired profile image
Lain-of-the-Wired

That's crazy.

Collapse
 
salma0110 profile image
salma0110

from where did you learn that u should use 32 , i didn't know that before
Thank you

Collapse
 
matrossuch profile image
Mat-R-Such • Edited

Python

def wave(string):
    s=[i for i in string]
    for i in range(len(s)):
        s[i] = s[i].upper()
        yield ''.join(s)
        s = [i for i in string]

for i in wave('hello'):
    print(i)
Collapse
 
aminnairi profile image
Amin

JavaScript

function charactersIndexes(string) {
  return string.split("").reduce(function(indexes, character, index) {
    if (!/\s/.test(character)) {
      indexes.push(index);
    }

    return indexes
  }, []);
}

function upperCaseAt(index, string) {
  return string.split("").map(function(character, position) {
    if (position === index) {
      return character.toUpperCase();
    }

    return character;
  }).join("");
}

function wave(input) {
  return charactersIndexes(input).map((index) => upperCaseAt(index, input));
}

console.log(wave("hello"));
// [ 'Hello', 'hEllo', 'heLlo', 'helLo', 'hellO' ]

console.log(wave("h e l l o"));
// [ 'H e l l o', 'h E l l o', 'h e L l o', 'h e l L o', 'h e l l O' ]
Enter fullscreen mode Exit fullscreen mode

Playground

Play with it on Repl.it.

Collapse
 
ielena33 profile image
ielena

expect(received).toEqual(expected) // deep equality

- Expected  - 5
+ Received  + 5

  Array [
-   "Hello",
-   "hEllo",
-   "heLlo",
-   "helLo",
-   "hellO",
+   0,
+   1,
+   2,
+   3,
+   4,
  ]

  26 |     test("returns a mexican wave", () => {
  27 |         expect(mexicanWave("")).toEqual([]);
> 28 |         expect(mexicanWave("hello")).toEqual(["Hello", "hEllo", "heLlo", "helLo", "hellO"]);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
brightone profile image
Oleksii Filonenko

Rust, with iterators:

pub fn wave(input: &str) -> Vec<String> {
    use std::iter;

    iter::repeat(input)
        .take(input.len())
        .enumerate()
        .map(|(i, part)| {
            part.chars()
                .enumerate()
                .map(|(j, c)| if i == j { c.to_ascii_uppercase() } else { c })
                .collect::<String>()
        })
        .collect::<Vec<_>>()
}
Collapse
 
hectorpascual profile image

My python sol :

def wave(s):
    wave_list = []
    for i,c in enumerate(s):
        wave_list.append(s[:i]+c.upper()+s[i+1:])
    return wave_list

In one line with a generator and a lambda function :

wave = lambda s : [f'{s[:i]}{c.upper()}{s[i+1:]}' for i,c in enumerate(s)]
Collapse
 
jeddevs profile image
Theo

Love it

Collapse
 
alvaromontoro profile image
Alvaro Montoro

Funny thing. The other day I was creating a wave animation using text in CSS. It used font-weights instead of case change, but updating it to adapt to this challenge should be fairly easy. Although it wouldn't return an array, but generate an animation instead.

Collapse
 
jay profile image
Jay

Rust "one-liner" Playground

fn wave(s: &str) -> Vec<String> {
    (0..s.len())
        .map(|i| {
            s.chars()
                .enumerate()
                .map(|(j, c)| if j == i { c.to_ascii_uppercase() } else { c })
                .collect::<String>()
        })
        .filter(|strs| !(strs.chars().all(|c| c.is_lowercase() || c.is_whitespace())))
        .collect()
}
Collapse
 
serbuvlad profile image
Șerbu Vlad Gabriel • Edited

Go:

func mexicanWave(s string) []string {
    if !utf8.ValidString(s) {
        panic("invalid string")
    }   
    // capacity can be replaced with len(s) if iterating over
    // the string twice is a bigger problem than allocating too
    // much memory
    ss := make([]string, 0, utf8.RuneCountInString(s))
    for i, r := range s {
        u := unicode.ToUpper(r)
        ss = append(ss, s[:i] + string(u) + s[i+utf8.RuneLen(r):])
    }
    return ss
}

Play with it here.

Collapse
 
thepeoplesbourgeois profile image
Josh • Edited

As an Elixir function, including documentation! And a language feature I'm writing about in an upcoming post UPDATE: the post is up!! 😃

defmodule W do
  @doc ~S"""
  Takes in a string, ensures it's downcased, converts it to a charlist,
  and then processes the charlist by subtracting the proper amount from
  the `next` raw byte to arrive at the same capitalized character in the
  ASCII chart. This letter is then inserted into an iodata list in
  between the raw bytes that have already been `waved` through and the
  `rest` of the raw bytes. This iodata list is then converted back to a
  string, which is added to the head of a list of strings that represent
  the in-motion version of the wave. To finalize the in-motion wave, it
  is reversed once the input charlist has been fully processed.

  When `next` is not a lowercase letter, it is added to `waved` without
  adding a new string to `waves`.

  ## Examples

      iex>W.ave("HELLO")
      ["Hello", "hEllo", "heLlo", "helLo", "hellO"]

      iex>W.ave("hello world!")
      ["Hello world!", "hEllo world!", "heLlo world!", "helLo world!", "hellO world!",
       "hello World!", "hello wOrld!", "hello woRld!", "hello worLd!", "hello worlD!"]
  """
  def ave(string) do
    string
      |> String.downcase
      |> String.to_charlist
      |> do_wave
  end

  @ascii_upcase_val 32 # ?a - ?A

  defp do_wave(waving, waves \\ [], waved \\ '')
  defp do_wave([] = _done_waving, waves, _waved), do: Enum.reverse(waves)
  defp do_wave([next | rest], waves, waved) when next in ?a..?z do
    uppercase = next - @ascii_upcase_val
    do_wave(rest, [IO.iodata_to_binary([waved, uppercase, rest]) | waves], [waved, next])
  end
  defp do_wave([next | rest], waves, waved), do: do_wave(rest, waves, [waved, next])
end
Collapse
 
willsmart profile image
willsmart • Edited

A little JS impl using a regex and matchAll (avail in recent browsers):

wave = crowd => [...crowd.matchAll(/[a-z]/g)].map(({0:match, input, index}) => input.substring(0,index) + match.toUpperCase() + input.substring(index+1))

Output:

> JSON.stringify(wave("go our team"),null,2)
< "[
  "Go our team",
  "gO our team",
  "go Our team",
  "go oUr team",
  "go ouR team",
  "go our Team",
  "go our tEam",
  "go our teAm",
  "go our teaM"
]"
Collapse
 
craigmc08 profile image
Craig McIlwrath • Edited
import Data.Char (toUpper, toLower) 

mexicanWave a=[[($(a!!j))(if j==i then toUpper else toLower)|j<-[0..(length a-1)]]|i<-[0..length a-1]]

One long, ugly line of haskell. (I guess 2 including the import)

Edit: missed note about skipping whitespace. Will fix it soon

Collapse
 
vivek97 profile image
Vivek97

JAVA

public String [] wave(String input)
{
 String [] array = new String[input.length()];

 for(int i=0; i<input.length();i++)
    {
     if(!input.substring(i,i+1).equals(" "))
        { input = input.toLowerCase();              
        array[i]= input.substring(0,i) + input.substring(i, i+1).toUpperCase()+input.substring(i+1);            
        System.out.print(array[i]+", ");
    }   
    }
 return array;  
}