Tuesday 10 September 2019

Penalty shootout rules

The team kicking first in penalty shootout enjoys a huge advantage, winning roughly 60% of the time. A recent paper (A comparison of penalty shootout designs in soccer) discusses and compares the three rules devised to overcome the unfairness, which are:
  1. Alternating (ABBA) Rule: the order of the first two penalties (AB) is the mirror image of the next two (BA), and this sequence is continued even in the possible sudden death stage of penalty shootouts (the sixth round of penalties is started by team B, the seventh by team A, and so on).
  2. Catch-Up Rule: the order of the penalties in a given round (including the sudden death) is the mirror image of the previous round except if the first team failed and the second scored in the previous round when the order of the teams remains unchanged.
  3. Adjusted Catch-Up Rule: the first five rounds of penalties, started by team A, are kicked according to the Catch-Up Rule, but team B is the first kicker in the sudden death (sixth round) such that the first mover is alternated in this stage.



Let's skip the mathematical computation and jump directly to the conclusion: the Catch up rule does not outperform the Alternating rule while the Adjusted Catch-up rule is fairer than both the Catch up rule and the Alternating rule.

I wrote a python program (https://github.com/sunmingtao/penalty-shootout) to simulate these rules. The assumption is the first kicker has a success rate of 3/4 while the second kicker has a success rate of 2/3. 

The result of the Alternating rule and Catch up rule is very close to that on the paper. However, my program indicates the gap of the result between the Adjusted Catch-up rule and the other two rules is not as large as the paper claims. In fact, the result seems too close to reveal any statistical significance.



In the Euro 2008 quarter final of Italy against Spain, Buffon won the coin toss and yet chose to kick second. We all know the sad outcome for Italy. I wonder if Buffon was too just proud to go first.

Monday 9 September 2019

Python Sonarqube with unit test coverage

Prerequisite

Sonarqube is installed (Create a new project named 'shootout')
Sonar scanner is installed
Anaconda is installed

Install nose and coverage

pip install nose
pip install coverage

Python Project structure

main.py
sample/
    hello.py
testcase/
    test.py

Sonar property file

create sonar-project.properties under the root folder

Contents of the file

sonar.projectKey=shootout #corresponds to the project name in Sonarqube
sonar.projectName=Penalty shootout
sonar.projectVersion=1.0
sonar.language=py
sonar.python.xunit.reportPath=nosetests.xml
sonar.python.coverage.reportPaths=coverage.xml
sonar.python.xunit.skipDetails=true
sonar.coverage.exclusions=testcase/**/* 

Activate the Anaconda virtual environment

Note: It appears that nose can only run in the virtual environment, hence the need of the activation.

conda init

Generate nosetests.xml and coverage.xml

coverage erase 
nosetests --with-coverage --with-xunit
coverage xml

Analyze the project

sonar-scanner

View the result

http://localhost:9000

Troubleshooting

https://github.com/sunmingtao/sample-code/issues/11
https://github.com/sunmingtao/sample-code/issues/12
https://github.com/sunmingtao/sample-code/issues/13
https://github.com/sunmingtao/sample-code/issues/14
https://github.com/sunmingtao/sample-code/issues/15
https://github.com/sunmingtao/sample-code/issues/16

Sunday 1 September 2019

JMockit incompatible with JDK 10/11

Running Unit tests under JDK 10/11 produces error below. It was OK under JDK 8.

[ERROR] Errors: 
[ERROR]   BusinessRepositoryTest>AbstractRepositoryTest.setUp:23 » NoClassDefFound Could...
[ERROR]   BusinessRepositoryTest>AbstractRepositoryTest.setUp:23 » ExceptionInInitializer
[ERROR]   BusinessUserRepositoryTest>AbstractRepositoryTest.setUp:23 » NoClassDefFound C...
[ERROR]   BusinessUserRepositoryTest>AbstractRepositoryTest.setUp:23 » NoClassDefFound C...
[ERROR]   TransactionRepositoryTest>AbstractRepositoryTest.setUp:23 » NoClassDefFound Co...
[ERROR]   TransactionRepositoryTest>AbstractRepositoryTest.setUp:23 » NoClassDefFound Co...
[ERROR]   TransactionRepositoryTest>AbstractRepositoryTest.setUp:23 » NoClassDefFound Co...
[ERROR]   BusinessCustomerLinkServiceTest>AbstractRepositoryTest.setUp:23 » NoClassDefFound
[ERROR]   BusinessServiceTest.save:30 » NullPointer
[INFO] 
[ERROR] Tests run: 9, Failures: 0, Errors: 9, Skipped: 0

The contents of AbstractRepositoryTest is nothing but a Mockup of a public static method

@BeforeEach
void setUp() throws Exception {
    new MockUp<CafamilyUtils>() {
        @mockit.Mock
        public String getLoggedInUserID() {
            return "system";
        }
    };
}

The version of jmockit is too old (1.24) in pom.xml. It needs to be upgraded to a much newer version (1.47). Besides JMockit also requires the -javaagent JVM initialization parameter to be used, according to
https://jmockit.github.io/tutorial/Introduction.html#runningTests

<dependencies>
   <dependency>
      <groupId>org.jmockit</groupId>
      <artifactId>jmockit</artifactId>
      <version>${jmockit.version}</version>
      <scope>test</scope>
   </dependency>
</dependencies>


<plugins>
   <plugin>
      <artifactId>maven-surefire-plugin</artifactId>
      <version>2.22.2</version> <!-- or some other version -->
      <configuration>
         <argLine>
            -javaagent:${settings.localRepository}/org/jmockit/jmockit/${jmockit.version}/jmockit-${jmockit.version}.jar
         </argLine>
      </configuration>
   </plugin>
</plugins>

Sunday 25 August 2019

Static method MockUp effect is global

Use mockit.MockUp.MockUp() to mock public static methods. One caveat is that the mocked behaviour applies to other test methods as well. (test2() prints "abc", which is surprising, isn't it?)

@ExtendWith(value = {MockitoExtension.class})
@RunWith(JUnitPlatform.class)
class MyTest {
    
    @Test
    void test1() throws IOException {
        new MockUp<System>() {
            @mockit.Mock
            public String getProperty(String key){
                return "abc";
            }
            
        };
        
        assertThat(System.getProperty("a"), is("abc"));
    }
    
    @Test
    void test2() throws IOException {
        System.out.println(System.getProperty("a")); //Prints abc
    }
    

}

Junit 5 temp directory quick start

The tempDirectory extension makes it quite handy to test file reading/writing functions.

Maven dependency

<dependency>
   <groupId>org.junit-pioneer</groupId>
   <artifactId>junit-pioneer</artifactId>
   <version>0.1.2</version>
   <scope>test</scope>
</dependency>

Java

@Test
@ExtendWith(TempDirectory.class)
void withProperties(@TempDir Path tempDir) throws IOException {
    Path path = tempDir.resolve("env.properties");
    String properties = "a=b";
    Files.write(path, properties.getBytes());
    Map<String, Object> props = EnvPropertiesLoader.loadEnvProperties();
    assertThat(props.get("a"), is("b"));
}

Thursday 22 August 2019

Double-checked locking should not be used

Extracted from sonarqube

Double-checked locking is the practice of checking a lazy-initialized object's state both before and after a synchronized block is entered to determine whether or not to initialize the object.
It does not work reliably in a platform-independent manner without additional synchronization for mutable instances of anything other than float or int. Using double-checked locking for the lazy initialization of any other type of primitive or mutable object risks a second thread using an uninitialized or partially initialized member while the first thread is still creating it, and crashing the program.

There are multiple ways to fix this. The simplest one is to simply not use double checked locking at all, and synchronize the whole method instead. With early versions of the JVM, synchronizing the whole method was generally advised against for performance reasons. But synchronized performance has improved a lot in newer JVMs, so this is now a preferred solution. If you prefer to avoid using synchronized altogether, you can use an inner static class to hold the reference instead. Inner static classes are guaranteed to load lazily.

Noncompliant Code Example

@NotThreadSafe
public class DoubleCheckedLocking {
    private static Resource resource;

    public static Resource getInstance() {
        if (resource == null) {
            synchronized (DoubleCheckedLocking.class) {
                if (resource == null)
                    resource = new Resource();
            }
        }
        return resource;
    }

    static class Resource {

    }
}

Compliant Solution

@ThreadSafe
public class SafeLazyInitialization {
    private static Resource resource;

    public synchronized static Resource getInstance() {
        if (resource == null)
            resource = new Resource();
        return resource;
    }

    static class Resource {
    }
}
With inner static holder:
@ThreadSafe
public class ResourceFactory {
    private static class ResourceHolder {
        public static Resource resource = new Resource(); // This will be lazily initialised
    }

    public static Resource getResource() {
        return ResourceFactory.ResourceHolder.resource;
    }

    static class Resource {
    }
}
Using "volatile":

class ResourceFactory {
  private volatile Resource resource;

  public Resource getResource() {
    Resource localResource = resource;
    if (localResource == null) {
      synchronized (this) {
        localResource = resource;
        if (localResource == null) {
          resource = localResource = new Resource();
        }
      }
    }
    return localResource;
  }

  static class Resource {
  }
}

Wednesday 21 August 2019

Swift non-optional, optional (?), and optional(!)


class Country {
    let name: String
    var capitalCity: City!
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
    deinit {
        print ("country deinit")
    }
}

class City {
    let name: String
    unowned let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
    deinit {
        print ("city deinit")
    }
}

var country: Country = Country(name: "Canada", capitalName: "Ottawa")
print ("country capital city is \(country.capitalCity.name)")

Why the exclamation mark is needed after City?

Without making it the variable optional, the code won't compile, because the country instance won't be considered fully initialised and therefore cannot be passed as an argument to initiate the city instance (country: self).

Can exclamation mark be replaced by question mark?

Yes, it can. But to access this property, it needs to be unwrapped. So country.capitalCity.name is to be replaced by country.capitalCity!.name. If we know for sure a property will never be null, it's better to be defined as City! rather than City?.

Swift Automatic reference counting (Weak variable)

Weak reference


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Person{
    var name: String
    var fruit: Fruit?
    
    init(name: String) {
        self.name = name
        print ("Person init")
    }
    
    deinit {
        print ("Person denit")
    }
}

class Fruit{
    var name: String
    init(name: String) {
        self.name = name
        print ("Fruit init")
    }
    
    deinit {
        print ("Fruit denit")
    }
}

var person: Person? = Person(name: "john")
person!.fruit = Fruit(name: "apple")
person = nil //Prints "Person denit", "Fruit denit"

After line 28, the fruit object has one strong reference on it (the property person.fruit), and the person object also has one strong reference on it (the variable person).

At line 29, two things happen.

1. The person object has no reference on it and thus gets deallocated. It prints "Person deinit"
2. Since the person object is gone, the fruit object has no reference on it, and thus gets deallocated. It prints "Fruit deinit"


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Person{
    var name: String
    var fruit: Fruit?
    
    init(name: String) {
        self.name = name
        print ("Person init")
    }
    
    deinit {
        print ("Person denit")
    }
}

class Fruit{
    var name: String
    var person: Person?
    
    init(name: String) {
        self.name = name
        print ("Fruit init")
    }
    
    deinit {
        print ("Fruit denit")
    }
}

var person: Person? = Person(name: "john")
var fruit: Fruit? = Fruit(name: "apple")
person!.fruit = fruit
fruit!.person = person
person = nil
fruit = nil //Nothing is deinitilised. 

After line 32, the fruit object has two strong references on it (the variable fruit, and the property person.fruit). Similarly the person object has two strong references on it (the variable person, and the property fruit.person)

After line 34, both the fruit objects and the person objects have one strong reference on it (the property person.fruit and fruit.person respectively). Hence nothing is deinitilised.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Person{
    var name: String
    weak var fruit: Fruit?
    
    init(name: String) {
        self.name = name
        print ("Person init")
    }
    
    deinit {
        print ("Person deinit")
    }
}

class Fruit{
    var name: String
    var person: Person?
    
    init(name: String) {
        self.name = name
        print ("Fruit init")
    }
    
    deinit {
        print ("Fruit deinit")
    }
}

var person: Person? = Person(name: "john")
var fruit: Fruit? = Fruit(name: "apple")
person!.fruit = fruit
fruit!.person = person
fruit = nil //Prints "Fruit deinit"

After line 32, the fruit object has one strong reference (the variable fruit) and one weak reference (the property person.fruit)

At line 33, the strong reference is broken, and there is only one weak reference left, which can't stop the object being deinitialised.



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Person{
    var name: String
    weak var fruit: Fruit?
    
    init(name: String) {
        self.name = name
        print ("Person init")
    }
    
    deinit {
        print ("Person deinit")
    }
}

class Fruit{
    var name: String
    var person: Person?
    
    init(name: String) {
        self.name = name
        print ("Fruit init")
    }
    
    deinit {
        print ("Fruit deinit")
    }
}

var person: Person? = Person(name: "john")
var fruit: Fruit? = Fruit(name: "apple")
person!.fruit = fruit
fruit!.person = person
person = nil
fruit = nil //Prints "Fruit deinit, Person deinit"

After line 32, the fruit object has one strong reference (the variable fruit) and one weak reference (the property person.fruit). The person object two strong references (the variable person and the property fruit.person)

At line 33, one strong reference on the person object is broken, but since there is another strong reference, object person can't be deallocated.

At line 34, the strong reference on the fruit object is broken, the fruit object is deallocated, thus breaking the other strong reference on the person object. Hence the person is deallocated.

Unowned reference


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Person{
    var name: String
    var fruit: Fruit?
    
    init(_ name: String) {
        self.name = name
        print ("Person init")
    }
    
    deinit {
        print ("Person deinit")
    }
}

class Fruit{
    var name: String
    unowned var person: Person
    
    init(_ name: String, person: Person) {
        self.name = name
        self.person = person
        print ("Fruit init")
    }
    
    deinit {
        print ("Fruit deinit")
    }
}

var person: Person? = Person("john")
person!.fruit = Fruit("apple", person: person!)
person = nil //Prints "Person deinit, Fruit deinit"

After line 31, the person object has one strong reference (var person), and one unowned reference (property fruit.person).

At line 32, the strong reference on the person object is broken, and the person object is deallocated.

Note unowned cannot be used on optional type. (i.e. replacing unowned by weak in the above code causes compilation error)

Thursday 8 August 2019

HttpClient's post request doesn't reach the outside world

The issue is HttpClient's post request cannot reach the outside world due to the Internet access restriction. Even after having configured the proxy on jvm, it still doesn't work.

The error message reads

14:31:06.604 java[31504]: 2019-08-06 14:31:06.604  INFO 31504 --- [tp2088371948-52] o.apache.http.impl.execchain.RetryExec   : Retrying request to {s}->https://www.google.com:443
14:33:13.837 java[31504]: 2019-08-06 14:33:13.837 ERROR 31504 --- [tp2088371948-52] a.g.n.t.service.utils.HttpClientUtil     : Post request failed: https://www.google.com/recaptcha/api/siteverify
14:33:13.837 java[31504]: 
14:33:13.837 java[31504]: java.net.SocketException: Network is unreachable (connect failed)
14:33:13.837 java[31504]:       at java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:1.8.0_222]
14:33:13.837 java[31504]:       at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) ~[na:1.8.0_222]
14:33:13.837 java[31504]:       at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) ~[na:1.8.0_222]
14:33:13.837 java[31504]:       at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) ~[na:1.8.0_222]
14:33:13.837 java[31504]:       at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) ~[na:1.8.0_222]
14:33:13.837 java[31504]:       at java.net.Socket.connect(Socket.java:589) ~[na:1.8.0_222]
14:33:13.837 java[31504]:       at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:339) ~[httpclient-4.5.6.jar!/:4.5.6]

