# Chunky vs. Smooth Strings

Consider a string of length N, such as Peanut Butter with N = 13. Notice that there are N-1 pairs of neighboring characters in the string. For Peanut Butter, the first of the 12 pairs is Pe, the second is ea, the last is er.

When the pairs are mostly different characters, the string has a chunky quality, e.g. chUnky.
When these pairs are mostly the same character, the string has a smooth quality, e.g. sssmmsss.

Define the chunkiness of a string to be the ratio of the number of pairs with two different characters to the total number of pairs (N-1).

Define the smoothness of a string to be the ratio of the number of pairs with two identical characters to the total number of pairs (N-1).

For example, Peanut Butter only has one pair with identical characters (tt), so its smoothness is 1/12 or 0.0833 and its chunkiness is 11/12 or 0.9167.

Empty strings and strings with only one character are defined to be 100% smooth and 0% chunky.

## Challenge

Write a program that takes in a string of arbitrary length and outputs either its chunkiness or smoothness ratio as a floating point value.

• Take input via stdin or the command line, or you may write a function that takes a string.
• You can assume the input string only contains printable ASCII characters (and hence is single-line).
• Print the float to stdout to 4 or more decimal places, or you can choose to return it if you wrote a function. Decimal places that convey no information are not required, e.g. 0 is fine instead of 0.0000.
• Choose chunkiness or smoothness as you prefer. Just be sure to say which one your program outputs.

The shortest code in bytes wins.

## Examples

Peanut Butter → Chunkiness: 0.91666666666, Smoothness: 0.08333333333
chUnky → Chunkiness: 1.0, Smoothness: 0.0
sssmmsss → Chunkiness: 0.28571428571, Smoothness: 0.71428571428
999 → Chunkiness: 0.0, Smoothness: 1.0
AA → Chunkiness: 0.0, Smoothness: 1.0
Aa → Chunkiness: 1.0, Smoothness: 0.0
! → Chunkiness: 0.0, Smoothness: 1.0
[empty string] → Chunkiness: 0.0, Smoothness: 1.0

Bonus question: Which do you prefer, chunky or smooth strings?

• -1 No overhanded tag. Commented Jul 14, 2015 at 2:45
• +1 Conclusive proof that chunky peanut butter should be the default. Commented Jul 14, 2015 at 3:25
• Some languages have a hard time reading no input at all. Would it be admissible to assume that the input is newline-terminated? Commented Jul 14, 2015 at 4:25
• @BrainSteel Chunky should only be the default if you're a computer; they like having chunks available. Peanut butter made for people should hide those implementation details, and be smooth as silk for the user. Commented Jul 14, 2015 at 13:01
• Hm.. with all the fully chunky chunkiness solutions, I wonder whether there are fully smooth smoothness solutions?
– ojdo
Commented Jul 15, 2015 at 14:12

# CJam, 19 bytes

q_1>_@.=:!1b\,d/4mO


100% chunky source code that calculates chunkiness.

Try this chunky goodness online.

### How it works

q_                  e# Read from STDIN and push a copy.
1>_               e# Discard the first character and push a copy.
@              e# Rotate the original on top.
.=            e# Vectorized comparison (1 if equal, 0 if not).
:!          e# Mapped logical NOT (0 if equal, 1 if not).
1b        e# Base 1 conversion (sum).
\,      e# Swap with the shortened string and push its length.
d/    e# Cast to double and divide.
4mO e# Round to four decimal places.


Obviously, NaN rounded to 4 decimal places is 0.

• I don't think you need to round it to 4 digits. It says "4 or more", and trailing zeros are not needed. This is much more elegant than the 2ew approach I tried. The 0/1 letter special cases were killing me. Commented Jul 14, 2015 at 3:56
• @RetoKoradi Rounding maps NaN to 0. I don't know a shorter way. Commented Jul 14, 2015 at 3:58
• Yes, while playing with it some more, I just noticed that you get a NaN for 1-character inputs. The short inputs are by far the most painful part with this one. BTW, the online link has slightly different code than the version you posted. One _ moved. Not sure if it matters. Commented Jul 14, 2015 at 4:02
• @RetoKoradi Sure does. The linked code wan't 100% chunky. :P Commented Jul 14, 2015 at 4:03
• @AlexA. Jam with fruit chunks is at least 10% chunky. Commented Jul 14, 2015 at 14:53

