All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.thesett.junit.extensions.util.MathUtils Maven / Gradle / Ivy

Go to download

JUnit Toolkit enhances JUnit with performance testing, asymptotic behaviour analysis, and concurrency testing.

There is a newer version: 0.9.52
Show newest version
/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.thesett.junit.extensions.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.thesett.common.properties.ParsedProperties;
import com.thesett.common.util.CommandLineParser;

/**
 * Mathematical support methods for the toolkit. Caculating averages, variances, min/max for test latencies and
 * generating linear/exponential sequences for test size/concurrency ramping up.
 *
 * 

The sequence specifications are of the form [lowest(, ...)(, highest)](,sample=s)(,exp), where round brackets * enclose optional values. Using this pattern form it is possible to specify a single value, a range of values divided * into s samples, a range of values divided into s samples but distributed exponentially, or a fixed set of samples. * *

The duration arguments are of the form (dD)(hH)(mM)(sS), where round brackets enclose optional values. At least * one of the optional values must be present. * *

*
CRC Card
Responsibilities Collaborations *
Generate a sequene of integers from a sequence specification. *
Parse an encoded duration into milliseconds. *
* * @author Rupert Smith */ public class MathUtils { /** Used for debugging. */ // private static final Logger log = Logger.getLogger(MathUtils.class); /** The sequence defintion matching regular expression. */ public static final String SEQUENCE_REGEXP = "^(\\[[0-9:]+\\])(:samples=[0-9]+)?(:exp)?$"; /** The regular expression that matches sequence definitions. */ private static final Pattern SEQUENCE_PATTERN = Pattern.compile(SEQUENCE_REGEXP); /** The duration definition matching regular expression. */ public static final String DURATION_REGEXP = "^(\\d+D)?(\\d+H)?(\\d+M)?(\\d+S)?$"; /** The regular expression that matches the duration expression. */ public static final Pattern DURATION_PATTERN = Pattern.compile(DURATION_REGEXP); /** For matching name=value pairs. */ public static final String NAME_VALUE_REGEXP = "^\\w+=\\w+$"; /** For matching name=[value1: value2: ...] variations. */ public static final String NAME_VALUE_VARIATION_REGEXP = "^\\w+=\\[[\\w:]+\\]$"; /** For matching name=[n: ... :m](:sample=s)(:exp) sequences. */ public static final String NAME_VALUE_SEQUENCE_REGEXP = "^\\w+=(\\[[0-9:]+\\])(:samples=[0-9]+)?(:exp)?$"; /** The regular expression that matches name=value pairs and variations. */ public static final Pattern NAME_VALUE_PATTERN = Pattern.compile("(" + NAME_VALUE_REGEXP + ")|(" + NAME_VALUE_VARIATION_REGEXP + ")|(" + NAME_VALUE_SEQUENCE_REGEXP + ")"); /** * Runs a quick test of the sequence generation methods to confirm that they work as expected. * * @param args The command line parameters. */ public static void main(String[] args) { // Use the command line parser to evaluate the command line. CommandLineParser commandLine = new CommandLineParser( new String[][] { { "s", "The sequence definition.", "[m:...:n](:sample=s)(:exp)", "true", MathUtils.SEQUENCE_REGEXP }, { "d", "The duration definition.", "dDhHmMsS", "false", MathUtils.DURATION_REGEXP } }); // Capture the command line arguments or display errors and correct usage and then exit. ParsedProperties options = null; try { options = new ParsedProperties(commandLine.parseCommandLine(args)); } catch (IllegalArgumentException e) { // It is noted that the command line is illegal, so the exception can be ignored. e = null; System.out.println(commandLine.getErrors()); System.out.println(commandLine.getUsage()); System.exit(-1); } // Extract the command line options. String sequence = options.getProperty("s"); String durationString = options.getProperty("d"); System.out.println("Sequence is: " + printArray(parseSequence(sequence))); if (durationString != null) { System.out.println("Duration is: " + parseDuration(durationString)); } } /** * Given a start and end and a number of steps this method generates a sequence of evenly spaced integer values, * starting at the start (inclusive) and finishing at the end (inclusive) with the specified number of values in the * sequence. The sequence returned may contain less than the specified number where the integer range between start * and end is too small to contain that many. * *

As the results are integers, they will not be perfectly evenly spaced but a best-fit. * * @param start The sequence start. * @param end The sequence end. * @param steps The number of steps. * * @return The sequence. */ public static int[] generateSequence(int start, int end, int steps) { // Check that there are at least two steps. if (steps < 2) { throw new IllegalArgumentException("There must be at least 2 steps."); } List result = new ArrayList(); // Calculate the sequence using floating point, then round into the results. double fStart = start; double fEnd = end; double fCurrent = start; for (int i = 0; i < steps; i++) { fCurrent = (((fEnd - fStart) / (steps - 1)) * i) + fStart; roundAndAdd(result, fCurrent); } // Return the results after converting to a primitive array. return intListToPrimitiveArray(result); } /** * Given a start and end and a number of steps this method generates a sequence of expontentially spaced integer * values, starting at the start (inclusive) and finishing at the end (inclusive) with the specified number of * values in the sequence. An exponentially spaced sequence is one where the ratio between any two consecutive * numbers in the sequence remains constant. The sequence returned may contain less than the specified number where * the difference between two consecutive values is too small (this is more likely at the start of the sequence, * where the values are closer together). * *

As the results are integers, they will not be perfectly exponentially spaced but a best-fit. * * @param start The sequence start. * @param end The sequence end. * @param steps The number of steps. * * @return The sequence. */ public static int[] generateExpSequence(int start, int end, int steps) { // Check that there are at least two steps. if (steps < 2) { throw new IllegalArgumentException("There must be at least 2 steps."); } List result = new ArrayList(); // Calculate the sequence using floating point, then round into the results. double fStart = start; double fEnd = end; // float fCurrent = start; double diff = fEnd - fStart; double factor = java.lang.Math.pow(diff, (1.0f / (steps - 1))); for (int i = 0; i < steps; i++) { // This is a cheat to get the end exactly on and lose the accumulated rounding error. if (i == (steps - 1)) { result.add(end); } else { roundAndAdd(result, fStart - 1.0f + java.lang.Math.pow(factor, i)); } } // Return the results after converting to a primitive array. return intListToPrimitiveArray(result); } /** * Parses a string defintion of a sequence into an int array containing the sequence. The definition will conform to * the regular expression: "^(\[[0-9,]+\])(,samples=[0-9]+)?(,exp)?$". This splits it into three parts, an array of * integers, the optional sample count and the optional exponential flag. * * @param sequenceDef The sequence definition. * * @return The sequence as a fully expanded int array. */ public static int[] parseSequence(CharSequence sequenceDef) { // Match the sequence definition against the regular expression for sequences. Matcher matcher = SEQUENCE_PATTERN.matcher(sequenceDef); // Check that the argument is of the right format accepted by this method. if (!matcher.matches()) { throw new IllegalArgumentException("The sequence definition is not in the correct format."); } // Get the total number of matching groups to see if either of the optional samples or exponential flag // goups were set. int numGroups = matcher.groupCount(); // Split the array of integers on commas. String intArrayString = matcher.group(1); String[] intSplits = intArrayString.split("[:\\[\\]]"); int[] sequence = new int[intSplits.length - 1]; for (int i = 1; i < intSplits.length; i++) { sequence[i - 1] = Integer.parseInt(intSplits[i]); } // Check for the optional samples count. int samples = 0; if ((numGroups > 1) && (matcher.group(2) != null)) { String samplesGroup = matcher.group(2); String samplesString = samplesGroup.substring(",samples=".length()); samples = Integer.parseInt(samplesString); } // Check for the optional exponential flag. boolean expFlag = false; if ((numGroups > 2) && (matcher.group(3) != null)) { expFlag = true; } // If there is a sample count and 2 or more sequence values defined, then generate the sequence from the first // and last sequence values. if ((samples != 0) && (sequence.length >= 2)) { int start = sequence[0]; int end = sequence[sequence.length - 1]; if (!expFlag) { sequence = generateSequence(start, end, samples); } else { sequence = generateExpSequence(start, end, samples); } } return sequence; } /** * Parses a duration defined as a string, giving a duration in days, hours, minutes and seconds into a number of * milliseconds equal to that duration. * * @param duration The duration definition string. * * @return The duration in millliseconds. */ public static long parseDuration(CharSequence duration) { // Match the duration against the regular expression. Matcher matcher = DURATION_PATTERN.matcher(duration); // Check that the argument is of the right format accepted by this method. if (!matcher.matches()) { throw new IllegalArgumentException("The duration definition is not in the correct format."); } // This accumulates the duration. long result = 0; int numGroups = matcher.groupCount(); // Extract the days. if (numGroups >= 1) { String daysString = matcher.group(1); result += (daysString == null) ? 0 : (Long.parseLong(daysString.substring(0, daysString.length() - 1)) * 24 * 60 * 60 * 1000); } // Extract the hours. if (numGroups >= 2) { String hoursString = matcher.group(2); result += (hoursString == null) ? 0 : (Long.parseLong(hoursString.substring(0, hoursString.length() - 1)) * 60 * 60 * 1000); } // Extract the minutes. if (numGroups >= 3) { String minutesString = matcher.group(3); result += (minutesString == null) ? 0 : (Long.parseLong(minutesString.substring(0, minutesString.length() - 1)) * 60 * 1000); } // Extract the seconds. if (numGroups >= 4) { String secondsString = matcher.group(4); result += (secondsString == null) ? 0 : (Long.parseLong(secondsString.substring(0, secondsString.length() - 1)) * 1000); } return result; } /** * Pretty prints an array of ints as a string. * * @param array The array to pretty print. * * @return The pretty printed string. */ public static String printArray(int[] array) { String result = "["; for (int i = 0; i < array.length; i++) { result += array[i]; result += (i < (array.length - 1)) ? ", " : ""; } result += "]"; return result; } /** * Returns the maximum value in an array of integers. * * @param values The array to find the amx in. * * @return The max value. */ public static int maxInArray(int[] values) { if ((values == null) || (values.length == 0)) { throw new IllegalArgumentException("Cannot find the max of a null or empty array."); } int max = values[0]; for (int value : values) { max = (max < value) ? value : max; } return max; } /** * The #toArray methods of collections cannot be used with primitive arrays. This loops over and array list of * Integers and outputs and array of int. * * @param result The array of Integers to convert. * * @return An array of int. */ private static int[] intListToPrimitiveArray(List result) { int[] resultArray = new int[result.size()]; int index = 0; for (int r : result) { resultArray[index] = result.get(index); index++; } return resultArray; } /** * Rounds the specified floating point value to the nearest integer and adds it to the specified list of integers, * provided it is not already in the list. * * @param result The list of integers to add to. * @param value The new candidate to round and add to the list. */ private static void roundAndAdd(Collection result, double value) { int roundedValue = (int) Math.round(value); if (!result.contains(roundedValue)) { result.add(roundedValue); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy