Nivå upp din .sortera spel

0
22

Sortering är en super smidiga JavaScript-metod som kan visa värden i en matris i en viss ordning. Oavsett om det är fastigheter listor av pris, burger leder av avstånd, eller i närheten glada timmar med betyg, sortering kedjor av information är ett gemensamt behov.

Om du redan gör detta med JavaScript på ett projekt, du kommer sannolikt att använda den inbyggda array .sortera metod, som är i samma familj i rad metoder som ingår .filter .karta och .minska.

Låt oss ta en titt på hur man gör det!

En snabb anteckning om biverkningar

Innan vi går in på detaljer om hur att använda .sortera, det är en mycket viktig detalj som måste åtgärdas. Medan många av de ES5 rad metoder, till exempel .filter .karta och .minska kommer tillbaka en ny matris och lämna den ursprungliga orörda .sortera för att sortera arrayen på plats. Om detta är oönskade, en ES6 teknik för att undvika detta är att använda sprida operatör för att på ett kortfattat sätt skapa en ny array.

const foo = [“c”, “b”, “a”];
const bar = [‘x’,’z’,’y’];
const fooSorted = foo.sortera();
const barSorted = […bar].sortera();

konsolen.log({foo, fooSorted, bar, barSorted});

/*
{
“foo”: [ “a”, “b”, “c” ],
“fooSorted”: [ “a”, “b”, “c” ],
“bar”: [ “x”, “z”, “y” ],
“barSorted”: [ “x”, “y”, “z” ]
}
*/

foo och fooSorted båda refererar till samma array, men bar och barSorted är nu enskilda kedjor.

Allmän översikt

Den enda parameter .sortera metod är en funktion. De specifikationer som hänvisar till detta som compareFn — jag kommer att hänvisa till det som “jämförelse-funktionen” för resten av inlägget. Denna jämförelse funktion som tar emot två parametrar, som jag kommer att hänvisa till som a och b. a och b är två punkter som vi kommer att jämföra. Om du inte ger en jämförelse funktion, matrisen kommer att tvinga varje element i en sträng och sortera enligt Unicode-poäng.

Om du vill att en vara beställs först i matrisen, jämförelse-funktionen ska returnera ett negativt heltal; för b, ett positivt heltal. Om du vill att de två för att behålla sina nuvarande ordning, returnera 0.

Om du inte förstår, oroa dig inte! Förhoppningsvis kommer det att bli mycket mer tydliga med några exempel.

Att jämföra siffror

Ett av de enklaste callbacks för att skriva är ett nummer som jämförelse.

const tal = [13,8,2,21,5,1,3,1];
const byValue = (a,b) => a – b;
const sorteras = […nummer].sortera(byValue);
konsolen.log(sorterade); // [1,1,2,3,5,8,13,21]

Om a är större än b, a – b kommer tillbaka ett positivt tal, så att b kommer att sorteras först.

Jämföra strängar

När man jämför strängarna, > och < operatörer kommer att jämföra värden baserat på varje sträng är Unicode-värde. Detta innebär att alla versaler kommer att vara “mindre” än alla gemener, vilket kan leda till oväntade resultat.

JavaScript har en metod för att hjälpa till med att jämföra strängar: String.prototyp.localeCompare metod. Denna metod accepterar en jämförelse sträng, ett språk och en alternativ objekt. De alternativ som objektet accepterar några egenskaper (som du kan se här), men jag tycker att de mest användbara är “känslighet.” Detta kommer att påverka hur jämförelser mellan arbete brev variationer såsom fallet och accent.

const strängar = [‘Über’, ‘alpha’, ‘Nitälskan’, ‘über’, ‘om’, ‘Om’, ‘Alpha’, ‘nitälskan’];

const sortBySensitivity = känslighet => (a, b) => a.localeCompare(
b,
odefinierad, // locale string — odefinierat sätt att använda webbläsaren standard
{ känslighet }
);

const byAccent = sortBySensitivity (accent’);
const byBase = sortBySensitivity (“bas”);
const byCase = sortBySensitivity (“fallet”);
const byVariant = sortBySensitivity(‘variant’); // default

