porównanie Wyczerpujące Map za pomocą funkcji wyższego rzędu
mam zamiar podejść do tego w ten sam sposób ja zbliżył porównania tablicy w tym podobną odpowiedź: How to compare arrays in JavaScript?
mam zamiar przejść przez kodzie bit po bicie, ale będę mieć pełną runnable przykład pod koniec
Płytki porównanie
Po pierwsze, zaczniemy od ogólnej funkcji porównywania map. W ten sposób możemy zrobić wszelkiego rodzaju porównań na obiekty mapy, nie tylko testowanie równości
to mapCompare
funkcja zgadza się z naszej intuicji na temat Mapy powinny być porównywane - porównujemy każdy klawisz z m1
wobec każdego klawisza od m2
. Uwaga, ten konkretny komparator robi płytkie porównanie.Będziemy obsługiwać głębokie porównania w chwilę
const mapCompare = f => (m1, m2) => {
const aux = (it, m2) => {
let {value, done} = it.next()
if (done) return true
let [k, v] = value
return f (v, m2.get(k), $=> aux(it, m2))
}
return aux(m1.entries(), m2) && aux(m2.entries(), m1)
}
Jedyną rzeczą, która może nie być od razu jasne jest $=> aux(it, m2)
thunk. Mapy mają wbudowany generator, .entries()
, a my możemy skorzystać z leniwej oceny, zwracając wczesną odpowiedź false
, gdy tylko zostanie znaleziona niesparowana para klucz/wartość. Oznacza to, że musimy napisać nasze komparatory w nieco szczególny sposób.
const shortCircuitEqualComparator = (a, b, k) =>
a === b ? true && k() : false
a
i b
są wartości z m1.get(somekey)
i m2.get(somekey)
odpowiednio. iff dwie wartości są ściśle równe(), tylko wtedy chcemy kontynuować porównanie - w tym przypadku zwracamy true && k()
, gdzie k()
jest pozostałą częścią porównania para klucz/wartość. Z drugiej strony, jeśli a
i b
nie pasują, możemy wrócić wcześnie false
i pominąć porównując resztę wartości - czyli już wiemy, że m1
i m2
nie pasują jeśli dowolnya
/b
pary NIE mecz.
Wreszcie, możemy zdefiniować mapEqual
- to również proste. To właśnie mapCompare
użyciu naszych specjalnych shortCircuitEqualComparator
const mapEqual = mapCompare (shortCircuitEqualComparator)
Rzućmy okiem na to, jak działa
// define two maps that are equal but have keys in different order
const a = new Map([['b', 2], ['a', 1]])
const b = new Map([['a', 1], ['b', 2]])
// define a third map that is not equal
const c = new Map([['a', 3], ['b', 2]])
// verify results
// a === b should be true
// b === a should be true
console.log('true', mapEqual(a, b)) // true true
console.log('true', mapEqual(b, a)) // true true
// a === c should be false
// c === a should be false too
console.log('false', mapEqual(a, c)) // false false
console.log('false', mapEqual(c, a)) // false false
Heck tak. Sprawy wyglądają dobrze ...
Głębokie porównań z Rick & Morty
Teraz mamy fricken słodki mapCompare
rodzajowy, głębokie porównanie jest bardzo proste. Zauważ, że faktycznie implementujemy mapDeepCompare
samemu za pomocą .
Używamy niestandardowego komparatora, który po prostu sprawdza, czy a
i b
są obiektami Map - jeśli tak, to powtarzamy korzystanie z mapDeepCompare
w zagnieżdżonych Mapach; pamiętaj też, aby zadzwonić pod numer ... && k()
, aby sprawdzić, czy pozostałe pary klucz/wartość są porównywane. Jeżeli a
lub b
jest obiektem non-Map, normalny porównanie robi używając f
i mijamy kontynuację k
wzdłuż bezpośrednio
const mapDeepCompare = f => mapCompare ((a, b, k) => {
console.log(a, b)
if (a instanceof Map && b instanceof Map)
return mapDeepCompare (f) (a,b) ? true && k() : false
else
return f(a,b,k)
})
Teraz mapDeepCompare
możemy wykonać dowolny rodzaj głębokiej porównania na zagnieżdżonych Maps . Pamiętaj, że równość to tylko jedna z rzeczy, które możemy sprawdzić.
Bez dalszych wyjaśnień, mapDeepEqual
. Co ważne, uzyskujemy ponowne użycie naszego shortCircuitEqualComparator
, które zdefiniowaliśmy wcześniej. To bardzo wyraźnie pokazuje, że nasze komparatory mogą być (ponownie) używane do płytkich porównań map lub.
const mapDeepEqual = mapDeepCompare (shortCircuitEqualComparator)
Zobaczmy to działa
// define two nested maps that are equal but have different key order
const e = new Map([
['a', 1],
['b', new Map([['c', 2]])]
])
const f = new Map([
['b', new Map([['c', 2]])],
['a', 1]
])
// define a third nested map that is not equal
const g = new Map([
['b', new Map([
['c', 3]
])],
['a', 1]
])
// e === f should be true
// f === e should also be true
console.log('true', mapDeepEqual(e, f)) // true
console.log('true', mapDeepEqual(f, e)) // true
// e === g should be false
// g === e should also be false
console.log('false', mapDeepEqual(e, g)) // false
console.log('false', mapDeepEqual(g, e)) // false
OK, a tylko upewnić. Co się stanie, jeśli zadzwonimy pod numer mapEqual
w naszych zagnieżdżonych Mapach e
i f
? Od mapEqual
robi płytkie porównanie, oczekujemy, że wynik powinien być false
console.log('false', mapEqual(e, f)) // false
console.log('false', mapEqual(f, e)) // false
I tam masz. Płytkie i głębokie porównanie obiektów mapy ES6. Można by napisać prawie identyczny zestaw funkcji do obsługi zestawu ES6. Zostawię to jako ćwiczenie dla czytelników.
kod Runnable demo
To jest cały kod powyżej zebrane w jednym runnable demo. Każde wywołanie console.log
wyprowadza <expected>, <actual>
. Tak więc true, true
lub false, false
byłoby testem przechodzącym - podczas gdy true, false
byłby testem zakończonym niepowodzeniem.
// mapCompare :: ((a, a, (* -> Bool)) -> Bool) -> (Map(k:a), Map(k:a)) -> Bool
const mapCompare = f => (m1, m2) => {
const aux = (it, m2) => {
let {value, done} = it.next()
if (done) return true
let [k, v] = value
return f (v, m2.get(k), $=> aux(it, m2))
}
return aux(m1.entries(), m2) && aux(m2.entries(), m1)
}
// mapDeepCompare :: ((a, a, (* -> Bool)) -> Bool) -> (Map(k:a), Map(k:a)) -> Bool
const mapDeepCompare = f => mapCompare ((a, b, k) => {
if (a instanceof Map && b instanceof Map)
return mapDeepCompare (f) (a,b) ? true && k() : false
else
return f(a,b,k)
})
// shortCircuitEqualComparator :: (a, a, (* -> Bool)) -> Bool
const shortCircuitEqualComparator = (a, b, k) =>
a === b ? true && k() : false
// mapEqual :: (Map(k:a), Map(k:a)) -> Bool
const mapEqual = mapCompare (shortCircuitEqualComparator)
// mapDeepEqual :: (Map(k:a), Map(k:a)) -> Bool
const mapDeepEqual = mapDeepCompare (shortCircuitEqualComparator)
// fixtures
const a = new Map([['b', 2], ['a', 1]])
const b = new Map([['a', 1], ['b', 2]])
const c = new Map([['a', 3], ['b', 2]])
const d = new Map([['a', 1], ['c', 2]])
const e = new Map([['a', 1], ['b', new Map([['c', 2]])]])
const f = new Map([['b', new Map([['c', 2]])], ['a', 1]])
const g = new Map([['b', new Map([['c', 3]])], ['a', 1]])
// shallow comparison of two equal maps
console.log('true', mapEqual(a, b))
console.log('true', mapEqual(b, a))
// shallow comparison of two non-equal maps (differing values)
console.log('false', mapEqual(a, c))
console.log('false', mapEqual(c, a))
// shallow comparison of two other non-equal maps (differing keys)
console.log('false', mapEqual(a, d))
console.log('false', mapEqual(d, a))
// deep comparison of two equal nested maps
console.log('true', mapDeepEqual(e, f))
console.log('true', mapDeepEqual(f, e))
// deep comparison of two non-equal nested maps
console.log('false', mapDeepEqual(e, g))
console.log('false', mapDeepEqual(g, e))
// shallow comparison of two equal nested maps
console.log('false', mapEqual(e, f))
console.log('false', mapEqual(f, e))
Ponieważ jak już wiesz Quint nie zrobi to za Ciebie, Twoje pytanie jest ograniczona do "Czy istnieje narzędzie do testowania jednostek, które rozumie zestawy i mapy ES6" (co prawdopodobnie powinno być tytułem), które nie jest "dobrym pytaniem" według zasad SO. Ewentualnie możesz poszukać odpowiedzi opartej na kodzie na swoje potrzeby, ale aktywnie odrzucasz taką odpowiedź. Więc jakiej odpowiedzi szukasz? – Amit
Nie wiem, że QUnit nie zadziała. Może być jakiś sposób, aby to zrobić, może nie przez "deepEqual". Nie jestem na tyle arogancki, aby złożyć takie oświadczenie jak "QUnit will not work", stąd pytanie. I nie odrzucam aktywnie odpowiedzi opartych na kodach. Chciałem tylko podkreślić, co robię w QUnit jako przybliżenie i jakie problemy to powoduje. Odpowiedź, której szukam: czy istnieje sposób na porównanie zestawów/map w QUnit? Jeśli nie, co mogę zrobić? – light