Since there was some interest in seeing the same implemented in Java, in this article, we’ll see how we can implement the same Hamming encoder/decoder using Java and the Vavr library.
Previous articles from the series:
Encoder
package com.pivovarit.hamming.vavr; import static io.vavr.API.$; import static io.vavr.API.Case; import static io.vavr.API.Match; class VavrHammingEncoder implements HammingEncoder { private final HammingHelper helper = new HammingHelper(); @Override public EncodedString encode(BinaryString input) { String result = helper.getHammingCodewordIndices(input.getValue().length()) .map(i -> toHammingCodeValue(i, input)) .reduce(String::concat); return EncodedString.of(result); } private String toHammingCodeValue(int it, BinaryString input) { return Match(it + 1).of( Case($(HammingHelper::isPowerOfTwo), () -> helper.getParityBit(it, input)), Case($(), () -> helper.getDataBit(it, input)) ); } }
Decoder
package com.pivovarit.hamming.vavr; import io.vavr.collection.List; import io.vavr.collection.Stream; import static io.vavr.API.$; import static io.vavr.API.Case; import static io.vavr.API.Match; class VavrHammingDecoder implements HammingDecoder { private final HammingHelper helper = new HammingHelper(); private final HammingMessageExtractor extractor = new HammingMessageExtractor(); @Override public boolean isValid(EncodedString input) { return indexesOfInvalidParityBits(input).isEmpty(); } @Override public BinaryString decode(EncodedString input) { EncodedString corrected = Match(indexesOfInvalidParityBits(input).isEmpty()).of( Case($(true), () -> input), Case($(false), () -> withBitFlippedAt(input, indexesOfInvalidParityBits(input).reduce((a, b) -> a + b) - 1)) ); return extractor.stripHammingMetadata(corrected); } private List<Integer> indexesOfInvalidParityBits(EncodedString input) { return Stream.iterate(1, i -> i * 2) .takeWhile(it -> it < input.getValue().length()) .filter(it -> helper.parityIndicesSequence(it - 1, input.getValue().length()) .map(v -> toBinaryInt(input, v)) .fold(toBinaryInt(input, it - 1), (a, b) -> a ^ b) != 0) .toList(); } private Integer toBinaryInt(EncodedString input, Integer v) { return Integer.valueOf(Character.toString(input.getValue().charAt(v))); } private EncodedString withBitFlippedAt(EncodedString source, int ind) { char it = source.getValue().charAt(ind); StringBuilder builder = new StringBuilder(source.getValue()); builder.setCharAt(ind, it == '0' ? '1' : '0'); return EncodedString.of(builder.toString()); } }
HammingMessageExtractor
package com.pivovarit.hamming.vavr; import io.vavr.Tuple; import io.vavr.collection.Stream; class HammingMessageExtractor { BinaryString stripHammingMetadata(EncodedString input) { String raw = Stream.from(0, 1).take(input.getValue().length()) .map(i -> Tuple.of(i + 1, Character.toString(input.getValue().charAt(i)))) .filter(t -> !HammingHelper.isPowerOfTwo(t._1)) .map(i -> i._2) .foldLeft("", String::concat); return BinaryString.of(raw); } }
HammingHelper
package com.pivovarit.hamming.vavr; import io.vavr.collection.Stream; class HammingHelper { static boolean isPowerOfTwo(int i) { return (i & -i) == i; } int codewordSize(int msgLength) { return Stream.from(2, 1) .filter(r -> msgLength + r + 1 <= 1 << r) .map(r -> r + msgLength) .head(); } Stream<Integer> getHammingCodewordIndices(int msgLength) { return Stream.from(0, 1) .take(codewordSize(msgLength)); } String getParityBit(int codeWordIndex, BinaryString msg) { return parityIndicesSequence(codeWordIndex, codewordSize(msg.getValue().length())) .map(it -> getDataBit(it, msg)) .map(Integer::valueOf) .reduce((a, b) -> (a + b) % 2) .toString(); } String getDataBit(int ind, BinaryString input) { return Character.toString( input.getValue().charAt(ind - Integer.toBinaryString(ind).length())); } Stream<Integer> parityIndicesSequence(int startIndex, int endExclusive) { return Stream.from(startIndex, 1) .take(endExclusive - startIndex) .zipWithIndex() .filter(t -> (t._2 % ((2 * (startIndex + 1)))) < startIndex + 1) .map(t -> t._1) .drop(1); } }
Sources
A complete example can be found on GitHub.