const accentSorted = […stråkar].sortera(byAccent);
const baseSorted = […stråkar].sortera(byBase);
const caseSorted = […stråkar].sortera(byCase);
const variantSorted = […stråkar].sortera(byVariant);

konsolen.log({accentSorted, baseSorted, caseSorted, variantSorted});

/*
{
“accentSorted”: [ “alpha”, “Alfa”, “uber”, “Uber”, “Über”, “über”, “Entusiasm”, “entusiasm” ],
“baseSorted”: [ “alpha”, “Alfa”, “Über”, “über”, “uber”, “Uber”, “Entusiasm”, “entusiasm” ],
“caseSorted”: [ “alpha”, “Alfa”, “über”, “uber”, “Über”, “Uber”, “entusiasm”, “Entusiasm” ],
“variantSorted”: [ “alpha”, “Alfa”, “uber”, “Uber”, “über”, “Über”, “entusiasm”, “Entusiasm” ]
}
*/

För mig, baseSorted verkar vara den mest logiska för de flesta alfabetisk sortering — ‘ü’, ‘u’, ‘Ü’, och ” U ” är likvärdiga, så att de förblir i den ordning som i den ursprungliga matrisen.

Kör funktioner innan jämföra värden

Du kanske vill köra en jämförelse-funktionen på ett värde som härrör från varje array-element. Första, låt oss skriva en jämförelse funktion fabriken som kommer att “karta” över elementet innan du ringer jämförelse funktion.

const sortByMapped = (karta,compareFn) => (a,b) => compareFn(karta(a),karta(b));

Ett användningsfall för detta är sortering baserat på attribut för ett objekt.

const inköp = [
{ name: ‘Popcorn’, pris: 5.75 },
{ name: “Film-Biljett”, pris: 12 },
{ name: ‘Soda’, pris: 3.75 },
{ name: ‘Godis’, pris: 5 },
];

const sortByMapped = (karta,compareFn) => (a,b) => compareFn(karta(a),karta(b));
const byValue = (a,b) => a – b;
const toPrice = e => e.pris;
const byPrice = sortByMapped(toPrice,byValue);

konsolen.log([…köp].sortera(byPrice));

/*
[
{ name: “Soda”, pris: 3.75 },
{ name: “Godis”, pris: 5 },
{ name: “Popcorn”, pris: 5.75 },
{ name: “biobiljett”, pris: 12 }
]
*/

Ett annat fall kan vara att jämföra en rad av datum.

const datum = [‘2018-12-10’, ‘1991-02-10’, ‘2015-10-07’, ‘1990-01-11’];
const sortByMapped = (karta,compareFn) => (a,b) => compareFn(karta(a),karta(b));
const toDate = e => new Date(e).getTime();
const byValue = (a,b) => a – b;
const byDate = sortByMapped(uppdaterad,byValue);

konsolen.log([…datum].sortera(byDate));
// [“1990-01-11”, “1991-02-10”, “2015-10-07”, “2018-12-10”]

Att vända ett slags

Det finns vissa fall där du kanske vill omvända resultatet av en jämförelse funktion. Detta är subtilt annorlunda än att göra en form och sedan vända resultatet i vägen band hanteras: om du omvända resultatet, band kommer också att vara i omvänd ordning.

För att skriva en högre ordningens funktion som accepterar en jämförelse funktion och returnerar en ny, du kommer att behöva för att vända tecken på att den jämförelsen är returvärdet.

const flipComparison = fn => (a,b) => -fn(a,b);
const byAlpha = (a,b) => a.localeCompare(b, null, { känslighet: ‘bas’ });
const byReverseAlpha = flipComparison(byAlpha);

konsolen.log([‘A’, ‘B’, ‘C’].sortera(byReverseAlpha)); / / [“C”, “B”, “A”]

Kör en tiebreaker sortera

Det finns tillfällen då du kanske vill ha ett “tie-breaker” form — det är en annan jämförelse funktion som används i händelse av oavgjort.

