简体   繁体   中英

Get all squares' sizes which fit inside a rectangle?‽?

I am doing the following programming exercise: Rectangle into Squares . The statement is:

The drawing below gives an idea of how to cut a given "true" rectangle into squares ("true" rectangle meaning that the two dimensions are different).

替代文本

Can you translate this drawing into an algorithm?

You will be given two dimensions

a positive integer length (parameter named lng) a positive integer width (parameter named wdth)

You will return an array or a string (depending on the language; Shell bash, PowerShell and Fortran return a string) with the size of each of the squares.

 sqInRect(5, 3) should return [3, 2, 1, 1] sqInRect(3, 5) should return [3, 2, 1, 1] or (Haskell) squaresInRect 5 3 `shouldBe` Just [3,2,1,1] squaresInRect 3 5 `shouldBe` Just [3,2,1,1] or (Fsharp) squaresInRect 5 3 should return Some [3,2,1,1] squaresInRect 3 5 should return Some [3,2,1,1] or (Swift) squaresInRect 5 3 should return [3,2,1,1] as optional squaresInRect 3 5 should return [3,2,1,1] as optional or (Cpp) sqInRect(5, 3) should return {3, 2, 1, 1} sqInRect(3, 5) should return {3, 2, 1, 1} (C) C returns a structure, see the "Solution" and "Examples" tabs. Your result and the reference test solution are compared by strings.

Notes:

 lng == wdth as a starting case would be an entirely different problem and the drawing is planned to be interpreted with lng != wdth.

