Skip to content

Commit 8b56a0b

Browse files
authored
Merge pull request #213 from cicirello/cycle-distance
Cycle distance
2 parents feba6ee + 7b8dc23 commit 8b56a0b

File tree

7 files changed

+150
-1
lines changed

7 files changed

+150
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ type-search-index.zip
77
package-search-index.zip
88
module-search-index.zip
99
tag-search-index.zip
10+
dependency-reduced-pom.xml

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7-
## [Unreleased] - 2022-04-16
7+
## [Unreleased] - 2022-05-31
88

99
### Added
10+
* CycleDistance: implementation of cycle distance described in https://doi.org/10.3390/app12115506
1011

1112
### Changed
1213

src/main/java/org/cicirello/permutations/Permutation.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ public final class Permutation implements Serializable, Iterable<Permutation>, C
4444

4545
private static final long serialVersionUID = 1L;
4646

47+
/**
48+
* Raw permutation, which should consist of a permutation of the integers in [0, permutation.length).
49+
*/
4750
private final int[] permutation;
4851

4952
/**
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* JavaPermutationTools: A Java library for computation on permutations and sequences
3+
* Copyright 2005-2022 Vincent A. Cicirello, <https://www.cicirello.org/>.
4+
*
5+
* This file is part of JavaPermutationTools (https://jpt.cicirello.org/).
6+
*
7+
* JavaPermutationTools is free software: you can
8+
* redistribute it and/or modify it under the terms of the GNU
9+
* General Public License as published by the Free Software
10+
* Foundation, either version 3 of the License, or (at your
11+
* option) any later version.
12+
*
13+
* JavaPermutationTools is distributed in the hope
14+
* that it will be useful, but WITHOUT ANY WARRANTY; without even
15+
* the implied warranty of MERCHANTABILITY or FITNESS FOR A
16+
* PARTICULAR PURPOSE. See the GNU General Public License for more
17+
* details.
18+
*
19+
* You should have received a copy of the GNU General Public License
20+
* along with JavaPermutationTools. If not, see <http://www.gnu.org/licenses/>.
21+
*/
22+
package org.cicirello.permutations.distance;
23+
24+
import org.cicirello.permutations.Permutation;
25+
26+
/**
27+
* <p>Cycle distance is the count of the number of non-singleton permutation cycles
28+
* between a pair of permutations. Cycle distance is a semi-metric, satisfying all
29+
* of the metric properties except for the triangle inequality.</p>
30+
*
31+
* <p>It was introduced in the following article:</p>
32+
*
33+
* <p>Vincent A. Cicirello. 2022. <a href="https://www.cicirello.org/publications/applsci-12-05506.pdf">Cycle
34+
* Mutation: Evolving Permutations via Cycle Induction</a>, <i>Applied Sciences</i>, 12(11), Article 5506 (June 2022).
35+
* doi:<a href="https://doi.org/10.3390/app12115506">10.3390/app12115506</a></p>
36+
*
37+
* <p>Runtime: O(n), where n is the permutation length.</p>
38+
*
39+
* @author <a href=https://www.cicirello.org/ target=_top>Vincent A. Cicirello</a>,
40+
* <a href=https://www.cicirello.org/ target=_top>https://www.cicirello.org/</a>
41+
*/
42+
public final class CycleDistance implements NormalizedPermutationDistanceMeasurer {
43+
44+
/**
45+
* Constructs the distance measurer as specified in the class documentation.
46+
*/
47+
public CycleDistance() {
48+
49+
}
50+
51+
/**
52+
* {@inheritDoc}
53+
*
54+
* @throws IllegalArgumentException if p1.length() is not equal to p2.length().
55+
*/
56+
@Override
57+
public int distance(Permutation p1, Permutation p2) {
58+
if (p1.length() != p2.length()) {
59+
throw new IllegalArgumentException("Permutations must be the same length");
60+
}
61+
boolean[] used = new boolean[p1.length()];
62+
for (int k = 0; k < used.length; k++) {
63+
if (p1.get(k) == p2.get(k)) {
64+
used[p1.get(k)] = true;
65+
}
66+
}
67+
int i = 0;
68+
for (i = 0; i < used.length; i++) {
69+
if (!used[p1.get(i)]) {
70+
break;
71+
}
72+
}
73+
74+
int[] invP1 = p1.getInverse();
75+
int cycleCount = 0;
76+
int iLast = i;
77+
78+
while (i < used.length) {
79+
int j = p1.get(i);
80+
while (!used[j]) {
81+
used[j] = true;
82+
j = p2.get(i);
83+
i = invP1[j];
84+
}
85+
cycleCount++;
86+
for (i = iLast + 1; i < used.length; i++) {
87+
if (!used[p1.get(i)]) {
88+
break;
89+
}
90+
}
91+
iLast = i;
92+
}
93+
return cycleCount;
94+
}
95+
96+
@Override
97+
public int max(int length) {
98+
return length >> 1;
99+
}
100+
}

