Skip to content

Commit 107716f

Browse files
authored
Associative utility functions (#404)
* Associative utility functions * assoc-in tests * update-in tests * Linting is bayayyd
1 parent 58bb61c commit 107716f

File tree

3 files changed

+214
-1
lines changed

3 files changed

+214
-1
lines changed

src/basilisp/core.lpy

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1272,13 +1272,55 @@
12721272
([m k default]
12731273
(basilisp.lang.runtime/get m k default)))
12741274

1275+
(defn assoc-in
1276+
"Associate value in a nested associative structure, with ks as a sequence of
1277+
keys and v as the new value. If no map exists for any key in ks, a new empty
1278+
map will be created."
1279+
[m ks v]
1280+
(let [fk (first ks)
1281+
rks (rest ks)]
1282+
(if (seq rks)
1283+
(basilisp.lang.runtime/assoc m fk (assoc-in (get m fk) rks v))
1284+
(basilisp.lang.runtime/assoc m fk v))))
1285+
1286+
(defn get-in
1287+
"Return the entry of an associative data structure addressed by the sequence of
1288+
keys ks or default (default: nil) if the value is not found."
1289+
([m ks]
1290+
(get-in m ks nil))
1291+
([m ks default]
1292+
(let [fk (first ks)
1293+
rks (rest ks)]
1294+
(if (seq rks)
1295+
(if-let [child-map (basilisp.lang.runtime/get m fk)]
1296+
(get-in child-map rks default)
1297+
default)
1298+
(basilisp.lang.runtime/get m fk default)))))
1299+
12751300
(defn update
12761301
"Updates the value for key k in associative data structure m with the return value
12771302
from calling (f old-v & args). If m is nil, use an empty map. If k is not in m,
12781303
old-v will be nil."
12791304
[m k f & args]
12801305
(apply basilisp.lang.runtime/update m k f args))
12811306

1307+
(defn update-in
1308+
"Updates the value for key k in associative data structure m with the return value
1309+
from calling (f old-v & args). If m is nil, use an empty map. If k is not in m,
1310+
old-v will be nil."
1311+
[m ks f & args]
1312+
(let [fk (first ks)
1313+
rks (rest ks)]
1314+
(if (seq rks)
1315+
(basilisp.lang.runtime/assoc m
1316+
fk
1317+
(apply update-in
1318+
(or (basilisp.lang.runtime/get m fk) {})
1319+
rks
1320+
f
1321+
args))
1322+
(apply basilisp.lang.runtime/update m fk f args))))
1323+
12821324
(defn map-entry
12831325
"With one argument, coerce the input to a map entry. With two arguments, return a
12841326
map entry containing key and value."
@@ -1292,6 +1334,12 @@
12921334
([k v]
12931335
(basilisp.lang.map.MapEntry/of k v)))
12941336

