25
\$\begingroup\$

I love BATCH, despite its shocking lack of functional commands, despite even due to its lack of non-integer support. Why? Because this works:

SET var=SET
%var% i=0

This would evaluate to:

SET var=SET
SET i=0

Fantastic, isn't it? I've used this technique in a BATCH program before, because it saves bytes!

Your challenge, should you accept it, would be to "golf" BATCH programs in this way. You are to decrease the byte size of an input BATCH program by including SET statements that would evaluate to portions of the program, and in no other way modify the program. (This disallows, say, renaming a variable name to something shorter. Keep in mind that BATCH, asides from variables, is case insensitive.) Your score is calculated as thus:

score = # of characters in your program + 5*(net result bytes in test cases below)

I reserve the right to add more test cases, so as to discourage working to optimize the program for the test cases.

For the sake of this challenge, your SET statements cannot contain control characters (|, <, >, %) or linebreaks. You may not modify the code other than to move pieces of code inside a set statement. (That is, you may not remove unnecessary whitespace, replace EQU with ==, etc.) We will assume that the lines end with \n.

Test cases

Each test case is in a separate code block, and each test case is self-contained, meaning you should golf only assuming what's given within it. (I.e., if you SET d=SET in one program, that statement will not be automatically given to any other program). Each example result can be found after each test case. There is a line between test cases.

@ECHO OFF
SET increment=10
:loop
IF %increment% EQU 0 GOTO end
ECHO %increment%
SET /A %increment%-=1
GOTO loop
:end
EXIT

@ECHO OFF
SET /p INPUT=Enter input here:
SET R=%1
ECHO Last char of input here: %R:~-1%

@ECHO OFF
SET increment=10
:e
GOTO f
ECHO f
:f
GOTO g
ECHO g
:g
GOTO h
ECHO h
:h
GOTO i
ECHO i
:i
GOTO j
ECHO j
:j
IF 3==4 ( ECHO 4 ) ELSE ( ECHO 5 )
IF 5==3 ( GOTO l ) ELSE ( GOTO k )
:k
ECHO Done.
ECHO BATCH OUT!!
EXIT
:l
GOTO g

ECHO Hello, Hello, Hello, hello, hello, Hello, Hello!, hello, ello!, Lello.

Example outputs:

@ECHO OFF
SET increment=10
:loop
IF %increment% EQU 0 GOTO end
ECHO %increment%
SET /A %increment%-=1
GOTO loop
:end
EXIT
(0 bytes saved)

@ECHO OFF
SET %i%= input here:
SET /p INPUT=Enter%i%
SET R=%1
ECHO Last char of %i%%R:~-1%
(3 bytes gained)

@ECHO OFF
SET increment=10
SET g=GOTO 
SET e=ECHO 
:e
%g%f
%e%f
:f
%g%g
%e%g
:g
%g%h
%e%h
:h
%g%i
%e%i
:i
%g%j
%e%j
:j
IF 3==4 ( %e%4 ) ELSE ( %e%5 )
IF 5==3 ( %g%l ) ELSE ( %g%k )
:k
%e%Done.
%e%BATCH OUT!!
EXIT
:l
%g%g
(10 chars saved)

SET %h%=ello,
ECHO H%h% H%h% H%h% h%h% h%h% H%h% Hello!, h%h% ello!, Lello.
(1 character saved)

