Challenge 14

package net.p3consulting.so.challenges;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Challenge14 {
  public static void main(final String[] args) {
  final String classPath = System.getProperty("java.class.path", ".");
  final String[] classPathElements = classPath.split(System.getProperty("path.separator"));

  final File dir = new File(classPathElements[0]);
  final Pattern pattern = Pattern.compile(".*/challenge14_\\d+.csv");
  Arrays.stream(dir.listFiles()).sorted().forEach(
    file -> {
     String fileName = null;
     try {
      fileName = file.getCanonicalPath();
      if (pattern.matcher(fileName).matches()) {
       fileName = fileName.substring(fileName.lastIndexOf('/'));
       System.out.println(fileName);

       int[][] problems = readFile(fileName);

       int totalValue = 0;
       char status;
       int start = -1;
       boolean debug = false;
       boolean problem = true;
       Pattern pat = Pattern.compile("((C*DC*M)|(C*MC*D))C*L?");

       for (int p = 0; p < problems.length; p++) {
        int[] data = problems[p];
        Arrays.sort(data);
        final StringBuilder concatStatus = new StringBuilder("");
        final Stack solStack = new Stack<>();

        for (int i = 0; i < data.length - 1; i++) {
         if (data[i] + 1 == data[i + 1])
          status = 'C'; // (C)ontinuous
         else if (data[i] == data[i + 1])
          status = 'D'; // (D)uplicate
         else if (data[i] + 2 == data[i + 1])
          status = 'M'; // (M)issing
         else if (
           (data[i] - 1 == (i > 0 ? data[i - 1] : Integer.MAX_VALUE)) ||
             (data[i] - 2 == (i > 0 ? data[i - 1] : Integer.MAX_VALUE))
         )
          status = 'L';
         else
          status = 'A'; // (A)ny other

         concatStatus.append(status);
        }
        // the latest number classification is a special case since no next value to compare with
        if ((data[data.length - 1] == data[data.length - 2] + 1) // C
          || (data[data.length - 1] == data[data.length - 2] + 2) // M
          || (data[data.length - 1] == data[data.length - 2]) // D
        ) {
         concatStatus.append('L');
        }

        final String cs = concatStatus.toString();
        if (debug)
         System.out.println("\nconcatStatus '" + cs + "' " + Arrays.toString(data));
        final Matcher m = pat.matcher(cs);
        if (m.find()) {
         int pos;
         int maxPos;
         int maxEnd;
         int maxLen = Integer.MIN_VALUE;
         do {
          int startPos = m.start(0);
          int endPos = m.end(0);

          if (debug)
           System.out.println("ID: " + (p + 1) + " @" + start + ", status: '" + cs
             + "' matches: "
             + m.start(0) + " to " + m.end(0) + " " +
             cs.substring(m.start(0), m.end(0)));
          int len = endPos - startPos;
          if (len == maxLen) {
           // push another solution
           solStack.push(new int[]{startPos, endPos});
          } else if (len > maxLen) {
           // replace the solutions
           solStack.clear();
           solStack.push(new int[]{startPos, endPos});

           maxLen = len;
          }
          pos = startPos + 1;

          if (debug)
           System.out.println("\tnext pos " + pos);
          if (pos == Integer.MAX_VALUE)
           break;
         }
         while (m.find(pos));

         while (!solStack.empty()) {
          int[] positions = solStack.pop();

          maxPos = positions[0];
          maxEnd = positions[1];

          if (debug)
           System.out.println("\tmax at " + maxPos + " to " + maxEnd);
          int dupIdx = cs.substring(maxPos, maxEnd).indexOf('D');
          int missIdx = cs.substring(maxPos, maxEnd).indexOf('M');

          final List noise = new ArrayList<>();
          for (int i = 0; i < data.length; i++) {
           if (i < maxPos || i > maxEnd - 1)
            noise.add(data[i]);
          }

          maxEnd--;

          System.out.println("ID: " + (p + 1) +
            " " + Arrays.toString(data) + "\n\tFrom " + data[maxPos] + " to " + data[maxEnd]
            + " " + (debug ? cs + " " : "")
            + ", sum: " + (data[maxPos] + data[maxEnd])
            + ", missing: " + ((missIdx == -1) ? "" : data[maxPos + missIdx] + 1)
            + ", duplicate: " + ((dupIdx == -1) ? "" : data[maxPos + dupIdx])
            + ", noise: " + noise
          );

          totalValue += data[maxPos] + data[maxEnd];
         }
        } else {
         System.out.println("ID: " + (p + 1) + " @" + start + ", status: '" + cs + "' DOESN'T matches.");
        }

       } // p
       System.out.println("Total: " + totalValue);
      }
     } catch (IOException ex) {
      throw new RuntimeException(ex);
     }
    }
  );

}

private static int[][] readFile(final String filePath) {
  final InputStream is = Challenge14.class.getResourceAsStream(filePath);
  final ByteArrayOutputStream baos = new ByteArrayOutputStream();
  String content;
  int[][] result;
  try {
   byte[] byteChunk = new byte[4096];
   int n;

   while ((n = is.read(byteChunk)) > 0) {
    baos.write(byteChunk, 0, n);
   }

   content = baos.toString(StandardCharsets.UTF_8.name());
   String[] lines = content.split("\n");
   result = new int[lines.length][];
   int resIdx = 0;
   for (String line : lines) {
    String[] numbers = line.split(",\s*");
    int[] numLine = new int[numbers.length];
    int idx = 0;
    for (String num : numbers) numLine[idx++] = Integer.valueOf(num);
    result[resIdx++] = numLine;
   }
  } catch (final IOException e) {
   throw new RuntimeException(e);
  }

  return result;
}
}

/*
PATTERN( ( (C*DC*M) | (C*MC*D) ) C* L? )
DEFINE
C AS val + 1 = NEXT(val), -- Continuous
D AS val = NEXT(val), -- Duplicate
M AS val + 2 = NEXT(val), -- Missing
L AS val - 1 = PREV(val) OR val - 2 = PREV(val) -- Latest
*/