Is there random nextInt with weights? - java

I am using random class to generate random numbers between 1 and 5 like myrandom.nextInt(6) and it is working fine however i would like to know if there is a way to give a specific number a weight to increase it is probability to appear, lets say instead of %20 probability i want number "4" to have %40 probability and other numbers 1,2,3,5 share the rest of %60 probability equally. is there a way for this?
Thanks in advance.

I use an array. E.g.
int[] arr = {4, 4, 4, 5, 5, 6};
int a = arr[random.nextInt(arr.length)];
For a more dynamic solution, try this. The weights do not have to add up to any particular value.
public static <T> T choice(Map<? extends T, Double> map) {
if (map == null || map.size() == 0)
throw new IllegalArgumentException();
double sum = 0;
for (double w : map.values()) {
if (Double.compare(w, 0) <= 0 || Double.isInfinite(w) || Double.isNaN(w))
throw new IllegalArgumentException();
sum += w;
}
double rand = sum * Math.random();
sum = 0;
T t = null;
for (Map.Entry<? extends T, Double> entry : map.entrySet()) {
t = entry.getKey();
if ((sum += entry.getValue()) >= rand)
return t;
}
return t;
}
You can easily add / remove / change entries from the map whenever you like. Here is an example of how you use this method.
Map<Integer, Double> map = new HashMap<>();
map.put(1, 40.0);
map.put(2, 50.0);
map.put(3, 10.0);
for (int i = 0; i < 10; i++)
System.out.println(choice(map));

pbadcefp's answer is probably the easiest. Since you stated in the comments that you need it to be "dynamic", here's an alternate approach. Note that the weights basically specify how often the number appears in the array to pick from
public int weightedRandom( Random random, int max, Map<Integer, Integer> weights ) {
int totalWeight = max;
for( int weight : weights.values() ) {
totalWeight += weight - 1;
}
int choice = random.nextInt( totalWeight );
int current = 0;
for( int i = 0; i < max; i++ ) {
current += weights.containsKey( i ) ? weights.get( i ) : 1;
if( choice < current ) {
return i;
}
}
throw new IllegalStateException();
}
Example usage:
Map<Integer, Integer> weights = new HashMap<>();
weights.put( 1, 0 ); // make choosing '1' impossible
weights.put( 4, 3 ); // '4' appears 3 times rather than once
int result = weightedRandom( new Random(), 5, weights );
Basically, this is equivalent to pbadcefp's solution applied on the array { 0, 2, 3, 4, 4, 4 }
You will have to adapt this if you want to use percentages. Just calculate weights accordingly. Also, I didn't test cornercases on this, so you might want to test this a little bit more extensively.
This is by no means a complete solution, but I prefer giving something to work on over complete solutions since you should do some of the work yourself.
I'll also go on record and say that IMHO this is over-engineered; but you wanted something like this.

You'll have to generate a larger range of numbers (like from 1 to 100) and use ranges to return the numbers you really want. Eg: (in pseudocode)
r = randint(1..100)
if (r >= 1 && r <= 20) // 20% chance
return 1
else if (r >= 21 && r <= 60) // 40% chance
return 2
Etc.

Related

Find minimum number of packs that make a given value

