14
\$\begingroup\$

The challenge

Given an input string, and an integer n - truncate any runs of consecutive characters to a maximum of n length. The characters can be anything, including special characters. The function should be case sensitive, and n can range from 0 to infinity.

Example inputs/outputs:

f("aaaaaaabbbccCCCcc", 2) //"aabbccCCcc" 
f("aaabbbc", 1) //"abc"
f("abcdefg", 0) //""
f("aaaaaaabccccccccCCCCCC@", 4) //"aaaabccccCCCC@"

Scoring

The scoring is based on the number of bytes used. Thus

function f(s,n){return s.replace(new RegExp("(.)\\1{"+n+",}","g"),function(x){return x.substr(0, n);});}

would be 104 points.

Happy golfing!

Edit: removed language restriction, but I still would love to see javascript answers

\$\endgroup\$
  • 1
    \$\begingroup\$ Why don't allow ES6 ? \$\endgroup\$ – TuxCrafting Jul 8 '16 at 15:01
  • 7
    \$\begingroup\$ I'd recommend losing the language requirement. Javascript is one of the most common languages here. Self answering with what you got would probably invite people to help you golf, or try to beat you with another approach. Further, if you get enough reputation you can add a bounty to the question with a specific language in mind. If that doesn't sit well with you, you could modify this question into a tips question and try to ask for specific golfing help. \$\endgroup\$ – FryAmTheEggman Jul 8 '16 at 15:02
  • \$\begingroup\$ Removed language restriction and changed scoring rules as a result. I would still love to see javascript entries, but I guess I can live with some 4-5 character golf languages. \$\endgroup\$ – TestSubject06 Jul 8 '16 at 15:39
  • \$\begingroup\$ Welcome to Programming Puzzles & Code Golf! Code golf challenges are scored by length in bytes by default. While scoring by length in characters is possible, you're bound to get some answers like this one. \$\endgroup\$ – Dennis Jul 8 '16 at 15:42
  • \$\begingroup\$ Oh, god. Changed to byte scoring. \$\endgroup\$ – TestSubject06 Jul 8 '16 at 15:52

19 Answers 19

6
\$\begingroup\$

Python 2, 52 bytes

lambda s,n:reduce(lambda r,c:r+c*(r[-n:]!=c*n),s,'')

Written out as a program (54 bytes):

s,n=input();r=''
for c in s:r+=c*(r[-n:]!=c*n)
print r

Iterates through the input string s, appending each character to the output string r unless that last n characters of r are that character.

I though this would fail n==0 because r[-0:] is not the last 0 characters (empty string), but the entire string. But, it works because the string remains empty, so its keeps matching the 0-character string.

A recursive lambda gave 56 because of the repetition

f=lambda s,n:s and s[:f(s[1:],n)[:n]!=s[0]*n]+f(s[1:],n)

An alternate strategy to keep a counter i of repeats of the last character also turned out longer than just checking the last n characters directly.

\$\endgroup\$
6
\$\begingroup\$

C, 81 78

Modifies the incoming string.

c,a;f(p,n)char*p;{char*s=p;for(;*p;s+=c<n)*s=*p++,a^*s?c=0:++c,a=*s;c=a=*s=0;}

Test Program

Requires two parameters, the first is the string to truncate, the second is the length limit.

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

int main(int argc, const char **argv)
{
    char *input=malloc(strlen(argv[1])+1);
    strcpy(input,argv[1]);
    f(input,atoi(argv[2]));
    printf("%s\n",input);
    free(input);
    return 0;
}

Explanation:

c,a;                 //declare two global integers, initialized to zero.
                     //c is the run length, a is the previous character
f(char*p,int n){...} //define function f to truncate input
char*s=p;            //copy p to s; p is source, s is destination
for(;*p              //while there is a source character
;s+=c<n)             //increment copied pointer if run is under the limit
*s=*p++,             //copy from source to destination, increment source
a^*s?c=0:++c,        //if previous character != current then run=0 else increment run
a=*s;                //previous character = current source character
c=a=*s=0;            //after loop, terminate destination string with NUL and reset c and a.

This works because the source pointer will always be equal to or greater than the destination pointer, so we can write over the string as we parse it.

\$\endgroup\$
  • \$\begingroup\$ This is amazing, can you explain it? \$\endgroup\$ – TestSubject06 Jul 8 '16 at 16:50
  • \$\begingroup\$ @TestSubject06 - Added an explanation. \$\endgroup\$ – owacoder Jul 8 '16 at 17:01
  • \$\begingroup\$ Does this work with the n=0 case? I can't get it to compile to test over here. \$\endgroup\$ – TestSubject06 Jul 8 '16 at 17:06
  • \$\begingroup\$ Yes, it does. I added a test program so you can compile. \$\endgroup\$ – owacoder Jul 8 '16 at 17:11
  • \$\begingroup\$ Awesome, couldn't find any counter examples. Short and it works! \$\endgroup\$ – TestSubject06 Jul 8 '16 at 17:23
5
\$\begingroup\$

Haskell, 36 bytes

import Data.List
(.group).(=<<).take

Point-free version of \n s -> concatMap (take n) (group s).

\$\endgroup\$
4
\$\begingroup\$

Javascript ES6, 60 54 55 43 bytes

-12 bytes thanks to @TestSubject06 and @Downgoat

(s,n)=>s.replace(/(.)\1*/g,x=>x.slice(0,n))

Example runs:

f("aaaaaaabbbccCCCcc"      , 2) -> "aabbccCCcc" 
f("aaabbbc"                , 1) -> "abc"
f("abcdefg"                , 0) -> ""
f("aaaaaaabccccccccCCCCCC@", 4) -> "aaaabccccCCCC@"
f("a"                      , 1) -> "a"
\$\endgroup\$
  • \$\begingroup\$ f("a", 1) -> "" \$\endgroup\$ – TestSubject06 Jul 8 '16 at 17:46
  • 1
    \$\begingroup\$ Since your RegExp isn't dynamically controlled in any way you can save some bytes with RegExp("(.)\\1*","g") -> /(.)\1*/g \$\endgroup\$ – TestSubject06 Jul 8 '16 at 18:10
  • 1
    \$\begingroup\$ Convert RegExp("(.)\\1*","g") to /(.)\1*/g \$\endgroup\$ – Downgoat Jul 8 '16 at 18:10
  • 1
    \$\begingroup\$ I don't see this getting smaller in JS unless we come at it from a completely different angle. Good job @Dendrobium! \$\endgroup\$ – TestSubject06 Jul 8 '16 at 18:18
  • 1
    \$\begingroup\$ Shave one byte by changing (s,n) to s=>n, and the usage becomes f("aaaaaaabbbccCCCcc")(2) \$\endgroup\$ – Patrick Roberts Aug 5 '16 at 0:40
3
\$\begingroup\$

MATL, 9 bytes

Y'i2$X<Y"

Try it Online

Explanation

        % Implicitly grab input as a string
Y'      % Perform run-length encoding. Pushes the values and the run-lengths to the stack
i       % Explicitly grab the second input
2$X<    % Compute the minimum of the run lengths and the max run-length
Y"      % Perform run-length decoding with these new run lengths
        % Implicitly display the result
\$\endgroup\$
  • \$\begingroup\$ '@@@@@bbbbbcccddeegffsassss' 3 returned '@@@bbbcccddeegffsass' which is missing the final 's' \$\endgroup\$ – TestSubject06 Jul 8 '16 at 16:55
  • \$\begingroup\$ @TestSubject06 Thanks for pointing that out. \$\endgroup\$ – Suever Jul 8 '16 at 17:29
2
\$\begingroup\$

CJam, 12 bytes

{e`\af.e<e~}

Try it online!

Explanation

e`   e# Run-length encode the input. Gives a list of pair [length character].
\a   e# Swap with maximum and wrap in an array.
f.e< e# For each run, clamp the run-length to the given maximum.
e~   e# Run-length decode.
\$\endgroup\$
2
\$\begingroup\$

Pyth, 16 12 bytes

rm,hS,Qhdedrz8 9
ss<RQmM_Mrz8

Try it online!

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

Python 2, 56 bytes

import re
lambda s,n:re.sub(r'(.)(\1{%d})\1*'%n,r'\2',s)
\$\endgroup\$
2
\$\begingroup\$

gs2, 6 bytes

Encoded in CP437:

╠c╨<ΘΣ

This is an anonymous function (block) that expect a number on top of the stack and a string below it.

     Σ   Wrap previous five bytes in a block:
╠          Pop number into register A.
 c         Group string.
    Θ      Map previous two bytes over each group:
  ╨<         Take the first A bytes.

Try it online. (The code here is lines, dump, read number, [the answer], run-block.)

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

Perl 6,  38  36 bytes

->$_,$n {S:g/(.)$0**{$n..*}/{$0 x$n}/}
->$_,\n{S:g/(.)$0**{n..*}/{$0 x n}/}

Explanation:

-> $_, \n { # pointy block lambda
  # regex replace ( return without modifying variant )
  # globally
  S:global /
    # a char
    (.)
    # followed by 「n」 or more identical chars
    $0 ** { n .. * }
  /{
    # repeat char 「n」 times
    $0 x n
  }/
}

Test:

#! /usr/bin/env perl6
use v6.c;
use Test;

my &truncate-char-runs-to = ->$_,\n{S:g/(.)$0**{n..*}/{$0 x n}/}

my @tests = (
  ("aaaaaaabbbccCCCcc", 2) => "aabbccCCcc",
  ("aaabbbc", 1) => "abc",
  ("abcdefg", 0) => "",
  ("aaaaaaabccccccccCCCCCC@", 4) => "aaaabccccCCCC@",
);

plan +@tests;

for @tests -> $_ ( :key(@input), :value($expected) ) {
  is truncate-char-runs-to(|@input), $expected, qq'("@input[0]", @input[1]) => "$expected"';
}
1..4
ok 1 - ("aaaaaaabbbccCCCcc", 2) => "aabbccCCcc"
ok 2 - ("aaabbbc", 1) => "abc"
ok 3 - ("abcdefg", 0) => ""
ok 4 - ("aaaaaaabccccccccCCCCCC@", 4) => "aaaabccccCCCC@"
\$\endgroup\$
0
\$\begingroup\$

Javascript ES5, 73

function f(s,n){return s.replace(RegExp("(.)(\\1{"+n+"})\\1*","g"),"$2")}

Re-uses Lynn's regex from her Python answer.

\$\endgroup\$
  • \$\begingroup\$ Your code does not handle the case where n is zero, it just returns the whole original string. \$\endgroup\$ – TestSubject06 Jul 8 '16 at 17:03
  • \$\begingroup\$ Yes, in Firefox, you can drop the braces and return statement, although that syntax is (sadly) deprecated and will be removed (it was actually absent a few versions back, didn't realize they brought it back). \$\endgroup\$ – Dendrobium Jul 8 '16 at 17:08
  • \$\begingroup\$ You can also drop the new keyword for -4 bytes. \$\endgroup\$ – Dendrobium Jul 8 '16 at 17:12
  • \$\begingroup\$ @TestSubject06 Thanks, I've edited my answer and I believe it passes the test cases now. \$\endgroup\$ – FryAmTheEggman Jul 8 '16 at 17:35
0
\$\begingroup\$

Perl 5, 50 bytes

46 bytes code + 3 for -i and 1 for -p

Takes the number to truncate to via -i.

s!(.)\1+!$&=~s/(.{$^I}).+/$1/r!ge

Usage

perl -i4 -pe 's!(.)\1+!$&=~s/(.{$^I}).+/$1/r!ge' <<< 'aaaaaaabccccccccCCCCCC@'
aaaabccccCCCC@
\$\endgroup\$
  • \$\begingroup\$ Why is -p only one byte? \$\endgroup\$ – someonewithpc Jul 10 '16 at 19:01
  • \$\begingroup\$ @someonewithpc when it can be combined with the -e these options only consume 1 byte. If the script has to be run from a file it costs 3 for the space and he flag itself. There's a meta post I'll try and find but I'm on mobile right now. \$\endgroup\$ – Dom Hastings Jul 10 '16 at 19:37
  • \$\begingroup\$ @someonewithpc meta.codegolf.stackexchange.com/questions/273/… \$\endgroup\$ – Dom Hastings Jul 10 '16 at 19:43
0
\$\begingroup\$

Bash 46 bytes

read c;sed -r ":l;s/(.)(\1{$c})(.*)/\2\3/;t l"

Usage: Enter the number of characters to limit, press enter and enter the string. Ctrl + D to exit sed (send EOF).

\$\endgroup\$
0
\$\begingroup\$

Java 7, 107 106 bytes

String c(String s,int i){String x="";for(int i=-1;++i<j;)x+="$1";return s.replaceAll("(.)\\1{"+i+",}",x);}

Previous alternative inline for-loop for String concatenation (which is 1 byte more than String s="";for(int i=-1;++i<j;)s+="$1"; unfortunately):

String c(String s,int i){return s.replaceAll("(.)\\1{"+i+",}",new String(new char[i]).replace("\0","$1")));}

Ungolfed & test cases:

Try it here.

class Main {
  static String c(String s, int i){
    String x="";
    for(int j = -1; ++j < i;){
      x += "$1";
    }
    return s.replaceAll("(.)\\1{"+i+",}", x);
  }

  public static void main(String[] a){
    System.out.println(c("aaaaaaabbbccCCCcc", 2));
    System.out.println(c("aaabbbc", 1));
    System.out.println(c("abcdefg", 0));
    System.out.println(c("aaaaaaabccccccccCCCCCC@", 4));
    System.out.println(c("@@@@@bbbbbcccddeegffsassss", 5));
  }
}

Output:

aabbccCCcc
abc

aaaabccccCCCC@
@@@@@bbbbbcccddeegffsassss
\$\endgroup\$
0
\$\begingroup\$

Javascript (using external library) (115 bytes)

(s,r)=>_.From(s).Aggregate((c,n)=>{if(c.a!=n){c.c=1;c.a=n}else{c.c++}if(c.c<=r){c.b+=n}return c},{a:"",b:"",c:0}).b

Link to lib: https://github.com/mvegh1/Enumerable

Code explanation: Load the string into library, which internally parses as char array. Apply an accumulator on the sequence, passing in a custom object as a seed value. Property a is the current element, b is the accumulated string, and c is the sequential count of the current element. The accumulator checks if the current iteration value, n, is equal to the last element value, c.a. If not, we reset the count to 1 and set the current element. If the count of the current element is less than or equal to the desired length, we accumulate it to the return string. Finally, we return property b, the accumulated string. Not the golfiest code, but happy I got a solution that works...

enter image description here

\$\endgroup\$
0
\$\begingroup\$

J, 31 30 bytes

((<.#@>)#{.@>@])]<;.1~1,2~:/\]

Groups the input string into runs (substrings) of identical characters, and takes the minimum of the length of that run and the max length that was input for truncating the string. Then copies the first character of each run that many times.

Usage

   f =: ((<.#@>)#{.@>@])]<;.1~1,2~:/\]
   2 f 'aaaaaaabbbccCCCcc'
aabbccCCcc
   1 f 'aaabbbc'
abc
   0 f 'abcdefg'

   4 f 'aaaaaaabccccccccCCCCCC@'
aaaabccccCCCC@

Explanation

((<.#@>)#{.@>@])]<;.1~1,2~:/\]  Input: k on LHS, s on RHS
                             ]  Get s
                        2~:/\   Test if each pair of consecutive chars are not equal
                      1,        Prepend a 1
                ]               Get s
                 <;.1~          Chop s where a 1 occurs to get the runs in s
    #@>                         Get the length of each run
  <.                            Take the min of the length and k
         {.@>@]                 Get the head of each run
        #                       Copy the head of each run min(k, len(run)) times
                                Return that string as the result
\$\endgroup\$
0
\$\begingroup\$

Dyalog APL, 22 20 bytes

(∊⊢↑¨⍨⎕⌊⍴¨)⊢⊂⍨1,2≠/⊢

Prompts for n and takes input string as argument.

( the tacit function ...
     flatten
    ⊢↑¨⍨ each element of the argument (i.e. each partition) truncated to
    ⎕⌊⍴¨ the minimum of the numeric input and the current length
) [end of tacit function] applied to
⊢⊂⍨ the input partitioned at the ᴛʀᴜᴇs of
1, ᴛʀᴜᴇ prepended to (the first character is not equal to its non-extant predecessor)
2≠/⊢ the pair-wise not-equal of characters in the input

\$\endgroup\$
0
\$\begingroup\$

Ruby, 32 bytes

->s,n{s.gsub(/(.)\1*/){$&[0,n]}}
\$\endgroup\$
-1
\$\begingroup\$

TCC, 7 5 bytes

$~(;)

Input is a string and a number, seperated by space.

Try it online!

       | Printing is implicit
$~     | Limit occurence
  (;   | First part of input
    )  | Second part of input
\$\endgroup\$
  • 1
    \$\begingroup\$ Neither revision of your answer worked with the tcc.lua file with timestamp 16-07-25 16:57 UTC, which didn't have the ability to read multiple inputs at once. If your answer requires a version of the language that postdates the challenge, you must label it as non-competing in the header. I'll remove my downvote when you do. \$\endgroup\$ – Dennis Jul 26 '16 at 17:07

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.