Optionale Parameter für Methode, die Array entgegennimmt

Mat

Aktives Mitglied
Moin,

ich hab eine Hilfsfunktion für die gleichmäßig verteilte Darstellung von Elementen in Arrays. Im Moment habe ich nur 2 Parameter: Padding-Größe und auszugebender Array. Die Padding-Größe ist optional, aber muss übergeben werden, weil man Methoden natürlich nicht überladen kann.

Ich wollte noch weitere optionale Parameter einbauen, aber dann wird die Funktion zu unhandlich. Optionale Parameter an das Ende hängen ist auch problematisch, wenn vorher ein Array oder ...Rest-Parameter kommt. Deswegen dachte ich an ein Config-Objekt als 1. Parameter.

Wie mache ich das am besten? Für die hier gezeigte Funktion ist es nicht so wichtig, aber allgemein hätte ich gerne Autovervollständigung beim Objekt, damit es leichter konstruiert werden kann. Und ich möchte auch nicht alle Attribute jedes Mal auf null oder undefined prüfen müssen. Soll ich eine Klasse schreiben, die Felder und Getter anbietet? Oder eine Art generische Factory/Builder-Funktion?

Code
Javascript:
'use strict';

export default class DTHelper {
    /**
     * @example f(null, [5,3,12])   => [   5,  3, 12   ]
     * @example f(2, [1,2,3])       => [   1,  2,  3   ]
     * @param padding {null|Number}
     * @param elements {Array}
     * @return String
     */
    static asPrettyString(padding, elements) {
        let prettyArray = Array.from(elements);
        const maxLen = Math.max(
            padding,
            Math.max.apply(
                null,
                elements.map((elem) => this.#getElemLength(elem))
            )
        );

        prettyArray = prettyArray.map((elem) => {
            const elemLength = this.#getElemLength(elem);
            let prettyElement;
            if (elemLength === 0) {
                prettyElement = `${String.prototype.padStart(maxLen)} `;
            } else {
                prettyElement = `${String.prototype.padStart(maxLen - elemLength + 1)}${elem}`;
            }
            return prettyElement;
        });

        return `[ ${prettyArray.join()}${String.prototype.padStart(maxLen)} ]`;
    }

    static #getElemLength(elem) {
        return elem !== undefined && elem !== null ? elem.toString().length : 0;
    }
}

Beispiele

Javascript:
function beispiele() {
    let nums = [0, 1, 2, 3, 4, 5, 6, 7, 8];
    let testValues = [3, 9, 12, null, 5, 18, 99, 7, -12];

    /* Beispiel mit padding = null
                    [    0,   1,   2,   3,   4,   5,   6,   7,   8    ]
    padding auto:   [    3,   9,  12,    ,   5,  18,  99,   7, -12    ]
    */
    console.log(`${''.padEnd(16)}${DTHelper.asPrettyString(3, nums)}`);
    console.log(`${'padding auto:'.padEnd(16)}${DTHelper.asPrettyString(null, testValues)}\n`);

    /* Beispiel mit zu kleinem Wert
                    [    0,   1,   2,   3,   4,   5,   6,   7,   8    ]
    padding 2:      [    3,   9,  12,    ,   5,  18,  99,   7, -12    ]
    */
    console.log(`${''.padEnd(16)}${DTHelper.asPrettyString(3, nums)}`);
    console.log(`${'padding 2:'.padEnd(16)}${DTHelper.asPrettyString(2, testValues)}\n`);

    /* Beispiel mit optimalem Wert
                    [    0,   1,   2,   3,   4,   5,   6,   7,   8    ]
    padding 3:      [    3,   9,  12,    ,   5,  18,  99,   7, -12    ]
    */
    console.log(`${''.padEnd(16)}${DTHelper.asPrettyString(3, nums)}`);
    console.log(`${'padding 3:'.padEnd(16)}${DTHelper.asPrettyString(3, testValues)}\n`);

    /* Beispiel mit größerem Wert
                    [     0,    1,    2,    3,    4,    5,    6,    7,    8     ]
    padding 4:      [     3,    9,   12,     ,    5,   18,   99,    7,  -12     ]
    */
    console.log(`${''.padEnd(16)}${DTHelper.asPrettyString(4, nums)}`);
    console.log(`${'padding 4:'.padEnd(16)}${DTHelper.asPrettyString(4, testValues)}\n`);
}
 