I am trying to solve similar to this problem but with some modifications:
"Given a value V, if we want to make a change for V cents, and we have an infinite supply of each of C = { C1, C2, .. , Cm} valued coins, what is the minimum number of coins to make the change?"
Input: coins[] = {25, 10, 5}, V = 30
Output: Minimum 2 coins required
We can use one coin of 25 cents and one of 5 cents
In my case instead of just an array of numbers I have an array of objects. That in each object I have a qty and price.
I want to print the minimum number of objects that form the given qty, and after that print the price, something like:
2 x 5 9.95
1 x 3 5.95
I came to this code but I cannot find how to complete the task:
public static void main(String[] args) {
Product croissant = new Product("Croissant", "CF", null);
Pack CF_1 = new Pack(3, 5.95);
Pack CF_2 = new Pack(5, 9.95);
Pack CF_3 = new Pack(9, 16.99);
croissant.addPack(CF_1);
croissant.addPack(CF_2);
croissant.addPack(CF_3);
System.out.println(minCoins(croissant, 13));
}
static int minCoins(Product product, int V) {
// table[i] will be storing
// the minimum number of coins
// required for i value. So
// table[V] will have result
int table[] = new int[V + 1];
// Base case (If given value V is 0)
table[0] = 0;
// Initialize all table values as Infinite
for (int i = 1; i <= V; i++)
table[i] = Integer.MAX_VALUE;
// Compute minimum coins required for all
// values from 1 to V
for (int i = 1; i <= V; i++) {
// Go through all coins smaller than i
for (Pack pack : product.packList) {
if (pack.qty <= i) {
int sub_res = table[i - pack.qty];
if (sub_res != Integer.MAX_VALUE
&& sub_res + 1 < table[i])
table[i] = sub_res + 1;
}
}
}
return table[V];
}
You could get the list of packs that contribute to the minimum coins like below:
You start with given V then you look for the pack at which the table has value that is lesser by 1, because to reach V you must have had a value somewhere that is 1 lesser. If you found one, add it to the list, and reduce the next V by the quantity of the pack you found and continue.
Code is :
void printList(int[] table, Product product, int V) {
List<Pack> list = new ArrayList<>();
if ( table[V] == Integer.MAX_VALUE ) return list;
while ( V > 0 ) {
for (Pack pack : product.packList) {
if ( V >= pack.qty && table[V-pack.qty] == table[V] - 1 ) {
list.add(pack);
V = V-pack.qty;
break;
}
}
}
}
For your example of V = 13 , the list would be :
[{3, 5.95}, {5, 9.95}, {5, 9.95}]
This is assuming you implement toString() of the Pack class as;
public String toString() {
return "{" + this.qty + "," + this.price + "}";
}
You can reduce your list to a map if you would like using Collectors
something like : list.stream().collect(Collectors.groupingBy(Pack::getQty))

choose a random value from a inverse weigthed map