The solution is using HttpClients.createSystem() instead of HttpCients.createDefault() to create the HttpClient instance.

 createSystem

public static CloseableHttpClient createSystem()

    Creates CloseableHttpClient instance with default configuration based on system properties. 

Thursday 1 August 2019

Font awesome CSS

To display this little icon





we can use the font awesome css.

It looks extremely simple to use. Here is an example from w3school.

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">

<i class="fa fa-home"></i>

The problem arises when I copy the css file locally. What I get looks...





It is because the css file alone is not enough to render the image. I also need to save the image files locally.

Where to find the image files? If we hit

https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css


@font-face {
  font-family: 'FontAwesome';
  src: url('../fonts/fontawesome-webfont.eot?v=4.7.0');
  src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');
  font-weight: normal;
  font-style: normal;
}

We need to download
  • fontawesome-webfont.eot
  • fontawesome-webfont.woff2
  • fontawesome-webfont.woff
  • fontawesome-webfont.ttf
  • fontawesome-webfont.svg  
Replace the css/font-awesome.css with fonts/<image-file-names>, e.g

https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/fonts/fontawesome-webfont.eot

Then put these image files in the fonts folder at the same level with css folder


How to delete a cookie

Naively, the code looks like this

for (Cookie cookie : request.getCookies()) {
    if (StringUtils.equalsIgnoreCase(cookie.getName(), "cookie-name")) {
        cookie.setValue("");
        cookie.setMaxAge(0);
        response.addCookie(cookie);
    }
}

