25
\$\begingroup\$

Oh, No!
the sentences are out of balance.
Quick! balance them.


A word is defined as follows:

A sequence of letters [A-Za-z] separated by any non-letter character ([0-9] ~+- etc.)

A word is BaLanCeD if its capitalization is the same forwards as it is backwards.

Given a sentence with words, balance each word by capitalizing a letter on the right-hand side for each uppercase letter on the left-hand side, and the other way around. Do not make uppercase letters lowercase.

Rules

  • For each letter n chars from the beginning of a word, it and the letter n chars from the end of the word should both be made uppercase if either of them is upper case.
  • All input will be printable ASCII (codepoints 32 to 126 ~).

No need to balance if:

  • The capital is in the middle of a term (word) (equal amount of letters on each side)
  • No capitals are present in the term

Example:

eeeeE → EeeeE  
eEeee → eEeEe
eeEee → eeEee
Eeee. → EeeE.
{EEee}1EEeEEEeeeeee → {EEEE}1EEeEEEEEEeEE

TesT CaSeS:

It was the first new century. →
IT was the first new century.

IiIIiiIIiiiI Ii iI iiiI iiii iIi. →
IiIIIIIIIIiI II II IiiI iiii iIi.

I sueD the airport for misplacing my Luggage. – I lost my Case. →
I SueD the airport for misplacing my LuggagE. – I lost my CasE.


The Policemen  CraCked THE CaSE of the lost cocaIne. →
ThE PolicemeN  CraCkeD THE CASE of the lost coCaIne.

JUST IN CASE. →
JUST IN CASE.

If(!1){ /\hElloBinOLIngo}elsE{ print( 'bOn aPPetit')} →
IF(!1){ /\hElLOBinOLInGo}ElsE{ print( 'bOn aPPeTIt')}

xK/TH"E7k['VyO%t>2`&{B9X.8^$GWc=!+@ 3Us}]JFM)jbuI,-:alPw*Qvpq5zi\Z(_n1dC~?<;fDAm0SR|eYg6#rN4Lho →
XK/TH"E7k['VyO%t>2`&{B9X.8^$GWC=!+@ 3US}]JFM)JbuI,-:aLPw*QvpQ5zi\Z(_n1DC~?<;fDAm0SR|eYg6#RN4LhO

Foo_bar →
FoO_bar

(the " →" will not be included in the input)

This is codegolf, so attempt to achieve least amount of bytes

\$\endgroup\$
6
  • 1
    \$\begingroup\$ Suggested test case: Foo_bar. To make sure that words are not matched with \w+. \$\endgroup\$ – Arnauld Feb 24 at 9:51
  • 5
    \$\begingroup\$ Suggest just using @Xcali's description as I didn't have a clue what this was all about until I read that. \$\endgroup\$ – Noodle9 Feb 24 at 10:49
  • 5
    \$\begingroup\$ The specifications of this challenge are very confusing. It reads as if there has been a paragraph cut out of the challenge. \$\endgroup\$ – Wheat Wizard Feb 24 at 11:57
  • \$\begingroup\$ what's a "term" \$\endgroup\$ – ASCII-only Feb 26 at 21:41
  • 1
    \$\begingroup\$ might wanna clarify that \$\endgroup\$ – ASCII-only Feb 26 at 21:51

21 Answers 21

8
\$\begingroup\$

JavaScript (Node.js),  76  72 bytes

Saved 2 bytes thanks to @tsh

s=>s.replace(/[a-z]+/gi,w=>Buffer(w).map((c,i,a)=>c&=a[w.length+~i]|95))

Try it online!

Commented

