Related
I've been trying to make my vector calculator subtract. It was doing it fine with vectors that store the same amount of numbers, for example: (+) 503 - (-) 305. It would give me a good result on this kind of cases.
But when I tried subtracting different size vectors the problems appeared. I tried to solve the problem by making different filters to make the program act how I want it. But now, instead of subtracting all it does is add.
This is my setup:
const int MAX = 102;
typedef int vector[MAX];
A(0): Puts all the values of a vector at 0. For example:
Before using it:
[0][3][5][0][5]
Where the first 0 means it is a positive number, if it was [1] it would mean its negative, that [3] means that there are 3 numbers stored in the vector.
After using A0:
[0][1][0][0][0][0][0]...[0]
ES_MAJOR_VECTOR checks if vector Z > X. On that case returns true, else it's going to return false.
SUMA_VEC basically adds Z to X and then assings the value to W.
void RESTA_VEC(vector Z, vector X, vector W) {
int ZigualX(0), Xmes1(0), Xactual(0), ZmajorX(0), XmajorZ(0), contador(0);
vector copia_Z, copia_X;
A0(copia_Z);
A0(copia_X);
for (int k = 0; k < MAX; k++)
{
copia_Z[k] = Z[k];
copia_X[k] = X[k];
}
if (Z[0] == X[0])
{
if (Z[0] == 0)
{
for (int y = MAX; y >= 2; y--)
{
if (Z[y] < X[y])
{//RESTA
Z[y] = Z[y] + 10;
W[y] = Z[y] - X[y];
X[y + 1] = X[y + 1] + 1;
}
if (Z[y] > X[y])
{
W[y] = Z[y] - X[y];
}
if (Z[y] == X[y])
{
W[y] = 0;
}
}
}
if (Z[0] == 1)
{
SUMA_VEC(Z, X, W);
}
}
if (Z[0] != X[0])
{
if (ES_MAJOR_VECTOR(Z, X) == true)
{
for (int y = MAX; y >= 2; y--)
{
if (Z[y] < X[y])
{
Z[y] = Z[y] + 10;
W[y] = Z[y] - X[y];
X[y + 1] = X[y + 1] + 1;
}
if (Z[y] > X[y])
{
W[y] = Z[y] - X[y];
}
if (Z[y] == X[y])
{
W[y] = 0;
}
}
}
else
{
for (int y = MAX; y >= 2; y--)
{
if (X[y] < Z[y])
{
X[y] = X[y] + 10;
W[y] = X[y] - Z[y];
Z[y + 1] = Z[y + 1] + 1;
}
if (X[y] > Z[y])
{
W[y] = X[y] - Z[y];
}
if (X[y] == Z[y])
{
W[y] = 0;
}
}
}
}
for (int h = 0; h < MAX; h++)
{
Z[h] = copia_Z[h];
X[h] = copia_X[h];
}
}
How can I solve this? I've thought that I need to check: If they are positive or negative, if they're both positive I have to execute a subtraction, if they're negative I have to add Z to X and assign it to W.
If the vectors are different (Meaning one is positive and the another one is negative) I have to check which one is bigger, do the subtraction and then assign either 0 or 1 depending on the bigger vector of the two used earlier.
For example:
+5050
-305
W[0] = +
-5050
+305
W[0] = -
After getting helped by a very kind user I could get to this conclusion:
void RESTA_ABS(vector Z, vector X, vector W) {
int error(0);
A0(W);
if (ES_MAJOR_VECTOR(Z,X))
{
for (int i = MAX-1; i >= 2; i--)
{
if (Z[i] < X[i]) {
Z[i] = Z[i] + 10;
W[i] = Z[i] - X[i];
X[i - 1] = X[i - 1] + 1;
}
else
{
W[i] = Z[i] - X[i];
}
}
}
else
{
cout << "ERROR - VECTOR 2 > VECTOR 1" << endl;
}
}
This module doesn't mind about the vector being positive or negative. It just does the subtraction.
So, to solve this, I used another module:
void RESTA_VEC(vector Z, vector X, vector W) {
if (X[0] == 0)
{
X[0] = 1;
}
else
{
X[0] = 0;
}
RESTA_ABS(Z, X, W);
if (ES_MAJOR_VECTOR(Z, X) == true)
{
W[0] = Z[0];
}
else
{
W[0] = X[0];
}
}
This one changes the number's sign of the second vector, making it negative if its positive, and positive if its negative. (This is determined by having a 0 or a 1 on the first vector position).
And then finally, what it does is to check the sign of the greater vector and assign it to the output vector W.
The only part left is checking which of the two vectors is greaters, because as I was testing it, on some cases, it would give me a wrong answer.
For example:
[+][9][1][2][3][4][5][6][7][8][9]
[+][9][1][1][2][3][4][5][6][7][8]
Would give:
[+][8][1][1][1][1][1][1][1][1]
But it doesn't make the opperation cause it thinks that the second vector is greater.
Translation: Is Z greater than X?
bool ES_MAJOR_VECTOR_SIGNE(vector Z, vector X) {
int END(0), UP(2);
return false;
if (Z[1] > X[1])
{
return true;
}
else
{
while (END != 1)
{
if (Z[UP] > X[UP])
{
return true;
END = 1;
}
UP++;
}
}
}
I tried this Codility test: MinAbsSum.
https://codility.com/programmers/lessons/17-dynamic_programming/min_abs_sum/
I solved the problem by searching the whole tree of possibilities. The results were OK, however, my solution failed due to timeout for large input. In other words the time complexity was not as good as expected. My solution is O(nlogn), something normal with trees. But this coding test was in the section "Dynamic Programming", and there must be some way to improve it. I tried with summing the whole set first and then using this information, but always there is something missing in my solution. Does anybody have an idea on how to improve my solution using DP?
#include <vector>
using namespace std;
int sum(vector<int>& A, size_t i, int s)
{
if (i == A.size())
return s;
int tmpl = s + A[i];
int tmpr = s - A[i];
return min (abs(sum(A, i+1, tmpl)), abs(sum(A, i+1, tmpr)));
}
int solution(vector<int> &A) {
return sum(A, 0, 0);
}
I could not solve it. But here's the official answer.
Quoting it:
Notice that the range of numbers is quite small (maximum 100). Hence,
there must be a lot of duplicated numbers. Let count[i] denote the
number of occurrences of the value i. We can process all occurrences
of the same value at once. First we calculate values count[i] Then we
create array dp such that:
dp[j] = −1 if we cannot get the sum j,
dp[j] >= 0 if we can get sum j.
Initially, dp[j] = -1 for all of j (except dp[0] = 0). Then we scan
through all the values a appearing in A; we consider all a such
that count[a]>0. For every such a we update dp that dp[j] denotes
how many values a remain (maximally) after achieving sum j. Note
that if the previous value at dp[j] >= 0 then we can set dp[j] =
count[a] as no value a is needed to obtain the sum j. Otherwise we
must obtain sum j-a first and then use a number a to get sum j. In
such a situation dp[j] = dp[j-a]-1. Using this algorithm, we can
mark all the sum values and choose the best one (closest to half of S,
the sum of abs of A).
def MinAbsSum(A):
N = len(A)
M = 0
for i in range(N):
A[i] = abs(A[i])
M = max(A[i], M)
S = sum(A)
count = [0] * (M + 1)
for i in range(N):
count[A[i]] += 1
dp = [-1] * (S + 1)
dp[0] = 0
for a in range(1, M + 1):
if count[a] > 0:
for j in range(S):
if dp[j] >= 0:
dp[j] = count[a]
elif (j >= a and dp[j - a] > 0):
dp[j] = dp[j - a] - 1
result = S
for i in range(S // 2 + 1):
if dp[i] >= 0:
result = min(result, S - 2 * i)
return result
(note that since the final iteration only considers sums up until S // 2 + 1, we can save some space and time by only creating a DP Cache up until that value as well)
The Java answer provided by fladam returns wrong result for input [2, 3, 2, 2, 3], although it gets 100% score.
Java Solution
import java.util.Arrays;
public class MinAbsSum{
static int[] dp;
public static void main(String args[]) {
int[] array = {1, 5, 2, -2};
System.out.println(findMinAbsSum(array));
}
public static int findMinAbsSum(int[] A) {
int arrayLength = A.length;
int M = 0;
for (int i = 0; i < arrayLength; i++) {
A[i] = Math.abs(A[i]);
M = Math.max(A[i], M);
}
int S = sum(A);
dp = new int[S + 1];
int[] count = new int[M + 1];
for (int i = 0; i < arrayLength; i++) {
count[A[i]] += 1;
}
Arrays.fill(dp, -1);
dp[0] = 0;
for (int i = 1; i < M + 1; i++) {
if (count[i] > 0) {
for(int j = 0; j < S; j++) {
if (dp[j] >= 0) {
dp[j] = count[i];
} else if (j >= i && dp[j - i] > 0) {
dp[j] = dp[j - i] - 1;
}
}
}
}
int result = S;
for (int i = 0; i < Math.floor(S / 2) + 1; i++) {
if (dp[i] >= 0) {
result = Math.min(result, S - 2 * i);
}
}
return result;
}
public static int sum(int[] array) {
int sum = 0;
for(int i : array) {
sum += i;
}
return sum;
}
}
I invented another solution, better than the previous one. I do not use recursion any more.
This solution works OK (all logical tests passed), and also passed some of the performance tests, but not all. How else can I improve it?
#include <vector>
#include <set>
using namespace std;
int solution(vector<int> &A) {
if (A.size() == 0) return 0;
set<int> sums, tmpSums;
sums.insert(abs(A[0]));
for (auto it = begin(A) + 1; it != end(A); ++it)
{
for (auto s : sums)
{
tmpSums.insert(abs(s + abs(*it)));
tmpSums.insert(abs(s - abs(*it)));
}
sums = tmpSums;
tmpSums.clear();
}
return *sums.begin();
}
This solution (in Java) scored 100% for both (correctness and performance)
public int solution(int[] a){
if (a.length == 0) return 0;
if (a.length == 1) return a[0];
int sum = 0;
for (int i=0;i<a.length;i++){
sum += Math.abs(a[i]);
}
int[] indices = new int[a.length];
indices[0] = 0;
int half = sum/2;
int localSum = Math.abs(a[0]);
int minLocalSum = Integer.MAX_VALUE;
int placeIndex = 1;
for (int i=1;i<a.length;i++){
if (localSum<half){
if (Math.abs(2*minLocalSum-sum) > Math.abs(2*localSum - sum))
minLocalSum = localSum;
localSum += Math.abs(a[i]);
indices[placeIndex++] = i;
}else{
if (localSum == half)
return Math.abs(2*half - sum);
if (Math.abs(2*minLocalSum-sum) > Math.abs(2*localSum - sum))
minLocalSum = localSum;
if (placeIndex > 1) {
localSum -= Math.abs(a[indices[placeIndex--]]);
i = indices[placeIndex];
}
}
}
return (Math.abs(2*minLocalSum - sum));
}
this solution treats all elements like they are positive numbers and it's looking to reach as close as it can to the sum of all elements divided by 2 (in that case we know that the sum of all other elements will be the same delta far from the half too -> abs sum will be minimum possible ).
it does so by starting with the first element and successively adding others to the "local" sum (and recording indices of elements in the sum) until it reaches sum of x >= sumAll/2. if that x is equal to sumAll/2 we have an optimal solution. if not, we go step back in the indices array and continue picking other element where last iteration in that position ended. the result will be a "local" sum having abs((sumAll - sum) - sum) closest to 0;
fixed solution:
public static int solution(int[] a){
if (a.length == 0) return 0;
if (a.length == 1) return a[0];
int sum = 0;
for (int i=0;i<a.length;i++) {
a[i] = Math.abs(a[i]);
sum += a[i];
}
Arrays.sort(a);
int[] arr = a;
int[] arrRev = new int[arr.length];
int minRes = Integer.MAX_VALUE;
for (int t=0;t<=4;t++) {
arr = fold(arr);
int res1 = findSum(arr, sum);
if (res1 < minRes) minRes = res1;
rev(arr, arrRev);
int res2 = findSum(arrRev, sum);
if (res2 < minRes) minRes = res2;
arrRev = fold(arrRev);
int res3 = findSum(arrRev, sum);
if (res3 < minRes) minRes = res3;
}
return minRes;
}
private static void rev(int[] arr, int[] arrRev){
for (int i = 0; i < arrRev.length; i++) {
arrRev[i] = arr[arr.length - 1 - i];
}
}
private static int[] fold(int[] a){
int[] arr = new int[a.length];
for (int i=0;a.length/2+i/2 < a.length && a.length/2-i/2-1 >= 0;i+=2){
arr[i] = a[a.length/2+i/2];
arr[i+1] = a[a.length/2-i/2-1];
}
if (a.length % 2 > 0) arr[a.length-1] = a[a.length-1];
else{
arr[a.length-2] = a[0];
arr[a.length-1] = a[a.length-1];
}
return arr;
}
private static int findSum(int[] arr, int sum){
int[] indices = new int[arr.length];
indices[0] = 0;
double half = Double.valueOf(sum)/2;
int localSum = Math.abs(arr[0]);
int minLocalSum = Integer.MAX_VALUE;
int placeIndex = 1;
for (int i=1;i<arr.length;i++){
if (localSum == half)
return 2*localSum - sum;
if (Math.abs(2*minLocalSum-sum) > Math.abs(2*localSum - sum))
minLocalSum = localSum;
if (localSum<half){
localSum += Math.abs(arr[i]);
indices[placeIndex++] = i;
}else{
if (placeIndex > 1) {
localSum -= Math.abs(arr[indices[--placeIndex]]);
i = indices[placeIndex];
}
}
}
return Math.abs(2*minLocalSum - sum);
}
The following is a rendering of the official answer in C++ (scoring 100% in task, correctness, and performance):
#include <cmath>
#include <algorithm>
#include <numeric>
using namespace std;
int solution(vector<int> &A) {
// write your code in C++14 (g++ 6.2.0)
const int N = A.size();
int M = 0;
for (int i=0; i<N; i++) {
A[i] = abs(A[i]);
M = max(M, A[i]);
}
int S = accumulate(A.begin(), A.end(), 0);
vector<int> counts(M+1, 0);
for (int i=0; i<N; i++) {
counts[A[i]]++;
}
vector<int> dp(S+1, -1);
dp[0] = 0;
for (int a=1; a<M+1; a++) {
if (counts[a] > 0) {
for (int j=0; j<S; j++) {
if (dp[j] >= 0) {
dp[j] = counts[a];
} else if ((j >= a) && (dp[j-a] > 0)) {
dp[j] = dp[j-a]-1;
}
}
}
}
int result = S;
for (int i =0; i<(S/2+1); i++) {
if (dp[i] >= 0) {
result = min(result, S-2*i);
}
}
return result;
}
You are almost 90% to the actual solution. It seems you understand recursion very well. Now, You should apply dynamic programming here with your program.
Dynamic Programming is nothing but memoization to the recursion so that we will not calculate same sub problems again and again. If same sub problems encounter , we return the previously calculated and memorized value. Memorization can be done with the help of a 2D array , say dp[][], where first state represent current index of array and second state represent summation.
For this problem specific, instead of giving calls to both states from each state, you sometimes can greedily take decision to skip one call.
I would like to provide the algorithm and then my implementation in C++. Idea is more or less the same as the official codility solution with some constant optimisation added.
Calculate the maximum absolute element of the inputs.
Calculate the absolute sum of the inputs.
Count the number of occurrence of each number in the inputs. Store the results in a vector hash.
Go through each input.
For each input, goes through all possible sums of any number of inputs. It is a slight constant optimisation to go only up to half of the possible sums.
For each sum that has been made before, set the occurrence count of the current input.
Check for each potential sum equal to or greater than the current input whether this input has already been used before. Update the values at the current sum accordingly. We do not need to check for potential sums less than the current input in this iteration, since it is evident that it has not been used before.
The above nested loop will fill in each possible sum with a value greater than -1.
Go through this possible sum hash again to look for the closest sum to half that is possible to make. Eventually, the min abs sum will be the difference of this from the half multiplied by two as the difference will be added up in both groups as the difference from the median.
The runtime complexity of this algorithm is O(N * max(abs(A)) ^ 2), or simply O(N * M ^ 2). That is because the outer loop is iterating M times and the inner loop is iterating sum times. The sum is basically N * M in worst case. Therefore, it is O(M * N * M).
The space complexity of this solution is O(N * M) because we allocate a hash of N items for the counts and a hash of S items for the sums. S is N * M again.
int solution(vector<int> &A)
{
int M = 0, S = 0;
for (const int e : A) { M = max(abs(e), M); S += abs(e); }
vector<int> counts(M + 1, 0);
for (const int e : A) { ++counts[abs(e)]; }
vector<int> sums(S + 1, -1);
sums[0] = 0;
for (int ci = 1; ci < counts.size(); ++ci) {
if (!counts[ci]) continue;
for (int si = 0; si < S / 2 + 1; ++si) {
if (sums[si] >= 0) sums[si] = counts[ci];
else if (si >= ci and sums[si - ci] > 0) sums[si] = sums[si - ci] - 1;
}
}
int min_abs_sum = S;
for (int i = S / 2; i >= 0; --i) if (sums[i] >= 0) return S - 2 * i;
return min_abs_sum;
}
Let me add my 50 cent, how to come up with the score 100% solution.
For me it was hard to understand the ultimate solution, proposed earlier in this thread.
So I started with warm-up solution with score 63%, because its O(NxNxM),
and because it doesn't use the fact that M is quite small value, and there are many duplicates in big arrays
here the key part is to understand how array isSumPossible is filled and interpreted:
how to fill array isSumPossible using numbers in input array:
if isSumPossible[sum] >= 0, i.e. sum is already possible, even without current number, then let's set it's value to 1 - count of current number, that is left unused for this sum, it'll go to our "reserve", so we can use it later for greater sums.
if (isSumPossible[sum] >= 0) {
isSumPossible[sum] = 1;
}
if isSumPossible[sum] <= 0, i.e. sum is considered not yet possible, with all input numbers considered previously, then let's check maybe
smaller sum sum - number is already considered as possible, and we have in "reserve" our current number (isSumPossible[sum - number] == 1), then do following
else if (sum >= number && isSumPossible[sum - number] == 1) {
isSumPossible[sum] = 0;
}
here isSumPossible[sum] = 0 means that we have used number in composing sum and it's now considered as possible (>=0), but we have no number in "reserve", because we've used it ( =0)
how to interpret filled array isSumPossible after considering all numbers in input array:
if isSumPossible[sum] >= 0 then the sum is possible, i.e. it can be reached by summation of some numbers in given array
if isSumPossible[sum] < 0 then the sum can't be reached by summation of any numbers in given array
The more simple thing here is to understand why we are searching sums only in interval [0, maxSum/2]:
because if find a possible sum, that is very close to maxSum/2,
ideal case here if we've found possible sum = maxSum/2,
if so, then it's obvious, that we can somehow use the rest numbers in input array to make another maxSum/2, but now with negative sign, so as a result of annihilation we'll get solution = 0, because maxSum/2 + (-1)maxSum/2 = 0.
But 0 the best case solution, not always reachable.
But we, nevertheless, should seek for the minimal delta = ((maxSum - sum) - sum),
so this we seek for delta -> 0, that's why we have this:
int result = Integer.MAX_VALUE;
for (int sum = 0; sum < maxSum / 2 + 1; sum++) {
if (isSumPossible[sum] >= 0) {
result = Math.min(result, (maxSum - sum) - sum);
}
}
warm-up solution
public int solution(int[] A) {
if (A == null || A.length == 0) {
return 0;
}
if (A.length == 1) {
return A[0];
}
int maxSum = 0;
for (int i = 0; i < A.length; i++) {
A[i] = Math.abs(A[i]);
maxSum += A[i];
}
int[] isSumPossible = new int[maxSum + 1];
Arrays.fill(isSumPossible, -1);
isSumPossible[0] = 0;
for (int number : A) {
for (int sum = 0; sum < maxSum / 2 + 1; sum++) {
if (isSumPossible[sum] >= 0) {
isSumPossible[sum] = 1;
} else if (sum >= number && isSumPossible[sum - number] == 1) {
isSumPossible[sum] = 0;
}
}
}
int result = Integer.MAX_VALUE;
for (int sum = 0; sum < maxSum / 2 + 1; sum++) {
if (isSumPossible[sum] >= 0) {
result = Math.min(result, maxSum - 2 * sum);
}
}
return result;
}
and after this we can optimize it, using the fact that there are many duplicate numbers in big arrays, and we come up with the solution with 100% score, its O(Mx(NxM)), because maxSum = NxM at worst case
public int solution(int[] A) {
if (A == null || A.length == 0) {
return 0;
}
if (A.length == 1) {
return A[0];
}
int maxNumber = 0;
int maxSum = 0;
for (int i = 0; i < A.length; i++) {
A[i] = Math.abs(A[i]);
maxNumber = Math.max(maxNumber, A[i]);
maxSum += A[i];
}
int[] count = new int[maxNumber + 1];
for (int i = 0; i < A.length; i++) {
count[A[i]]++;
}
int[] isSumPossible = new int[maxSum + 1];
Arrays.fill(isSumPossible, -1);
isSumPossible[0] = 0;
for (int number = 0; number < maxNumber + 1; number++) {
if (count[number] > 0) {
for (int sum = 0; sum < maxSum / 2 + 1; sum++) {
if (isSumPossible[sum] >= 0) {
isSumPossible[sum] = count[number];
} else if (sum >= number && isSumPossible[sum - number] > 0) {
isSumPossible[sum] = isSumPossible[sum - number] - 1;
}
}
}
}
int result = Integer.MAX_VALUE;
for (int sum = 0; sum < maxSum / 2 + 1; sum++) {
if (isSumPossible[sum] >= 0) {
result = Math.min(result, maxSum - 2 * sum);
}
}
return result;
}
I hope I've made it at least a little clear
Kotlin solution
Time complexity: O(N * max(abs(A))**2)
Score: 100%
import kotlin.math.*
fun solution(A: IntArray): Int {
val N = A.size
var M = 0
for (i in 0 until N) {
A[i] = abs(A[i])
M = max(M, A[i])
}
val S = A.sum()
val counts = MutableList(M + 1) { 0 }
for (i in 0 until N) {
counts[A[i]]++
}
val dp = MutableList(S + 1) { -1 }
dp[0] = 0
for (a in 1 until M + 1) {
if (counts[a] > 0) {
for (j in 0 until S) {
if (dp[j] >= 0) {
dp[j] = counts[a]
} else if (j >= a && dp[j - a] > 0) {
dp[j] = dp[j - a] - 1
}
}
}
}
var result = S
for (i in 0 until (S / 2 + 1)) {
if (dp[i] >= 0) {
result = minOf(result, S - 2 * i)
}
}
return result
}
I'm posting this although much has already been posted about this question. I didn't want to post as an answer since it's not working. The answer to this post (Finding the rank of the Given string in list of all possible permutations with Duplicates) did not work for me.
So I tried this (which is a compilation of code I've plagiarized and my attempt to deal with repetitions). The non-repeating cases work fine. BOOKKEEPER generates 83863, not the desired 10743.
(The factorial function and letter counter array 'repeats' are working correctly. I didn't post to save space.)
while (pointer != length)
{
if (sortedWordChars[pointer] != wordArray[pointer])
{
// Swap the current character with the one after that
char temp = sortedWordChars[pointer];
sortedWordChars[pointer] = sortedWordChars[next];
sortedWordChars[next] = temp;
next++;
//For each position check how many characters left have duplicates,
//and use the logic that if you need to permute n things and if 'a' things
//are similar the number of permutations is n!/a!
int ct = repeats[(sortedWordChars[pointer]-64)];
// Increment the rank
if (ct>1) { //repeats?
System.out.println("repeating " + (sortedWordChars[pointer]-64));
//In case of repetition of any character use: (n-1)!/(times)!
//e.g. if there is 1 character which is repeating twice,
//x* (n-1)!/2!
int dividend = getFactorialIter(length - pointer - 1);
int divisor = getFactorialIter(ct);
int quo = dividend/divisor;
rank += quo;
} else {
rank += getFactorialIter(length - pointer - 1);
}
} else
{
pointer++;
next = pointer + 1;
}
}
Note: this answer is for 1-based rankings, as specified implicitly by example. Here's some Python that works at least for the two examples provided. The key fact is that suffixperms * ctr[y] // ctr[x] is the number of permutations whose first letter is y of the length-(i + 1) suffix of perm.
from collections import Counter
def rankperm(perm):
rank = 1
suffixperms = 1
ctr = Counter()
for i in range(len(perm)):
x = perm[((len(perm) - 1) - i)]
ctr[x] += 1
for y in ctr:
if (y < x):
rank += ((suffixperms * ctr[y]) // ctr[x])
suffixperms = ((suffixperms * (i + 1)) // ctr[x])
return rank
print(rankperm('QUESTION'))
print(rankperm('BOOKKEEPER'))
Java version:
public static long rankPerm(String perm) {
long rank = 1;
long suffixPermCount = 1;
java.util.Map<Character, Integer> charCounts =
new java.util.HashMap<Character, Integer>();
for (int i = perm.length() - 1; i > -1; i--) {
char x = perm.charAt(i);
int xCount = charCounts.containsKey(x) ? charCounts.get(x) + 1 : 1;
charCounts.put(x, xCount);
for (java.util.Map.Entry<Character, Integer> e : charCounts.entrySet()) {
if (e.getKey() < x) {
rank += suffixPermCount * e.getValue() / xCount;
}
}
suffixPermCount *= perm.length() - i;
suffixPermCount /= xCount;
}
return rank;
}
Unranking permutations:
from collections import Counter
def unrankperm(letters, rank):
ctr = Counter()
permcount = 1
for i in range(len(letters)):
x = letters[i]
ctr[x] += 1
permcount = (permcount * (i + 1)) // ctr[x]
# ctr is the histogram of letters
# permcount is the number of distinct perms of letters
perm = []
for i in range(len(letters)):
for x in sorted(ctr.keys()):
# suffixcount is the number of distinct perms that begin with x
suffixcount = permcount * ctr[x] // (len(letters) - i)
if rank <= suffixcount:
perm.append(x)
permcount = suffixcount
ctr[x] -= 1
if ctr[x] == 0:
del ctr[x]
break
rank -= suffixcount
return ''.join(perm)
If we use mathematics, the complexity will come down and will be able to find rank quicker. This will be particularly helpful for large strings.
(more details can be found here)
Suggest to programmatically define the approach shown here (screenshot attached below) given below)
I would say David post (the accepted answer) is super cool. However, I would like to improve it further for speed. The inner loop is trying to find inverse order pairs, and for each such inverse order, it tries to contribute to the increment of rank. If we use an ordered map structure (binary search tree or BST) in that place, we can simply do an inorder traversal from the first node (left-bottom) until it reaches the current character in the BST, rather than traversal for the whole map(BST). In C++, std::map is a perfect one for BST implementation. The following code reduces the necessary iterations in loop and removes the if check.
long long rankofword(string s)
{
long long rank = 1;
long long suffixPermCount = 1;
map<char, int> m;
int size = s.size();
for (int i = size - 1; i > -1; i--)
{
char x = s[i];
m[x]++;
for (auto it = m.begin(); it != m.find(x); it++)
rank += suffixPermCount * it->second / m[x];
suffixPermCount *= (size - i);
suffixPermCount /= m[x];
}
return rank;
}
#Dvaid Einstat, this was really helpful. It took me a WHILE to figure out what you were doing as I am still learning my first language(C#). I translated it into C# and figured that I'd give that solution as well since this listing helped me so much!
Thanks!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
namespace CsharpVersion
{
class Program
{
//Takes in the word and checks to make sure that the word
//is between 1 and 25 charaters inclusive and only
//letters are used
static string readWord(string prompt, int high)
{
Regex rgx = new Regex("^[a-zA-Z]+$");
string word;
string result;
do
{
Console.WriteLine(prompt);
word = Console.ReadLine();
} while (word == "" | word.Length > high | rgx.IsMatch(word) == false);
result = word.ToUpper();
return result;
}
//Creates a sorted dictionary containing distinct letters
//initialized with 0 frequency
static SortedDictionary<char,int> Counter(string word)
{
char[] wordArray = word.ToCharArray();
int len = word.Length;
SortedDictionary<char,int> count = new SortedDictionary<char,int>();
foreach(char c in word)
{
if(count.ContainsKey(c))
{
}
else
{
count.Add(c, 0);
}
}
return count;
}
//Creates a factorial function
static int Factorial(int n)
{
if (n <= 1)
{
return 1;
}
else
{
return n * Factorial(n - 1);
}
}
//Ranks the word input if there are no repeated charaters
//in the word
static Int64 rankWord(char[] wordArray)
{
int n = wordArray.Length;
Int64 rank = 1;
//loops through the array of letters
for (int i = 0; i < n-1; i++)
{
int x=0;
//loops all letters after i and compares them for factorial calculation
for (int j = i+1; j<n ; j++)
{
if (wordArray[i] > wordArray[j])
{
x++;
}
}
rank = rank + x * (Factorial(n - i - 1));
}
return rank;
}
//Ranks the word input if there are repeated charaters
//in the word
static Int64 rankPerm(String word)
{
Int64 rank = 1;
Int64 suffixPermCount = 1;
SortedDictionary<char, int> counter = Counter(word);
for (int i = word.Length - 1; i > -1; i--)
{
char x = Convert.ToChar(word.Substring(i,1));
int xCount;
if(counter[x] != 0)
{
xCount = counter[x] + 1;
}
else
{
xCount = 1;
}
counter[x] = xCount;
foreach (KeyValuePair<char,int> e in counter)
{
if (e.Key < x)
{
rank += suffixPermCount * e.Value / xCount;
}
}
suffixPermCount *= word.Length - i;
suffixPermCount /= xCount;
}
return rank;
}
static void Main(string[] args)
{
Console.WriteLine("Type Exit to end the program.");
string prompt = "Please enter a word using only letters:";
const int MAX_VALUE = 25;
Int64 rank = new Int64();
string theWord;
do
{
theWord = readWord(prompt, MAX_VALUE);
char[] wordLetters = theWord.ToCharArray();
Array.Sort(wordLetters);
bool duplicate = false;
for(int i = 0; i< theWord.Length - 1; i++)
{
if(wordLetters[i] < wordLetters[i+1])
{
duplicate = true;
}
}
if(duplicate)
{
SortedDictionary<char, int> counter = Counter(theWord);
rank = rankPerm(theWord);
Console.WriteLine("\n" + theWord + " = " + rank);
}
else
{
char[] letters = theWord.ToCharArray();
rank = rankWord(letters);
Console.WriteLine("\n" + theWord + " = " + rank);
}
} while (theWord != "EXIT");
Console.WriteLine("\nPress enter to escape..");
Console.Read();
}
}
}
If there are k distinct characters, the i^th character repeated n_i times, then the total number of permutations is given by
(n_1 + n_2 + ..+ n_k)!
------------------------------------------------
n_1! n_2! ... n_k!
which is the multinomial coefficient.
Now we can use this to compute the rank of a given permutation as follows:
Consider the first character(leftmost). say it was the r^th one in the sorted order of characters.
Now if you replace the first character by any of the 1,2,3,..,(r-1)^th character and consider all possible permutations, each of these permutations will precede the given permutation. The total number can be computed using the above formula.
Once you compute the number for the first character, fix the first character, and repeat the same with the second character and so on.
Here's the C++ implementation to your question
#include<iostream>
using namespace std;
int fact(int f) {
if (f == 0) return 1;
if (f <= 2) return f;
return (f * fact(f - 1));
}
int solve(string s,int n) {
int ans = 1;
int arr[26] = {0};
int len = n - 1;
for (int i = 0; i < n; i++) {
s[i] = toupper(s[i]);
arr[s[i] - 'A']++;
}
for(int i = 0; i < n; i++) {
int temp = 0;
int x = 1;
char c = s[i];
for(int j = 0; j < c - 'A'; j++) temp += arr[j];
for (int j = 0; j < 26; j++) x = (x * fact(arr[j]));
arr[c - 'A']--;
ans = ans + (temp * ((fact(len)) / x));
len--;
}
return ans;
}
int main() {
int i,n;
string s;
cin>>s;
n=s.size();
cout << solve(s,n);
return 0;
}
Java version of unrank for a String:
public static String unrankperm(String letters, int rank) {
Map<Character, Integer> charCounts = new java.util.HashMap<>();
int permcount = 1;
for(int i = 0; i < letters.length(); i++) {
char x = letters.charAt(i);
int xCount = charCounts.containsKey(x) ? charCounts.get(x) + 1 : 1;
charCounts.put(x, xCount);
permcount = (permcount * (i + 1)) / xCount;
}
// charCounts is the histogram of letters
// permcount is the number of distinct perms of letters
StringBuilder perm = new StringBuilder();
for(int i = 0; i < letters.length(); i++) {
List<Character> sorted = new ArrayList<>(charCounts.keySet());
Collections.sort(sorted);
for(Character x : sorted) {
// suffixcount is the number of distinct perms that begin with x
Integer frequency = charCounts.get(x);
int suffixcount = permcount * frequency / (letters.length() - i);
if (rank <= suffixcount) {
perm.append(x);
permcount = suffixcount;
if(frequency == 1) {
charCounts.remove(x);
} else {
charCounts.put(x, frequency - 1);
}
break;
}
rank -= suffixcount;
}
}
return perm.toString();
}
See also n-th-permutation-algorithm-for-use-in-brute-force-bin-packaging-parallelization.
I need to place numbers within a grid such that it doesn't collide with each other. This number placement should be random and can be horizontal or vertical. The numbers basically indicate the locations of the ships. So the points for the ships should be together and need to be random and should not collide.
I have tried it:
int main()
{
srand(time(NULL));
int Grid[64];
int battleShips;
bool battleShipFilled;
for(int i = 0; i < 64; i++)
Grid[i]=0;
for(int i = 1; i <= 5; i++)
{
battleShips = 1;
while(battleShips != 5)
{
int horizontal = rand()%2;
if(horizontal == 0)
{
battleShipFilled = false;
while(!battleShipFilled)
{
int row = rand()%8;
int column = rand()%8;
while(Grid[(row)*8+(column)] == 1)
{
row = rand()%8;
column = rand()%8;
}
int j = 0;
if(i == 1) j= (i+1);
else j= i;
for(int k = -j/2; k <= j/2; k++)
{
int numberOfCorrectLocation = 0;
while(numberOfCorrectLocation != j)
{
if(row+k> 0 && row+k<8)
{
if(Grid[(row+k)*8+(column)] == 1) break;
numberOfCorrectLocation++;
}
}
if(numberOfCorrectLocation !=i) break;
}
for(int k = -j/2; k <= j/2; k++)
Grid[(row+k)*8+(column)] = 1;
battleShipFilled = true;
}
battleShips++;
}
else
{
battleShipFilled = false;
while(!battleShipFilled)
{
int row = rand()%8;
int column = rand()%8;
while(Grid[(row)*8+(column)] == 1)
{
row = rand()%8;
column = rand()%8;
}
int j = 0;
if(i == 1) j= (i+1);
else j= i;
for(int k = -j/2; k <= j/2; k++)
{
int numberOfCorrectLocation = 0;
while(numberOfCorrectLocation != i)
{
if(row+k> 0 && row+k<8)
{
if(Grid[(row)*8+(column+k)] == 1) break;
numberOfCorrectLocation++;
}
}
if(numberOfCorrectLocation !=i) break;
}
for(int k = -j/2; k <= j/2; k++)
Grid[(row)*8+(column+k)] = 1;
battleShipFilled = true;
}
battleShips++;
}
}
}
}
But the code i have written is not able to generate the numbers randomly in the 8x8 grid.
Need some guidance on how to solve this. If there is any better way of doing it, please tell me...
How it should look:
What My code is doing:
Basically, I am placing 5 ships, each of different size on a grid. For each, I check whether I want to place it horizontally or vertically randomly. After that, I check whether the surrounding is filled up or not. If not, I place them there. Or I repeat the process.
Important Point: I need to use just while, for loops..
You are much better of using recursion for that problem. This will give your algorithm unwind possibility. What I mean is that you can deploy each ship and place next part at random end of the ship, then check the new placed ship part has adjacent tiles empty and progress to the next one. if it happens that its touches another ship it will due to recursive nature it will remove the placed tile and try on the other end. If the position of the ship is not valid it should place the ship in different place and start over.
I have used this solution in a word search game, where the board had to be populated with words to look for. Worked perfect.
This is a code from my word search game:
bool generate ( std::string word, BuzzLevel &level, CCPoint position, std::vector<CCPoint> &placed, CCSize lSize )
{
std::string cPiece;
if ( word.size() == 0 ) return true;
if ( !level.inBounds ( position ) ) return false;
cPiece += level.getPiece(position)->getLetter();
int l = cPiece.size();
if ( (cPiece != " ") && (word[0] != cPiece[0]) ) return false;
if ( pointInVec (position, placed) ) return false;
if ( position.x >= lSize.width || position.y >= lSize.height || position.x < 0 || position.y < 0 ) return false;
placed.push_back(position);
bool used[6];
for ( int t = 0; t < 6; t++ ) used[t] = false;
int adj;
while ( (adj = HexCoord::getRandomAdjacentUnique(used)) != -1 )
{
CCPoint nextPosition = HexCoord::getAdjacentGridPositionInDirection((eDirection) adj, position);
if ( generate ( word.substr(1, word.size()), level, nextPosition, placed, lSize ) ) return true;
}
placed.pop_back();
return false;
}
CCPoint getRandPoint ( CCSize size )
{
return CCPoint ( rand() % (int)size.width, rand() % (int)size.height);
}
void generateWholeLevel ( BuzzLevel &level,
blockInfo* info,
const CCSize &levelSize,
vector<CCLabelBMFont*> wordList
)
{
for ( vector<CCLabelBMFont*>::iterator iter = wordList.begin();
iter != wordList.end(); iter++ )
{
std::string cWord = (*iter)->getString();
// CCLog("Curront word %s", cWord.c_str() );
vector<CCPoint> wordPositions;
int iterations = 0;
while ( true )
{
iterations++;
//CCLog("iteration %i", iterations );
CCPoint cPoint = getRandPoint(levelSize);
if ( generate (cWord, level, cPoint, wordPositions, levelSize ) )
{
//Place pieces here
for ( int t = 0; t < cWord.size(); t++ )
{
level.getPiece(wordPositions[t])->addLetter(cWord[t]);
}
break;
}
if ( iterations > 1500 )
{
level.clear();
generateWholeLevel(level, info, levelSize, wordList);
return;
}
}
}
}
I might add that shaped used in the game was a honeycomb. Letter could wind in any direction, so the code above is way more complex then what you are looking for I guess, but will provide a starting point.
I will provide something more suitable when I get back home as I don't have enough time now.
I can see a potential infinite loop in your code
int j = 0;
if(i == 1) j= (i+1);
else j= i;
for(int k = -j/2; k <= j/2; k++)
{
int numberOfCorrectLocation = 0;
while(numberOfCorrectLocation != i)
{
if(row+k> 0 && row+k<8)
{
if(Grid[(row)*8+(column+k)] == 1) break;
numberOfCorrectLocation++;
}
}
if(numberOfCorrectLocation !=i) break;
}
Here, nothing prevents row from being 0, as it was assignd rand%8 earlier, and k can be assigned a negative value (since j can be positive). Once that happens nothing will end the while loop.
Also, I would recommend re-approaching this problem in a more object oriented way (or at the very least breaking up the code in main() into multiple, shorter functions). Personally I found the code a little difficult to follow.
A very quick and probably buggy example of how you could really clean your solution up and make it more flexible by using some OOP:
enum Orientation {
Horizontal,
Vertical
};
struct Ship {
Ship(unsigned l = 1, bool o = Horizontal) : length(l), orientation(o) {}
unsigned char length;
bool orientation;
};
class Grid {
public:
Grid(const unsigned w = 8, const unsigned h = 8) : _w(w), _h(h) {
grid.resize(w * h);
foreach (Ship * sp, grid) {
sp = nullptr;
}
}
bool addShip(Ship * s, unsigned x, unsigned y) {
if ((x <= _w) && (y <= _h)) { // if in valid range
if (s->orientation == Horizontal) {
if ((x + s->length) <= _w) { // if not too big
int p = 0; //check if occupied
for (int c1 = 0; c1 < s->length; ++c1) if (grid[y * _w + x + p++]) return false;
p = 0; // occupy if not
for (int c1 = 0; c1 < s->length; ++c1) grid[y * _w + x + p++] = s;
return true;
} else return false;
} else {
if ((y + s->length) <= _h) {
int p = 0; // check
for (int c1 = 0; c1 < s->length; ++c1) {
if (grid[y * _w + x + p]) return false;
p += _w;
}
p = 0; // occupy
for (int c1 = 0; c1 < s->length; ++c1) {
grid[y * _w + x + p] = s;
p += _w;
}
return true;
} else return false;
}
} else return false;
}
void drawGrid() {
for (int y = 0; y < _h; ++y) {
for (int x = 0; x < _w; ++x) {
if (grid.at(y * w + x)) cout << "|S";
else cout << "|_";
}
cout << "|" << endl;
}
cout << endl;
}
void hitXY(unsigned x, unsigned y) {
if ((x <= _w) && (y <= _h)) {
if (grid[y * _w + x]) cout << "You sunk my battleship" << endl;
else cout << "Nothing..." << endl;
}
}
private:
QVector<Ship *> grid;
unsigned _w, _h;
};
The basic idea is create a grid of arbitrary size and give it the ability to "load" ships of arbitrary length at arbitrary coordinates. You need to check if the size is not too much and if the tiles aren't already occupied, that's pretty much it, the other thing is orientation - if horizontal then increment is +1, if vertical increment is + width.
This gives flexibility to use the methods to quickly populate the grid with random data:
int main() {
Grid g(20, 20);
g.drawGrid();
unsigned shipCount = 20;
while (shipCount) {
Ship * s = new Ship(qrand() % 8 + 2, qrand() %2);
if (g.addShip(s, qrand() % 20, qrand() % 20)) --shipCount;
else delete s;
}
cout << endl;
g.drawGrid();
for (int i = 0; i < 20; ++i) g.hitXY(qrand() % 20, qrand() % 20);
}
Naturally, you can extend it further, make hit ships sink and disappear from the grid, make it possible to move ships around and flip their orientation. You can even use diagonal orientation. A lot of flexibility and potential to harness by refining an OOP based solution.
Obviously, you will put some limits in production code, as currently you can create grids of 0x0 and ships of length 0. It's just a quick example anyway. I am using Qt and therefore Qt containers, but its just the same with std containers.
I tried to rewrite your program in Java, it works as required. Feel free to ask anything that is not clearly coded. I didn't rechecked it so it may have errors of its own. It can be further optimized and cleaned but as it is past midnight around here, I would rather not do that at the moment :)
public static void main(String[] args) {
Random generator = new Random();
int Grid[][] = new int[8][8];
for (int battleShips = 0; battleShips < 5; battleShips++) {
boolean isHorizontal = generator.nextInt(2) == 0 ? true : false;
boolean battleShipFilled = false;
while (!battleShipFilled) {
// Select a random row and column for trial
int row = generator.nextInt(8);
int column = generator.nextInt(8);
while (Grid[row][column] == 1) {
row = generator.nextInt(8);
column = generator.nextInt(8);
}
int lengthOfBattleship = 0;
if (battleShips == 0) // Smallest ship should be of length 2
lengthOfBattleship = (battleShips + 2);
else // Other 4 ships has the length of 2, 3, 4 & 5
lengthOfBattleship = battleShips + 1;
int numberOfCorrectLocation = 0;
for (int k = 0; k < lengthOfBattleship; k++) {
if (isHorizontal && row + k > 0 && row + k < 8) {
if (Grid[row + k][column] == 1)
break;
} else if (!isHorizontal && column + k > 0 && column + k < 8) {
if (Grid[row][column + k] == 1)
break;
} else {
break;
}
numberOfCorrectLocation++;
}
if (numberOfCorrectLocation == lengthOfBattleship) {
for (int k = 0; k < lengthOfBattleship; k++) {
if (isHorizontal)
Grid[row + k][column] = 1;
else
Grid[row][column + k] = 1;
}
battleShipFilled = true;
}
}
}
}
Some important points.
As #Kindread said in an another answer, the code has an infinite loop condition which must be eliminated.
This algorithm will use too much resources to find a solution, it should be optimized.
Code duplications should be avoided as it will result in more maintenance cost (which might not be a problem for this specific case), and possible bugs.
Hope this answer helps...
Please see my own answer, I think I did it!
Hi,
An example question for a programming contest was to write a program that finds out how much polyominos are possible with a given number of stones.
So for two stones (n = 2) there is only one polyominos:
XX
You might think this is a second solution:
X
X
But it isn't. The polyominos are not unique if you can rotate them.
So, for 4 stones (n = 4), there are 7 solutions:
X
X XX X X X X
X X XX X XX XX XX
X X X XX X X XX
The application has to be able to find the solution for 1 <= n <=10
PS: Using the list of polyominos on Wikipedia isn't allowed ;)
EDIT: Of course the question is: How to do this in Java, C/C++, C#
I started this project in Java. But then I had to admit I didn't know how to build polyominos using an efficient algorithm.
This is what I had so far:
import java.util.ArrayList;
import java.util.List;
public class Main
{
private int countPolyminos(int n)
{
hashes.clear();
count = 0;
boolean[][] matrix = new boolean[n][n];
createPolyominos(matrix, n);
return count;
}
private List<Integer> hashes = new ArrayList<Integer>();
private int count;
private void createPolyominos(boolean[][] matrix, int n)
{
if (n == 0)
{
boolean[][] cropped = cropMatrix(matrix);
int hash = hashMatrixOrientationIndependent(matrix);
if (!hashes.contains(hash))
{
count++;
hashes.add(hash);
}
return;
}
// Here is the real trouble!!
// Then here something like; createPolyominos(matrix, n-1);
// But, we need to keep in mind that the polyominos can have ramifications
}
public boolean[][] copy(boolean[][] matrix)
{
boolean[][] b = new boolean[matrix.length][matrix[0].length];
for (int i = 0; i < matrix.length; ++i)
{
System.arraycopy(matrix[i], 0, b, 0, matrix[i].length);
}
return b;
}
public boolean[][] cropMatrix(boolean[][] matrix)
{
int l = 0, t = 0, r = 0, b = 0;
// Left
left: for (int x = 0; x < matrix.length; ++x)
{
for (int y = 0; y < matrix[x].length; ++y)
{
if (matrix[x][y])
{
break left;
}
}
l++;
}
// Right
right: for (int x = matrix.length - 1; x >= 0; --x)
{
for (int y = 0; y < matrix[x].length; ++y)
{
if (matrix[x][y])
{
break right;
}
}
r++;
}
// Top
top: for (int y = 0; y < matrix[0].length; ++y)
{
for (int x = 0; x < matrix.length; ++x)
{
if (matrix[x][y])
{
break top;
}
}
t++;
}
// Bottom
bottom: for (int y = matrix[0].length; y >= 0; --y)
{
for (int x = 0; x < matrix.length; ++x)
{
if (matrix[x][y])
{
break bottom;
}
}
b++;
}
// Perform the real crop
boolean[][] cropped = new boolean[matrix.length - l - r][matrix[0].length - t - b];
for (int x = l; x < matrix.length - r; ++x)
{
System.arraycopy(matrix[x - l], t, cropped, 0, matrix[x].length - t - b);
}
return cropped;
}
public int hashMatrix(boolean[][] matrix)
{
int hash = 0;
for (int x = 0; x < matrix.length; ++x)
{
for (int y = 0; y < matrix[x].length; ++y)
{
hash += matrix[x][y] ? (((x + 7) << 4) * ((y + 3) << 6) * 31) : ((((x+5) << 9) * (((y + x) + 18) << 7) * 53));
}
}
return hash;
}
public int hashMatrixOrientationIndependent(boolean[][] matrix)
{
int hash = 0;
hash += hashMatrix(matrix);
for (int i = 0; i < 3; ++i)
{
matrix = rotateMatrixLeft(matrix);
hash += hashMatrix(matrix);
}
return hash;
}
public boolean[][] rotateMatrixRight(boolean[][] matrix)
{
/* W and H are already swapped */
int w = matrix.length;
int h = matrix[0].length;
boolean[][] ret = new boolean[h][w];
for (int i = 0; i < h; ++i)
{
for (int j = 0; j < w; ++j)
{
ret[i][j] = matrix[w - j - 1][i];
}
}
return ret;
}
public boolean[][] rotateMatrixLeft(boolean[][] matrix)
{
/* W and H are already swapped */
int w = matrix.length;
int h = matrix[0].length;
boolean[][] ret = new boolean[h][w];
for (int i = 0; i < h; ++i)
{
for (int j = 0; j < w; ++j)
{
ret[i][j] = matrix[j][h - i - 1];
}
}
return ret;
}
}
There are only 4,461 polynominoes of size 10, so we can just enumerate them all.
Start with a single stone. To expand it by one stone, try add the new stone in at all empty cells that neighbour an existing stone. Do this recursively until reaching the desired size.
To avoid duplicates, keep a hash table of all polynominoes of each size we've already enumerated. When we put together a new polynomino, we check that its not already in the hash table. We also need to check its 3 rotations (and possibly its mirror image). While duplicate checking at the final size is the only strictly necessary check, checking at each step prunes recursive branches that will yield a new polynomino.
Here's some pseudo-code:
polynomino = array of n hashtables
function find_polynominoes(n, base):
if base.size == n:
return
for stone in base:
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
new_stone.x = stone.x + dx
new_stone.y = stone.y + dy
if new_stone not in base:
new_polynomino = base + new_stone
is_new = true
for rotation in [0, 90, 180, 270]:
if new_polynomino.rotate(rotation) in polynomino[new_polynomino.size]:
is_new = false
break
if is_new:
polynomino[new_polynomino.size].add(new_polynomino)
Just solved this as well in java. Since all here appear to have performance issues. I give you mine as well.
Board reprsentation:
2 arrays of integers. 1 for the rows and 1 for the columns.
Rotation: column[i]=row[size-(i+1)], row[i] = reverse(column[i]) where reverse is the bits reversed according to the size (for size = 4 and first 2 bits are taken: rev(1100) = 0011)
Shifting block: row[i-1] = row[i], col[i]<<=1
Check if bit is set: (row[r] & (1<<c)) > 0
Board uniqueness: The board is unique when the array row is unique.
Board hash: Hashcode of the array row
..
So this makes all operations fast. Many of them would have been O(size²) in the 2D array representation instead of now O(size).
Algorithm:
Start with the block of size 1
For each size start from the blocks with 1 stone less.
If it's possible to add the stone. Check if it was already added to the set.
If it's not yet added. Add it to the solution of this size.
add the block to the set and all its rotations. (3 rotations, 4 in total)
Important, after each rotation shift the block as left/top as possible.
+Special cases: do the same logic for the next 2 cases
shift block one to the right and add stone in first column
shift block one to the bottom and add stone in first row
Performance:
N=5 , time: 3ms
N=10, time: 58ms
N=11, time: 166ms
N=12, time: 538ms
N=13, time: 2893ms
N=14, time:17266ms
N=15, NA (out of heapspace)
Code:
https://github.com/Samjayyy/logicpuzzles/tree/master/polyominos
The most naive solution is to start with a single X, and for each iteration, build the list of unique possible next-states. From that list, build the list of unique states by adding another X. Continue this until the iteration you desire.
I'm not sure if this runs in reasonable time for N=10, however. It might, depending on your requirements.
I think I did it!
EDIT: I'm using the SHA-256 algorithm to hash them, now it works correct.
Here are the results:
numberOfStones -> numberOfPolyominos
1 -> 1
2 -> 1
3 -> 2
4 -> 7
5 -> 18
6 -> 60
7 -> 196
8 -> 704
9 -> 2500
10 -> terminated
Here is the code (Java):
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
/* VPW Template */
public class Main
{
/**
* #param args
*/
public static void main(String[] args) throws IOException
{
new Main().start();
}
public void start() throws IOException
{
/* Read the stuff */
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] input = new String[Integer.parseInt(br.readLine())];
for (int i = 0; i < input.length; ++i)
{
input[i] = br.readLine();
}
/* Process each line */
for (int i = 0; i < input.length; ++i)
{
processLine(input[i]);
}
}
public void processLine(String line)
{
int n = Integer.parseInt(line);
System.out.println(countPolyminos(n));
}
private int countPolyminos(int n)
{
hashes.clear();
count = 0;
boolean[][] matrix = new boolean[n][n];
matrix[n / 2][n / 2] = true;
createPolyominos(matrix, n - 1);
return count;
}
private List<BigInteger> hashes = new ArrayList<BigInteger>();
private int count;
private void createPolyominos(boolean[][] matrix, int n)
{
if (n == 0)
{
boolean[][] cropped = cropMatrix(matrix);
BigInteger hash = hashMatrixOrientationIndependent(cropped);
if (!hashes.contains(hash))
{
// System.out.println(count + " Found!");
// printMatrix(cropped);
// System.out.println();
count++;
hashes.add(hash);
}
return;
}
for (int x = 0; x < matrix.length; ++x)
{
for (int y = 0; y < matrix[x].length; ++y)
{
if (matrix[x][y])
{
if (x > 0 && !matrix[x - 1][y])
{
boolean[][] clone = copy(matrix);
clone[x - 1][y] = true;
createPolyominos(clone, n - 1);
}
if (x < matrix.length - 1 && !matrix[x + 1][y])
{
boolean[][] clone = copy(matrix);
clone[x + 1][y] = true;
createPolyominos(clone, n - 1);
}
if (y > 0 && !matrix[x][y - 1])
{
boolean[][] clone = copy(matrix);
clone[x][y - 1] = true;
createPolyominos(clone, n - 1);
}
if (y < matrix[x].length - 1 && !matrix[x][y + 1])
{
boolean[][] clone = copy(matrix);
clone[x][y + 1] = true;
createPolyominos(clone, n - 1);
}
}
}
}
}
public boolean[][] copy(boolean[][] matrix)
{
boolean[][] b = new boolean[matrix.length][matrix[0].length];
for (int i = 0; i < matrix.length; ++i)
{
System.arraycopy(matrix[i], 0, b[i], 0, matrix[i].length);
}
return b;
}
public void printMatrix(boolean[][] matrix)
{
for (int y = 0; y < matrix.length; ++y)
{
for (int x = 0; x < matrix[y].length; ++x)
{
System.out.print((matrix[y][x] ? 'X' : ' '));
}
System.out.println();
}
}
public boolean[][] cropMatrix(boolean[][] matrix)
{
int l = 0, t = 0, r = 0, b = 0;
// Left
left: for (int x = 0; x < matrix.length; ++x)
{
for (int y = 0; y < matrix[x].length; ++y)
{
if (matrix[x][y])
{
break left;
}
}
l++;
}
// Right
right: for (int x = matrix.length - 1; x >= 0; --x)
{
for (int y = 0; y < matrix[x].length; ++y)
{
if (matrix[x][y])
{
break right;
}
}
r++;
}
// Top
top: for (int y = 0; y < matrix[0].length; ++y)
{
for (int x = 0; x < matrix.length; ++x)
{
if (matrix[x][y])
{
break top;
}
}
t++;
}
// Bottom
bottom: for (int y = matrix[0].length - 1; y >= 0; --y)
{
for (int x = 0; x < matrix.length; ++x)
{
if (matrix[x][y])
{
break bottom;
}
}
b++;
}
// Perform the real crop
boolean[][] cropped = new boolean[matrix.length - l - r][matrix[0].length - t - b];
for (int x = l; x < matrix.length - r; ++x)
{
System.arraycopy(matrix[x], t, cropped[x - l], 0, matrix[x].length - t - b);
}
return cropped;
}
public BigInteger hashMatrix(boolean[][] matrix)
{
try
{
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update((byte) matrix.length);
md.update((byte) matrix[0].length);
for (int x = 0; x < matrix.length; ++x)
{
for (int y = 0; y < matrix[x].length; ++y)
{
if (matrix[x][y])
{
md.update((byte) x);
} else
{
md.update((byte) y);
}
}
}
return new BigInteger(1, md.digest());
} catch (NoSuchAlgorithmException e)
{
System.exit(1);
return null;
}
}
public BigInteger hashMatrixOrientationIndependent(boolean[][] matrix)
{
BigInteger hash = hashMatrix(matrix);
for (int i = 0; i < 3; ++i)
{
matrix = rotateMatrixLeft(matrix);
hash = hash.add(hashMatrix(matrix));
}
return hash;
}
public boolean[][] rotateMatrixRight(boolean[][] matrix)
{
/* W and H are already swapped */
int w = matrix.length;
int h = matrix[0].length;
boolean[][] ret = new boolean[h][w];
for (int i = 0; i < h; ++i)
{
for (int j = 0; j < w; ++j)
{
ret[i][j] = matrix[w - j - 1][i];
}
}
return ret;
}
public boolean[][] rotateMatrixLeft(boolean[][] matrix)
{
/* W and H are already swapped */
int w = matrix.length;
int h = matrix[0].length;
boolean[][] ret = new boolean[h][w];
for (int i = 0; i < h; ++i)
{
for (int j = 0; j < w; ++j)
{
ret[i][j] = matrix[j][h - i - 1];
}
}
return ret;
}
Here's my solution in Java to the same problem. I can confirm Martijn's numbers (see below). I've also added in the rough time it takes to compute the results (mid-2012 Macbook Retina Core i7). I suppose substantial performance improvements could be achieved via parallelization.
numberOfStones -> numberOfPolyominos
1 -> 1
2 -> 1
3 -> 2
4 -> 7
5 -> 18
6 -> 60
7 -> 196
8 -> 704 (3 seconds)
9 -> 2500 (46 seconds)
10 -> 9189 (~14 minutes)
.
/*
* This class is a solution to the Tetris unique shapes problem.
* That is, the game of Tetris has 7 unique shapes. These 7 shapes
* are all the possible unique combinations of any 4 adjoining blocks
* (i.e. ignoring rotations).
*
* How many unique shapes are possible with, say, 7 or n blocks?
*
* The solution uses recursive back-tracking to construct all the possible
* shapes. It uses a HashMap to store unique shapes and to ignore rotations.
* It also uses a temporary HashMap so that the program does not needlessly
* waste time checking the same path multiple times.
*
* Even so, this is an exponential run-time solution, with n=10 taking a few
* minutes to complete.
*/
package com.glugabytes.gbjutils;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class TetrisBlocks {
private HashMap uShapes;
private HashMap tempShapes;
/* Get a map of unique shapes for n squares. The keys are string-representations
* of each shape, and values are corresponding boolean[][] arrays.
* #param squares - number of blocks to use for shapes, e.g. n=4 has 7 unique shapes
*/
public Map getUniqueShapes(int squares) {
uShapes = new HashMap();
tempShapes = new HashMap();
boolean[][] data = new boolean[squares*2+1][squares*2+1];
data[squares][squares] = true;
make(squares, data, 1); //start the process with a single square in the center of a boolean[][] matrix
return uShapes;
}
/* Recursivelly keep adding blocks to the data array until number of blocks(squares) = required size (e.g. n=4)
* Make sure to eliminate rotations. Also make sure not to enter infinite backtracking loops, and also not
* needlessly recompute the same path multiple times.
*/
private void make(int squares, boolean[][] data, int size) {
if(size == squares) { //used the required number of squares
//get a trimmed version of the array
boolean[][] trimmed = trimArray(data);
if(!isRotation(trimmed)) { //if a unique piece, add it to unique map
uShapes.put(arrayToString(trimmed), trimmed);
}
} else {
//go through the grid 1 element at a time and add a block next to an existing block
//do this for all possible combinations
for(int iX = 0; iX < data.length; iX++) {
for(int iY = 0; iY < data.length; iY++) {
if(data[iX][iY] == true) { //only add a block next to an existing block
if(data[iX+1][iY] != true) { //if no existing block to the right, add one and recuse
data[iX+1][iY] = true;
if(!isTempRotation(data)) { //only recurse if we haven't already been on this path before
make(squares, data, size+1);
tempShapes.put(arrayToString(data), data); //store this path so we don't repeat it later
}
data[iX+1][iY] = false;
}
if(data[iX-1][iY] != true) { //repeat by adding a block on the left
data[iX-1][iY] = true;
if(!isTempRotation(data)) {
make(squares, data, size+1);
tempShapes.put(arrayToString(data), data);
}
data[iX-1][iY] = false;
}
if(data[iX][iY+1] != true) { //repeat by adding a block down
data[iX][iY+1] = true;
if(!isTempRotation(data)) {
make(squares, data, size+1);
tempShapes.put(arrayToString(data), data);
}
data[iX][iY+1] = false;
}
if(data[iX][iY-1] != true) { //repeat by adding a block up
data[iX][iY-1] = true;
if(!isTempRotation(data)) {
make(squares, data, size+1);
tempShapes.put(arrayToString(data), data);
}
data[iX][iY-1] = false;
}
}
}
}
}
}
/**
* This function basically removes all rows and columns that have no 'true' flags,
* leaving only the portion of the array that contains useful data.
*
* #param data
* #return
*/
private boolean[][] trimArray(boolean[][] data) {
int maxX = 0;
int maxY = 0;
int firstX = data.length;
int firstY = data.length;
for(int iX = 0; iX < data.length; iX++) {
for (int iY = 0; iY < data.length; iY++) {
if(data[iX][iY]) {
if(iY < firstY) firstY = iY;
if(iY > maxY) maxY = iY;
}
}
}
for(int iY = 0; iY < data.length; iY++) {
for (int iX = 0; iX < data.length; iX++) {
if(data[iX][iY]) {
if(iX < firstX) firstX = iX;
if(iX > maxX) maxX = iX;
}
}
}
boolean[][] trimmed = new boolean[maxX-firstX+1][maxY-firstY+1];
for(int iX = firstX; iX <= maxX; iX++) {
for(int iY = firstY; iY <= maxY; iY++) {
trimmed[iX-firstX][iY-firstY] = data[iX][iY];
}
}
return trimmed;
}
/**
* Return a string representation of the 2D array.
*
* #param data
* #return
*/
private String arrayToString(boolean[][] data) {
StringBuilder sb = new StringBuilder();
for(int iX = 0; iX < data.length; iX++) {
for(int iY = 0; iY < data[0].length; iY++) {
sb.append(data[iX][iY] ? '#' : ' ');
}
sb.append('\n');
}
return sb.toString();
}
/**
* Rotate an array clockwise by 90 degrees.
* #param data
* #return
*/
public boolean[][] rotate90(boolean[][] data) {
boolean[][] rotated = new boolean[data[0].length][data.length];
for(int iX = 0; iX < data.length; iX++) {
for(int iY = 0; iY < data[0].length; iY++) {
rotated[iY][iX] = data[data.length - iX - 1][iY];
}
}
return rotated;
}
/**
* Checks to see if two 2d boolean arrays are the same
* #param a
* #param b
* #return
*/
public boolean equal(boolean[][] a, boolean[][] b) {
if(a.length != b.length || a[0].length != b[0].length) {
return false;
} else {
for(int iX = 0; iX < a.length; iX++) {
for(int iY = 0; iY < a[0].length; iY++) {
if(a[iX][iY] != b[iX][iY]) {
return false;
}
}
}
}
return true;
}
public boolean isRotation(boolean[][] data) {
//check to see if it's a rotation of a shape that we already have
data = rotate90(data); //+90*
String str = arrayToString(data);
if(!uShapes.containsKey(str)) {
data = rotate90(data); //180*
str = arrayToString(data);
if(!uShapes.containsKey(str)) {
data = rotate90(data); //270*
str = arrayToString(data);
if(!uShapes.containsKey(str)) {
return false;
}
}
}
return true;
}
public boolean isTempRotation(boolean[][] data) {
//check to see if it's a rotation of a shape that we already have
data = rotate90(data); //+90*
String str = arrayToString(data);
if(!tempShapes.containsKey(str)) {
data = rotate90(data); //180*
str = arrayToString(data);
if(!tempShapes.containsKey(str)) {
data = rotate90(data); //270*
str = arrayToString(data);
if(!tempShapes.containsKey(str)) {
return false;
}
}
}
return true;
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
TetrisBlocks tetris = new TetrisBlocks();
long start = System.currentTimeMillis();
Map shapes = tetris.getUniqueShapes(8);
long end = System.currentTimeMillis();
Iterator it = shapes.keySet().iterator();
while(it.hasNext()) {
String shape = (String)it.next();
System.out.println(shape);
}
System.out.println("Unique Shapes: " + shapes.size());
System.out.println("Time: " + (end-start));
}
}
Here's some python that computes the answer. Seems to agree with Wikipedia. It isn't terribly fast because it uses lots of array searches instead of hash tables, but it still takes only a minute or so to complete.
#!/usr/bin/python
# compute the canonical representation of polyomino p.
# (minimum x and y coordinate is zero, sorted)
def canonical(p):
mx = min(map(lambda v: v[0], p))
my = min(map(lambda v: v[1], p))
return sorted(map(lambda v: (v[0]-mx, v[1]-my), p))
# rotate p 90 degrees
def rotate(p):
return canonical(map(lambda v: (v[1], -v[0]), p))
# add one tile to p
def expand(p):
result = []
for (x,y) in p:
for (dx,dy) in ((-1,0),(1,0),(0,-1),(0,1)):
if p.count((x+dx,y+dy)) == 0:
result.append(canonical(p + [(x+dx,y+dy)]))
return result
polyominos = [[(0,0)]]
for i in xrange(1,10):
new_polyominos = []
for p in polyominos:
for q in expand(p):
dup = 0
for r in xrange(4):
if new_polyominos.count(q) != 0:
dup = 1
break
q = rotate(q)
if not dup: new_polyominos.append(q)
polyominos = new_polyominos
print i+1, len(polyominos)
Here is my full Python solution inspired by #marcog's answer. It prints the number of polyominos of sizes 2..10 in about 2s on my laptop.
The algorithm is straightforward:
Size 1: start with one square
Size n + 1: take all pieces of size n and try adding a single square to all possible adjacent positions. This way you find all possible new pieces of size n + 1. Skip duplicates.
The main speedup came from hashing pieces to quickly check if we've already seen a piece.
import itertools
from collections import defaultdict
n = 10
print("Number of Tetris pieces up to size", n)
# Times:
# n is number of blocks
# - Python O(exp(n)^2): 10 blocks 2.5m
# - Python O(exp(n)): 10 blocks 2.5s, 11 blocks 10.9s, 12 block 33s, 13 blocks 141s (800MB memory)
smallest_piece = [(0, 0)] # We represent a piece as a list of block positions
pieces_of_size = {
1: [smallest_piece],
}
# Returns a list of all possible pieces made by adding one block to given piece
def possible_expansions(piece):
# No flatMap in Python 2/3:
# https://stackoverflow.com/questions/21418764/flatmap-or-bind-in-python-3
positions = set(itertools.chain.from_iterable(
[(x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)] for (x, y) in piece
))
# Time complexity O(n^2) can be improved
# For each valid position, append to piece
expansions = []
for p in positions:
if not p in piece:
expansions.append(piece + [p])
return expansions
def rotate_90_cw(piece):
return [(y, -x) for (x, y) in piece]
def canonical(piece):
min_x = min(x for (x, y) in piece)
min_y = min(y for (x, y) in piece)
res = sorted((x - min_x, y - min_y) for (x, y) in piece)
return res
def hash_piece(piece):
return hash(tuple(piece))
def expand_pieces(pieces):
expanded = []
#[
# 332322396: [[(1,0), (0,-1)], [...]],
# 323200700000: [[(1,0), (0,-2)]]
#]
# Multimap because two different pieces can happen to have the same hash
expanded_hashes = defaultdict(list)
for piece in pieces:
for e in possible_expansions(piece):
exp = canonical(e)
is_new = True
if exp in expanded_hashes[hash_piece(exp)]:
is_new = False
for rotation in range(3):
exp = canonical(rotate_90_cw(exp))
if exp in expanded_hashes[hash_piece(exp)]:
is_new = False
if is_new:
expanded.append(exp)
expanded_hashes[hash_piece(exp)].append(exp)
return expanded
for i in range(2, n + 1):
pieces_of_size[i] = expand_pieces(pieces_of_size[i - 1])
print("Pieces with {} blocks: {}".format(i, len(pieces_of_size[i])))