Zuletzt bearbeitet:
Als mögliche Lösung fällt mir folgendes ein:
Javascript:
class Options {
    foo;
    bar;

    static defaults = {
        foo: 1,
        bar: 2,
    };
}

class Logic {
    /**
     *
     * @param {String} value
     * @param {Options} options
     */
    doStuff(value, options) {
        options = Object.assign({}, Options.defaults, options);
        options.foo; // 42
        options.bar; // 2
    }
}

let logic = new Logic();
logic.doStuff('hello world', {
    foo: 42,
});

In VSCode klappt dann auch die Autovervollständigung:
1601377639735.png


Gängiger ist aber sog. Parameter destructing.
Du hast dann zwar keine "options" Variable mehr, diese ist dann aber auch nicht mehr notwendig:
Javascript:
class Logic {
    doStuff(value, { foo = 1, bar = 2 } = {}) {
        console.log(foo); // 42
        console.log(bar); // 1
    }
}

let logic = new Logic();
logic.doStuff('hello world', {
    foo: 42,
});

Klappt auch mit Arrays:
Javascript:
class Logic {
    doStuff(value, [foo = 1, bar = 2] = []) {
        console.log(foo); // 42
        console.log(bar); // 1
    }
}

let logic = new Logic();
logic.doStuff('hello world', [42]);
 
Zuletzt bearbeitet:
Danke, Destructuring klingt interessant.. ist genau das was ich gesucht hatte

Edit: Ich will nur mal sehen, was der Codeformatter daraus macht:

Javascript:
class DTHelper {
    /**
     * @example f([5,3,12])   => [   5,  3, 12   ]
     * @example f([1,2,3], {padding: 2})       => [   1,  2,  3   ]
     * @example f([5,3,12], {brackets: false})   =>   5,  3, 12
     * @param elements {Array}
     * @param comma {Boolean}
     * @param brackets {Boolean}
     * @param padding {Number}
     * @return String
     */
    static asPrettyString(elements, { comma = true, brackets = true, padding = 0 } = {}) {
        let prettyArray = Array.from(elements);
        const maxLen = Math.max(
            padding,
            Math.max.apply(
                null,
                elements.map((elem) => this.#getElemLength(elem))
            )
        );

        prettyArray = prettyArray.map((elem) => {
            const elemLength = this.#getElemLength(elem);
            let prettyElement;
            if (elemLength === 0) {
                prettyElement = `${String.prototype.padStart(maxLen)} `;
            } else {
                prettyElement = `${String.prototype.padStart(maxLen - elemLength + 1)}${elem}`;
            }
            return prettyElement;
        });

        let joinChar = comma ? ',' : ' ';

        let prefix = ' ';
        let postfix = ' ';
        if (brackets) {
            prefix = '[';
            postfix = ']';
        }

        return `${prefix}${prettyArray.join(joinChar)}${String.prototype.padStart(maxLen)}${postfix}`;
    }
}

Code:
                  0  1  2  3  4  5  6  7  8 
padding 1:      [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]
 
Zuletzt bearbeitet:
Ich weiß du hast nicht danach gefragt, aber mir fallen ein paar Dinge auf die nicht ganz optimal sind.
Vielleicht hilft es dir ja :)

