/** Created by psingh on 5/12/16. Problem 5.12 from EPI */
public class UnifRandomNumberGenerator {
  private static final int NUM_TESTS = (int) Math.pow(10, 6);

  private static int zeroOneRandom() {
    Random rgen = new Random();
    return rgen.nextInt(2);
  }

  private static int UnifRandom(int lowerBound, int upperBound) {
    final int numOutcomes = upperBound - lowerBound + 1;
    int result;
    do {
      result = 0;
      for (int i = 0; (1 << i) < numOutcomes; ++i) result = (result << 1) | zeroOneRandom();
    } while (result >= numOutcomes);
    return lowerBound + result;
  }

  public static void main(String[] args) throws Exception {
    if (args.length == 2) {
      int a, b;
      a = Integer.parseInt(args[0]);
      b = Integer.parseInt(args[1]);
      int x = UnifRandom(a, b);
      assert (a <= x && x <= b);
    } else {
      Supplier<CloneableInputsMap> formInputs =
          () -> {
            Random rgen = new Random();
            int a = rgen.nextInt(100);
            int b = rgen.nextInt(100) + a + 1;
            CloneableInputsMap inputs = new CloneableInputsMap();
            inputs.addInteger("a", a);
            inputs.addInteger("b", b);
            return inputs;
          };
      Function<CloneableInputsMap, Integer> runAlg =
          (inputs) -> UnifRandom(inputs.getInteger("a"), inputs.getInteger("b"));
      BiFunction<Integer, CloneableInputsMap, Boolean> checkResults =
          (observed, extra) ->
              extra.getInteger("a") <= observed && observed <= extra.getInteger("b");

      AlgVerifierInterfaces<Integer, CloneableInputsMap> algVerifier =
          new InputOutputVerifier<>(checkResults);
      AlgorithmFactory algorithmFactory =
          new AlgorithmRunnerAndVerifier<>(
              "UnifRandomNumberGenerator", NUM_TESTS, formInputs, runAlg, algVerifier);
      algorithmFactory.run();
    }
  }
}
/**
 * Created by psingh on 5/28/16. Problem 7.2
 *
 * @summary Delete each "b" and replace each "a" with a "dd" in a given character array.
 *     <p><u><b>Strategy</b></u> 1) On the first pass, count the number of non-"b" characters and
 *     the number of "a", using the variables writeIdx and aCnt, respectively. For each non-"b"
 *     character, write it to writeIdx on the first pass.
 *     <p>The desired string will be writeIdx + aCnt characters long. Assume the character array is
 *     big enough.
 *     <p>2) Start writing final string backwards, from right to left, while iterating from writeIdx
 *     down to 0, taking care to write "a" as "dd".
 */
public class ReplaceAndRemove {
  private static final int ARR_LEN = (int) Math.pow(10, 2);
  private static final int NUM_TESTS = (int) Math.pow(10, 6);

  /**
   * @param s character array
   * @return length of string in character array after algorithm.
   */
  private static int replaceAndRemove(char[] s, int len) {
    int writeIdx = 0, aCnt = 0;
    // First pass: ignore "b"s, write everything else. Count the number of "a"s
    for (int i = 0; i < len; ++i) {
      if (s[i] != 'b') {
        s[writeIdx++] = s[i];
      }
      if (s[i] == 'a') {
        aCnt += 1;
      }
    }

    // Second pass: iterate backwars (right to left), while replacing "a"s with "dd"s
    int finalSize = writeIdx + aCnt;
    int currIdx = writeIdx - 1; // iterate from right to left over the characters we just wrote
    writeIdx = finalSize - 1; // start writing right to left from where final string will end

    while (currIdx >= 0) {
      if (s[currIdx] == 'a') {
        s[writeIdx--] = 'd';
        s[writeIdx--] = 'd';
      } else {
        s[writeIdx--] = s[currIdx];
      }
      currIdx -= 1;
    }

    return finalSize;
  }

  private static String knownOutput(char[] s, int len) {
    String str = new String(s, 0, len);
    str = str.replace("b", "");
    str = str.replace("a", "dd");
    return str;
  }

  public static void main(String[] args) throws Exception {
    Supplier<CloneableInputsMap> formInputs =
        () -> {
          CloneableInputsMap inputs = new CloneableInputsMap();
          Random rgen = new Random();
          char[] s = new char[(ARR_LEN << 1) + 1];
          MiscHelperMethods.randCharArray(() -> (char) ('a' + rgen.nextInt(26)), ARR_LEN, s);
          inputs.addCharArray("s", s);
          inputs.addInteger("len", ARR_LEN);
          return inputs;
        };
    Function<CloneableInputsMap, Integer> runAlg =
        (inputs) -> replaceAndRemove(inputs.getCharArray("s"), inputs.getInteger("len"));

    Function<CloneableInputsMap, Integer> knownOutput =
        (inputs) -> {
          String expected = knownOutput(inputs.getCharArray("s"), inputs.getInteger("len"));
          inputs.addCharArray("s", expected.toCharArray());
          return expected.length();
        };

    Function<CloneableInputsMap, CloneableInputsMap> saveExtraResults =
        (inputs) -> {
          CloneableInputsMap extraResults = new CloneableInputsMap();
          extraResults.addCharArray("s", inputs.getCharArray("s"));
          return extraResults;
        };
    AlgVerifierInterfaces.QuadFunction<
            Integer, Integer, CloneableInputsMap, CloneableInputsMap, Boolean>
        checkAns =
            (observed, expected, algExtraResults, expExtraResults) ->
                observed.equals(expected)
                    && algExtraResults.getCharArray("s").length >= observed
                    && expExtraResults.getCharArray("s").length >= observed
                    && String.valueOf(algExtraResults.getCharArray("s"))
                        .substring(0, observed)
                        .equals(
                            String.valueOf(expExtraResults.getCharArray("s"))
                                .substring(0, observed));

    AlgVerifierInterfaces<Integer, CloneableInputsMap> algVerifier =
        new OutputOutputExtraExtraVerifier<>(checkAns);
    AlgorithmFactory algorithmFactory =
        new AlgorithmRunnerAndVerifier<>(
            "Replace and Remove",
            NUM_TESTS,
            formInputs,
            runAlg,
            knownOutput,
            saveExtraResults,
            algVerifier);

    System.out.println(
        String.format("Running algorithms on character arrays of length %d...", ARR_LEN));
    //    algorithmFactory.setSequential();
    algorithmFactory.run();
  }
}