loading...
Cover image for Daily Challenge #270 - Fix String Case

Daily Challenge #270 - Fix String Case

thepracticaldev profile image dev.to staff ・1 min read

In this challenge, you will be given a string that may have mixed uppercase and lowercase letters and your task is to convert that string to either lowercase only or uppercase only based on:

  • making as few changes as possible
  • if the string contains equal number of uppercase and lowercase letters, convert the string to lowercase.
For example:
solve("coDe") = "code". Lowercase characters > uppercase. Change only the "D" to lowercase.
solve("CODe") = "CODE". Uppercase characters > lowecase. Change only the "e" to uppercase.
solve("coDE") = "code". Upper == lowercase. Change all to lowercase.

Tests:
solve("code")
solve("CODe")
solve("COde")
solve("Code")

Good luck!


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

Want to propose a challenge idea 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

pic
Editor guide
 

JavaScript

Something like this should do it:

const solve = str => str.replace(/[a-z]/g, '').length > str.length / 2
   ? str.toUpperCase()
   : str.toLowerCase()
 

“A simple step for RegEx. A huge leap for a bunch of loops and ifs.”

 

Python solution. Uses helper function that counts the matching elements in a list. I use that to compare the string to itself converted to upper- and lowercase.

TIO link

def matching(l1, l2):
    # Returns the number of matching corresponding elements of l1 and l2
    return sum(e1 == e2 for e1, e2 in zip(l1, l2))

def solve(s):
    return s.lower() if matching(s, s.upper()) <= matching(s, s.lower()) else s.upper()

cases = ["coDe", "CODe", "coDE", "code", "CODe", "COde", "Code"]
for case in cases:
    print(f'{case} -> {solve(case)}')
 

in nim:

import strutils

proc solve*(s: string): string =
    var lower = 0
    var upper = 0
    var lowerResult = ""
    var upperResult = ""

    for c in s:
        if c.isLowerAscii():
            lowerResult.add(c)
            upperResult.add(c.toUpperAscii())
            inc(lower)
        else:
            lowerResult.add(c.toLowerAscii())
            upperResult.add(c)
            inc(upper)

    if lower >= upper:
        return lowerResult

    return upperResult

probably too verbose but that way I am only looping once through the string :-)

 

I am only looping once through the string :-)

Though at the cost of building an entire output string (including potential case conversions), you're then discarding.

I'd probably go for a more straightforward approach:

import sequtils, strutils

func solve*(s: string): string =
  if s.countIt(it.isUpperAscii) > s.len.div(2):
    s.toUpperAscii
  else:
    s.toLowerAscii
 

True story..

Your solution is nice and short. 👍

Honestly i don't know what's more "efficient".

I don’t think efficiency matters much here, but my solution has a lower cyclomatic complexity and is potentially easier to understand for others.

 
def solve(inp):
    lower_count = 0
    length = len(inp)
    for i in inp:
        if i.islower():
            lower_count += 1
    if 2*lower_count >= length:
        return inp.lower()
    return inp.upper()

Tests:
solve("code") = "code"
solve("CODe") = "CODE"
solve("COde") = "code"
solve("Code") = "code"

Please let me know if there are any changes to be made.

 

Here is the simple solution without any advanced approach (e.g. ASCII code converting, regular expression and so on.):

function solve($s) {
  $upperCases = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  $lowerCases = strtolower($upperCases);

  $upperCaseCount = 0;
  $lowerCaseCount = 0;

  $index = 0;

  for (; $index<strlen($s); $index++) {
    if (strpos($upperCases, $s[$index]) !== false) {
      $upperCaseCount += 1;
    }
    if (strpos($lowerCases, $s[$index]) !== false) {
      $lowerCaseCount += 1;
    }
  }

  if ($upperCaseCount > $lowerCaseCount) {
    return strtoupper($s);
  }

  if ($upperCaseCount <= $lowerCaseCount) {
    return strtolower($s);
  }
}
 

Here's a C implementation that does a pretty good job at being as lazy as possible (which is what I always aspire to, even if it ends up being a lot of work to get to be lazy)

It updates the string in-place, passing through only once if nothing is to be done, or if its original guess of the final case was right. Otherwise, it passes through again only up to the last char it guessed wrong on.
Worst case, it iterates the string twice (minus one last character on the second pass), but the typical case is more like a single pass.

In C for old-times sake, and because if you're bothering to do this sort of thing at all it'd better be in a performant language just to make any of it worth the effort.

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

#define DEB(...) __VA_ARGS__

#define ascii_A 65
#define ascii_Z 90
#define ascii_case_bitmask 0x20
#define isUpperCase(___c) (((___c) & ascii_case_bitmask) == 0)
#define toggleCase(___c) ((___c) ^= ascii_case_bitmask)

