3
\$\begingroup\$

(See also the continuation of this post.)

This time, I have a mapping type:

com.github.coderodde.mapping.Mapping.java:

package com.github.coderodde.mapping;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * This class implements a mapping from a domain set to a range set.
 * 
 * @param <D> the domain element type.
 * @param <R> the range element type.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Sep 3, 2023)
 * @since 1.6 (Sep 3, 2023)
 */
public final class Mapping<D, R> {

    final Map<D, R> data = new HashMap<>();
    
    public void map(D domainValue, R rangeValue) {
        checkDomainValueNotYetMapped(domainValue);
        data.put(domainValue, rangeValue);
    }
    
    public boolean isMapped(D domainValue) {
        return data.containsKey(
                Objects.requireNonNull(
                        domainValue,
                        "The input domain value is null."));
    }
    
    public R map(D domainValue) {
        checkDomainValueIsMapped(domainValue);
        return data.get(domainValue);
    }
    
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        
        boolean first = true;
        
        for (Map.Entry<D, R> entry : data.entrySet()) {
            if (first) {
                first = false;
            } else {
                sb.append(", ");
            }
            
            sb.append("(")
              .append(entry.getKey())
              .append(", ")
              .append(entry.getValue())
              .append(")");
        }
        
        sb.append("]");
        return sb.toString();
    }
    
    private void checkDomainValueNotYetMapped(D domainValue) {
        if (data.containsKey(
                Objects.requireNonNull(domainValue, "Domain value is null."))) {
            throw new DuplicateDomainValueException(
                    "Trying to map a domain value ["
                            + domainValue
                            + "] twice.");
        }
    }
    
    private void checkDomainValueIsMapped(D domainValue) {
        if (!data.containsKey(domainValue)) {
            throw new DomainValueIsNotMappedException(
                    "Domain value [" + domainValue + "] is not mapped.");
        }
    }
}

Also, I can compose mappings:

com.github.coderodde.mapping.MappingComposition.java:

package com.github.coderodde.mapping;

import java.util.Map;

/**
 * This class provides facilities for composing mappings.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Sep 3, 2023)
 * @since 1.6 (Sep 3, 2023)
 */
public final class MappingComposition {
    
    /**
     * Composes two functions together. For example, if 
     * {@code mapping1 = [(1, 'a'], (2, 'b'), (3, 'c')]} and 
     * {@code mapping2 = [('a', "Alice"), ('c', "Clarice")]}, then the composed
     * mapping is {@code [(1, "Alice"), (3, "Clarice")]}.
     * 
     * @param <D> the domain value type of the first mapping.
     * @param <T> the domain value type of the second mapping. This is the same
     *            as the range value type of the first mapping.
     * @param <R> the range value type of the second mapping.
     * @param mapping1 the first mapping.
     * @param mapping2 the second mapping.
     * @return a composed function.
     */
    public static <D, T, R> Mapping<D, R> compose(Mapping<D, T> mapping1, 
                                                  Mapping<T, R> mapping2) {
        Mapping<D, R> result = new Mapping<>();
        
        for (Map.Entry<D, T> entry1 : mapping1.data.entrySet()) {
            D domainValue = entry1.getKey();
            T temporaryValue = entry1.getValue();
            
            if (mapping2.isMapped(temporaryValue)) {
                R rangeValue = mapping2.map(temporaryValue);
                result.map(domainValue, rangeValue);
            }
        }
        
        return result;
    }
}

... I need some exception definitions:

package com.github.coderodde.mapping;

/**
 * Instances of this class are thrown on occasions where the domain value is not
 * yet mapped to a range value.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Sep 3, 2023)
 * @since 1.6 (Sep 3, 2023)
 */
public final class DomainValueIsNotMappedException extends RuntimeException {
    
    public DomainValueIsNotMappedException(String exceptionMessage) {
        super(exceptionMessage);
    }
}
package com.github.coderodde.mapping;

/**
 * Instances of this class are thrown on occasions where the domain value is 
 * already mapped and we are trying to map it to a second range value.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Sep 3, 2023)
 * @since 1.6 (Sep 3, 2023)
 */
public final class DuplicateDomainValueException extends RuntimeException {
    
    public DuplicateDomainValueException(String exceptionMessage) {
        super(exceptionMessage);
    }
}

Finally, the demonstration program follows:

package com.github.coderodde.mapping;

class MappingCompositionDemo {

    public static void main(String[] args) {
        mapDual();
        mapFour();
    }
    
    private static void mapDual() {
        Mapping<Character, Integer> mapping1 = new Mapping<>();
        Mapping<Integer, String> mapping2 = new Mapping<>();
        
        mapping1.map('a', 1);
        mapping1.map('b', 2);
        mapping1.map('c', 3);
        
        mapping2.map(1, "Alice");
        mapping2.map(2, "Clarice");
        
        System.out.println(MappingComposition.compose(mapping1, mapping2));
    }
    
    private static void mapFour() {
        Mapping<Character, Integer> mapping1 = new Mapping<>();
        Mapping<Integer, String> mapping2 = new Mapping<>();
        Mapping<String, Long> mapping3 = new Mapping<>();
        Mapping<Long, Float> mapping4 = new Mapping<>();
        
        mapping1.map('a', 1);
        mapping1.map('b', 2);
        mapping1.map('c', 3);
        
        mapping2.map(1, "Alice");
        mapping2.map(3, "Clarice");
        
        mapping3.map("Alice", 11L);
        mapping3.map("Bob", 12L);
        mapping3.map("Clarice", 13L);
        
        mapping4.map(11L, 11.0f);
        mapping4.map(12L, 12.0f);
        mapping4.map(13L, 13.0f);
        
        System.out.println(
                MappingComposition.compose(
                        MappingComposition.compose(mapping1, mapping2),
                        MappingComposition.compose(mapping3, mapping4)));
    }
}

Demo output

[(a, Alice), (b, Clarice)]
[(a, 11.0), (c, 13.0)]

Critique request

As always, I would like to hear whatever comes to mind.

\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

Since Mapping's domainValue method's parameters represent keys in data map might worth calling them differently closer to their purpose. Probably they are called domainValue since while calling the methods values of other Mappings are passed in.

With exceptions interrupting the flow the MappingCompositionDemo is an example of either all succeed either all fail, if this isn't the intended behaviour the code gets cluttered with try/catch blocks when other scenarios are implemented.

Mapping#toString method's complexity could be improved by appending separator string , after each entry string and deleting the last separator string occurrence before other further appends.

@Override
public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("[");
        
    for (Map.Entry<D, R> entry : data.entrySet()) {
            
        sb.append("(")
          .append(entry.getKey())
          .append(", ")
          .append(entry.getValue())
          .append("), ");
    }

    sb.delete(sb.length() - 2, sb.length());

    sb.append("]");
    return sb.toString();
}
\$\endgroup\$
3
\$\begingroup\$

It appears that we prohibit null in the domain, but allow it in the range. This is by no means a bad thing (assuming it's intentional - and the use of explicit containsKey checks rather than checking the return values of Map.putIfAbsent and Map.get for null seems to suggest that it is), though it may require some attention - for example, it seems like the compose method will throw a NullPointerException if mapping1 maps any value to null, since it passes each value in mapping1's range to mapping2.isMapped, which in turn passes its argument to Objects.requireNonNull

I also note that toString manually implements the string joining, rather than using one of the standard library's existing String-joining utilities. We might be able to simplify that method using String.join, or perhaps using a Stream that gets collected by Collectors.joining

Finally, I'm not convinced we need distinct exception types for this. It seems like there are existing, well-known exception types which can communicate what is going on just as clearly (especially with an informative exception message). IllegalStateException (for attempted double mappings) and NoSuchElementException (for undefined lookups) come to mind

\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.