loading...

Daily Challenge #44 - Mexican Wave

thepracticaldev profile image dev.to staff ・1 min read

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!

Posted on by:

thepracticaldev profile

dev.to staff

@thepracticaldev

The hardworking team behind dev.to ❀️

Discussion

markdown guide
 

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

Without use slice

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

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

Always amazes me when people get it into one line. πŸ‘

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.

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

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.

 

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

 

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 Β―\_(ツ)_/Β―

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.

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.

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.

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.

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.

EDIT: replying to the wrong comment, sorry πŸ˜…

 
 

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

 
 

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.

 

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

 
 

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

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

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

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

Playground

Play with it on Repl.it.

 

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.

 

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

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.

 

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)
 

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
 

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

 

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;  
}
 
public void wave(String input)
{
 char[] arr = input.toCharArray();
  for(int i=0; i<arr.length;i++)
    {
     if(!(arr[i]==' '))
        {
        arr[i] = Character.toUpperCase(arr[i]);
        System.out.print(String.valueOf(arr)+", ");
        arr[i] = Character.toLowerCase(arr[i]);
       }
    }
}
 
public void wave(String input)
{
 char[] arr = input.toCharArray();
  for(int i=0; i<arr.length;i++)
    {
     if(!(arr[i]==' '))
        {
        arr[i] = Character.toUpperCase(arr[i]);
        System.out.print(String.valueOf(arr)+", ");
        arr[i] = Character.toLowerCase(arr[i]);
       }
    }
}
 
 

Nice, I have been having issues with repl loading up.
Not the website or even an repl page just the code within it,
getting that dreaded: Failed to connect, retrying message.
Then after about 5 minutes it loading, have you been having similar?
(sorry for being a pain)

 

Sorry I haven't had that kind of issue. It's ok

 

Nice! The runtime complexity is pretty substantial, but it's a really good first pass on the problem πŸ‘

 

ruby <3

def wave(s)
  s.size.times.filter_map { |i| s[i] =~ /\S/ && s.dup.tap { @1[i] = @1[i].upcase } }
end

puts wave("hello world")
 

C#

var wave = str.Select((c, i)=> str.Substring(0, i) + str.Substring(i, 1).ToUpper() + str.Substring(i+1, str.Length-i-1)).Where(s => !s.All(c => c.ToString() == c.ToString().ToLower()));