# Pyth, 13 12 bytes

csJnVztz|lJ1


Fully chunky code calculating chunkiness.

csJnVztz|lJ1
Implicit: z = input()
nV            n, !=, vectorized over
z           z
tz         z[:-1]
V implitly truncates.
J              Store it in J.
s               sum J
c                floating point divided by
|       logical or
lJ     len(J)
1    1

• In the online version, I get an error when I leave the input empty. So as far as I can tell, it fails the last test case. Commented Jul 14, 2015 at 8:06
• @RetoKoradi That's weird - it works fine in the offline version. It's probably a bug with the online website. Commented Jul 14, 2015 at 8:10
• @RetoKoradi Confirmed - the very use of z cause an error on the empty input online. I'll go and fix that bug. This code is fine, however. Commented Jul 14, 2015 at 8:12
• It works if I hit return once in the input box. But other strings do not need a return at the end. With nothing typed in the input box, it seems to get no input at all, and your code blows up when it tries to use the input. Commented Jul 14, 2015 at 8:15
• @RetoKoradi Thanks. I think I know the problem, shouldn't be hard to fix. Commented Jul 14, 2015 at 8:17

# TI-BASIC, 46 bytes

Input Str1
If 2≤length(Str1
mean(seq(sub(Str1,X,1)=sub(Str1,X-1,1),X,2,length(Str1
Ans


sub(x1,x2,x3 gives the substring of string x1 starting (one-based) at number x2 and ending at number x3, then seq( builds a sequence.

Gives the smoothness value. The Ans variable is 0 by default, so we don't need an Else to the If statement, or to store anything to Ans beforehand.

# APL, 10 bytes

This reads input from stdin and prints the chunkiness to stdout. The algorithm is the same one used for the J solution.

(+/÷⍴)2≠/⍞


## Matlab (37 36 bytes)

This can be done with the following anonymous function, which returns chunkiness:

f=@(x)nnz(diff(x))/max(numel(x)-1,1)


• In old Matlab versions (such as R2010b) you need + to cast the char array x to a double array:

f=@(x)nnz(diff(+x))/max(numel(x)-1,1)


But that's not the case in recent versions (tested in R2014b), which saves one byte. Thanks to Jonas for his comment.

• The expression with max handles the one-character and zero-character cases (for chunkiness)

Example:

>> f=@(x)nnz(diff(x))/max(numel(x)-1,1)
f =
@(x)nnz(diff(x))/max(numel(x)-1,1)

>> f('Peanut Butter')
ans =
0.9167

• On R2014b, diff('abc') will not produce a warning. Commented Jul 14, 2015 at 8:52

# ><>, 40 36 bytes

00ii:0(?v:@=1$-{+{1+}}20. ~:0=+,n;>~  This program returns the chunkiness of a string. ## Explanation 00i Push 0 (# unequal pairs), 0 (length of string - 1) and read first char [main loop] i Read a char :0(?v If we've hit EOF, go down a row :@=1$-      Check (new char != previous char)
{+          Add to unequal pairs count
{1+         Increment length by 1
}}20.       Continue loop

[output]

~~          Pop last two chars (top one will be -1 for EOF)
:0=+        Add 1 to length if zero (to prevent division by zero errors)
,           Divide, giving chunkiness
n;          Output and halt


## Previous submission (37 + 3 = 40 bytes)

l1)?v1n;
n,$&\l1-}0& \; l2(?^$:@=&+&>


This program returns the smoothness of a string. Input is via the -s flag, e.g.

py -3 fish.py chunkiness.fish -s "Peanut Butter"


## C#, 94 89 bytes

Sub 100 bytes, so I guess that's some form of victory in itself?

This is a function definition (allowed as per the spec) which returns the smoothness of the input string:

Func<string,float>(s=>s.Length>1?s.Zip(s.Skip(1),(a,b)=>a==b?1f:0).Sum()/(s.Length-1):1);


Pretty straightforward, if the length is 0 or 1 it returns 1, otherwise it compares the string to itself less the first character, then returns the number of identical pairs divided by the number of pairs.

Edit - replaced Substring with Skip. Rookie mistake!

## J, 14 13 bytes

Computes the chunkiness. Kudos to J for defining 0 % 0 to be equal to 0.

(]+/%#)2~:/\]


Try it online

Here is an explanation:

   NB. sample input
in =. 'sssmmsss'

NB. all infixes of length 2
2 ]\ in
ss
ss
sm
mm
ms
ss
ss

NB. are the parts of the infix different?
2 ~:/\ in
0 0 1 0 1 0 0

NB. sum and item count of the previous result
(+/ , #) 2 ~:/\ in
2 7

NB. arithmetic mean: chunkiness
(+/ % #) 2 ~:/\ in
0.285714

NB. as a tacit verb
(] +/ % #) 2 ~:/\ ]

NB. 0 divided by 0 is 0 in J
0 % 0
0

• (]+/%#)2~:/\] saves 1 byte. Commented Apr 26, 2018 at 6:18
• @FrownyFrog Cool! How could I miss this? Commented Apr 26, 2018 at 7:36
• Could you add a TIO-link with test code? Commented Apr 26, 2018 at 10:00

## CJam, 23 bytes

q_,Y<SS+@?2ew_::=:+\,d/


Explanation:

q                           e# read input
_,                         e# its length
Y<                       e# is less than 2?
SS+                    e# then a smooth string
@?                  e# otherwise the input
2ew               e# pairs of consecutive characters
_::=           e# map to 1 if equal, 0 if not
:+         e# sum (number of equal pairs)
\,       e# total number of pairs
d/     e# divide


This outputs the smoothness ratio.

# CJam, 16 bytes

1q2ew::=_:+\,d/*


Cheaty source code that calculates smoothness.

For inputs of length 0 or 1, this prints the correct result before exiting with an error. With the Java interpreter, error output goes to STDERR (as it should).

If you try the code online, just ignore everything but the last line of output.

### How it works

1q               e# Push a 1 and read from STDIN.
2ew            e# Push the overlapping slices of length 2.
e# This will throw an error for strings of length 0 or 1,
e# so the stack (1) is printed and the program finishes.
::=         e# Twofold vectorized comparision.
_:+      e# Push a copy and add the Booleans.
\,    e# Swap with the original and compute its length.
d/  e# Cast to double and divide.
* e# Multiply with the 1 on the bottom of the stack.


# Julia, 52 bytes

Smoothness!

s->(n=length(s))<2?1:mean([s[i]==s[i+1]for i=1:n-1])


This creates an unnamed function that accepts a string and returns a numeric value.

If the length of the input is less than 2, the smoothness is 1, otherwise we calculate the proportion of identical adjacent characters by taking the mean of an array of logicals.

# Nim, 10596 91 bytes

proc f(s):any=
var a=0;for i,c in s[.. ^2]:a+=int(s[i+1]!=c)
a.float/max(s.len-1,1).float


Trying to learn Nim. This calculates the chunkiness of a string.

(If I try to read this as Python the indentation looks all messed up... Now it looks more like Ruby...)

# Python 3, 63 Bytes

This is an anonymous lambda function which takes a string as the argument, and returns it's chunkiness.

lambda n:~-len(n)and sum(x!=y for x,y in zip(n,n[1:]))/~-len(n)


To use it, give it a name and call it.

f=lambda n:~-len(n)and sum(x!=y for x,y in zip(n,n[1:]))/~-len(n)
f("Peanut Butter") -> 0.08333333333333333
f("!")             -> 0
f("")              -> -0.0

• Instead of the anonymous function, you can use: def f(n):, which has exactly the same number of characters as lambda n:. This removes the need to name your function. Commented Jul 15, 2015 at 18:02
• @TristanReid def f(n): also needs a return Commented Jul 16, 2015 at 7:27
• Oops! Good catch - I'm new to codegolf, I should assume more thought has gone into this and test locally. Apologies! Commented Jul 16, 2015 at 23:20

# Python 3, 52 bytes

lambda s:sum(map(str.__ne__,s,s[1:]))/(len(s)-1or 1)


This calculates chunkiness, and outputs -0.0 for the empty string. If don't like negative zeroes you can always fix that with an extra byte:

lambda s:sum(map(str.__ne__,s,s[1:]))/max(len(s)-1,1)


f[]=1
f[_]=1
f x=sum[1|(a,b)<-zip=<<tail$x,a==b]/(sum(x>>[1])-1)  Outputs smoothness. e.g. f "Peanut Butter" -> 8.333333333333333e-2. How it works: f[]=1 -- special case: empty list f[_]=1 -- special case: one element list f x= -- x has at least two elements (a,b)<-zip=<<tail$x        -- for every pair (a,b), drawn from zipping x with the tail of itself
,a==b   -- where a equals b
[1|                        ]  -- take a 1
sum                              -- and sum it
/                     -- divide  by
(x>>[1])         -- map each element of x to 1
sum                  -- sum it
-1       -- and subtract 1


sum(x>>[1]) is the length of x, but because Haskell's strong type system requires to feed fractionals to /, I can't use length which returns integers. Converting integers to fractionals via fromInteger$length x is far too long. • Did you try working with Rationals? Commented Jul 14, 2015 at 19:27 • @recursion.ninja: no, I didn't because I think a 18 byte import Data.Ratio is too expensive. – nimi Commented Jul 15, 2015 at 15:16 ## JavaScript (ES6), 55 bytes ### Smoothness, 56 bytes f=x=>(z=x.match(/(.)(?=\1)/g))?z.length/--x.length:+!x[1]  ### Chunkiness, 55 bytes f=x=>(z=x.match(/(.)(?!\1)/g))&&--z.length/--x.length||0  ### Demo Calculates smoothness, as that is what I prefer. Only works in Firefox for now, as it is ES6. f=x=>(z=x.match(/(.)(?=\1)/g))?z.length/--x.length:+!x[1] O.innerHTML += f('Peanut Butter') + '\n'; O.innerHTML += f('chUnky') + '\n'; O.innerHTML += f('sssmmsss') + '\n'; O.innerHTML += f('999') + '\n'; O.innerHTML += f('AA') + '\n'; O.innerHTML += f('Aa') + '\n'; O.innerHTML += f('!') + '\n'; O.innerHTML += f('') + '\n'; <pre id=O></pre> # KDB(Q), 30 Returns smoothness. {1^sum[x]%count x:1_(=':)(),x}  # Explanation  (),x / ensure input is always list x:1_(=':) / check equalness to prev char and reassign to x sum[x]%count / sum equalness divide by count N-1 1^ / fill null with 1 since empty/single char will result in null { } / lamdba  # Test q){1^sum[x]%count x}1_(=':)(),x}"Peanut Butter" 0.08333333 q){1^sum[x]%count x:1_(=':)(),x}"sssmmsss" 0.7142857 q){1^sum[x]%count x:1_(=':)(),x}"Aa" 0f q){1^sum[x]%count x:1_(=':)(),x}"aa" 1f q){1^sum[x]%count x:1_(=':)(),x}"chUnky" 0f q){1^sum[x]%count x:1_(=':)(),x}"!" 1f q){1^sum[x]%count x:1_(=':)(),x}"" 1f  # Ruby, 69 66 bytes ->s{(c=s.chars.each_cons(2).count{|x,y|x!=y}.to_f/~-s.size)>0?c:0}  Try it online! Shaved of a few bytes with comments from IMP. Also, with the upcoming version 2.7.0 of Ruby it's possible to save some bytes by replacing |x,y|x!=y with @1!=@2 • if you move the .to_f/~-s.size into the assignment of c, then you can shave a byte off with the ternary operation: f=->s{(c=s.chars.each_cons(2).count{|x,y|x!=y}.to_f/~-s.size)>0?c:0} – IMP1 Commented Jul 25, 2019 at 9:39 • Also, do you need the f=? I'm not 100% on the rules about that. The challenge says you can return a function that takes a string, which a stabby lambda is. – IMP1 Commented Jul 25, 2019 at 9:40 • also, while the Perl answer might be the same length, it's not got that 100% chunkiness that this answer does. – IMP1 Commented Jul 25, 2019 at 9:43 # Python 3, 69 bytes No one has posted a Python solution yet, so here's a fairly straightforward implementation of a "chunkiness" function. It short-circuits on a string of length 1, and prints 0 (which is an integer rather than a float but seems to be allowed according to the rules). On an empty string, it outputs -0.0 rather than 0.0. Arguably this is could be considered acceptable, as -0.0 == 0 == 0.0 returns True. def c(s):l=len(s)-1;return l and sum(s[i]!=s[i+1]for i in range(l))/l  Examples: >>> c(''); c('a'); c('aaa'); c("Peanut Butter") -0.0 0 0.0 0.916666666667 >>> -0.0 == 0 == 0.0 True  (Python 3 is used for its default float division.) # C, 83 bytes float f(char*s){int a=0,b=0;if(*s)while(s[++a])b+=s[a]!=s[a-1];return--a?1.*b/a:b;}  A function returning chunkiness. ### Explanation float f(char *s) {  Accept a C string, and return a float (double would work but is more chars). int a=0, b=0;  Counters - a for total pairs, b for non-matching pairs. Using int limits the "arbitrary length" of the string, but that's only a minor violation of the requirements and I'm not going to fix it. if (*s)  Special case the empty string - leave both counters zero.  while (s[++a])  Non-empty string - iterate through it with pre-increment (so first time through the loop, s[a] will be the second character. If the string has only one character, the loop body will not be entered, and a will be 1.  b += s[a]!=s[a-1];  If the current character differs from the previous, increment b. return --a ? 1.*b/a : b; }  After the loop, there are three possibilities: 'a==0,b==0' for an empty input, 'a==1,b==0' for a single-character input or 'a>1,b>=0' for multi-character input. We subtract 1 from a (the ? operator is a sequence point, so we're safe), and if it's zero, we have the second case, so should return zero. Otherwise, b/a is what we want, but we must promote b to a floating-point type first or we'll get integer division. For an empty string, we'll end up with a negative zero, but the rules don't disallow that. ### Tests: #include <stdio.h> int main(int argc, char **argv) { while (*++argv) printf("%.6f %s\n", f(*argv), *argv); }  Which gives: ./chunky 'Peanut Butter' chUnky sssmmsss 999 AA Aa '!' '' 0.916667 Peanut Butter 1.000000 chUnky 0.285714 sssmmsss 0.000000 999 0.000000 AA 1.000000 Aa 0.000000 ! -0.000000  as required. # Perl, 69 Function returning smoothness: sub f{($_)=@_;$s=s/(.)(?!\1)//sgr;chop;!$_||length($s)/length;}  ### Explanation sub f { # read argument into$_
($_) = @_; # copy$_ to $s, removing any char not followed by itself # /s to handle newlines as all other characters$s = s/(.)(?!\1)//sgr;

# reduce length by one (unless empty)
chop;

# $_ is empty (false) if length was 0 or 1 # return 1 in that case, else number of pairs / new length !$_ || length($s)/length; }  ### Tests printf "%.6f %s\n", f($a=$_),$a foreach (@ARGV);

0.083333 Peanut Butter
0.000000 chUnky
0.714286 sssmmsss
1.000000 999
1.000000 AA
0.000000 Aa
1.000000 !
1.000000


# Ruby, 63 Bytes

Outputs chunkiness.

f=->s{s.chars.each_cons(2).count{|x,y|x!=y}/[s.size-1.0,1].max}

Similar to @daniero's solution, but slightly shortened by directly dividing by the string length - 1 and then relying on .count to be zero with length 0 & 1 strings (the .max ensures I won't divide by 0 or -1).

## Mathematica, 73 72 bytes

This doesn't win anything for size, but it's straightforward:

Smoothness

N@Count[Differences@#,_Plus]/(Length@#-1)&@StringSplit[#,""]&

In[177]:= N@Count[Differences@#,_Plus]/(Length@#-1)&@StringSplit[#,""] &@"sssmmsss"

Out[177]= 0.285714

• Length[#] -> Length@# saves a stroke. So does eliminating N@ and changing 1 to 1. Commented Jul 29, 2015 at 18:32
• @hYPotenuser yep. missed it. Commented Jul 29, 2015 at 18:42

# GeL: 76 73 characters

Smoothness.

@set{d;0}
?\P$1=@incr{d} ?= \Z=@lua{c=@column -1\;return c<1and 1or$d/c}


Sample run:

bash-4.3$for i in 'Peanut Butter' 'chUnky' 'sssmmsss' '999' 'AA' 'Aa' '!' ''; do > echo -n "$i -> "
>     echo -n "$i" | gel -f smooth.gel > echo > done Peanut Butter -> 0.083333333333333 chUnky -> 0 sssmmsss -> 0.71428571428571 999 -> 1 AA -> 1 Aa -> 0 ! -> 1 -> 1  (GeL = Gema + Lua bindings. Much better, but still far from winning.) # Gema: 123 120 characters Smoothness. @set{d;0} ?\P$1=@incr{d}
?=
\Z=@subst{\?\*=\?.\*;@cmpn{@column;1;10;10;@fill-right{00000;@div{$d0000;@sub{@column;1}}}}}  Sample run: bash-4.3$ for i in 'Peanut Butter' 'chUnky' 'sssmmsss' '999' 'AA' 'Aa' '!' ''; do
>     echo -n "$i -> " > echo -n "$i" | gema -f smooth.gema
>     echo
> done
Peanut Butter -> 0.0833
chUnky -> 0.0000
sssmmsss -> 0.7142
999 -> 1.0000
AA -> 1.0000
Aa -> 0.0000
! -> 1.0
-> 1.0


(Was more an exercise for myself to see what are the chances to solve it in a language without floating point number support and generally painful arithmetic support. The 2nd line, especially the \P sequence, is pure magic, the last line is real torture.)

# Java 8, 84 82 bytes

s->{float l=s.length()-1,S=s.split("(.)(?=\\1)").length-1;return l<1?1:S<1?0:S/l;}


Outputs Smoothness.

Try it online.

Explanation:

s->{                     // Method with String parameter and float return-type
float l=s.length()-1,  //  Length of the input-String minus 1 (amount of pairs in total)
S=s.split("(.)(?=\\1)").length-1;
//  Length of the input split by duplicated pairs (with lookahead)
return l<1?            //  If the length of the input is either 0 or 1:
1              //   Return 1
:S<1?           //  Else-if S is -1 or 0:
0              //   Return 0
:               //  Else:
S/l;}          //   Return duplicated pairs divided by the length-1


# Coconut, 38 bytes

s->sum(map((!=),s,s[1:]))/-~len(s[2:])


Try it online!

A Python 3 port would be 50 bytes.

## PowerShell, 55 bytes

Smoothness

%{$s=$_;@([char[]]$s|?{$_-eq$a;$a=$_;$i++}).count/--\$i}


Seems a little bit silly to get a variable in stdin and then give it an identifier, but its quicker than having a function.

# Python 3, 61 Bytes

calculate chunkiness:

f=lambda s: sum(a!=b for a,b in zip(s,s[1:]))/max(len(s)-1,1)


# K (22)

tweaked WooiKent's Q solution:

{1^(+/x)%#x:1_=':(),x}


# Mathematica, 107 bytes

Calculates chunkiness by taking half of the Levenshtein distance between each digraph and its reverse.

f[s_]:=.5EditDistance@@{#,StringReverse@#}&/@StringCases[s,_~Repeated~{2},Overlaps->All]//Total@#/Length[#]&


If you'd prefer an exact rational answer, delete .5 and place a /2 before the last &` for no penalty. The program itself has chunkiness 103/106, or about .972.