Unfortunately, it doesn't work because a cookie is identified by its name, domain, and path. But we don't change the domain or path, do we? Actually the browser will send only name=value in the HTTP Cookie header.

Other attributes (secure, domain, path, expiration) are only available for cookies that we set into the response yourself. They are used to create the Set-Cookie response headers.

Therefore cookie.getDomain() and cookie.getPath() always return null.

The solution is explicitly set domain and path


for (Cookie cookie : request.getCookies()) {
    if (StringUtils.equalsIgnoreCase(cookie.getName(), "cookie-name")) {
     cookie.setDomain("localhost");
     cookie.setPath("/");
        cookie.setValue("");
        cookie.setMaxAge(0);
        response.addCookie(cookie);
    }
}

Wednesday 10 July 2019

Switch git user on command line

Check who is the current user

$ git config --list  

Change user


$ git config --global user.name "Bob"
$ git config --global user.email "bob@example.com"

Delete old password

Open keychain



 Search 'git', delete 'github.com'



Reference: https://superuser.com/questions/1064197/how-to-switch-git-user-at-terminal

Monday 8 July 2019

Mapstruct quickstart

Maven pom.xml
    <properties>
        <org.mapstruct.version>1.3.0.Final</org.mapstruct.version>
    </properties>

    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>

 <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.5.1</version> <!-- or newer version -->
        <configuration>
            <source>1.8</source>
            <target>1.8</target>
            <annotationProcessorPaths>
                <path>
                    <groupId>org.mapstruct</groupId>
                    <artifactId>mapstruct-processor</artifactId>
                    <version>${org.mapstruct.version}</version>
                </path>
                <!-- other annotation processors -->
            </annotationProcessorPaths>
        </configuration>
    </plugin>

