Daily Challenge #29 - Xs and Os

twitter logo ・1 min read

Part of "Daily Challenge" series

Can you check to see if a string has the same amount of 'x's and 'o's?

Examples input/output:

XO("ooxx") => true
XO("xooxx") => false
XO("ooxXm") => true
XO("zpzpzpp") => true // when no 'x' and 'o' is present should return true
XO("zzoo") => false

Note: The method must return a boolean and be case insensitive. The string can contain any character


This challenge comes from user joh_pot. 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!

twitter logo DISCUSS (48)
markdown guide
 

Haskell again

import Data.Char (toLower)

compare_xos :: String -> Bool
compare_xos = same . foldl kevin (0, 0)
  where
    kevin (x, o) c
      | toLower c == 'x' = (x + 1, o)
      | toLower c == 'o' = (x, o + 1)
      | otherwise        = (x, o)
    same (x, o) = x == o
 
 
 

I can't do anything useful with it yet, but it's fun to solve coding katas :D.

 

Very easy (and rather fast) in Perl:

sub xo { local ($_) = @_; tr/xX// == tr/oO// }

It uses the transliteration operator which returns the number of matching characters in scalar context, and numeric comparison imposes scalar context.

The tests:

use Test::More;

ok   xo('ooxx');
ok ! xo('xooxx');
ok   xo("ooxXm");
ok   xo("zpzpzpp");
ok ! xo("zzoo");

done_testing();
 

Perl sorcery 😁. This syntax always turns me inside out hehe. Nice job πŸ‘

 

Sure, but this one is pretty easy to demystify, luckily: tr/set1/set2/ just replaces characters in set1 with those in set2 (positionally), and returns the number of characters replaced/deleted. So in this code, he just compares the number of X's replaced with the number of O's replaced, and includes the lower-case variants too.

The local($_) is just there to let him make the code more brief. $_ is the "default input/pattern-searching space", so its like the default argument that tr will search. But it's also a global, so this lets you make a local copy and set it to be the first argument to the function.

If he didn't do that, it might look like this:

sub xo { my $xos = shift; ($xos =~ tr/xX//) == ($xos =~ tr/oO//) }

So just a bit more verbose.

 

JavaScript

const xo = s => (s.match(/x/gi) || []).length == (s.match(/o/gi) || []).length;

A solution using regular expressions in JavaScript. Live demo on CodePen.

 

Oh look, you had already come up with the same idea as I did!

 

Yes. Although mine looks a bit uglier because I ran into an issue: if the string doesn't have Xs or Os, the result of match is null so it fails when trying to do .length (test checkXO("zpzpzpp") on your code).

I had to add the fallback into empty array for those cases. But I feel like there has to be a cleaner way of doing the same thing.

Yeah, I found the same issue when I tried to solve it in Codewars. So I had to do the same. Looking through other people's solutions, there's one that I find interesting that uses split() instead of a regular expression. Something like this:

const xo = str => str.toLowerCase().split('x').length == str.toLowerCase().split('y').length

The split approach is interesting. I was playing with different options, but not with this. Also, I found that using toLowerCase() made the code considerably slower, it works considerably faster if you use a regular expression in the split:

const xo6 = str => str.split(/x/i).length == str.split(/o/i).length;

I think this is my favourite solution. I hadn't checked its speed, but I thought that toLowerCase() probably didn't make it any smoother than returning an empty array if there are no matches.

I checked different solutions with jsperf and this was the second fastest by a little (but results may vary.)

 

C 😱 don't kill me please

#include <stdio.h>
#include <string.h>
#include <stdbool.h>

// bool as string
#define BOOL_STR(b) b ? "true" : "false"

// X/x check
static bool check_x(char d) {
    if (d == 88 || d == 120) return true;
    return false;
}

// O/o check
static bool check_o(char d){
    if (d == 79 || d == 111) return true;
    return false;
}

static bool count_xo(const char* d){
    // null pointer
    if(!d) return true;
    // get size
    size_t l = strlen(d);
    // no data
    if(!l) return true;
    // res counters
    unsigned xc = 0, oc = 0;
    // mid point
    unsigned mp = l / 2, rm = l % 2;
    // check for 88 (X) and 79 (O)
    // O(N/2)
    for(unsigned i = 0, j = l - 1; i < mp; i++, j--){
        // X counter
        xc += check_x(d[i]) + check_x(d[j]);
        // O counter
        oc += check_o(d[i]) + check_o(d[j]);
    }
    // remainder
    if(rm){
        // X counter
        xc += check_x(d[mp + 1]);
        // O counter
        oc += check_o(d[mp + 1]);
    }

    // res
    return (xc == oc ? true : false);
}

int main(void) { 
    // test strings
    const char* str_00 = "ooxx";
    const char* str_01 = "xooxx";
    const char* str_02 = "ooxXm";
    const char* str_03 = "zpzpzpp";
    const char* str_04 = "zzoo";
    // results
    printf("%s: %s\n", str_00, BOOL_STR(count_xo(str_00)));
    printf("%s: %s\n", str_01, BOOL_STR(count_xo(str_01)));
    printf("%s: %s\n", str_02, BOOL_STR(count_xo(str_02)));
    printf("%s: %s\n", str_03, BOOL_STR(count_xo(str_03)));
    printf("%s: %s\n", str_04, BOOL_STR(count_xo(str_04)));
    // no error
    return 0; 
}

Results:

ooxx: true
xooxx: false
ooxXm: true
zpzpzpp: true
zzoo: false
 

I feel like the C might've already done you in? dang, that's some verbose low-level lang 😢

 

Well that's C, just how it is, you either hate it or you love it 😁. I missed a comma there; what I wanted to say was: guys, please don't kill me, this is C and it's gonna get ugly.

 

Javascript (pretty readable):

const makeArrayOfGivenChar = (text, givenChar) => text.split('').filter(c => c.toLowerCase() === givenChar.toLowerCase())

function XO(str) {
  const x = makeArrayOfGivenChar(str, 'x')
  const o = makeArrayOfGivenChar(str, 'o')
  return x.length === o.length
}
 

Well done! I like a one-liner and clever answers as much as the next person HOWEVER I also love seeing elegant, more readable, and even sometimes simplified (broken down more step by step) solutions!

Awesome job!

 

Thank you Jacob! I have the same opinion! πŸ‘

 

How about some ugly oneliner?

XO=p=>!(new Set(p.toLowerCase().split``.reduce((l,i)=>[l[0]+(i=='x'),l[1]+(i=='o')],[0,0])).size-1)

Which produces the following output:

XO("ooxx"); // true
XO("xooxx"); // false
XO("ooxXm"); // true
XO("zpzpzpp"); // true
XO("zzoo"); // false

It basically works by lowercasing everything and building an array with the number of Xs and Os. After that, it builds a Set with it, and check if the Set size is 1 or 2!

 

Not a pretty solution, but it seems to work in Ruby

def gossip_girl(string)
  new_string = string.downcase

  if new_string.include?("x") == false && new_string.include?("o") == false
    true
  elsif new_string.include?("x") == true && new_string.include?("o") == true
    x_count = new_string.count("x")
    o_count = new_string.count("o")

    if x_count == o_count
      true
    else
      false
    end
  else
    false
  end
end

Returns

gossip_girl("ooxx") => true 
gossip_girl("xooxx") => false 
gossip_girl("ooxXm") => true 
gossip_girl("zpzpzpp") => true 
gossip_girl("zzoo") => false 
 

WAIT. Here's a much cleaner solution in Ruby.

def gossip_girl(string)
  string.downcase.count("x") == string.downcase.count("o")
end

Which returns

gossip_girl("ooxx") => true 
gossip_girl("xooxx") => false 
gossip_girl("ooxXm") => true 
gossip_girl("zpzpzpp") => true 
gossip_girl("zzoo") => false 

This is why you refactor!

 

It turns out that you could avoid the #downcase with string.count("xX") == string.count("oO")

I don't know whether the performance would be significantly different, though it would avoid creating a couple of extra string instances.

There are some other interesting variations for #count, such as using it to count lower case letters with "abGvf".count("a-z")

 

Very nice! I love your choice of method name πŸ˜‚ And it's great that you went back to refactor once you realized there were optimizations you could make. No matter what "stakeholders" say about features driving revenue, any developer worth their salt knows that a clean codebase drives features. πŸ‘πŸ‘πŸ™Œ

Thanks. I can't think of X's and O's without Gossip Girl.

Clean codebases are so much easier to work with. Not to mention, cost and performance savings.

 
 

How about a reduce to keep it in one loop? (JS)

XO = str => 
  [...str].reduce(
    (acc, c) => acc + (c == 'x' || c == 'X') - (c == 'o' || c == 'O'),
    0
  ) == 0;

// Check it
["ooxx","xooxx","ooxXm","zpzpzpp","zzoo"].map(s =>
  `${s} => ${XO(s)}`
).join('\n');
/* ^
"ooxx => true
xooxx => false
ooxXm => true
zpzpzpp => true
zzoo => false"
*/
 

Oh, nice! I wouldn't have thought to use the array spread syntax to get the array of characters. 🎩✨

 

This is my solution, I'm still learning javascript so nothing fancy

function XO(string) {
  const strArr = string.toLowerCase().split('');
  let os = 0, xs = 0;
  strArr.forEach(word => {
    switch (word) {
      case 'o': 
        os++;
        break;
      case 'x': 
        xs++;
        break;
    }
  });
  return os === xs;
}
 

JavaScript:

const XO = (string) => {
    const chars = string.toLowerCase().replace(/[^xo]/g, '').split('');
    const reducer = (accumulator, char) => char === 'x' ? ++accumulator : --accumulator;

    return chars.reduce(reducer, 0) === 0;
};

or (less readable):

const XO = (string) => {
  return (string.match(/[xo]/gi) || []).reduce((acc, char) => char === 'x' ? ++acc : --acc, 0) === 0;
};
 

Elixir:

defmodule XO do
  def same(string),
    do: count(string, "x") == count(string, "o")

  defp count(string, letter) do
    string
    |> String.graphemes()
    |> Enum.filter(&(&1 == letter))
    |> Enum.count()
  end
end
 

I broke out the work a little more, I tried to be a tad clever and utilize switch but wasn't able to get it to work quite the way I wanted.

Hopefully, the slightly more vanilla JS will be useful in some way.

function XO(s) {
  const arr = [...s];
  let countX = 0;
  let countO = 0;
  arr.map((ele, i) => {
    if (ele === `x`) {
      countX++;
      delete arr[i];
    }
    if (ele === `X`) {
      countX++;
      delete arr[i];
    }
    if (ele === `o`) {
      countO++;
      delete arr[i];
    }
    if (ele === `O`) {
      countO++;
      delete arr[i];
    } else {
      delete arr[i];
    }
  });
  const compare = countO === countX;
  return compare;
}

console.log(XO(`xxxoooxxxoooxxxoooxoxoppppppppppppxoooxx`)); // true
// console.log(XO(`ooxx`)); // true
// console.log(XO(`xooxx`)); // false
// console.log(XO(`zpzpzpp`)); // true
// console.log(XO(`zzoo`)); // false
// console.log(XO(`zzoOOOoo`));
 

I was removing elements with plans of creating two for loops going in opposite directions to speed up the process... but that was for fun and it lost interest in it.

 

Hmmm... Something like this might work in JavaScript

const checkXO = str => str.match(/x/gi).length == str.match(/o/gi).length
 

Ruby

def xo(str)
  tally = 0
  str.each_char do |c|
    case c.upcase
    when "X" then tally += 1
    when "O" then tally -= 1
    end
  end
  tally.zero?
end

Shorter solutions, but this only loops through the string once which might be nice for enormous strings.

 

Also in C :P

#include <stdio.h>

void XO(const char *s, int len) {
    int i;
    int countx = 0;
    int counto = 0;
    for(i = 0; i < len; ++i) {
        char t = *(s+i);
        if(t == 'o' || t == 'O')
            counto++;
        if(t == 'x' || t == 'X')
            countx++;
    }
    (counto == countx) ? printf("true\n") : printf("false\n");
}

int main(int argc, const char * argv[]) {
    const char *s1 = "ooxx";
    const char *s2 = "xooxx";
    const char *s3 = "ooxXm";
    const char *s4 = "zpzpzpp";
    const char *s5 = "zzoo";

    XO(s1, 4);
    XO(s2, 5);
    XO(s3, 5);
    XO(s4, 7);
    XO(s5, 4);

    return 0;
}

 

I'll leave this here:

#include <iostream>
#include <string>

bool IsX (char c) { return ((c == 'x') || (c == 'X')); }
bool IsO (char c) { return ((c == 'o') || (c == 'O')); }

bool XO(const char *str) {
    bool ret;
    std::string s(str);

    size_t is_x = std::count_if(s.begin(), s.end(), IsX);
    size_t is_o = std::count_if(s.begin(), s.end(), IsO);

    (is_x == is_o) ? ret = true : ret = false;
    return ret;
}

void println(bool r) {
    r == true ? std::cout << "true\n" : std::cout << "false\n";
}

int main(int argc, const char * argv[]) {
    println(XO("ooxx"));
    println(XO("xooxx"));
    println(XO("ooxXm"));
    println(XO("zpzpzpp"));
    println(XO("zzoo"));
    return 0;
}
 
const exsAndOhs = // "πŸŽ™ they haunt me…" (https://www.youtube.com/watch?v=0uLI6BnVh6w)
  (string) => string
    .toLowerCase()
    .split("")
    .reduce((count, letter) =>
      /x/.test(letter) ? count += 1
        : /o/.test(letter) ? count -= 1
        : count
    , 0) === 0;


exsAndOhs("xoxo") // => true
exsAndOhs("like ghosts, they want me") // => false
exsAndOhs("to make them all, they won't let go") // => false
exsAndOhs("ex's and ohs") // => true

TERNARY CHAINS?? Yeah. I'm a loner, Dottie. A rebel…

 

Now in elixir because hey all the cool kids are using not-Javascript here.

defmodule ExsAndOhs do

  def try_to_get_over_them(string) do
    string
      |> String.downcase
      |> test(0) # The `Enum` module's for chumps.
  end

  defp test("", count), do: count == 0  # whole string is traversed
  defp test("x" <> string, count), do:  # next grapheme is an "x"
    test(string, count + 1)             
  defp test("o" <> string, count), do:  # next grapheme is an "o"
    test(string, count - 1) 
  defp test(string, count), do:         # String.next_grapheme/1 is a tuple πŸ˜ƒ
    string
      |> String.next_grapheme
      |> elem(1)
      |> test(count)

end
 

A little Python here.

def count_xo(s):
  count_x, count_o = 0, 0
  for c in s:
    if c.lower() == "x":
      count_x += 1
    if c.lower() == "o":
      count_o += 1
  return count_x == count_o

This method uses a for loop to go through each character. It converts each character to lower case for a case insensitive comparison with "x" and "o" and increments the respective counters. The return line checks for count equality to return a boolean.

The time complexity in big O notation is O(n) or linear, where n is the size of input string s, and the space complexity is O(1) or constant since it just uses two variables to keep track of counts.

 

Is there a reason why you prefer a for loop over a single call to lower on the str, and then returning lowered.count('x') == lowered.count('o') ?

 

This can be done in linear time and linear space in terms of complexity by keeping track of the number of letter in a Map object as you traverse the string. Do note that the letter should be case insensitive which that actually gives us a hint that we can make each of the character to be always lowercased before using it as a cache key.

Pseudocode:

a = 'xoxo'
m = Map()
loop for each n in a {
l = n.toLowerCase()

m.set(l, 1) if not m.has(l)
else m.set(l, 1 + m.get(l))
}

return True if not m.has('x') and not m.has('o')

return m.get('x') == m.get('o');

 

Javascript with no loops:

function XO(str) {
    let str_arr = str.toLowerCase()
        .replace(/[^xo]/g, '')
        .split('')    //Convert String into an Array to allow using Array.sort()
        .sort();

    //All of the o's will be placed before the x's thanks to Array.sort()
    //Add 1 to account for 0-indexing and to turn -1 into 0 when there are no o's
    let oCount = str_arr.lastIndexOf('o') + 1;
    let xCount = str_arr.length - oCount;

    return !(xCount - oCount);
}

//Run the test cases
const testCases = [
        "ooxx",
        "xooxx",
        "ooxXm",
        "zpzpzpp",
        "zzoo"
    ];
for(str of testCases) {
    let result = XO(str);
    console.log(`"${str}" => ${result}`);
}
 

Python goes there :

def XO(input):
    x_count = 0
    o_count = 0
    for c in input:
        if c.lower() == 'x':
            x_count += 1
        elif c.lower() == 'o':
            o_count += 1
    return bool(x_count == o_count)

[V2] - One liner :

XO = lambda input : bool(input.lower().count('x') == input.lower().count('o'))

 

Clojure

(defn xo [s]
  (def freq (frequencies (seq s)))
  (= (get freq \x) (get freq \o)))
 

Scala

def xo(s: String): Boolean = {
  s.count(_ == 'x') == s.count(_ == 'o')
}
Classic DEV Post from Oct 30 '18

Realizations from being a frustrated developer

There are developers out there like me who struggles to step up his/her game.

dev.to staff profile image
The hardworking team behind dev.to ❀️

dev.to now has dark theme. 🌝

Go to the "misc" section of your settings and select night theme

P.S. You can also change font to sans serif, which a lot of folks prefer. πŸ’–

P.P.S. It's also the best place to talk code amongst thoughtful developers, and it's totally open source