s =>                     // s = input string
s.replace(               // replace in s:
  /[a-z]+/gi,            //   match all words in a case-insensitive way
  w =>                   //   for each word w:
    Buffer(w)            //     turn it into a Buffer
    .map((c, i, a) =>    //     for each character c at position i in this array a[]:
      c &=               //       bitwise AND of c with:
        a[w.length + ~i] //         the counterpart character taken from the end of
                         //         the word
        | 95             //         OR 0b1011111
                         ///        (all letter bits except the lowercase bit)
    )                    //     end of map()
                         //     implicit coercion of the Buffer to a string
)                        // end of replace()
\$\endgroup\$
1
  • 3
    \$\begingroup\$ c&95^c&a[w.length+~i]&32->c&(a[w.length+~i]|~32) \$\endgroup\$ – tsh Feb 24 at 10:32
5
\$\begingroup\$

Jelly, 18 bytes

=ŒsŒgṁ@µŒu=ŒuṚTƲ¦)

A full program that takes a string argument and prints the result.

Try it online!

How?

=ŒsŒgṁ@µŒu=ŒuṚTƲ¦) - Main Link: list of characters (S)
 Œs                - swap-case (only affects alphabetic characters)
=                  - (S) equals (that); vectorises
   Œg              - group runs of equal elements
     ṁ@            - (S) mould-like (that) -> list of "words"
                       (any single non-alphabetic character is now a word)
       µ         ) - for each (word in that):
                ¦  -   sparse application...
               Ʋ   -   ...to indices: last four links as a monad:
           Œu      -       upper-case (word)
          =        -       (word) equals (that); vectorises
             Ṛ     -       reverse
              T    -       truthy indices
        Œu         -   ...action: upper-case (the character at the index)
                   - implicit, smashing print (just prints the characters in the list of lists)

Seems a bit long to me:

  • The partitioning atoms (œṖ, œṗ, œP, and œp) don't seem to help much here even though it seems like they should.
  • Perhaps there is a way to shorten code by zipping (") a dyadic function with the reverse of each (U)?
\$\endgroup\$
5
\$\begingroup\$

Perl 5 -p, 60 bytes

s|\pL+|$i=0;join'',map$F[--$i]=~/[A-Z]/?uc:$_,@F=$&=~/./g|ge

Try it online!

\$\endgroup\$
2
  • \$\begingroup\$ 32 bytes Try it online! \$\endgroup\$ – Nahuel Fouilleul Feb 25 at 10:43
  • 1
    \$\begingroup\$ You should post that as a new answer. It's a lot shorter and you should get credit. Plus, I don't even understand how it works. \$\endgroup\$ – Xcali Feb 25 at 18:01
5
\$\begingroup\$

C (gcc), 132 \$\cdots\$ 115 114 bytes

Saved 3 11 bytes thanks to AZTECCO!!!

f(s,e)char*s,*e;{for(;!isalpha(*s);++s);for(e=s;isalpha(*++e););for(*e&&f(e);s<e;++s)*--e&*s&32||(*s&=95,*e&=95);}

Try it online!

Modifies the input string to have in-word balanced capitalisation.

Explanation

f(s,e)char*s,*e;                           // function taking a string   
{                                          // parameter s and also declares   
                                           // e as a string pointer
  for(;!isalpha(*s);++s);                  // loop until s pointes to the   
                                           // beginning of the next word   
  for(e=s;isalpha(*++e););                 // loop until e pointes to one  
                                           // past the end of that word   
  for(                                     // loop over this word   
      *e&&f(e);                            // while e points to one past
                                           // the current word     
                                           // recursively handle the next 
                                           // word if we're not at the end    
               s<e;                        // loop while s is before e  
                   ++s)*--e                // bump s and e towards the middle   
                   ++s)                    // bump s forwards at the end of  
                                           // each loop  
                       *--e                // bump e backwards at the   
                                           // beginning of each loop 
                       *--e&*s&32||        // if both characters aren't  
                                   (       // both lower case then   
                                    *s&=95 // make the first character upper             
                                   ,       // case regardless of its case    
                                    *e&=95 // and also make the second    
                                           // character upper case    
                                   );      // regardless of its case   
}                                          //       
\$\endgroup\$
4
  • 1
    \$\begingroup\$ Nice! SuggestTry it online! \$\endgroup\$ – AZTECCO Feb 25 at 8:42
  • 1
    \$\begingroup\$ @AZTECCO Sweet, just cache the end of word and leave out synching at the start - thanks! :))) \$\endgroup\$ – Noodle9 Feb 25 at 9:54
  • 2
    \$\begingroup\$ Wait.. You can get rid of b by placing function call before last loop! Try it online! \$\endgroup\$ – AZTECCO Feb 25 at 10:54
  • 2
    \$\begingroup\$ @AZTECCO Oh, that's diabolical - thanks! :D \$\endgroup\$ – Noodle9 Feb 25 at 11:07
