2017-03-21 5 views
4

Betrachten Sie das Problem der Zerlegung von Millisekunden in lesbare Zeiteinheiten. Stellen Sie sich eine Funktion hatte, die dasEine ganze Zahl in Blöcke zerlegen, funktionell

> breakupMillis(100000000) 
Array [ 0, 40, 46, 3, 1 ] 

was bedeutet, dass 100 Millionen Millisekunden haben 1 Nacht, 3 Stunden, 46 Minuten und 40 Sekunden genau.

Die Funktion könnte durch die Annahme einer Reihe von Modulen verallgemeinert werden, wie diese

> breakup(100000000, [1000, 60, 60, 24]) 
Array [ 0, 40, 46, 3, 1 ] 

Diese Funktion (hypothetisch) für andere Dinge verwendet werden könnten:

> breakup(1000, [8, 8, 8]) 
Array [ 0, 5, 7, 1 ] 

was bedeutet, dass in dezimal 1000 01750 in Oktal. Hier

ist die Funktion, die ich schrieb, dies zu tun:

const breakup = (n, l) => l.map(p => 
    { const q = n % p; n = (n - q)/p; return q; }).concat(n); 

Diese Funktion ist in Ordnung, es ist sogar referentiell transparent, aber ich habe zwei, ganz ästhetisch, Beschwerden.

  • die map. Das fühlt sich an wie ein Job für reduce, obwohl ich nicht sehe, wie.
  • Umschreiben der Variablen n. Ich mag es überhaupt nicht var zu verwenden; mit einem Geheimnisvar macht es schlimmer.

Meine Frage betrifft nur die zweite. Wie schreibe ich die Funktion neu, so dass sie keine Variable verwendet (die tatsächlich variieren)? Wenn die map verschwindet, nehme ich das als Soße.

+0

habe ich irgendwie das Gefühl, das auf Codereview – Tschallacka

+0

In Haskell gehört: ' breakup nl = zipMit mod l. scanl div n l ' – Bergi

Antwort

1

Das fühlt sich wie ein Job für reduce, obwohl ich nicht sehe, wie.

Alles, was ein Array iteriert mit reduce :-)

Wir müssen getan werden, um passieren zwei Dinge durch (für den Akku): die Nummer, die wir immer noch die Pause haben, und die Liste der Ergebnisse . Wir können ES6 Destrukturierung verwenden und ein Array als Tupel:

function breakup(n, units) { 
    const [n, res] = units.reduce(([n, res], u) => { 
     const q = n % u; 
     res.push((n-q)/u); 
     return [q, res]; 
    }, [n, units]); 
    return [n, ...res]; 
} 

Aber die push ist immer noch hässlich. Nicht nur weil es mutiert (wo wir auch concat hätten verwenden können), sondern was wir wirklich wollen, ist eine Funktion, die das abstrahiert. Leider gibt es diese nicht in JS - wir würden nach einer scan oder mapping accumulation suchen. Wir konnten

function breakup(n, units) { 
    const [rest, res] = units.mapAccum((n, u) => { 
     const q = n % u; 
     return [q, (n-q)/u]; 
    }, [n, units]); 
    return [...res, rest]; 
} 
function breakup(n, units) { 
    const mods = units.scan((n, u) => Math.floor(n/u), units); 
    return mods.map((q, i) => i<units.length ? q % units[i] : q); 
} 

schreiben Ich werde die (funktional, effizient, gut lesbare, usw.) Durchführung von denen als Übung dem Leser überlassen.

2

Hier ist eine andere Art und Weise Sie es tun können, eine rekursive Prozedur und ein kleiner Helfer mit quotrem - die n ein Zähler gegeben, und einen Nenner d, kehrt [<quotient>, <remainder>]

const quotrem = (n, d) => [n/d >> 0, n % d] 
 

 
const breakup = (n, [x,...xs]) => { 
 
    if (x === undefined) { 
 
    return [n] 
 
    } 
 
    else { 
 
    let [q, r] = quotrem(n, x) 
 
    return [r, ...breakup(q, xs)] 
 
    } 
 
} 
 

 
console.log(breakup(1000, [8, 8, 8])) 
 
// [ 0, 5, 7, 1 ] 
 

 
console.log(breakup(100000000, [1000, 60, 60, 24])) 
 
// [ 0, 40, 46, 3, 1 ]

Wenn Sie‘ Wenn Sie mit dem destrukturierten Array nicht besonders vertraut sind, können Sie weitere Helfer hinzufügen (isEmpty, head und tail), um mit dem Array zu interagieren in eine explizitere Weise

const isEmpty = xs => xs.length === 0 
 
const head = xs => xs[0] 
 
const tail = xs => xs.slice(1) 
 
const quotrem = (n, d) => [n/d >> 0, n % d] 
 

 
const breakup = (n, xs) => { 
 
    if (isEmpty(xs)) { 
 
    return [n] 
 
    } 
 
    else { 
 
    let [q, r] = quotrem(n, head(xs)) 
 
    return [r, ...breakup(q, tail(xs))] 
 
    } 
 
} 
 

 
console.log(breakup(1000, [8, 8, 8])) 
 
// [ 0, 5, 7, 1 ] 
 

 
console.log(breakup(100000000, [1000, 60, 60, 24])) 
 
// [ 0, 40, 46, 3, 1 ]

+0

Ich würde lieber für 'arguments [1] .length == 0' testen, nicht darauf verlassen, dass das Element niemals' undefiniert' ist. – Bergi

+1

@Bergi Danke für den Kommentar - Ich habe eine zweite Antwort hinzugefügt, die das Array ein bisschen anders behandelt^_^ – naomik

+0

Ich bin neugierig auf das genaue Verhalten von '>> 0'. Ist es identisch mit Math.floor()? – Malvolio

0

ich diesen Code würde vorschlagen:

const breakup = (n, l) => 
 
    l.reduce(([n, ...res], p) => ([(n - n % p)/p, n % p, ...res]), [n]) 
 
    .reverse(); 
 

 
// Demo call 
 
console.log(breakup(100000000, [1000, 60, 60, 24]));