Java
public class Car {
 
    private String make;
    private int numberOfSeats;
    private CarType type;
    
    public Car(String make, int numberOfSeats, CarType type) {
        super();
        this.make = make;
        this.numberOfSeats = numberOfSeats;
        this.type = type;
    }
    public String getMake() {
        return make;
    }
    public void setMake(String make) {
        this.make = make;
    }
    public int getNumberOfSeats() {
        return numberOfSeats;
    }
    public void setNumberOfSeats(int numberOfSeats) {
        this.numberOfSeats = numberOfSeats;
    }
    public CarType getType() {
        return type;
    }
    public void setType(CarType type) {
        this.type = type;
    }
}

public class CarDto {
 
    private String make;
    private int seatCount;
    private String type;
    public String getMake() {
        return make;
    }
    public void setMake(String make) {
        this.make = make;
    }
    public int getSeatCount() {
        return seatCount;
    }
    public void setSeatCount(int seatCount) {
        this.seatCount = seatCount;
    }
    public String getType() {
        return type;
    }
    public void setType(String type) {
        this.type = type;
    }
}

@Mapper
public interface CarMapper {
    CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
    
    @Mapping(source = "numberOfSeats", target = "seatCount")
    CarDto carToCarDto(Car car);
}

public enum CarType {
    SEDAN, HATCHBACK;
}