4
\$\begingroup\$

Husk, 16 13 bytes

ṁṠzo?aID↔ġo¬√

Try it online!

            ġo¬√    # first ġroup the input into sublists (words) according to whether the elements are/aren't letters
ṁ                   # now map across this list of words and concatenate the results into a string, with the function
 Sz        ↔        # zip together each word and its reverse using
   μ      )         # 2-argument lambda function:
    ?  D⁰           # if arg 1 is uppercase
     a   ²          # return uppercase of arg 2
      I             # otherwise return arg 2 unchanged
\$\endgroup\$
3
\$\begingroup\$

Ruby, 73 72 bytes

->s{s.gsub(/[A-Z]+/i){|a|r=0;w='';a.bytes{|b|w<<(a[r-=1]>?Z?b:b&95)};w}}

Try it online!

\$\endgroup\$
3
\$\begingroup\$

Retina 0.8.2, 62 bytes

T`l`L`.(?=([A-Za-z])*)(?<=(?=(?>(?<-1>.)*)[A-Z])(?>[A-Za-z]+))

Try it online! Explanation:

.(?=([A-Za-z])*)

Count the number of letters following the match.

(?<=(?=)(?>[A-Za-z]+))

Skip back to the beginning of the word, and then...

(?>(?<-1>.)*)

... match the same number of characters, ...

[A-Z]

... followed by an uppercase letter.

T`l`L`

Uppercase matching letters.

\$\endgroup\$
3
\$\begingroup\$

Stax, 19 16 bytes

Å;√∩b₧Aµ╗∞q«J,╕*

Run and debug it

Replaces each match of [A-Za-z]+ using the ruleset.

-3, borrowing the untruth idea from Jonathan Allan's answer.

Explanation

V^{c{96<mr:1{]^}&}R implicit input of string
V^                  regex: "[A-Za-z]+"
  {c{96<mr:1{]^}&}R replace each occurrence with the following regex:
   c                copy the match
    {96<m           boolean array for uppercase letters
         r:1        reverse and take truthy indices
            {]^}&   uppercase the match at those indices
\$\endgroup\$
3
\$\begingroup\$

Red, 155 152 bytes

