1 /* 2 * Jalview - A Sequence Alignment Editor and Viewer (2.11.1.4) 3 * Copyright (C) 2021 The Jalview Authors 4 * 5 * This file is part of Jalview. 6 * 7 * Jalview is free software: you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License 9 * as published by the Free Software Foundation, either version 3 10 * of the License, or (at your option) any later version. 11 * 12 * Jalview is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty 14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 15 * PURPOSE. See the GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>. 19 * The Jalview Authors are detailed in the 'AUTHORS' file. 20 */ 21 package jalview.util; 22 23 import static org.testng.AssertJUnit.assertEquals; 24 import static org.testng.AssertJUnit.assertTrue; 25 26 import jalview.gui.JvOptionPane; 27 28 import java.util.Arrays; 29 import java.util.Random; 30 31 import org.testng.annotations.BeforeClass; 32 import org.testng.annotations.Test; 33 34 public class QuickSortTest 35 { 36 37 @BeforeClass(alwaysRun = true) setUpJvOptionPane()38 public void setUpJvOptionPane() 39 { 40 JvOptionPane.setInteractiveMode(false); 41 JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION); 42 } 43 44 private static final String c1 = "Blue"; 45 46 private static final String c2 = "Yellow"; 47 48 private static final String c3 = "Orange"; 49 50 private static final String c4 = "Green"; 51 52 private static final String c5 = "Pink"; 53 54 @Test(groups = { "Functional" }) testSort_byIntValues()55 public void testSort_byIntValues() 56 { 57 int[] values = new int[] { 3, 0, 4, 3, -1 }; 58 Object[] things = new Object[] { c1, c2, c3, c4, c5 }; 59 60 QuickSort.sort(values, things); 61 assertTrue(Arrays.equals(new int[] { -1, 0, 3, 3, 4 }, values)); 62 // note sort is not stable: c1/c4 are equal but get reordered 63 Object[] expect = new Object[] { c5, c2, c4, c1, c3 }; 64 assertTrue(Arrays.equals(expect, things)); 65 } 66 67 /** 68 * Test the alternative sort objects by integer method 69 */ 70 @Test(groups = { "Functional" }) testSortByInt()71 public void testSortByInt() 72 { 73 int[] values = new int[] { 3, 0, 4, 3, -1 }; 74 Object[] things = new Object[] { c1, c2, c3, c4, c5 }; 75 76 /* 77 * sort ascending 78 */ 79 QuickSort.sortByInt(values, things, true); 80 assertTrue(Arrays.equals(new int[] { -1, 0, 3, 3, 4 }, values)); 81 assertTrue(Arrays.equals(new Object[] { c5, c2, c1, c4, c3 }, things)); 82 83 /* 84 * resort descending; c1/c4 should not change order 85 */ 86 QuickSort.sortByInt(values, things, false); 87 assertTrue(Arrays.equals(new int[] { 4, 3, 3, 0, -1 }, values)); 88 assertTrue(Arrays.equals(new Object[] { c3, c1, c4, c2, c5 }, things)); 89 } 90 91 @Test(groups = { "Functional" }) testSort_byFloatValues()92 public void testSort_byFloatValues() 93 { 94 float[] values = new float[] { 3f, 0f, 4f, 3f, -1f }; 95 Object[] things = new Object[] { c1, c2, c3, c4, c5 }; 96 QuickSort.sort(values, things); 97 assertTrue(Arrays.equals(new float[] { -1f, 0f, 3f, 3f, 4f }, values)); 98 // note sort is not stable: c1/c4 are equal but get reordered 99 assertTrue(Arrays.equals(new Object[] { c5, c2, c4, c1, c3 }, things)); 100 } 101 102 @Test(groups = { "Functional" }) testSort_byDoubleValues()103 public void testSort_byDoubleValues() 104 { 105 double[] values = new double[] { 3d, 0d, 4d, 3d, -1d }; 106 Object[] things = new Object[] { c1, c2, c3, c4, c5 }; 107 QuickSort.sort(values, things); 108 assertTrue(Arrays.equals(new double[] { -1d, 0d, 3d, 3d, 4d }, values)); 109 // note sort is not stable: c1/c4 are equal but get reordered 110 assertTrue(Arrays.equals(new Object[] { c5, c2, c4, c1, c3 }, things)); 111 } 112 113 /** 114 * Sort by String is descending order, case-sensitive 115 */ 116 @Test(groups = { "Functional" }) testSort_byStringValues()117 public void testSort_byStringValues() 118 { 119 Object[] things = new Object[] { c1, c2, c3, c4, c5 }; 120 String[] values = new String[] { "JOHN", "henry", "lucy", "henry", 121 "ALISON" }; 122 QuickSort.sort(values, things); 123 assertTrue(Arrays.equals(new String[] { "lucy", "henry", "henry", 124 "JOHN", "ALISON" }, values)); 125 assertTrue(Arrays.equals(new Object[] { c3, c2, c4, c1, c5 }, things)); 126 } 127 128 /** 129 * Test whether sort is stable i.e. equal values retain their mutual ordering. 130 */ 131 @Test(groups = { "Functional" }, enabled = false) testSort_withDuplicates()132 public void testSort_withDuplicates() 133 { 134 int[] values = new int[] { 3, 4, 2, 4, 1 }; 135 Object[] letters = new Object[] { "A", "X", "Y", "B", "Z" }; 136 QuickSort.sort(values, letters); 137 assertTrue(Arrays.equals(new int[] { 1, 2, 3, 4, 4 }, values)); 138 // this fails - do we care? 139 assertTrue(Arrays.equals(new Object[] { "Z", "Y", "A", "X", "B" }, 140 letters)); 141 } 142 143 /** 144 * Test of method that sorts chars by a float array 145 */ 146 @Test(groups = { "Functional" }) testSort_charSortByFloat_mostlyZeroValues()147 public void testSort_charSortByFloat_mostlyZeroValues() 148 { 149 char[] residues = new char[64]; 150 for (int i = 0; i < 64; i++) 151 { 152 residues[i] = (char) i; 153 } 154 float[] counts = new float[64]; 155 counts[43] = 16; 156 counts[59] = 7; 157 counts[62] = -2; 158 QuickSort.sort(counts, residues); 159 assertEquals(62, residues[0]); // negative sorts to front 160 assertEquals(59, residues[62]); // 7 sorts to next-to-end 161 assertEquals(43, residues[63]); // 16 sorts to end 162 } 163 164 /** 165 * Timing test - to be run manually as needed, not part of the automated 166 * suite. <br> 167 * It shows that the optimised sort is 3-4 times faster than the simple 168 * external sort if the data to be sorted is mostly zero, but slightly slower 169 * if the data is fully populated with non-zero values. Worst case for an 170 * array of size 256 is about 100 sorts per millisecond. 171 */ 172 @Test(groups = { "Timing" }, enabled = false) testSort_timingTest()173 public void testSort_timingTest() 174 { 175 char[] residues = new char[256]; 176 for (int i = 0; i < residues.length; i++) 177 { 178 residues[i] = (char) i; 179 } 180 float[] counts = new float[residues.length]; 181 182 int iterations = 1000000; 183 184 /* 185 * time it using optimised sort (of a mostly zero-filled array) 186 */ 187 long start = System.currentTimeMillis(); 188 for (int i = 0; i < iterations; i++) 189 { 190 Arrays.fill(counts, 0f); 191 counts[43] = 16; 192 counts[59] = 7; 193 counts[62] = -2; 194 QuickSort.sort(counts, residues); 195 } 196 long elapsed = System.currentTimeMillis() - start; 197 System.out 198 .println(String 199 .format("Time for %d optimised sorts of mostly zeros array length %d was %dms", 200 iterations, counts.length, elapsed)); 201 202 /* 203 * time it using unoptimised external sort 204 */ 205 start = System.currentTimeMillis(); 206 for (int i = 0; i < iterations; i++) 207 { 208 Arrays.fill(counts, 0f); 209 counts[43] = 16; 210 counts[59] = 7; 211 counts[62] = -2; 212 QuickSort.charSortByFloat(counts, residues, true); 213 } 214 elapsed = System.currentTimeMillis() - start; 215 System.out 216 .println(String 217 .format("Time for %d external sorts of mostly zeros array length %d was %dms", 218 iterations, counts.length, elapsed)); 219 220 /* 221 * optimised external sort, well-filled array 222 */ 223 Random random = new Random(); 224 float[] randoms = new float[counts.length]; 225 for (int i = 0; i < randoms.length; i++) 226 { 227 randoms[i] = random.nextFloat(); 228 } 229 230 start = System.currentTimeMillis(); 231 for (int i = 0; i < iterations; i++) 232 { 233 System.arraycopy(randoms, 0, counts, 0, randoms.length); 234 QuickSort.sort(counts, residues); 235 } 236 elapsed = System.currentTimeMillis() - start; 237 System.out 238 .println(String 239 .format("Time for %d optimised sorts of non-zeros array length %d was %dms", 240 iterations, counts.length, elapsed)); 241 242 /* 243 * time unoptimised external sort, filled array 244 */ 245 start = System.currentTimeMillis(); 246 for (int i = 0; i < iterations; i++) 247 { 248 System.arraycopy(randoms, 0, counts, 0, randoms.length); 249 QuickSort.charSortByFloat(counts, residues, true); 250 } 251 elapsed = System.currentTimeMillis() - start; 252 System.out 253 .println(String 254 .format("Time for %d external sorts of non-zeros array length %d was %dms", 255 iterations, counts.length, elapsed)); 256 } 257 258 /** 259 * Test that exercises sort without any attempt at fancy optimisation 260 */ 261 @Test(groups = { "Functional" }) testCharSortByFloat()262 public void testCharSortByFloat() 263 { 264 char[] residues = new char[64]; 265 for (int i = 0; i < 64; i++) 266 { 267 residues[i] = (char) i; 268 } 269 float[] counts = new float[64]; 270 counts[43] = 16; 271 counts[59] = 7; 272 counts[62] = -2; 273 274 /* 275 * sort ascending 276 */ 277 QuickSort.charSortByFloat(counts, residues, true); 278 assertEquals(62, residues[0]); 279 assertEquals(59, residues[62]); 280 assertEquals(43, residues[63]); 281 282 /* 283 * resort descending 284 */ 285 QuickSort.charSortByFloat(counts, residues, false); 286 assertEquals(62, residues[63]); 287 assertEquals(59, residues[1]); 288 assertEquals(43, residues[0]); 289 } 290 291 /** 292 * Test of method that sorts chars by an int array 293 */ 294 @Test(groups = { "Functional" }) testSort_charSortByInt_mostlyZeroValues()295 public void testSort_charSortByInt_mostlyZeroValues() 296 { 297 char[] residues = new char[64]; 298 for (int i = 0; i < 64; i++) 299 { 300 residues[i] = (char) i; 301 } 302 int[] counts = new int[64]; 303 counts[43] = 16; 304 counts[59] = 7; 305 counts[62] = -2; 306 QuickSort.sort(counts, residues); 307 assertEquals(62, residues[0]); // negative sorts to front 308 assertEquals(59, residues[62]); // 7 sorts to next-to-end 309 assertEquals(43, residues[63]); // 16 sorts to end 310 } 311 312 /** 313 * Test that exercises sorting without any attempt at fancy optimisation. 314 */ 315 @Test(groups = { "Functional" }) testCharSortByInt()316 public void testCharSortByInt() 317 { 318 char[] residues = new char[64]; 319 for (int i = 0; i < 64; i++) 320 { 321 residues[i] = (char) i; 322 } 323 int[] counts = new int[64]; 324 counts[43] = 16; 325 counts[59] = 7; 326 counts[62] = -2; 327 328 /* 329 * sort ascending 330 */ 331 QuickSort.charSortByInt(counts, residues, true); 332 assertEquals(62, residues[0]); 333 assertEquals(59, residues[62]); 334 assertEquals(43, residues[63]); 335 336 /* 337 * resort descending 338 */ 339 QuickSort.charSortByInt(counts, residues, false); 340 assertEquals(62, residues[63]); 341 assertEquals(59, residues[1]); 342 assertEquals(43, residues[0]); 343 } 344 345 /** 346 * Tests the alternative method to sort bby String in descending order, 347 * case-sensitive 348 */ 349 @Test(groups = { "Functional" }) testSortByString()350 public void testSortByString() 351 { 352 Object[] things = new Object[] { c1, c2, c3, c4, c5 }; 353 String[] values = new String[] { "JOHN", "henry", "lucy", "henry", 354 "ALISON" }; 355 356 /* 357 * sort descending 358 */ 359 QuickSort.sortByString(values, things, false); 360 assertTrue(Arrays.equals(new String[] { "lucy", "henry", "henry", 361 "JOHN", "ALISON" }, values)); 362 assertTrue(Arrays.equals(new Object[] { c3, c2, c4, c1, c5 }, things)); 363 364 /* 365 * resort ascending 366 */ 367 QuickSort.sortByString(values, things, true); 368 assertTrue(Arrays.equals(new String[] { "ALISON", "JOHN", "henry", 369 "henry", "lucy" }, values)); 370 // sort is stable: c2/c4 do not swap order 371 assertTrue(Arrays.equals(new Object[] { c5, c1, c2, c4, c3 }, things)); 372 } 373 } 374