void convertToPopularCase(char *s) {
    int stat_toggleCount = 0, stat_stringLength = (int)strlen(s);
    printf(">>> %s\n", s);

    int lastWrongIndex = -1, upperCount = 0, lowerCount = 0;
    char upperFTW = 0;
    for (int i=0; s[i]; i++) {
        const char c = s[i], upperC = c & ~ascii_case_bitmask, isUpper = isUpperCase(c);
        if (upperC < ascii_A || upperC > ascii_Z) continue; // non-letters are ignored
        upperCount += isUpper;
        lowerCount += 1 - isUpper;
        if (upperFTW != upperCount > lowerCount) {
            lastWrongIndex = i - 1;
            upperFTW = !upperFTW;
        }
        if (isUpper != upperFTW) {
            toggleCase(s[i]); // toggle case of char in string
            stat_toggleCount++;
        }
    }
    for (int i=0; i <= lastWrongIndex; i++) {
        const char c = s[i], upperC = c & ~ascii_case_bitmask, isUpper = isUpperCase(c);
        if (upperC < ascii_A || upperC > ascii_Z) continue; 
        if (isUpper != upperFTW) {
            toggleCase(s[i]); // toggle case of char in string
            stat_toggleCount++; 
        }
    }

    printf(" << %s\n    (Length: %d, chars toggled: %d, total string passes including cleanup: %f\n\n", s, stat_stringLength, stat_toggleCount, (stat_stringLength + (lastWrongIndex + 1))/(float)stat_stringLength);
}

int main()
{
    char testStrings[][100] = {
        "Following examples should be uppercase>>>",
        "CODE",
        "CODe",
        "cODE",
        "A MoNkEy",
        "123456789 CODE",
        "four - FIVEE + four - FIVEE",
        "four + fivee - FOUR - SIXXXX",

        "Following examples should be lowercase>>>",
        "code",
        "COde", "Code",
        "coDE", "codE",
        "MoNkEy",
        "123456789 code", "123456789 coDE",
        "four + fivee - FOUR - FIVEE",
    };

    for (int i=0, N = sizeof(testStrings)/sizeof(*testStrings); i<N; i++) {
        convertToPopularCase(testStrings[i]);
    }

    return 0;
}

Output:
(a "total string passes including cleanup" of 1 is where the string was just passed over once)

>>> Following examples should be uppercase>>>
 << following examples should be uppercase>>>
    (Length: 41, chars toggled: 1, total string passes including cleanup: 1.024390

>>> CODE
 << CODE
    (Length: 4, chars toggled: 0, total string passes including cleanup: 1.000000

>>> CODe
 << CODE
    (Length: 4, chars toggled: 1, total string passes including cleanup: 1.000000

>>> cODE
 << CODE
    (Length: 4, chars toggled: 3, total string passes including cleanup: 1.500000

>>> A MoNkEy
 << A MONKEY
    (Length: 8, chars toggled: 3, total string passes including cleanup: 1.000000

>>> 123456789 CODE
 << 123456789 CODE
    (Length: 14, chars toggled: 0, total string passes including cleanup: 1.714286

>>> four - FIVEE + four - FIVEE
 << FOUR - FIVEE + FOUR - FIVEE
    (Length: 27, chars toggled: 22, total string passes including cleanup: 1.925926

>>> four + fivee - FOUR - SIXXXX
 << FOUR + FIVEE - FOUR - SIXXXX
    (Length: 28, chars toggled: 27, total string passes including cleanup: 1.964286

>>> Following examples should be lowercase>>>
 << following examples should be lowercase>>>
    (Length: 41, chars toggled: 1, total string passes including cleanup: 1.024390

>>> code
 << code
    (Length: 4, chars toggled: 0, total string passes including cleanup: 1.000000

>>> COde
 << code
    (Length: 4, chars toggled: 4, total string passes including cleanup: 1.750000

>>> Code
 << code
    (Length: 4, chars toggled: 1, total string passes including cleanup: 1.250000

>>> coDE
 << code
    (Length: 4, chars toggled: 2, total string passes including cleanup: 1.000000

>>> codE
 << code
    (Length: 4, chars toggled: 1, total string passes including cleanup: 1.000000

>>> MoNkEy
 << monkey
    (Length: 6, chars toggled: 3, total string passes including cleanup: 1.833333

>>> 123456789 code
 << 123456789 code
    (Length: 14, chars toggled: 0, total string passes including cleanup: 1.000000

>>> 123456789 coDE
 << 123456789 code
    (Length: 14, chars toggled: 2, total string passes including cleanup: 1.000000

>>> four + fivee - FOUR - FIVEE
 << four + fivee - four - fivee
    (Length: 27, chars toggled: 9, total string passes including cleanup: 1.000000
 

Haskell solution:

import Data.List (foldl')
import Data.Char (toLower, toUpper, isLower)

solve :: String -> String
solve str =
  if fromIntegral nLower / nTotal >= 0.5
    then toLower <$> str
    else toUpper <$> str
  where
    (nLower, nTotal) = foldl' counter (0,0) str
    counter (nLower, nTotal) ch =
      if isLower ch
        then (nLower+1, nTotal+1)
        else (nLower, nTotal+1)
 

Is it really a challenge?

string solve(string input)
{
    return input.Count(c => char.IsUpper(c)) > input.Length / 2 
        ? input.ToUpper()
        : input.ToLower();
}
 

Kotlin

fun solve(input: String): String
    = if (input.count { it.isUpperCase() } > input.length / 2) input.toUpperCase() else input.toLowerCase()