func[s][a: charset[#"A"-#"Z"#"a"-#"z"]parse s[any[change copy w some a(repeat
i d: length? w[if w/:i < #"["[n: d - i + 1 w/:n: w/:n and 95]]w)| skip]]s]

Try it online!

\$\endgroup\$
3
\$\begingroup\$

Perl 5 (-p), 32 bytes

s/\pL+/$&&~reverse~$&&$"x"@+"/ge

The trick, to uppercase, is to AND characters with the negation of space.

How it works

For each sequence of letters

bitwise NOT

bitwise AND with space character repeated (at least the size of the match, here majorant is the position after match @+)

Reverse

bitwise negate

bitwise AND with matched.

Try it online!

\$\endgroup\$
3
\$\begingroup\$

Python 3, 236 230 217 bytes

My first answer in Code Golf!

Edit: -6 bytes thanks to @movatica

-6 bytes thanks to @Danis

-7 bytes thanks to @Alex bries

s=input()+'_'
o=''
i=0
for j in range(len(s)):
 p=s[j]
 if p<'A'or'a'<p<'Z'or p>'z':u=[c<'a'for c in s[i:j]];b=[x|y for(x,y)in zip(u,u[::-1])];o+=''.join([c,c.upper()][x]for(c,x)in zip(s[i:j],b))+p;i=j+1
print(o[:-1])

Try it online!

Commented code (original):

import re

...import the regex module to find non letter characters

s=input()+'_'

...take the imput from the console, and append a non letter character to complete the last iteration in the loop

o=''

...initialize the output string

i=0

...initialize the index to find the start of each new identified word

for j in range(len(s)):

...loop on the characters of the input

 if re.match('[^A-Za-z]',s[j]):

...identify a non letter character that separates words

u=[ord(c)<97for c in s[i:j]];

...list of boolean values corresponding to the letters in the word: True if uppercase

b=[x|y for(x,y)in zip(u,u[::-1])];

...reverse the list of boolean values, and apply or elementwise to obtain the symmetric list of uppercase characters

o+=''.join([[c,c.upper()][x]for(c,x)in zip(s[i:j],b)])+s[j];

...builds the output string: convert to uppercase according to the symmetric list built before; add the non letter character

i=j+1

...update the starting index of the next word.

print(o[:-1])

...prints the output balanced string (discarding the added last character)!

\$\endgroup\$
6
  • 1
    \$\begingroup\$ Welcome to Code Golf! Nice first answer! \$\endgroup\$ – Redwolf Programs Feb 25 at 18:17
  • 2
    \$\begingroup\$ Welcome, fellow Python golfer! :) I'm sorry to outgolf you on this one, but you got very similar concepts and can easily grab some techniques to golf them further! Here are two hints: use o+=.. instead of o=o+.. for one byte less and [c,c.upper()][x] instead of c.upper()if x else c for a 5 byte improvement. \$\endgroup\$ – movatica Feb 26 at 16:38
  • 1
    \$\begingroup\$ @movatica I know, i'm not a professional golfer, tried just for fun! :) Thanks for your tips! Thank you all! \$\endgroup\$ – SevC_10 Feb 26 at 16:51
  • 1
    \$\begingroup\$ "".join([...]) --> "".join(...), ord(c)<97 --> c<"a" \$\endgroup\$ – Danis Feb 27 at 19:40
  • 2
    \$\begingroup\$ Nice answer, i learnt a few tings from it too. You can also remove the re module by replacing the match with: z=s[j] #a line break if z<"A"or z>"z"or "Z"<z<"a": \$\endgroup\$ – Alex bries Feb 28 at 22:46
2
\$\begingroup\$

Charcoal, 29 bytes

F⁺Sψ¿№α↥ι⊞υι«⭆⮌⮌υ⎇№α⊟υ↥κκ¿℅ιι

Try it online! Link is to verbose version of code. Link includes test suite. Explanation:

F⁺Sψ

Loop over the input with a null terminator.

¿№α↥ι

If the current character is a letter, ...

⊞υι«

... push it to the predefined empty list, otherwise:

⭆⮌⮌υ⎇№α⊟υ↥κκ

Loop over a copy of the saved letters, retrieving them in reverse order each time, and uppercasing the copy if the reverse order letter is uppercase.

¿℅ιι

Print the non-letter unless it's the null terminator.

\$\endgroup\$
2
\$\begingroup\$

Java, 320 Bytes

s->java.util.regex.Pattern.compile("[a-zA-Z]+").matcher(s).replaceAll(m->java.util.stream.IntStream.range(0,m.group().length()).mapToObj(i->(Character.isUpperCase(m.group().charAt(m.group().length()-1-i))?Character.toUpperCase(m.group().charAt(i)):m.group().charAt(i))+"").collect(java.util.stream.Collectors.joining()))

Try it online!

Explanation

s->java.util.regex.Pattern.compile("[a-zA-Z]+").matcher(s)//match words
  .replaceAll(m->//replace each word, m is MatchResult, m.group() is current word
     java.util.stream.IntStream.range(0,m.group().length())//iterate over indexes of word
       .mapToObj(i->(Character.isUpperCase(m.group().charAt(m.group().length()-1-i))
       //check if character at reflected index is uppercase
    ? 
  Character.toUpperCase(m.group().charAt(i))//make uppercase
  :m.group().charAt(i)) //do not modify
   + "" //concatenate with empty string to force result to be a String
  ).collect(java.util.stream.Collectors.joining()))//collect as single String
\$\endgroup\$
2
\$\begingroup\$

J, 60 bytes

[:;]<@((+.&(91>3&u:)|.)`(,:toupper)});.1~1,2|@-/\e.&Alpha_j_

Try it online!

Well this is shockingly verbose for J. Surely I am missing some better alternatives, but this is what I have for now...

\$\endgroup\$
2
\$\begingroup\$

Python 3, 153, 149, 148, 110 bytes

Using re.sub is even more elegant than re.finditer...

lambda s:re.sub('[a-zA-Z]+',lambda w:''.join([c,c.upper()][d<'a']for c,d in zip(w[0],w[0][::-1])),s)
import re

Try it online!

\$\endgroup\$
2
\$\begingroup\$

AWK, 113 bytes 141 bytes

{for(b=1+split($0,l,"[ ]");--b;$b=e){a=split(toupper(m=l[b]),c,d=e=x);for(split(m,f,d);a;a--){if(f[++d]==c[d])f[a]=c[a];}for(;d;)e=f[d--]e}}1

Try it online!

The code got longer to address a bug related to whitespace parsing as noted by Dominic van Essen.

This isn't all that short, but AWK isn't well suited to this challenge (as far as I can tell) so I think it's worth posted anyway.

Here's how it works...

The first for loop processes each commandline argument in reverse order. First it has to break the arguments into words explicitly, since TIO doesn't provide a way to set RS and ORS variables easily.

b=1+split($0,l,"[ ]")

which sets variable b to the number of space delimited words found, and stores the array of words (some of which might be empty strings) in variable l. The 1+ is necessary to keep the code from skipping the last word.

then at the top of each loop,

--b

the code decrements b. The words in the array are processed in reverse order to avoid having to use another variable.

The body of the loop consists of three statements, the first one is:

a=split(toupper(m=l[b]),c,d=e=x)

which does a couple of things.

  • converts the current argument l[b] to upper case
  • splits that word into a character array, assigns that to variable c
  • sets variables d and e to an empty string
  • set variable a to the number of characters in the argument
  • stores the untranslated value of the current argument in variable m

The second statement is another loop, checking character by character and translating to uppercase as needed. It does that by initializing the loop with:

split(m,f,d)

which converts the current commandline argument to a character array, stored in variable f. At this point f has the original characters, and c has the same characters translated to uppercase.

The loop condition and "end of loop" action cause the code to iterate a times, which is the number of characters in the current argument.

The body of this second loop is single statement:

if(f[++d]==c[d])f[a]=c[a]

which translates the paired character to uppercase if the character we're checking is already uppercase. The loop decrements a and increments d each iterations, which keeps the pairing in sync.

The third statement in the body of the outer loop, is essentially a "join" operation concatenating the characters in the translated character array.

for(;d;)e=f[d--]e

The only trick here is using the d variable as the iteration control, since it was incremented up to the length of the commandline argument in the previous loop. Also working backwards means we can use f[d--]e without having to use a space between them since the closing bracket separates the variables.

Then the "end of loop" action on the outer loop assigns the re-assembled string to the current commandline argument.

$b=e

And all that's left to do is add a "truthy" test without any action to print everything.

1
\$\endgroup\$
4
  • 1
    \$\begingroup\$ This is really nice. Well done! I don't know why the TIO awk doesn't accept the standard -v RS=' ' and -v ORS=' ' command-line options, but using them on my local awk, one can sorten it to 104 bytes: {a=split(toupper($0),c,d=e=x);for(split($0,f,d);a;a--)if(f[++d]==c[d])f[a]=c[a];for(;d;)e=f[d--]e;$0=e}1 \$\endgroup\$ – Dominic van Essen Feb 28 at 11:03
  • 1
    \$\begingroup\$ (there is an issue, though, that awk is splitting on whitespace/spaces, rather than on all non-letter characters as specified in the challenge...) \$\endgroup\$ – Dominic van Essen Feb 28 at 11:05
  • 1
    \$\begingroup\$ (splitting issue can be resolved using a regex in GNU awk (gawk), which is also sadly not implemented on TIO: gawk -v RS='[^a-zA-Z]' -v ORS=' ' '{a=split(toupper($0),c,d=e=x);for(split($0,f,d);a;a--)if(f[++d]==c[d])f[a]=c[a];for(;d;)e=f[d--]e;$0=e}1'...) \$\endgroup\$ – Dominic van Essen Feb 28 at 11:28
  • \$\begingroup\$ ugh... I see what you mean about the whitespace and how relying on the commandline arguments splitting is a problem. I'll try to come with something that works on TIO that doesn't get confused by multiple spaces in a row on the input. \$\endgroup\$ – cnamejj Feb 28 at 19:22
2
\$\begingroup\$

05AB1E,  15  13 bytes

-1 thanks to Makonede! (Duplicate-TOS and then reverse-TOS has a one-byte built-in, Â.)
-1 thanks to Kevin Cruijssen! (Take a list of characters.)

.γa}εÂ.uÅÏu]˜

Try it online!

How?

.γa}εÂ.uÅÏu]˜ - (accepts a list of characters)
.γ }          - group by:
  a           -   is alphabetic?
    ε         - for each:
     Â        -   push a copy and then push a reversed copy
      .u      -   is upper-case? (vectorises)
        ÅÏ    -   apply at truthy indices (of that, to the forward copy):
          u   -     upper-case
           ]  - } } (close all loops)
            ~ - flatten
\$\endgroup\$
6
1
\$\begingroup\$

Julia, 76 bytes

n->replace(n,r"[a-zA-Z]+"=>x->(L=length(x)+1;map(c->c-32(x[L-=1]<'['<c),x)))

Try it online!

\$\endgroup\$
1
\$\begingroup\$

Java (JDK), 130 bytes

a->{for(int i=0,l=a.length,c,s,e;i<l;i++){for(s=i;i<l&&(c=a[i]&95)>64&c<91;i++);for(e=i;s<e;a[e]&=c)a[s]&=c=a[s++]^a[--e]^32|95;}}

Try it online!

Credits

\$\endgroup\$
1
  • \$\begingroup\$ @ceilingcat thanks! \$\endgroup\$ – Olivier Grégoire Feb 27 at 18:57
1
\$\begingroup\$

Japt, 30 bytes

òÈ+Y è"%L"
®íZmr"%a" ÔÏ?Xu:XÃc

Try it online!

I'm not happy with this, but I haven't actually been able to find something better. Inputs and outputs as an array of characters.

Explanation:

òÈ+Y è"%L"    
ò                      # Partition the input between pairs of characters where truthy:
 È+Y                   #  Join the two characters into a string
     è"%L"             #  Count how many are not in [A-Za-Z]
                       # Implicitly store as U

®íZmr"%a" ÔÏ?Xu:XÃc    #
®                      # For each word:
  Zmr"%a"              #  Replace lowercase letters with ""
          Ô            #  Reverse it
 í         Ï     Ã     #  Pair each with the original letter at that index
            ?          #  If the modified array has anything other than "":
             Xu        #   Replace the original letter with upper-case
               :X      #  Otherwise leave it unchanged
                  c    # Flatten all the words back into a single array of chars
\$\endgroup\$
1
  • \$\begingroup\$ i'm happy with it :p \$\endgroup\$ – Alex bries Feb 28 at 21:34
1
\$\begingroup\$

PowerShell, 91 82 bytes

$args-replace'[a-z]+',{-join(&{($w="$_")|% t*y|%{[char]($w[--$i]-bor95-band$_)}})}

Try it online!

where:

  • &{ creates a new context each contains $i=0
  • -bor is bitwise or, -band is bitwise and have inspired by the Arnauld's answer
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.