I'm using a Map with eligible words for a hangman game I'm developing. The Integer in the Map stores the times a word has been chosen, so in the beginning the Map looks like this:
alabanza 0
esperanza 0
comunal 0
aprender 0
....
After some plays, the Map would look like this
alabanza 3
esperanza 4
comunal 3
aprender 1
....
I'd like to choose the next word randomly but having the less chosen word a bigger probability of been chosen.
I've read Java - Choose a random value from a HashMap but one with the highest integer assigned but it's the opposite case.
I''ve also thought I could use a list with repeated words (the more times a word appears in the list, the more the probabilities of been chosen) but I've only managed to get to this:
int numberOfWords=wordList.size(); //The Map
List<String> repeatedWords=new ArrayList<>();
for (Map.Entry<String,Integer> entry : wordList.entrySet()) {
for (int i = 0; i < numberOfWords-entry.getValue(); i++) {
repeatedWords.add(entry.getKey());
}
}
Collections.shuffle(repeatedWords); //Should it be get(Random)?
String chosenWord=repeatedWords.get(0);
I think this fails when the amount of words chosen equals the amount of words.
Edit
Finally there's a problem with the probability of each word once they have different numbers. I've changed the point of view so I first put a probability of 1000 (It could be any number) and every time I choose a word, I reduce the probability a certain amount (let's say, 20%), so I use:
wordList.put(chosen,(int)(wordList.get(chosen)*0.8)+1);
After that I choose the word with the recipe Lajos Arpad or Ahmad Shahwan gave.
If the game were to be played many many times, all the probabilities would tend to 1, but that's not my case.
Thanks all who answered.
Try this:
import java.util.Map;
import java.util.HashMap;
import java.util.Random;
public class MyClass {
public static void main(String args[]) {
Map<String, Integer> wordList = new HashMap<>();
wordList.put("alabanza", 3);
wordList.put("esperanza", 4);
wordList.put("comunal", 3);
wordList.put("aprender", 1);
Map<String, Integer> results = new HashMap<>(4);
for (int i = 0; i < 100; i++) {
String name = randomize(wordList);
Integer old = results.getOrDefault(name, 0);
results.put(name, old + 1);
}
for (Map.Entry<String, Integer> e : results.entrySet()) {
System.out.println(e.getKey() + "\t" + e.getValue());
}
}
private static String randomize(Map<String, Integer> wordList) {
final Integer sum = wordList.values().stream().reduce(Integer::sum).orElse(0);
final int grandSum = (wordList.size() - 1) * sum;
final int random = new Random().nextInt(grandSum + 1);
int index = 0;
for (Map.Entry<String, Integer> e: wordList.entrySet()) {
index += (sum - e.getValue());
if (index >= random) {
return e.getKey();
}
}
return null;
}
}
Out put is the times a name was chosen over 100 trial:
aprender 37
alabanza 25
comunal 23
esperanza 15
You can try it yourself here.
I won't provide exact code, but basic idea.
Iterate over wordList.values() to find the maximum weight M and sum of weights S.
Now let each word w have likelihood (like probability, but they don't have to sum to 1) to be chosen M + 1 - wordList.get(w), so a word with weight 1 is M times more likely to be chosen than a word with weight M.
The sum of likelihoods will be (M + 1) * wordList.size() - S (that's why we need S). Pick a random number R between 0 and this sum.
Iterate over wordList.entrySet(), summing likelihoods as you go. When the sum passes R, that's the word you want.
Your map values are your weights.
You need to pick an integer lower than the weights sum.
You pick each String entry, with its weight. When the weight sum passes the random integer, you are on THE String.
This will give you :
public static void main(String ... args){
Map<String, Integer> wordList = new HashMap<>();
wordList.put("foo", 4);
wordList.put("foo2", 2);
wordList.put("foo3", 7);
System.out.println(randomWithWeight(wordList));
}
public static String randomWithWeight(Map<String, Integer> weightedWordList) {
int sum = weightedWordList.values().stream().collect(Collectors.summingInt(Integer::intValue));
int random = new Random().nextInt(sum);
int i = 0;
for (Map.Entry<String, Integer> e : weightedWordList.entrySet()){
i += e.getValue();
if (i > random){
return e.getKey();
}
}
return null;
}
For the sake of simplicity let us suppose that you have an array called occurrences, which has int elements (you will easily translate this into your data structure).
Now, let's find the maximum value:
int max = 0;
for (int i = 0; i < occurrences.length; i++) {
if (max < occurrences[i]) max = occurrences[i];
}
Let's increment it:
max++;
Now, let's give a weight of max to the items which have 0 as value, a weight of max - 1 to the items which occurred once, and so on (no item will have a weight of 0 since we incremented max):
int totalWeight = 0;
for (int j = 0; j < occurrences.length; j++) {
totalWeight += max - occurrences[j];
}
Note that all items will have their weight. Now, let's suppose you have a randomized integer, called r, where 0 < r <= totalWeight:
int resultIndex = -1;
for (int k = 0; (resultIndex < 0) && k < occurrences.length; k++) {
if (r <= max - occurrences[k]) resultIndex = k;
else r -= max - occurrences[k];
}
and the result is occurrences[resultIndex]

Weighted-random probability in Java

I have a list of fitness values (percentages), which are ordered in descending order:
List<Double> fitnesses = new ArrayList<Double>();
I would like to choose one of these Doubles, with an extreme likelyhood of it being the first one, then decreasing likelyhood for each item, until the final one is close to 0% chance of it being the final item in the list.
How do I go about achieving this?
Thanks for any advice.
If you want to select "one of these Doubles, with an extreme likelihood of it being the first one, then decreasing likelihood for each item, until the final one is close to 0% chance of it being the final item in the list" then it seems like you want an exponential probability function. (p = x2).
However, you will only know whether you have chosen the right function once you have coded a solution and tried it, and if it does not suit your needs then you will need to choose some other probability function, like a sinusoidal (p = sin( x * PI/2 )) or an inverse ratio (p = 1/x).
So, the important thing is to code an algorithm for selecting an item based on a probability function, so that you can then try any probability function you like.
So, here is one way to do it.
Note the following:
I am seeding the random number generator with 10 in order to always produce the same results. Remove the seeding to get different results at each run.
I am using a list of Integer for your "percentages" in order to avoid confusion. Feel free to replace with a list of Double once you have understood how things work.
I am providing a few sample probability functions. Try them to see what distributions they yield.
Have fun!
import java.util.*;
public final class Scratch3
{
private Scratch3()
{
}
interface ProbabilityFunction<T>
{
double getProbability( double x );
}
private static double exponential2( double x )
{
assert x >= 0.0 && x <= 1.0;
return StrictMath.pow( x, 2 );
}
private static double exponential3( double x )
{
assert x >= 0.0 && x <= 1.0;
return StrictMath.pow( x, 3 );
}
private static double inverse( double x )
{
assert x >= 0.0 && x <= 1.0;
return 1/x;
}
private static double identity( double x )
{
assert x >= 0.0 && x <= 1.0;
return x;
}
#SuppressWarnings( { "UnsecureRandomNumberGeneration", "ConstantNamingConvention" } )
private static final Random randomNumberGenerator = new Random( 10 );
private static <T> T select( List<T> values, ProbabilityFunction<T> probabilityFunction )
{
double x = randomNumberGenerator.nextDouble();
double p = probabilityFunction.getProbability( x );
int i = (int)( p * values.size() );
return values.get( i );
}
public static void main( String[] args )
{
List<Integer> values = Arrays.asList( 10, 11, 12, 13, 14, 15 );
Map<Integer,Integer> counts = new HashMap<>();
for( int i = 0; i < 10000; i++ )
{
int value = select( values, Scratch3::exponential3 );
counts.merge( value, 1, ( a, b ) -> a + b );
}
for( int value : values )
System.out.println( value + ": " + counts.get( value ) );
}
}
Here's another way of doing it that gives you the ability to approximate an arbitrary weight distribution.
The array passed to WeightedIndexPicker indicates the number of "buckets" (>0) that should be allocated to each index. In your case these would be descending, but they don't have to be. When you need an index, pick a random number between 0 and the total number of buckets and return the index associated with that bucket.
I've used an int weight array as it's easier to visualize and it avoids rounding errors associated with floating point.
import java.util.Random;
public class WeightedIndexPicker
{
private int total;
private int[] counts;
private Random rand;
public WeightedIndexPicker(int[] weights)
{
rand = new Random();
counts = weights.clone();
for(int i=1; i<counts.length; i++)
{
counts[i] += counts[i-1];
}
total = counts[counts.length-1];
}
public int nextIndex()
{
int idx = 0;
int pick = rand.nextInt(total);
while(pick >= counts[idx]) idx++;
return idx;
}
public static void main(String[] args)
{
int[] dist = {1000, 100, 10, 1};
WeightedIndexPicker wip = new WeightedIndexPicker(dist);
int idx = wip.nextIndex();
System.out.println(idx);
}
}
I don't think you need all this code to answer your question since your question seems to be much more about math than code. For example, using the apache commons maths library getting a distribution is easy:
ExponentialDistribution dist = new ExponentialDistribution(1);
// getting a sample (aka index into the list) is easy
dist.sample();
// lot's of extra code to display the distribution.
int NUM_BUCKETS = 100;
int NUM_SAMPLES = 1000000;
DoubleStream.of(dist.sample(NUM_SAMPLES))
.map(s->((long)s*NUM_BUCKETS)/NUM_BUCKETS)
.boxed()
.collect(groupingBy(identity(), TreeMap::new, counting()))
.forEach((k,v)->System.out.println(k.longValue() + " -> " + v));
However, as you said, there are so many possible distributions in the math library. If you are writing code for a specific purpose then the end user will probably want you to explain why you chose a specific distribution and why you set the parameters for that distribution the way you did. That's a math question and should be asked in the mathematics forum.

Restaurant Maximum Profit using Dynamic Programming

Its an assignment task,I have spend 2 days to come up with a solution but still having lots of confusion,however here I need to make few points clear. Following is the problem:
Yuckdonald’s is considering opening a series of restaurant along QVH. n possible locations are along a straight line and the distances of these locations from the start of QVH are in miles and in increasing order m1, m2, ...., mn. The constraints are as follows:
1. At each location, Yuckdonald may open one restaurant and expected profit from opening a restaurant at location i is given as pi
2. Any two restaurants should be at least k miles apart, where k is a positive integer
My solution:
public class RestaurantProblem {
int[] Profit;
int[] P;
int[] L;
int k;
public RestaurantProblem(int[] L , int[] P, int k) {
this.L = L;
this.P = P;
this.k = k;
Profit = new int[L.length];
}
public int compute(int i){
if(i==0)
return 0;
Profit[i]= P[i]+(L[i]-L[i-1]< k ? 0:compute(i-1));//if condition satisfies then adding previous otherwise zero
if (Profit[i]<compute(i-1)){
Profit[i] = compute(i-1);
}
return Profit[i];
}
public static void main(String args[]){
int[] m = {0,5,10,15,19,25,28,29};
int[] p = {0,10,4,61,21,13,19,15};
int k = 5;
RestaurantProblem rp = new RestaurantProblem(m, p ,k);
rp.compute(m.length-1);
for(int n : rp.Profit)
System.out.println(n);
}
}
This solution giving me 88 however if I exclude (Restaurant at 25 with Profit 13) and include (Restaurant 28 with profit 19) I can have 94 max...
point me if I am wrong or how can I achieve this if its true.
I was able to identify 2 mistakes:
You are not actually using dynamic programming
, you are just storing the results in a data structure, which wouldn't be that bad for performance if the program worked the way you have written it and if you did only 1 recursive call.
However you do at least 2 recursive calls. Therefore the program runs in Ω(2^n) instead of O(n).
Dynamic programming usually works like this (pseudocode):
calculate(input) {
if (value already calculated for input)
return previously calculated value
else
calculate and store value for input and return result
}
You could do this by initializing the array elements to -1 (or 0 if all profits are positive):
Profit = new int[L.length];
Arrays.fill(Profit, -1); // no need to do this, if you are using 0
public int compute(int i) {
if (Profit[i] >= 0) { // modify the check, if you're using 0 for non-calculated values
// reuse already calculated value
return Profit[i];
}
...
You assume the previous restaurant can only be build at the previous position
Profit[i] = P[i] + (L[i]-L[i-1]< k ? 0 : compute(i-1));
^
Just ignores all positions before i-1
Instead you should use the profit for the last position that is at least k miles away.
Example
k = 3
L 1 2 3 ... 100
P 5 5 5 ... 5
here L[i] - L[i-1] < k is true for all i and therefore the result will just be P[99] = 5 but it should be 34 * 5 = 170.
int[] lastPos;
public RestaurantProblem(int[] L, int[] P, int k) {
this.L = L;
this.P = P;
this.k = k;
Profit = new int[L.length];
lastPos = new int[L.length];
Arrays.fill(lastPos, -2);
Arrays.fill(Profit, -1);
}
public int computeLastPos(int i) {
if (i < 0) {
return -1;
}
if (lastPos[i] >= -1) {
return lastPos[i];
}
int max = L[i] - k;
int lastLastPos = computeLastPos(i - 1), temp;
while ((temp = lastLastPos + 1) < i && L[temp] <= max) {
lastLastPos++;
}
return lastPos[i] = lastLastPos;
}
public int compute(int i) {
if (i < 0) {
// no restaurants can be build before pos 0
return 0;
}
if (Profit[i] >= 0) { // modify the check, if you're using 0 for non-calculated values
// reuse already calculated value
return Profit[i];
}
int profitNoRestaurant = compute(i - 1);
if (P[i] <= 0) {
// no profit can be gained by building this restaurant
return Profit[i] = profitNoRestaurant;
}
return Profit[i] = Math.max(profitNoRestaurant, P[i] + compute(computeLastPos(i)));
}
To my understanding, the prolem can be modelled with a two-dimensional state space, which I don't find in the presented implementation. For each (i,j) in{0,...,n-1}times{0,...,n-1}` let
profit(i,j) := the maximum profit attainable for selecting locations
from {0,...,i} where the farthest location selected is
no further than at position j
(or minus infinity if no such solution exist)
and note that the recurrence relation
profit(i,j) = min{ p[i] + profit(i-1,lastpos(i)),
profit(i-1,j)
}
where lastpos(i) is the location which is farthest from the start, but no closer than k to position i; the first case above corresponds to selection location i into the solution while the second case corresponds to omitting location j in the solution. The overall solution can be obtained by evaluating profit(n-1,n-1); the evaluation can be done either recursively or by filling a two-dimensional array in a bottom-up manner and returning its contents at (n-1,n-1).

Find number of AP triplets in a number stream

Problem:
Given N integers A1, A2, …. AN, Dexter wants to know how many ways he can choose three numbers such that they are three consecutive terms of an arithmetic progression.
CodeChef link.
Here is my solution(Let "freq" be the counter)
1. Create a data store (array of sorted sets) to hold a sorted set of positions of number i in stream at index i in array.
2. for k: 0 to array.length
a. get Sorted Set S[k]
b. if SZ >=3, where SZ = S[k].size, compute SZ choose 3 and add it to freq
c. for r: 2*k-1 to k
for x in S[k]
find entries in S[r], say A, more than x and entries in S[r-i], say B, less than x.. freq += A*B
find entries in S[r], say A, less than x and entries in S[r-i], say B, more than x.. freq += A*B
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;
/**
*
* #author abhishek87
*/
class APTripletInStream {
public static void main(String[] args) {
int idx=0, numInStream;
Scanner scanIn = new Scanner(System.in), readLine;
String line = scanIn.nextLine();
readLine = new Scanner(line);
DataStore dStore = new DataStore(30000 + 1);
while(scanIn.hasNextLine()) {
line = scanIn.nextLine();
readLine = new Scanner(line);
while(readLine.hasNextInt()){
numInStream = readLine.nextInt();
dStore.add(++idx, numInStream);
}
break;
}
Long res = 0L;
try {
res = APProblemSolver.solveProblem(dStore);
} catch(Exception ex) {
res = 0L;
}
System.out.println(res);
}
}
class APProblemSolver {
public static Long solveProblem(DataStore dStore) {
Long freq = 0L;
int dSize = dStore.size();
for(int idx=1; idx<=dSize-1; idx++) {
Set currSet = dStore.getSetAtIndex(idx);
if(null != currSet && !currSet.isEmpty()) {
int size = currSet.size();
if(size >= 3) {
freq += (size*(long)(size-1)*(long)(size - 2)/6L);
}
for(int right = 2*idx-1; right > idx; right--){
if(right >= dSize)
continue;
Set rightSet = dStore.getSetAtIndex(right);
Set leftSet = dStore.getSetAtIndex(2*idx - right);
if(null != rightSet && null != leftSet) {
for(Object obj : currSet) {
Set leftSetHeadSet = ((TreeSet)leftSet).headSet(obj);
Set rightSetTailSet = ((TreeSet)rightSet).tailSet(obj);
freq += leftSetHeadSet.size() * rightSetTailSet.size();
Set leftSetTailSet = ((TreeSet)leftSet).tailSet(obj);
Set rightSetHeadSet = ((TreeSet)rightSet).headSet(obj);
freq += leftSetTailSet.size() * rightSetHeadSet.size();
}
}
}
}
}
return freq;
}
}
class DataStore {
private TreeSet[] list = null;
private int size;
public DataStore(int size) {
this.size = size;
list = new TreeSet[size];
}
public void add(Integer idx, Integer val) {
Set<Integer> i = list[val];
if(null == i) {
i = new TreeSet<Integer>();
i.add(idx);
list[val] = (TreeSet<Integer>)i;
} else{
((TreeSet<Integer>)list[val]).add(idx);
}
}
public int size() {
return size;
}
public Set getSetAtIndex(int idx) {
return list[idx];
}
}
Here is what I am looking for:
When I submit the problem, I get "time limit exceeded". Therefore I want to use NetBeans Profiler to estimate the time this solution takes so that I can improve it.
FYI - Time limit for successful submission is 3 seconds
Can anyone give me some pointers to improve my solution [I DO NOT want to change my solution] by:
Optimizing storage
Which parts of my solution are time consuming and have an obvious workaround
Example:
Input:
Number Of entries - 10.
Number Stream - 3 5 3 6 3 4 10 4 5 2.
Output:
9.
Explanation:
The followings are all 9 ways to choose a triplet:
(Ai, Aj, Ak) = (3, 3, 3)
(Ai, Aj, Ak) = (3, 4, 5)
(Ai, Aj, Ak) = (3, 4, 5)
(Ai, Aj, Ak) = (3, 4, 5)
(Ai, Aj, Ak) = (3, 4, 5)
(Ai, Aj, Ak) = (6, 4, 2)
(Ai, Aj, Ak) = (6, 4, 2)
(Ai, Aj, Ak) = (3, 4, 5)
(Ai, Aj, Ak) = (3, 4, 5)
I haven't checked your code in details but here's how I would do :
Sort your list -- 1
Iterate through your sorted list (i from 0 to n) -- 2
Iterate though the remaining part of the list (j from i+1 to n) -- 2.a
Lookup if (2*j-i) which would be the third element of the arithmetic progression -- 2.a.1
Step 1 is O(n*log(n)) but then it allows step 2.a.1 to be O(log(n-j)) thanks to binary search.
Here's my python implementation :
from bisect import bisect_left
def index_in_sorted(a, x):
'Locate the leftmost value exactly equal to x'
i = bisect_left(a, x)
if i != len(a) and a[i] == x:
return i
return None
numbers=[4,5,6,17,9,1,442,44,32,3,21,19]
print numbers
numbers.sort()
n = len(numbers)
for i in range(0,n):
n_i = numbers[i]
for j in range(i+1,n):
n_j = numbers[j]
n_k = 2*n_j - n_i
if index_in_sorted(numbers,n_k): # I could only process the end of numbers but it's not worth the pain
print "Found", n_i,n_j,n_k
You should implement lazy instantiation of your datastore.
public DataStore(int size) {
for(int i=0; i<size;i++)
list.add(i, new TreeSet<Integer>());
}
You create 30001 treesets during instantiation.
It would be much better to have map int -> Set of what is needed. Then in your code dStore.getSetAtIndex(right) if there is no set for this int , you instantiate it.
Obvious parts are:
for(Object objMore : leftSetTailSet) {
for(Object objLess : rightSetHeadSet) {
freq++;
}
}
can be changed to freq += leftSetTailSet*rightSetHeadSet;
Also I don't see dsStore size changing so :
instead of this: idx<=dStore.size()-1; in your for loop you could declare variable dsSize = dStore.size() and have idx < dsSize and if(right >= dsSize)
The big idea, if you have first two terms, then the third term is fixed.
Exploiting memory you can do much better.
Let's have an array of arrays.I don't know how you do this in Java, here's the C++ version.
vector<vector<int> > where
where[i]=positions in input where value=i
So {1,4,2,3,3} would look like
where[0]={}
where[1]={0}
where[2]={2}
where[3]={3,4}
where[4]={1}
If you initialize the the above vector of vector where, then the positions would be sorted.
Again you can set first 2 elements of AP and now instead of searching for third element in the original input stream, you can look it up easily in where.
I always end algorithm questions with:Can we do better? I'm sure there's a better way, I will update this answer if I hit it.

Categories

Resources