(See kata, Square into Squares. Protect trees! http://www.codewars.com/kata/54eb33e5bc1a25440d000891 for this problem).

 When the initial parameters are so that lng == wdth, the solution [lng] would be the most obvious but not in the spirit of this

kata so, in that case, return None/nil/null/Nothing

 return {} with C++, Array() with Scala. In that case the returned structure of C will have its sz component equal to 0. Return the string "nil" with Bash, PowerShell and Fortran. You can see more examples in "RUN SAMPLE TESTS".

I thought we could solve this exercise by calculating the closest power of two, to lngth * wdth (total dimension of the given square). So then we would just add each closest power of two to the sizes list.

I have written the following code:

import java.util.*;
public class SqInRect {
    public static List<Integer> sqInRect/*🔲✅*/(int lng, int wdth) {
    System.out.println("\nlng: "+lng);
    System.out.println("wdth: "+wdth);
    if(lng == wdth) return null;
    List<Integer> sizes = new ArrayList<Integer>();
    int totalSquares = lng * wdth;
    double pow = 0;
    for(int i = 1; totalSquares > 0; i++){
      pow = Math.pow(2,i);
      if(pow >= totalSquares){
        System.out.println("pow: "+pow);
        System.out.println("i: "+i);
        System.out.println("totalSquares: "+totalSquares);
        double prevPow = Math.pow(2,--i);
        System.out.println("prevPow: "+prevPow);
        totalSquares -= prevPow;
        sizes.add(i == 0 ? 1 : i);
        System.out.println("\nnew sizes: "+Arrays.toString(sizes.toArray()));
        i = 0;
      }        
    } 
    System.out.println("\nsizes: "+Arrays.toString(sizes.toArray()));
    return sizes;
    }
}

However we observe that some tests do not pass. For example, given the following tests:

import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import java.util.Random;

public class SqInRectTest {

    @Test
    public void test1() {
        List<Integer> res = new ArrayList<Integer>(Arrays.asList(3, 2, 1, 1));
        for (int r : res)
            assertEquals(res, SqInRect.sqInRect(5, 3));
    }
    @Test
    public void test2() {
        assertEquals(null, SqInRect.sqInRect(5, 5));
    }
  @Test
    public void test3() {
    List<Integer> res = new ArrayList<Integer>(Arrays.asList(120, 120, 120, 120, 120, 76,
                                                            44,32,12,12,8,4,4));
        assertEquals(res, SqInRect.sqInRect(676, 120));
    }
}

test3 would fail. Besides we can see a trace to know how the code behaves:

lng: 676
wdth: 120
pow: 131072.0
i: 17
totalSquares: 81120
prevPow: 65536.0

new sizes: [16]
pow: 16384.0
i: 14
totalSquares: 15584
prevPow: 8192.0

new sizes: [16, 13]
pow: 8192.0
i: 13
totalSquares: 7392
prevPow: 4096.0

new sizes: [16, 13, 12]
pow: 4096.0
i: 12
totalSquares: 3296
prevPow: 2048.0

new sizes: [16, 13, 12, 11]
pow: 2048.0
i: 11
totalSquares: 1248
prevPow: 1024.0

new sizes: [16, 13, 12, 11, 10]
pow: 256.0
i: 8
totalSquares: 224
prevPow: 128.0

new sizes: [16, 13, 12, 11, 10, 7]
pow: 128.0
i: 7
totalSquares: 96
prevPow: 64.0

new sizes: [16, 13, 12, 11, 10, 7, 6]
pow: 32.0
i: 5
totalSquares: 32
prevPow: 16.0

new sizes: [16, 13, 12, 11, 10, 7, 6, 4]
pow: 16.0
i: 4
totalSquares: 16
prevPow: 8.0

new sizes: [16, 13, 12, 11, 10, 7, 6, 4, 3]
pow: 8.0
i: 3
totalSquares: 8
prevPow: 4.0

new sizes: [16, 13, 12, 11, 10, 7, 6, 4, 3, 2]
pow: 4.0
i: 2
totalSquares: 4
prevPow: 2.0

new sizes: [16, 13, 12, 11, 10, 7, 6, 4, 3, 2, 1]
pow: 2.0
i: 1
totalSquares: 2
prevPow: 1.0

new sizes: [16, 13, 12, 11, 10, 7, 6, 4, 3, 2, 1, 1]
pow: 2.0
i: 1
totalSquares: 1
prevPow: 1.0

new sizes: [16, 13, 12, 11, 10, 7, 6, 4, 3, 2, 1, 1, 1]

sizes: [16, 13, 12, 11, 10, 7, 6, 4, 3, 2, 1, 1, 1]
expected:<[120, 120, 120, 120, 120, 76, 44, 32, 12, 12, 8, 4, 4]> but was:<[16, 13, 12, 11, 10, 7, 6, 4, 3, 2, 1, 1, 1]>

Could you help me realizing why this code does not fit for this exercise? What algorithm would you use to solve this question? How could we either write a pseudo-code answer and a Java version of it?

I tried the problem using the following approach. The basic idea is to cut squares from the rectangle as long the length and width are the same.

public static List<Integer> sqInRec (int length, int width) {
    List<Integer> list = new ArrayList<>();
    while(length!=width){
          if(length>width){
             length = length-width;
             list.add(width);
          }else{
              width = width- length;
              list.add(length);
          }
    }
    if(list.size()>0){
          list.add(length);
          return list;
    }

    return null;
}

Your algorithm cannot start from the smallest square and head up to the largest one. You want to fill the rest of the available space with the least number of squares. However, you don't know which square size you can start with in order to perfectly fill the rectangle with the least number of squares. You also don't know which square size is the best fit after that square size, and so on. That's why you would want to take the approach from biggest to smallest.

You want to fit the least number of squares in a rectangle by filling in the largest square you can fill, and then trying to fill in the largest square in the remaining space, and so on...


Personally, I'd go for a recursive approach. Get the rectangle, the square would be the small side, then make the rectangle smaller.

fun sqInRect(length: Int, width: Int, squares: ArrayList<Int> = arrayListOf()): ArrayList<Int>? {
    if (length == width && squares.size == 0) return null

    if (length <= 0 || width <= 0) return squares

    return if (width > length) {
        squares.add(length)

        sqInRect(length, width - length, squares)
    } else {
        squares.add(width)

        sqInRect(length - width, width, squares)
    }
}

The output of your third test was [120, 120, 120, 120, 120, 76, 44, 32, 12, 12, 8, 4, 4]


You can test the code on the KotlinPlayground. Just click here .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM