Skip to content

Commit 624e880

Browse files
Merge branch 'master' into jeeva/isogram
2 parents d895f93 + 8d14b49 commit 624e880

File tree

4 files changed

+323
-0
lines changed

4 files changed

+323
-0
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.thealgorithms.bitmanipulation;
2+
3+
/**
4+
* Implementation to count number of bits to be flipped to convert A to B
5+
*
6+
* Problem: Given two numbers A and B, count the number of bits needed to be
7+
* flipped to convert A to B.
8+
*
9+
* Example:
10+
* A = 10 (01010 in binary)
11+
* B = 20 (10100 in binary)
12+
* XOR = 30 (11110 in binary) - positions where bits differ
13+
* Answer: 4 bits need to be flipped
14+
*
15+
* Time Complexity: O(log n) - where n is the number of set bits
16+
* Space Complexity: O(1)
17+
*
18+
*@author [Yash Rajput](https://github.com/the-yash-rajput)
19+
*/
20+
public final class CountBitsFlip {
21+
22+
private CountBitsFlip() {
23+
throw new AssertionError("No instances.");
24+
}
25+
26+
/**
27+
* Counts the number of bits that need to be flipped to convert a to b
28+
*
29+
* Algorithm:
30+
* 1. XOR a and b to get positions where bits differ
31+
* 2. Count the number of set bits in the XOR result
32+
* 3. Use Brian Kernighan's algorithm: n & (n-1) removes rightmost set bit
33+
*
34+
* @param a the source number
35+
* @param b the target number
36+
* @return the number of bits to flip to convert A to B
37+
*/
38+
public static long countBitsFlip(long a, long b) {
39+
int count = 0;
40+
41+
// XOR gives us positions where bits differ
42+
long xorResult = a ^ b;
43+
44+
// Count set bits using Brian Kernighan's algorithm
45+
while (xorResult != 0) {
46+
xorResult = xorResult & (xorResult - 1); // Remove rightmost set bit
47+
count++;
48+
}
49+
50+
return count;
51+
}
52+
53+
/**
54+
* Alternative implementation using Long.bitCount().
55+
*
56+
* @param a the source number
57+
* @param b the target number
58+
* @return the number of bits to flip to convert a to b
59+
*/
60+
public static long countBitsFlipAlternative(long a, long b) {
61+
return Long.bitCount(a ^ b);
62+
}
63+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package com.thealgorithms.conversions;
2+
3+
import java.util.Locale;
4+
import java.util.Map;
5+
6+
/**
7+
* A utility class to convert between different units of time.
8+
*
9+
* <p>This class supports conversions between the following units:
10+
* <ul>
11+
* <li>seconds</li>
12+
* <li>minutes</li>
13+
* <li>hours</li>
14+
* <li>days</li>
15+
* <li>weeks</li>
16+
* <li>months (approximated as 30.44 days)</li>
17+
* <li>years (approximated as 365.25 days)</li>
18+
* </ul>
19+
*
20+
* <p>The conversion is based on predefined constants in seconds.
21+
* Results are rounded to three decimal places for consistency.
22+
*
23+
* <p>This class is final and cannot be instantiated.
24+
*
25+
* @see <a href="https://en.wikipedia.org/wiki/Unit_of_time">Wikipedia: Unit of time</a>
26+
*/
27+
public final class TimeConverter {
28+
29+
private TimeConverter() {
30+
// Prevent instantiation
31+
}
32+
33+
/**
34+
* Supported time units with their equivalent in seconds.
35+
*/
36+
private enum TimeUnit {
37+
SECONDS(1.0),
38+
MINUTES(60.0),
39+
HOURS(3600.0),
40+
DAYS(86400.0),
41+
WEEKS(604800.0),
42+
MONTHS(2629800.0), // 30.44 days
43+
YEARS(31557600.0); // 365.25 days
44+
45+
private final double seconds;
46+
47+
TimeUnit(double seconds) {
48+
this.seconds = seconds;
49+
}
50+
51+
public double toSeconds(double value) {
52+
return value * seconds;
53+
}
54+
55+
public double fromSeconds(double secondsValue) {
56+
return secondsValue / seconds;
57+
}
58+
}
59+
60+
private static final Map<String, TimeUnit> UNIT_LOOKUP
61+
= Map.ofEntries(Map.entry("seconds", TimeUnit.SECONDS), Map.entry("minutes", TimeUnit.MINUTES), Map.entry("hours", TimeUnit.HOURS), Map.entry("days", TimeUnit.DAYS), Map.entry("weeks", TimeUnit.WEEKS), Map.entry("months", TimeUnit.MONTHS), Map.entry("years", TimeUnit.YEARS));
62+
63+
/**
64+
* Converts a time value from one unit to another.
65+
*
66+
* @param timeValue the numeric value of time to convert; must be non-negative
67+
* @param unitFrom the unit of the input value (e.g., "minutes", "hours")
68+
* @param unitTo the unit to convert into (e.g., "seconds", "days")
69+
* @return the converted value in the target unit, rounded to three decimals
70+
* @throws IllegalArgumentException if {@code timeValue} is negative
71+
* @throws IllegalArgumentException if either {@code unitFrom} or {@code unitTo} is not supported
72+
*/
73+
public static double convertTime(double timeValue, String unitFrom, String unitTo) {
74+
if (timeValue < 0) {
75+
throw new IllegalArgumentException("timeValue must be a non-negative number.");
76+
}
77+
78+
TimeUnit from = resolveUnit(unitFrom);
79+
TimeUnit to = resolveUnit(unitTo);
80+
81+
double secondsValue = from.toSeconds(timeValue);
82+
double converted = to.fromSeconds(secondsValue);
83+
84+
return Math.round(converted * 1000.0) / 1000.0;
85+
}
86+
87+
private static TimeUnit resolveUnit(String unit) {
88+
if (unit == null) {
89+
throw new IllegalArgumentException("Unit cannot be null.");
90+
}
91+
TimeUnit resolved = UNIT_LOOKUP.get(unit.toLowerCase(Locale.ROOT));
92+
if (resolved == null) {
93+
throw new IllegalArgumentException("Invalid unit '" + unit + "'. Supported units are: " + UNIT_LOOKUP.keySet());
94+
}
95+
return resolved;
96+
}
97+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package com.thealgorithms.bitmanipulation;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import java.util.Random;
6+
import org.junit.jupiter.api.DisplayName;
7+
import org.junit.jupiter.api.Test;
8+
9+
/**
10+
* Unit tests for CountBitsFlip.
11+
* Covers:
12+
* - simple examples
13+
* - zeros and identical values
14+
* - negative numbers and two's complement edge cases
15+
* - Long.MIN_VALUE / Long.MAX_VALUE
16+
* - randomized consistency checks between two implementations
17+
*/
18+
@DisplayName("CountBitsFlip Tests")
19+
class CountBitsFlipTest {
20+
21+
@Test
22+
@DisplayName("Example: A=10, B=20 => 4 bits")
23+
void exampleTenTwenty() {
24+
long a = 10L;
25+
long b = 20L;
26+
long expected = 4L;
27+
assertEquals(expected, CountBitsFlip.countBitsFlip(a, b), "Brian Kernighan implementation should return 4");
28+
assertEquals(expected, CountBitsFlip.countBitsFlipAlternative(a, b), "Long.bitCount implementation should return 4");
29+
}
30+
31+
@Test
32+
@DisplayName("Identical values => 0 bits")
33+
void identicalValues() {
34+
long a = 123456789L;
35+
long b = 123456789L;
36+
long expected = 0L;
37+
assertEquals(expected, CountBitsFlip.countBitsFlip(a, b));
38+
assertEquals(expected, CountBitsFlip.countBitsFlipAlternative(a, b));
39+
}
40+
41+
@Test
42+
@DisplayName("Both zeros => 0 bits")
43+
void bothZeros() {
44+
assertEquals(0L, CountBitsFlip.countBitsFlip(0L, 0L));
45+
assertEquals(0L, CountBitsFlip.countBitsFlipAlternative(0L, 0L));
46+
}
47+
48+
@Test
49+
@DisplayName("Small example: A=15 (1111), B=8 (1000) => 3 bits")
50+
void smallExample() {
51+
long a = 15L; // 1111
52+
long b = 8L; // 1000
53+
long expected = 3L; // differs in three low bits
54+
assertEquals(expected, CountBitsFlip.countBitsFlip(a, b));
55+
assertEquals(expected, CountBitsFlip.countBitsFlipAlternative(a, b));
56+
}
57+
58+
@Test
59+
@DisplayName("Negative values: -1 vs 0 => 64 bits (two's complement all ones)")
60+
void negativeVsZero() {
61+
long a = -1L;
62+
long b = 0L;
63+
long expected = 64L; // all 64 bits differ
64+
assertEquals(expected, CountBitsFlip.countBitsFlip(a, b));
65+
assertEquals(expected, CountBitsFlip.countBitsFlipAlternative(a, b));
66+
}
67+
68+
@Test
69+
@DisplayName("Long.MIN_VALUE vs Long.MAX_VALUE => 64 bits")
70+
void minMaxLongs() {
71+
long a = Long.MIN_VALUE;
72+
long b = Long.MAX_VALUE;
73+
long expected = 64L; // MAX ^ MIN yields all ones on 64-bit long
74+
assertEquals(expected, CountBitsFlip.countBitsFlip(a, b));
75+
assertEquals(expected, CountBitsFlip.countBitsFlipAlternative(a, b));
76+
}
77+
78+
@Test
79+
@DisplayName("Randomized consistency: both implementations agree across many pairs")
80+
void randomizedConsistency() {
81+
final int iterations = 1000;
82+
final Random rnd = new Random(12345L); // deterministic seed for reproducibility
83+
84+
for (int i = 0; i < iterations; i++) {
85+
long a = rnd.nextLong();
86+
long b = rnd.nextLong();
87+
88+
long res1 = CountBitsFlip.countBitsFlip(a, b);
89+
long res2 = CountBitsFlip.countBitsFlipAlternative(a, b);
90+
91+
assertEquals(res2, res1, () -> String.format("Mismatch for a=%d, b=%d: impl1=%d, impl2=%d", a, b, res1, res2));
92+
}
93+
}
94+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.thealgorithms.conversions;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import java.util.stream.Stream;
7+
import org.junit.jupiter.api.DisplayName;
8+
import org.junit.jupiter.api.Test;
9+
import org.junit.jupiter.params.ParameterizedTest;
10+
import org.junit.jupiter.params.provider.CsvSource;
11+
import org.junit.jupiter.params.provider.MethodSource;
12+
13+
class TimeConverterTest {
14+
15+
@ParameterizedTest(name = "{0} {1} -> {2} {3}")
16+
@CsvSource({"60, seconds, minutes, 1", "120, seconds, minutes, 2", "2, minutes, seconds, 120", "2, hours, minutes, 120", "1, days, hours, 24", "1, weeks, days, 7", "1, months, days, 30.438", "1, years, days, 365.25", "3600, seconds, hours, 1", "86400, seconds, days, 1",
17+
"604800, seconds, weeks, 1", "31557600, seconds, years, 1"})
18+
void
19+
testValidConversions(double value, String from, String to, double expected) {
20+
assertEquals(expected, TimeConverter.convertTime(value, from, to));
21+
}
22+
23+
@Test
24+
@DisplayName("Zero conversion returns zero")
25+
void testZeroValue() {
26+
assertEquals(0.0, TimeConverter.convertTime(0, "seconds", "hours"));
27+
}
28+
29+
@Test
30+
@DisplayName("Same-unit conversion returns original value")
31+
void testSameUnitConversion() {
32+
assertEquals(123.456, TimeConverter.convertTime(123.456, "minutes", "minutes"));
33+
}
34+
35+
@Test
36+
@DisplayName("Negative value throws exception")
37+
void testNegativeValue() {
38+
assertThrows(IllegalArgumentException.class, () -> TimeConverter.convertTime(-5, "seconds", "minutes"));
39+
}
40+
41+
@ParameterizedTest
42+
@CsvSource({"lightyears, seconds", "minutes, centuries"})
43+
void testInvalidUnits(String from, String to) {
44+
assertThrows(IllegalArgumentException.class, () -> TimeConverter.convertTime(10, from, to));
45+
}
46+
47+
@Test
48+
@DisplayName("Null unit throws exception")
49+
void testNullUnit() {
50+
assertThrows(IllegalArgumentException.class, () -> TimeConverter.convertTime(10, null, "seconds"));
51+
52+
assertThrows(IllegalArgumentException.class, () -> TimeConverter.convertTime(10, "minutes", null));
53+
54+
assertThrows(IllegalArgumentException.class, () -> TimeConverter.convertTime(10, null, null));
55+
}
56+
57+
static Stream<org.junit.jupiter.params.provider.Arguments> roundTripCases() {
58+
return Stream.of(org.junit.jupiter.params.provider.Arguments.of(1.0, "hours", "minutes"), org.junit.jupiter.params.provider.Arguments.of(2.5, "days", "hours"), org.junit.jupiter.params.provider.Arguments.of(1000, "seconds", "minutes"));
59+
}
60+
61+
@ParameterizedTest
62+
@MethodSource("roundTripCases")
63+
@DisplayName("Round-trip conversion returns original value")
64+
void testRoundTripConversion(double value, String from, String to) {
65+
double converted = TimeConverter.convertTime(value, from, to);
66+
double roundTrip = TimeConverter.convertTime(converted, to, from);
67+
assertEquals(Math.round(value * 1000.0) / 1000.0, Math.round(roundTrip * 1000.0) / 1000.0, 0.05);
68+
}
69+
}

0 commit comments

Comments
 (0)