src/test/java/org/cicirello/permutations/distance/PermutationDistanceMaxTests.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,16 @@ public void testReinsertionDistance() {
216216
}
217217
}
218218

219+
@Test
220+
public void testCycleDistance() {
221+
CycleDistance d = new CycleDistance();
222+
for (int n = 0; n <= 7; n++) {
223+
int expected = bruteForceComputeMax(d,n);
224+
assertEquals(expected, d.max(n), "Failed on length: " + n);
225+
assertEquals(1.0*expected, d.maxf(n), EPSILON, "Failed on length: " + n);
226+
}
227+
}
228+
219229
//@Test // uncomment if we implement
220230
public void testEditDistance() {
221231
/* // Uncomment if we implement.

src/test/java/org/cicirello/permutations/distance/PermutationDistanceNormTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,14 @@ public void testReinsertionDistance() {
159159
}
160160
}
161161

162+
@Test
163+
public void testCycleDistance() {
164+
CycleDistance d = new CycleDistance();
165+
for (int n = 0; n <= 7; n++) {
166+
assertEquals(n<=1 ? 0.0 : 1.0, bruteForceComputeMax(d,n), EPSILON, "Failed on length: " + n);
167+
}
168+
}
169+
162170
//@Test // uncomment if we implement
163171
public void testEditDistance() {
164172
/* // Uncomment if we implement.

src/test/java/org/cicirello/permutations/distance/PermutationDistanceTests.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,32 @@ public void testEditDistance() {
799799
assertEquals(1.0, d.distancef(new Permutation(first), new Permutation(second)), EPSILON);
800800
}
801801

802+
@Test
803+
public void testCycleDistance() {
804+
CycleDistance d = new CycleDistance();
805+
identicalPermutations(d);
806+
int[][] cases = {
807+
{1, 0},
808+
{1, 2, 0},
809+
{1, 2, 3, 0},
810+
{1, 0, 3, 2},
811+
{1, 2, 3, 4, 5, 6, 7, 0},
812+
{7, 1, 2, 3, 4, 5, 6, 0},
813+
{7, 1, 5, 3, 4, 2, 6, 0},
814+
{7, 6, 5, 4, 3, 2, 1, 0}
815+
};
816+
int[] expected = {1, 1, 1, 2, 1, 1, 2, 4};
817+
for (int i = 0; i < expected.length; i++) {
818+
Permutation p1 = new Permutation(cases[i].length, 0);
819+
Permutation p2 = new Permutation(cases[i]);
820+
assertEquals(expected[i], d.distance(p1, p2));
821+
assertEquals(expected[i], d.distance(p2, p1));
822+
}
823+
IllegalArgumentException thrown = assertThrows(
824+
IllegalArgumentException.class,
825+
() -> d.distance(new Permutation(1), new Permutation(2))
826+
);
827+
}
802828

803829
private void identicalPermutations(PermutationDistanceMeasurer d) {
804830
for (int n = 0; n <= 10; n++) {

0 commit comments

Comments
 (0)