Test case
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class CarMapperTest {

    @BeforeEach
    void setUp() throws Exception {
    }

    @Test
    void test() {
        //given
        Car car = new Car( "Morris", 5, CarType.SEDAN );
     
        //when
        CarDto carDto = CarMapper.INSTANCE.carToCarDto( car );
     
        assertNotNull(carDto);
        assertThat(carDto.getMake(), is("Morris") );
        assertThat(carDto.getSeatCount(), is(5));
        assertThat(carDto.getType(), is("SEDAN"));
    }

}

Sunday 23 June 2019

Project Euler 154 - Exploring Pascal's pyramid

A triangular pyramid is constructed using spherical balls so that each ball rests on exactly three balls of the next lower level.
Then, we calculate the number of paths leading from the apex to each position:
A path starts at the apex and progresses downwards to any of the three spheres directly below the current position.
Consequently, the number of paths to reach a certain position is the sum of the numbers immediately above it (depending on the position, there are up to three numbers above it).
The result is Pascal's pyramid and the numbers at each level n are the coefficients of the trinomial expansion (x + y + z)n.
How many coefficients in the expansion of (x + y + z)200000 are multiples of 1012?

Solution

I have spent a week to solve this problem.

First step is observe the pattern



Now we can see the for any given level l, any row m (starting 0), any column n (starting 0), the value is Clm * Cmn