1337+
(defn find
1338+
"Find the map entry of k in m, if it exists. Return nil otherwise."
1339+
[m k]
1340+
(when-let [v (get m k)]
1341+
(map-entry k v)))
1342+
12951343
(defn key
12961344
"Return the key from a map entry."
12971345
[entry]

tests/basilisp/core_test.py

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,6 +1063,171 @@ def test_get(self):
10631063
assert 1 == core.get(vec.v(1, 2, 3), -3)
10641064
assert None is core.get(vec.v(1, 2, 3), -4)
10651065

1066+
def test_find(self):
1067+
assert None is core.find(None, "a")
1068+
assert core.map_entry("a", 1) == core.find(lmap.map({"a": 1}), "a")
1069+
assert None is core.find(lmap.map({"a": 1}), "b")
1070+
assert core.map_entry(0, 1) == core.find(vec.v(1, 2, 3), 0)
1071+
assert core.map_entry(1, 2) == core.find(vec.v(1, 2, 3), 1)
1072+
assert core.map_entry(2, 3) == core.find(vec.v(1, 2, 3), 2)
1073+
assert None is core.find(vec.v(1, 2, 3), 3)
1074+
1075+
def test_assoc_in(self):
1076+
assert lmap.map({"a": 1}) == core.assoc_in(None, vec.v("a"), 1)
1077+
assert lmap.map({"a": 8}) == core.assoc_in(lmap.map({"a": 1}), vec.v("a"), 8)
1078+
assert lmap.map({"a": 1, "b": "string"}) == core.assoc_in(
1079+
lmap.map({"a": 1}), vec.v("b"), "string"
1080+
)
1081+
1082+
assert lmap.map({"a": lmap.map({"b": 3})}) == core.assoc_in(
1083+
lmap.map({"a": lmap.map({"b": lmap.map({"c": 3})})}), vec.v("a", "b"), 3
1084+
)
1085+
assert lmap.map({"a": lmap.map({"b": lmap.map({"c": 4})})}) == core.assoc_in(
1086+
lmap.map({"a": lmap.map({"b": lmap.map({"c": 3})})}),
1087+
vec.v("a", "b", "c"),
1088+
4,
1089+
)
1090+
assert lmap.map(
1091+
{"a": lmap.map({"b": lmap.map({"c": 3, "f": 6})})}
1092+
) == core.assoc_in(
1093+
lmap.map({"a": lmap.map({"b": lmap.map({"c": 3})})}),
1094+
vec.v("a", "b", "f"),
1095+
6,
1096+
)
1097+
assert lmap.map(
1098+
{"a": lmap.map({"b": lmap.map({"c": 3}), "e": lmap.map({"f": 6})})}
1099+
) == core.assoc_in(
1100+
lmap.map({"a": lmap.map({"b": lmap.map({"c": 3})})}),
1101+
vec.v("a", "e"),
1102+
lmap.map({"f": 6}),
1103+
)
1104+
1105+
assert vec.v("a") == core.assoc_in(vec.Vector.empty(), vec.v(0), "a")
1106+
assert vec.v("c", "b") == core.assoc_in(vec.v("a", "b"), vec.v(0), "c")
1107+
assert vec.v("a", "c") == core.assoc_in(vec.v("a", "b"), vec.v(1), "c")
1108+
assert vec.v("a", "d", "c") == core.assoc_in(
1109+
vec.v("a", "b", "c"), vec.v(1), "d"
1110+
)
1111+
1112+
assert vec.v("a", vec.v("q", "r", "s", "w"), "c") == core.assoc_in(
1113+
vec.v("a", vec.v("q", "r", "s", "t"), "c"), vec.v(1, 3), "w"
1114+
)
1115+
assert vec.v(
1116+
"a", vec.v("q", "r", "s", lmap.map({"w": "y"})), "c"
1117+
) == core.assoc_in(
1118+
vec.v("a", vec.v("q", "r", "s", lmap.map({"w": "x"})), "c"),
1119+
vec.v(1, 3, "w"),
1120+
"y",
1121+
)
1122+
assert vec.v(
1123+
"a", vec.v("q", "r", "s", lmap.map({"w": "x", "v": "u"})), "c"
1124+
) == core.assoc_in(
1125+
vec.v("a", vec.v("q", "r", "s", lmap.map({"w": "x"})), "c"),
1126+
vec.v(1, 3, "v"),
1127+
"u",
1128+
)
1129+
1130+
def test_get_in(self):
1131+
assert 1 == core.get_in(lmap.map({"a": 1}), vec.v("a"))
1132+
assert None is core.get_in(lmap.map({"a": 1}), vec.v("b"))
1133+
assert 2 == core.get_in(lmap.map({"a": 1}), vec.v("b"), 2)
1134+
1135+
assert lmap.map({"b": lmap.map({"c": 3})}) == core.get_in(
1136+
lmap.map({"a": lmap.map({"b": lmap.map({"c": 3})})}), vec.v("a")
1137+
)
1138+
assert lmap.map({"c": 3}) == core.get_in(
1139+
lmap.map({"a": lmap.map({"b": lmap.map({"c": 3})})}), vec.v("a", "b")
1140+
)
1141+
assert 3 == core.get_in(
1142+
lmap.map({"a": lmap.map({"b": lmap.map({"c": 3})})}), vec.v("a", "b", "c")
1143+
)
1144+
assert None is core.get_in(
1145+
lmap.map({"a": lmap.map({"b": lmap.map({"c": 3})})}), vec.v("a", "b", "f")
1146+
)
1147+
assert None is core.get_in(
1148+
lmap.map({"a": lmap.map({"b": lmap.map({"c": 3})})}), vec.v("a", "e", "c")
1149+
)
1150+
assert "Not Found" == core.get_in(
1151+
lmap.map({"a": lmap.map({"b": lmap.map({"c": 3})})}),
1152+
vec.v("a", "b", "f"),
1153+
"Not Found",
1154+
)
1155+
assert "Not Found" == core.get_in(
1156+
lmap.map({"a": lmap.map({"b": lmap.map({"c": 3})})}),
1157+
vec.v("a", "e", "c"),
1158+
"Not Found",
1159+
)
1160+
1161+
assert "b" == core.get_in(vec.v("a", "b", "c"), vec.v(1))
1162+
assert "t" == core.get_in(
1163+
vec.v("a", vec.v("q", "r", "s", "t"), "c"), vec.v(1, 3)
1164+
)
1165+
assert "x" == core.get_in(
1166+
vec.v("a", vec.v("q", "r", "s", lmap.map({"w": "x"})), "c"),
1167+
vec.v(1, 3, "w"),
1168+
)
1169+
assert None is core.get_in(
1170+
vec.v("a", vec.v("q", "r", "s", lmap.map({"w": "x"})), "c"),
1171+
vec.v(1, 3, "v"),
1172+
)
1173+
1174+
def test_update_in(self):
1175+
assert lmap.map({"a": 2}) == core.update_in(
1176+
lmap.map({"a": 1}), vec.v("a"), core.inc
1177+
)
1178+
assert lmap.map({"a": 1, "b": lmap.map({"c": 3})}) == core.update_in(
1179+
lmap.map({"a": 1}), vec.v("b"), core.assoc, "c", 3
1180+
)
1181+
1182+
assert lmap.map({"a": lmap.map({"b": lmap.map({"c": 2})})}) == core.update_in(
1183+
lmap.map({"a": lmap.map({"b": lmap.map({"c": 3})})}),
1184+
vec.v("a", "b", "c"),
1185+
core.dec,
1186+
)
1187+
assert lmap.map({"a": lmap.map({"b": lmap.map({"c": 3})})}) == core.update_in(
1188+
lmap.map({"a": lmap.map({"b": lmap.map({"c": 3, "f": 6})})}),
1189+
vec.v("a", "b"),
1190+
core.dissoc,
1191+
"f",
1192+
)
1193+
assert lmap.map(
1194+
{"a": lmap.map({"b": lmap.map({"c": 3}), "e": lmap.map({"f": 6})})}
1195+
) == core.update_in(
1196+
lmap.map({"a": lmap.map({"b": lmap.map({"c": 3})})}),
1197+
vec.v("a"),
1198+
core.assoc,
1199+
"e",
1200+
lmap.map({"f": 6}),
1201+
)
1202+
1203+
assert vec.v(0, 2) == core.update_in(vec.v(1, 2), vec.v(0), core.dec)
1204+
assert vec.v(1, 3) == core.update_in(vec.v(1, 2), vec.v(1), core.inc)
1205+
assert vec.v("a", "B", "c") == core.update_in(
1206+
vec.v("a", "b", "c"), vec.v(1), lambda s: s.upper()
1207+
)
1208+
1209+
assert vec.v("a", vec.v("q", "r", "s", "T"), "c") == core.update_in(
1210+
vec.v("a", vec.v("q", "r", "s", "t"), "c"), vec.v(1, 3), lambda s: s.upper()
1211+
)
1212+
assert vec.v(
1213+
"a", vec.v("q", "r", "s", lmap.map({"w": "X"})), "c"
1214+
) == core.update_in(
1215+
vec.v("a", vec.v("q", "r", "s", lmap.map({"w": "x"})), "c"),
1216+
vec.v(1, 3, "w"),
1217+
lambda s: s.upper(),
1218+
)
1219+
assert vec.v(
1220+
"a",
1221+
vec.v("q", "r", "s", lmap.map({"w": "x", "v": lmap.map({"t": "u"})})),
1222+
"c",
1223+
) == core.update_in(
1224+
vec.v("a", vec.v("q", "r", "s", lmap.map({"w": "x"})), "c"),
1225+
vec.v(1, 3, "v"),
1226+
core.assoc,
1227+
"t",
1228+
"u",
1229+
)
1230+
10661231
def test_keys(self):
10671232
assert None is core.keys(lmap.map({}))
10681233
assert llist.l("a") == core.keys(lmap.map({"a": 1}))

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ commands =
6767

6868
[testenv:lint]
6969
parallel_show_output = {env:TOX_SHOW_OUTPUT:false}
70-
deps = prospector
70+
deps = prospector==1.1.6.2
7171
commands =
7272
prospector --profile-path={toxinidir}
7373

0 commit comments

Comments
 (0)