Javascript:
Math.max.apply(
    null,
    elements.map((elem) => this.#getElemLength(elem))
);
Das kann bei einem recht langem Array zu einem Stack-Overflow bzw. einem "too many function arguments" Fehler führen.
Sicherer (und vermutlich auch schneller) ist es hier mit einer einfachen Schleife zu arbeiten.

Ein ähnliches Problem hast du hier:
Javascript:
prettyArray = prettyArray.map((elem) => { ... })
Bei sehr großen Arrays hast du hier sehr viele Funktionsaufrufe.
Da du das Array sowieso überschreibst, kannst du hier ebenfalls mit einer Schleife arbeiten und die Einträge einfach überschreiben.
Zudem ruft du wieder für jedes Element #getElemLength() auf, was du in der oberen Funktion schon mal gemacht hast. Wenn sich die Länge der Elemente in den paar Zeilen nicht ändert (wovon auszugehen ist :p) kannst du die Längen auch zwischenspeichern und wiederverwenden.

Hier fallen mir zwei Sachen auf:
Javascript:
${String.prototype.padStart(maxLen)}
1. Warum generierst du den String immer wieder? Einmal würde reichen ;)
2. Du rufst padStart mit einem un-initialisierten String auf. Besser: ''.padStart(maxLen)

Zu guter Letzt:
Javascript:
prettyArray.join(joinChar)
Das könntest du direkt weiter oben erledigen, das spart dir eine weitere Iteration.

Hier eine überarbeitete Version:
Javascript:
class DTHelper {
    /**
     * @example f([5,3,12])   => [   5,  3, 12   ]
     * @example f([1,2,3], {padding: 2})       => [   1,  2,  3   ]
     * @example f([5,3,12], {brackets: false})   =>   5,  3, 12
     * @param elements {Array}
     * @param comma {Boolean}
     * @param brackets {Boolean}
     * @param padding {Number}
     * @return String
     */
    static asPrettyString(elements, { comma = true, brackets = true, padding = 0 } = {}) {
        const numElements = elements.length;

        if (numElements === 0) {
            return brackets ? '[]' : '  ';
        }

        let prettyString = '';
        let maxLen = padding;
        let elemLengths = new Array(numElements);
        let elemIndex = 0;

        for (elemIndex = 0; elemIndex < numElements; ++elemIndex) {
            const element = elements[elemIndex];
            // tipp: `(expr) == null` ist true bei null UND undefined
            // weil null == undefined
            const elemLength = element == null ? 0 : String(element).length;
            if (elemLength > maxLen) {
                maxLen = elemLength;
            }
            elemLengths[elemIndex] = elemLength;
        }

        const fixedPadding = ' '.repeat(maxLen);
        const joinChar = comma ? ',' : ' ';

        for (elemIndex = 0; elemIndex < numElements; ++elemIndex) {
            const elemLength = elemLengths[elemIndex];
            if (elemLength === 0) {
                prettyString += fixedPadding;
            } else {
                prettyString += ' '.repeat(maxLen - elemLength + 1);
                prettyString += elements[elemIndex];
            }

            prettyString += joinChar;
        }

        prettyString = prettyString.slice(0, -joinChar.length);

        let prefix = ' ';
        let postfix = ' ';
        if (brackets) {
            prefix = '[';
            postfix = ']';
        }

        return `${prefix}${prettyString}${fixedPadding}${postfix}`;
    }
}
 
Zuletzt bearbeitet:
Verwöhn' mich doch nicht so sehr :D

Edit: Ich hab noch nicht gelernt, wie man am besten Unit- und Performance-Tests in JS umsetzen kann.. aber wenn ich das erstmal hab, fällt mir sowas hoffentlich auch selbst auf.

Bezüglich des Stackoverflows.. ich sollte sowieso die maximale Größe übermittelter Arrays beschränken, da es ja eine Visualisierungshilfe sein soll. Ab einer gewissen Menge an Elementen reicht der Bildschirm nicht mehr aus.

Was null == undefined angeht: danke für den Tipp. Ich hatte nur noch im Hinterkopf, dass NaN != NaN true ist und deswegen Null Vertrauen in ähnliche Vergleiche. o_O
 
Zuletzt bearbeitet:
Zurück
Oben Unten