Skip to content

Commit 5f2c0bc

Browse files
committed
Add PlusMinusOptmizer to analyse sequences of +-><, addressing #28
1 parent 6d9aa31 commit 5f2c0bc

File tree

3 files changed

+223
-0
lines changed

3 files changed

+223
-0
lines changed

src/main/groovy/net/zomis/brainf/analyze/analyzers/BrainfuckAnalyzers.groovy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class BrainfuckAnalyzers {
1313
new WhileLoopAnalysis(),
1414
new CommandCountAnalysis(),
1515
new MemoryIndexAnalysis(),
16+
new PlusMinusOptimizer(),
1617
]
1718
analyzers
1819
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
package net.zomis.brainf.analyze.analyzers
2+
3+
import net.zomis.brainf.analyze.Brainalyze
4+
import net.zomis.brainf.analyze.BrainfuckAnalyzer
5+
import net.zomis.brainf.analyze.InspectionResult
6+
import net.zomis.brainf.analyze.MemoryCell
7+
import net.zomis.brainf.model.BrainfuckCommand
8+
import net.zomis.brainf.model.BrainfuckRunner
9+
import net.zomis.brainf.model.classic.BrainFCommand
10+
11+
class PlusMinusOptimizer implements BrainfuckAnalyzer {
12+
13+
private static final Set<BrainfuckCommand> handlableCommands = [BrainFCommand.ADD, BrainFCommand.SUBTRACT,
14+
BrainFCommand.NEXT, BrainFCommand.PREVIOUS] as Set
15+
16+
private final List<InspectionResult> results = []
17+
18+
private List<Integer> changesLeft = new ArrayList<>()
19+
private List<Integer> changesRight = new ArrayList<>()
20+
private int pointerChange
21+
private int commandStart
22+
private int commandsUsed
23+
24+
@Override
25+
void after(Brainalyze analyze, BrainfuckRunner runner) {
26+
finishAndReset(runner)
27+
results.each {
28+
analyze.addInspectionResult(it)
29+
}
30+
}
31+
32+
@Override
33+
void beforePerform(MemoryCell cell, BrainfuckRunner runner, BrainfuckCommand command) {
34+
if (!(command instanceof BrainFCommand)) {
35+
return
36+
}
37+
if (command == BrainFCommand.NONE) {
38+
return
39+
}
40+
if (!handlableCommands.contains(command)) {
41+
finishAndReset(runner)
42+
return
43+
}
44+
BrainFCommand cmd = (BrainFCommand) command
45+
switch (cmd) {
46+
case BrainFCommand.PREVIOUS:
47+
pointerMove(-1)
48+
break
49+
case BrainFCommand.NEXT:
50+
pointerMove(1)
51+
break
52+
case BrainFCommand.SUBTRACT:
53+
valueChange(-1)
54+
break
55+
case BrainFCommand.ADD:
56+
valueChange(1)
57+
break
58+
default:
59+
throw new IllegalStateException('Unexpected command: ' + command)
60+
}
61+
commandsUsed++
62+
}
63+
64+
void valueChange(int i) {
65+
int index = pointerChange
66+
List list
67+
if (pointerChange >= 0) {
68+
list = changesRight
69+
} else {
70+
index = Math.abs(pointerChange) - 1
71+
list = changesLeft
72+
}
73+
while (list.size() <= index) {
74+
list.add(0)
75+
}
76+
list.set(index, list.get(index) + i)
77+
}
78+
79+
void pointerMove(int i) {
80+
pointerChange += i
81+
}
82+
83+
boolean finishAndReset(BrainfuckRunner runner) {
84+
trim(changesLeft)
85+
trim(changesRight)
86+
// find out if it's better to go to the `pointerChange` from left or right
87+
// >>>>> + <<<<< <<<<< <<<<< - > + for example is better to go right first, then left
88+
int leftCost = changesLeft.size() + pointerChange
89+
int rightCost = changesRight.size() - 1 - pointerChange
90+
boolean fromLeftToEnd = leftCost < rightCost
91+
92+
StringBuilder str = new StringBuilder()
93+
if (fromLeftToEnd) {
94+
if (!changesRight.isEmpty()) {
95+
str.append(code(changesRight, '>' as char))
96+
str.append('<' * (changesRight.size() - 1))
97+
}
98+
if (!changesLeft.isEmpty()) {
99+
str.append('<')
100+
str.append(code(changesLeft, '<' as char))
101+
}
102+
if (leftCost > 0) {
103+
str.append('>' * leftCost)
104+
} else {
105+
str.append('<' * -leftCost)
106+
}
107+
} else {
108+
if (!changesLeft.isEmpty()) {
109+
str.append('<')
110+
str.append(code(changesLeft, '<' as char))
111+
str.append('>' * changesLeft.size())
112+
}
113+
if (!changesRight.isEmpty()) {
114+
str.append(code(changesRight, '>' as char))
115+
}
116+
if (rightCost > 0) {
117+
str.append('<' * rightCost)
118+
} else {
119+
str.append('>' * -rightCost)
120+
}
121+
}
122+
123+
124+
String shortestCode = str.toString()
125+
if (shortestCode.length() < this.commandsUsed) {
126+
this.results.add(new InspectionResult(InspectionResult.InspectionSeverity.HINT,
127+
commandStart, runner.code.commandIndex,
128+
"Code is unnecessarily complicated. Can be written as '$shortestCode'"))
129+
}
130+
131+
changesLeft.clear()
132+
changesRight.clear()
133+
pointerChange = 0
134+
commandStart = 0
135+
commandsUsed = 0
136+
}
137+
138+
static String code(List<Integer> list, char ch) {
139+
StringBuilder str = new StringBuilder()
140+
def it = list.listIterator()
141+
while (it.hasNext()) {
142+
int value = it.next()
143+
if (value > 0) {
144+
str.append('+' * value)
145+
}
146+
if (value < 0) {
147+
value = Math.abs(value)
148+
str.append('-' * value)
149+
}
150+
if (it.hasNext()) {
151+
str.append(ch)
152+
}
153+
}
154+
str.toString()
155+
}
156+
157+
static void trim(List<Integer> integers) {
158+
while (!integers.isEmpty() && integers.get(integers.size() - 1) == 0) {
159+
integers.remove(integers.size() - 1)
160+
}
161+
}
162+
163+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package net.zomis.brainf;
2+
3+
import net.zomis.brainf.analyze.InspectionResult;
4+
import net.zomis.brainf.analyze.analyzers.PlusMinusOptimizer;
5+
import org.junit.Assert;
6+
import org.junit.Test;
7+
import org.junit.runner.RunWith;
8+
import org.junit.runners.Parameterized;
9+
10+
import java.util.Arrays;
11+
import java.util.Collection;
12+
import java.util.List;
13+
14+
@RunWith(Parameterized.class)
15+
public class OptimizeTest extends BrainfuckTest {
16+
17+
@Parameterized.Parameters
18+
public static Collection<Object[]> data() {
19+
return Arrays.asList(new Object[][]{
20+
{"+>+++<++>--<->+>", "++>++>"},
21+
{">>>+++<<< <+> >>--", "<+>>>-->+++<"}, // -1, +3, end at +2. from right to pos
22+
{"<<<<< + >>>>> >+< <", null}, // -5, +1, end at -1. from right to pos
23+
{"+>+", null},
24+
{"+-", ""},
25+
{"<>", ""},
26+
{"+-<>", ""},
27+
{"+>-<->+<", ""},
28+
{"+>-<+>-<", "++>--<"},
29+
{"<+>-<+>-", "<++>--"},
30+
31+
});
32+
}
33+
34+
@Parameterized.Parameter(0)
35+
public String original;
36+
37+
@Parameterized.Parameter(1)
38+
public String cleaned;
39+
40+
@Test
41+
public void test() {
42+
getSource().addCommands(original);
43+
analyze(new PlusMinusOptimizer());
44+
System.out.println(getAnalyze().getInspectionResults());
45+
List<InspectionResult> results = getAnalyze().getInspectionResults();
46+
String actual = results.isEmpty() ? null : results.get(0).getDescription();
47+
String str = String.format("%s resulted in %s and not the expected %s", original, actual, cleaned);
48+
if (cleaned == null) {
49+
Assert.assertTrue(str, getAnalyze().getInspectionResults().isEmpty());
50+
return;
51+
}
52+
Assert.assertFalse(str, getAnalyze().getInspectionResults().isEmpty());
53+
Assert.assertTrue(str, getAnalyze().getInspectionResults().get(0).getDescription()
54+
.contains(cleaned));
55+
}
56+
57+
58+
59+
}

0 commit comments

Comments
 (0)