Skip to content

Commit 03c2949

Browse files
committed
adding MapLens#mappingValues, a lens over a map with mapped values
1 parent 538b645 commit 03c2949

File tree

2 files changed

+63
-11
lines changed

2 files changed

+63
-11
lines changed

src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.jnape.palatable.lambda.lens.lenses;
22

3+
import com.jnape.palatable.lambda.functions.builtin.fn2.Filter;
34
import com.jnape.palatable.lambda.lens.Lens;
45

56
import java.util.Collection;
@@ -8,11 +9,16 @@
89
import java.util.Map;
910
import java.util.Optional;
1011
import java.util.Set;
12+
import java.util.function.Function;
1113

14+
import static com.jnape.palatable.lambda.functions.builtin.fn2.Eq.eq;
15+
import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map;
16+
import static com.jnape.palatable.lambda.functions.builtin.fn2.ToCollection.toCollection;
1217
import static com.jnape.palatable.lambda.lens.Lens.lens;
1318
import static com.jnape.palatable.lambda.lens.Lens.simpleLens;
1419
import static com.jnape.palatable.lambda.lens.functions.View.view;
1520
import static com.jnape.palatable.lambda.lens.lenses.OptionalLens.unLiftA;
21+
import static java.util.stream.Collectors.toMap;
1622
import static java.util.stream.Collectors.toSet;
1723

1824
/**
@@ -24,8 +30,7 @@ private MapLens() {
2430
}
2531

2632
/**
27-
* Convenience static factory method for creating a lens that focuses on a copy of a Map. Useful for composition to
28-
* avoid mutating a map reference.
33+
* A lens that focuses on a copy of a Map. Useful for composition to avoid mutating a map reference.
2934
*
3035
* @param <K> the key type
3136
* @param <V> the value type
@@ -36,8 +41,7 @@ public static <K, V> Lens.Simple<Map<K, V>, Map<K, V>> asCopy() {
3641
}
3742

3843
/**
39-
* Convenience static factory method for creating a lens that focuses on a value at a key in a map, as an {@link
40-
* Optional}.
44+
* A lens that focuses on a value at a key in a map, as an {@link Optional}.
4145
*
4246
* @param k the key to focus on
4347
* @param <K> the key type
@@ -52,8 +56,7 @@ public static <K, V> Lens<Map<K, V>, Map<K, V>, Optional<V>, V> valueAt(K k) {
5256
}
5357

5458
/**
55-
* Convenience static factory method for creating a lens that focuses on a value at a key in a map, falling back to
56-
* <code>defaultV</code> if the value is missing.
59+
* A lens that focuses on a value at a key in a map, falling back to <code>defaultV</code> if the value is missing.
5760
*
5861
* @param k the key to focus on
5962
* @param defaultValue the default value to use in case of a missing value at key
@@ -67,7 +70,7 @@ public static <K, V> Lens.Simple<Map<K, V>, V> valueAt(K k, V defaultValue) {
6770
}
6871

6972
/**
70-
* Convenience static factory method for creating a lens that focuses on the keys of a map.
73+
* A lens that focuses on the keys of a map.
7174
*
7275
* @param <K> the key type
7376
* @param <V> the value type
@@ -84,8 +87,8 @@ public static <K, V> Lens.Simple<Map<K, V>, Set<K>> keys() {
8487
}
8588

8689
/**
87-
* Convenience static factory method for creating a lens that focuses on the values of a map. In the case of
88-
* updating the map, only the entries with a value listed in the update collection of values are kept.
90+
* A lens that focuses on the values of a map. In the case of updating the map, only the entries with a value listed
91+
* in the update collection of values are kept.
8992
*
9093
* @param <K> the key type
9194
* @param <V> the value type
@@ -104,8 +107,8 @@ public static <K, V> Lens.Simple<Map<K, V>, Collection<V>> values() {
104107
}
105108

106109
/**
107-
* Convenience static factory method for creating a lens that focuses on the inverse of a map (keys and values
108-
* swapped). In the case of multiple equal values becoming keys, the last one wins.
110+
* A lens that focuses on the inverse of a map (keys and values swapped). In the case of multiple equal values
111+
* becoming keys, the last one wins.
109112
*
110113
* @param <K> the key type
111114
* @param <V> the value type
@@ -122,4 +125,27 @@ public static <K, V> Lens.Simple<Map<K, V>, Map<V, K>> inverted() {
122125
return m;
123126
});
124127
}
128+
129+
/**
130+
* A lens that focuses on a map while mapping its values with the mapping function.
131+
*
132+
* @param fn the mapping function
133+
* @param <K> the key type
134+
* @param <V> the unfocused map value type
135+
* @param <V2> the focused map value type
136+
* @return a lens that focuses on a map while mapping its values
137+
*/
138+
public static <K, V, V2> Lens.Simple<Map<K, V>, Map<K, V2>> mappingValues(Function<? super V, ? extends V2> fn) {
139+
return Lens.simpleLens(m -> m.entrySet().stream().collect(toMap(Map.Entry::getKey, kv -> fn.apply(kv.getValue()))),
140+
(s, b) -> {
141+
//todo: remove this madness upon arrival of either invertible functions or Iso<V,V2>
142+
Set<K> retainKeys = Filter.<Map.Entry<K, V>>filter(kv -> eq(fn.apply(kv.getValue()), b.get(kv.getKey())))
143+
.andThen(map(Map.Entry::getKey))
144+
.andThen(toCollection(HashSet::new))
145+
.apply(s.entrySet());
146+
Map<K, V> copy = new HashMap<>(s);
147+
copy.keySet().retainAll(retainKeys);
148+
return copy;
149+
});
150+
}
125151
}

src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@
1414
import static com.jnape.palatable.lambda.lens.functions.Set.set;
1515
import static com.jnape.palatable.lambda.lens.functions.View.view;
1616
import static com.jnape.palatable.lambda.lens.lenses.MapLens.keys;
17+
import static com.jnape.palatable.lambda.lens.lenses.MapLens.mappingValues;
1718
import static java.util.Arrays.asList;
1819
import static java.util.Collections.emptyMap;
20+
import static java.util.Collections.singletonMap;
21+
import static java.util.Collections.unmodifiableMap;
1922
import static org.junit.Assert.assertEquals;
2023
import static org.junit.Assert.assertNotSame;
2124
import static org.junit.Assert.assertSame;
@@ -134,4 +137,27 @@ public void invertedFocusesOnMapWithKeysAndValuesSwitched() {
134137
put(1, "foo");
135138
}}, view(inverted, withDuplicateValues));
136139
}
140+
141+
@Test
142+
public void mappingValuesRetainsMapStructureWithMappedValues() {
143+
Map<String, String> m = unmodifiableMap(new HashMap<String, String>() {{
144+
put("foo", "1");
145+
put("bar", "2");
146+
put("baz", "3");
147+
}});
148+
Lens.Simple<Map<String, String>, Map<String, Integer>> mappingValues = mappingValues(Integer::parseInt);
149+
150+
assertEquals(new HashMap<String, Integer>() {{
151+
put("foo", 1);
152+
put("bar", 2);
153+
put("baz", 3);
154+
}}, view(mappingValues, m));
155+
156+
Map<String, String> updated = set(mappingValues, unmodifiableMap(new HashMap<String, Integer>() {{
157+
put("foo", 2);
158+
put("bar", 1);
159+
put("baz", 3);
160+
}}), m);
161+
assertEquals(singletonMap("baz", "3"), updated);
162+
}
137163
}

0 commit comments

Comments
 (0)