Genom att använda [].minska, du kan platta till en rad funktioner jämförelse till en enda.

const sortByMapped = map => compareFn => (a,b) => compareFn(karta(a),karta(b));
const flipComparison = fn => (a,b) => -fn(a,b);
const byValue = (a,b) => a – b;

const byPrice = sortByMapped(e => e.pris: byValue);
const byRating = sortByMapped(e => e.rating)(flipComparison(byValue));

const sortByFlattened = fns => (a,b) =>
fns.minska((acc, fn) => acc: | | fn(a,b), 0);

const byPriceRating = sortByFlattened([byPrice,byRating]);

const restauranger = [
{ name: “Foo’ s Burger Stand”, pris till: 1, betyg: 3 },
{ name: “Tapas Bar”, pris till: 3, betyg: 4 },
{ name: “Baz Pizza”, pris till: 3, betyg: 2 },
{ name: “Fantastisk Affär”, pris till: 1, betyg: 5 },
{ name: “Dyr”, pris: 5, betyg: 1 },
];

konsolen.log(restauranger.sortera(byPriceRating));

/*
{name: “Fantastisk Affär”, pris till: 1, betyg: 5}
{name: “Foo’ s Burger Stand”, pris till: 1, betyg: 3}
{name: “Tapas Bar”, pris till: 3, betyg: 4}
{name: “Baz Pizza”, pris till: 3, betyg: 2}
{name: “Dyr”, pris: 5, betyg: 1}
*/

Att skriva en slumpmässig sortera

Du kanske vill sortera en array “slumpmässigt.” En teknik som jag har sett är att använda följande funktion som jämförelse funktion.

const byRandom = () => Math.random() – .5;

Eftersom Matte.random() returnerar en “random” nummer mellan 0 och 1, byRandom funktionen ska returnera ett positivt tal hälften av tiden och ett negativt tal för den andra hälften. Detta verkar som om det skulle vara en bra lösning, men tyvärr, eftersom jämförelse-funktionen är inte “konsekvent” — vilket betyder att det inte får återvända till samma värde när den anropas flera gånger, med samma värderingar — det kan leda till att vissa oväntade resultat.

Till exempel, låt oss ta en matris med tal mellan 0 och 4. Om detta byRandom funktion var verkligen slumpmässigt, det kunde förväntas att det nya indexet i varje nummer skulle spridas ut jämnt över tillräckligt många iterationer. Den ursprungliga 0 värde som skulle vara lika sannolikt att vara i index-4 index 0 i den nya matrisen. Men i praktiken kommer denna funktion för bias varje nummer till sin ursprungliga position.

Se Pennan
Array.sortera() Random 👎 av Adam Giese (@AdamGiese)
på CodePen.

Avenida diagonal från övre vänstra statistiskt har störst värde. I en perfekt och verkligen är slumpmässig sortera, varje cell i tabellen skulle ligga på omkring 20%.

Fix för detta är att hitta ett sätt att se till att den jämförelse funktionen förblir konsekvent. Ett sätt att göra detta är att kartlägga slumpmässigt värde till varje element i arrayen innan jämförelse, så kort det bort efter.

const sortByMapped = map => compareFn => (a,b) => compareFn(karta(a),karta(b));
const värden = [0,1,2,3,4,5,6,7,8,9];
const withRandom = (e) => ({ random: Matematik.random(), original: e });
const toOriginal = ({ursprungliga}) => originalet;
const toRandom = ({slumpmässiga}) => slumpmässigt,
const byValue = (a,b) => a – b;
const byRandom = sortByMapped(toRandom)(byValue);

const shuffleArray = array => array
.karta(withRandom)
.sortera(byRandom)
.karta(toOriginal);

Detta säkerställer att varje element har en enda slumpmässigt värde som beräknas endast en gång per element snarare än att en gång per jämförelse. Detta tar bort sortering inriktning mot den ursprungliga position.

Se Pennan
Array.sortera() Random 👍 av Adam Giese (@AdamGiese)
på CodePen.