Due to the symmetry, there is no need to go through the entire a triangle. Only going through the red ones is sufficient.


To illustrate the idea, considering peeling an onion, we take off the outermost layer of the triangle, what is left is:


Then again take off the outermost layer, what is left is:


The complexity of the algorithm is O(n^2), which runs 19 minutes on my iMac. I struggle to make it run any faster. 

Thursday 6 June 2019

Project Euler 152 - Writing 1/2 as a sum of inverse squares

There are several ways to write the number 1/2 as a sum of inverse squares using distinct integers.
For instance, the numbers {2,3,4,5,7,12,15,20,28,35} can be used:


In fact, only using integers between 2 and 45 inclusive, there are exactly three ways to do it, the remaining two being: {2,3,4,6,7,9,10,20,28,35,36,45} and {2,3,4,6,7,9,12,15,28,30,35,36,45}.
How many ways are there to write the number 1/2 as a sum of inverse squares using distinct integers between 2 and 80 inclusive?


Solution

Wow, this one is tough. I spent more than 6 hours to solve it.

Upon observing the outcome of a very naive (and hence very slow) algorithm, it can be found that only a portion of numbers in the range of [2, 80] can be candidates that form the solution, namely

CANDIDATES = [2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15, 18, 20, 21, 24, 28, 30, 35, 36, 39, 40, 42, 45, 52, 56, 60, 63, 70, 72]

I don't know how to prove it, so I just hope it's the correct set of candidates.

My algorithm is very intuitive and requires no advanced mathematics knowledge.

First define S([a1, a2, a3, ..., ak]) = sum(1 / a1 ^ 2 + 1 / a2 ^ 2 + 1 / a ^ 3 + .... + 1 / ak ^ 2)

We start with the candidate array [2], search for the next smallest candidate (sc) so that S([2]) + S([sc]) <= 0.5. Obviously, in this case, the next smallest candidate is 3, so we append 3 to the candidate array and the array becomes [2, 3].

The candidate array keeps expanding until such smallest candidate cannot be found, then we update the last element of the candidate array to its 'next greater element' (we will refer to it as 'sibling' in the rest of the article) in the CANDIDATES list.

For example, the candidates array is [2, 3, 4, 5, 6, 12, 28, 52], so no such smallest candidate can be found so that S([2, 3, 4, 5, 6, 12, 28, 52]) + S([sc]) <= 0.5. Therefore the last element 52 is updated to its 'sibling ' 56, the candidate array becomes [2, 3, 4, 5, 6, 12, 28, 56].

if S(candidate_array) + S([sc]) happens to be 0.5, a solution is found.

The algorithm so far will find all the solutions although it's still quite slow. Consider this scenario:

candidate_array = [2, 4], the next smallest candidate that meets the criteria is 5, but even if we 'add up' the rest of all candidates, i.e. S([5, 6, 7, ...., 72]), the sum of S([2, 4]) and S([5, 6, 7, ...., 72]) is less than 0.5. We can see there is no solution for candidate array starting with [2, 4]. Similarly, there won't be a solution when it starts with [2, 5] and so on so forth. So we simply take off the last item off the list, and update the second last item (now it has become the last one) to its 'sibling' 3. When candidate_array = [3], there is also no solution. We take it off the list and end up with an empty array. And this is the terminating condition in the while loop.

It is worth mentioning that there is another condition that needs to be met to trigger the above scenario. That is, the next smallest candidate must equate the last element's 'sibling' in the CANDIDATES list, because the greatest candidate 72, happens to always meet this criteria, and therefore this particular case must be excluded. In the example above, 5 is 4's 'sibling', so the condition is met. On the other hand, the next smallest candidate for [2, 3, 4, 5, 9, 10, 13, 18, 24, 30, 35, 45, 52, 56] is 72, which is not 56's 'sibling'. Therefore the condition is not met and the scenario is not applied. 

The algorithm runs 4 seconds. I consider it not a half bad result.