\$\endgroup\$
  • 2
    \$\begingroup\$ Shortening batch for fun and profit! \$\endgroup\$ – Alex Carlsen Feb 23 '16 at 21:49
  • \$\begingroup\$ You need some more specifications. Of course AAA %increment%set a=increment¶AAA %%a%% is invalid, and AAA %1 BBB %2set a= BBB ¶AAA %1%a%%2 is valid. (iirc) So you need to formalize it. ( represents a newline) \$\endgroup\$ – user202729 May 23 '18 at 7:43
  • \$\begingroup\$ Do we need to handle code that has delayed expansion enabled, percent-sign escapes, or multi-line for / if statements? As per the last test case (which produces additional output as echo is on and there's no @ before the SET) is extraneous output acceptable from the golfed program? \$\endgroup\$ – Οurous Feb 24 at 22:49
  • 1
    \$\begingroup\$ Tcl all over again \$\endgroup\$ – Ven Mar 19 at 13:19
  • 1
    \$\begingroup\$ despite even due to? \$\endgroup\$ – Adám Aug 29 at 7:38
4
+400
\$\begingroup\$

Java 8, Java 10, 3884 799/795 program + 484 output = 4368 1283/1279 total

There are two limitations of this code:

  • It assumes that variables from A to Z are free. (uppercase)
  • It assumes that there are no more than 27 substitutions.
  • Oh, and because Scanner doesn't quite cut it, empty input dumps out stacktrace.

But hey - there is a pro!

  • Outputs best code. Always.

The code manages to perform better than examples provided by challenge author.

This golfed version has been made by Kevin.

Java 8

c->{List<String>S=new Stack();HashMap<String,Integer>h=new HashMap(),s=new HashMap();int v=65,l=c.length(),b,e;do{for(b=0,l=c.length(),s.clear();b!=l;b++)for(e=b;++e<=l;)S.add(c.substring(b,e));S.removeIf(t->t.length()<5|t.matches(".*[\n|<%>].*"));S.forEach(t->h.merge(t,1,Integer::sum));S.clear();h.entrySet().removeIf(t->t.getValue()==1);String Y=c;int L=l;char V=(char)v;h.forEach((k,x)->{String i=Y,t;for(int j,I,q;i.contains(k);i=t+"%"+V+"%"+i.substring(j+k.length(),i.length())){for(I=-1,t=i.substring(q=0,j=i.indexOf(k));(I=t.indexOf("%",++I))>=0;q++);if(q%2>0)return;}i="SET "+V+"="+k+"\n"+i;if(i.length()<L)s.put(i,L-i.length());});h.clear();v++;c=s.isEmpty()?c:s.entrySet().stream().max((x,y)->x.getValue()>y.getValue()?1:-1).get().getKey();}while(l>c.length());return c;}

Try it online!

Java 10

c->{var S=new Stack<String>();HashMap<String,Integer>h=new HashMap(),s=new HashMap();int v=65,l=c.length(),b,e;do{for(b=0,l=c.length(),s.clear();b!=l;b++)for(e=b;++e<=l;)S.add(c.substring(b,e));S.removeIf(t->t.length()<5|t.matches(".*[\n|<%>].*"));S.forEach(t->h.merge(t,1,(x,y)->x+y));S.clear();h.entrySet().removeIf(t->t.getValue()==1);var Y=c;int L=l;var V=(char)v;h.forEach((k,x)->{String i=Y,t;for(int j,I,q;i.contains(k);i=t+"%"+V+"%"+i.substring(j+k.length(),i.length())){for(I=-1,t=i.substring(q=0,j=i.indexOf(k));(I=t.indexOf("%",++I))>=0;q++);if(q%2>0)return;}i="SET "+V+"="+k+"\n"+i;if(i.length()<L)s.put(i,L-i.length());});h.clear();v++;c=s.isEmpty()?c:s.entrySet().stream().max((x,y)->x.getValue()>y.getValue()?1:-1).get().getKey();}while(l>c.length());return c;}

Try it online!.

Original version

It's not golfed at all, I just wanted to have some fun, not to suffer. If you, dear reader, would like to golf this answer, please do it.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;

public class Main {
	List<String> substrings = new ArrayList<String>();
	HashMap<String, Integer> hm = new HashMap<String, Integer>();
	HashMap<String, Integer> scores = new HashMap<String, Integer>();
	
	private int v1 = 65;
	
	public static String rfos(String inputString, String stringToReplace,
	        String stringToReplaceWith) {

	    int length = stringToReplace.length();
	    int inputLength = inputString.length();

	    int startingIndexofTheStringToReplace = inputString.indexOf(stringToReplace);

	    if(count(inputString.substring(0, startingIndexofTheStringToReplace), "%") % 2 == 1)
	    	return null;
	    
	    String finalString = inputString.substring(0, startingIndexofTheStringToReplace) + stringToReplaceWith
	            + inputString.substring(startingIndexofTheStringToReplace + length, inputLength);

	    return finalString;

	}
	
	public static int count(String text, String find) {
        int index = 0, count = 0, length = find.length();
        while( (index = text.indexOf(find, index)) != -1 ) {                
                index += length; count++;
        }
        return count;
	}
	
	private String process(String program) {
		int begin = 0, end, il = program.length();
		
		scores.clear();
		
		while(begin != program.length()) {
			for(end = begin + 1; end < program.length() + 1; end++)
				substrings.add(program.substring(begin, end));
			begin++;
		}
		
		substrings.removeIf(new Predicate<String>() {
			@Override
			public boolean test(String arg0) {
				return arg0.length() <= 4 || arg0.contains("\n")
						|| arg0.contains("|")
						|| arg0.contains("<")
						|| arg0.contains("%")
						|| arg0.contains(">");
			}
		});
		
		substrings.forEach(new Consumer<String>() {

			@Override
			public void accept(String t) {
				if(hm.containsKey(t)) {
					hm.replace(t, hm.get(t) + 1);
				} else {
					hm.put(t, 1);
				}
			}
			
		});
		
		substrings.clear();
		
		hm.entrySet().removeIf(new Predicate<Map.Entry<String, Integer>>() {

			@Override
			public boolean test(Map.Entry<String, Integer> t) {
				return t.getValue() == 1;
			}
			
		});
		
		hm.forEach(new BiConsumer<String, Integer>() {
			
			@Override
			public void accept(String arg0, Integer arg1) {
				String iteration = program;
				boolean between = false;
				while(iteration.contains(arg0)) {
					iteration = rfos(iteration, arg0, "%" + Character.toString((char) v1) + "%");
					if(iteration == null)
						return;
				}
				iteration = "SET " + Character.toString((char) v1) + "=" + arg0 + "\n" + iteration;
				if(iteration.length() < program.length())
					scores.put(iteration, program.length() - iteration.length());
			}
			
		});
		
		hm.clear();
		v1++;
		
		if(scores.isEmpty())
			return program;
		else
			return scores.entrySet().stream().max((entry1, entry2) -> entry1.getValue() > entry2.getValue() ? 1 : -1).get().getKey();
	}

	public static void main(String[] args) {
		Main processor = new Main();
		int genid = 0, before = 0, after = 0;
		String currentCode = new Scanner(System.in).useDelimiter("\\Z").next();
		
		System.out.println("Calculating first generation...");
		
		do {
			String cc = processor.process(currentCode);
			before = currentCode.length();
			after = cc.length();
			
			currentCode = cc;
			
			if(before > after) {
				System.out.println("Generation " + genid++);
				System.out.println(before + " -> " + after);
				System.out.println("***\n" + cc + "\n***");
			} else {
				System.out.println("Generation FAIL " + genid++);
				System.out.println(before + " -> " + after);
				System.out.println("***\n" + cc + "\n***");
			}
		} while(before > after);
		
		
	}

}

Example output:

SET B=GOTO 
SET A=ECHO 
@%A%OFF
SET increment=10
:e
%B%f
%A%f
:f
%B%g
%A%g
:g
%B%h
%A%h
:h
%B%i
%A%i
:i
%B%j
%A%j
:j
IF 3==4 ( %A%4 ) ELSE ( %A%5 )
IF 5==3 ( %B%l ) ELSE ( %B%k )
:k
%A%Done.
%A%BATCH OUT!!
EXIT
:l
%B%g

Try it online!

\$\endgroup\$
  • \$\begingroup\$ I think all that "java.util." is repetitive. You may wany to simplify your code to import java.util.*. \$\endgroup\$ – A _ Aug 29 at 8:22
  • \$\begingroup\$ I thought jdk imports do not count? \$\endgroup\$ – Mark Jeronimus Aug 29 at 8:52
  • \$\begingroup\$ @A_ you may modify my answer as you wish (unless it's valid and keeps the spirit) \$\endgroup\$ – Krzysztof Szewczyk Aug 29 at 12:21
  • 1
    \$\begingroup\$ "If you, dear reader, would like to golf this answer, please do it." 799 bytes in Java 8 or 795 bytes in Java 10+. You're welcome. :) Can definitely be golfed some more, but this will do for now. \$\endgroup\$ – Kevin Cruijssen Aug 29 at 13:28
  • 2
    \$\begingroup\$ @KevinCruijssen Thanks for the contribution. I've added your version to the post. Feel free to edit it without asking me if you find something better. \$\endgroup\$ – Krzysztof Szewczyk Aug 29 at 13:32

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.