Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 39 additions & 13 deletions src/main/java/com/thealgorithms/strings/Alphabetical.java
Original file line number Diff line number Diff line change
@@ -1,32 +1,58 @@
package com.thealgorithms.strings;

import java.util.Locale;

/**
* Utility class for checking if a string's characters are in alphabetical order.
* Utility class for checking whether a string's characters are in non-decreasing
* lexicographical order based on Unicode code points (case-insensitive).
* <p>
* This does NOT implement language-aware alphabetical ordering (collation rules).
* It simply compares lowercase Unicode character values.
* <p>
* Alphabetical order is a system whereby character strings are placed in order
* based on the position of the characters in the conventional ordering of an
* alphabet.
* Non-letter characters are not allowed and will cause the check to fail.
* <p>
* Reference: <a href="https://en.wikipedia.org/wiki/Alphabetical_order">Wikipedia: Alphabetical Order</a>
* Reference:
* <a href="https://en.wikipedia.org/wiki/Alphabetical_order">Wikipedia: Alphabetical order</a>
*/
public final class Alphabetical {

private Alphabetical() {
}

/**
* Checks whether the characters in the given string are in alphabetical order.
* Non-letter characters will cause the check to fail.
* Checks whether the characters in the given string are in non-decreasing
* lexicographical order (case-insensitive).
* <p>
* Rules:
* <ul>
* <li>String must not be null or blank</li>
* <li>All characters must be letters</li>
* <li>Comparison is based on lowercase Unicode values</li>
* <li>Order must be non-decreasing (equal or increasing allowed)</li>
* </ul>
*
* @param s the input string
* @return {@code true} if all characters are in alphabetical order (case-insensitive), otherwise {@code false}
* @param s input string
* @return {@code true} if characters are in non-decreasing order, otherwise {@code false}
*/
public static boolean isAlphabetical(String s) {
s = s.toLowerCase();
for (int i = 0; i < s.length() - 1; ++i) {
if (!Character.isLetter(s.charAt(i)) || s.charAt(i) > s.charAt(i + 1)) {
if (s == null || s.isBlank()) {
return false;
}

String normalized = s.toLowerCase(Locale.ROOT);

if (!Character.isLetter(normalized.charAt(0))) {
return false;
}

for (int i = 1; i < normalized.length(); i++) {
char prev = normalized.charAt(i - 1);
char curr = normalized.charAt(i);

if (!Character.isLetter(curr) || prev > curr) {
return false;
}
}
return !s.isEmpty() && Character.isLetter(s.charAt(s.length() - 1));
return true;
}
}
42 changes: 36 additions & 6 deletions src/test/java/com/thealgorithms/strings/AlphabeticalTest.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,45 @@
package com.thealgorithms.strings;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.params.provider.Arguments.arguments;

import java.util.stream.Stream;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

public class AlphabeticalTest {
@DisplayName("Alphabetical.isAlphabetical()")
class AlphabeticalTest {

@ParameterizedTest(name = "\"{0}\" → Expected: {1}")
@CsvSource({"'abcdefghijklmno', true", "'abcdxxxyzzzz', true", "'123a', false", "'abcABC', false", "'abcdefghikjlmno', false", "'aBC', true", "'abc', true", "'xyzabc', false", "'abcxyz', true", "'', false", "'1', false"})
void testIsAlphabetical(String input, boolean expected) {
assertEquals(expected, Alphabetical.isAlphabetical(input));
static Stream<Arguments> testCases() {
// Workaround for SpotBugs false positive (NAB_NEEDLESS_BOOLEAN_CONSTANT_CONVERSION)
// due to JUnit Arguments.of(Object...) auto-boxing
return Stream.of(arguments("", Boolean.FALSE, "Should return false for empty string"), arguments(" ", Boolean.FALSE, "Should return false for blank string"), arguments("a1b2", Boolean.FALSE, "Should return false when string contains numbers"),
arguments("abc!DEF", Boolean.FALSE, "Should return false when string contains symbols"), arguments("#abc", Boolean.FALSE, "Should return false when first character is not a letter"), arguments("abc", Boolean.TRUE, "Should return true for non-decreasing order"),
arguments("aBcD", Boolean.TRUE, "Should return true for mixed case increasing sequence"), arguments("a", Boolean.TRUE, "Should return true for single letter"), arguments("'", Boolean.FALSE, "Should return false for single symbol"),
arguments("aabbcc", Boolean.TRUE, "Should return true for repeated letters"), arguments("cba", Boolean.FALSE, "Should return false when order decreases"), arguments("abzba", Boolean.FALSE, "Should return false when middle breaks order"));
}

private void assertAlphabetical(String input, boolean expected, String message) {
// Arrange & Act
boolean result = Alphabetical.isAlphabetical(input);

// Assert
assertEquals(expected, result, message);
}

@Test
@DisplayName("Should return false for null input")
void nullInputTest() {
assertAlphabetical(null, false, "Should return false for null input");
}

@ParameterizedTest(name = "{2}")
@MethodSource("testCases")
@DisplayName("Alphabetical cases")
void isAlphabeticalTest(String input, boolean expected, String message) {
assertAlphabetical(input, expected, message);
}
}
Loading