<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Steve Adams]]></title><description><![CDATA[Writing from Victoria, BC, Canada.]]></description><link>https://steve-adams.me/</link><image><url>https://steve-adams.me/favicon.png</url><title>Steve Adams</title><link>https://steve-adams.me/</link></image><generator>Ghost 5.52</generator><lastBuildDate>Fri, 23 Feb 2024 07:15:20 GMT</lastBuildDate><atom:link href="https://steve-adams.me/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Arc]]></title><description><![CDATA[<p>I&apos;m old and inflexible, like an ancient tortoise or dried out clay, but when I discovered the <a href="https://arc.net/">Arc web browser</a> I was an instant convert. Literally instant. I haven&apos;t used Safari, Chrome, or Firefox since.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://images.unsplash.com/photo-1613142007386-6e5aabd52b68?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDM0fHx0b3J0b2lzZXxlbnwwfHx8fDE3MDY4NTA3MjZ8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" class="kg-image" alt loading="lazy" width="5740" height="3827" srcset="https://images.unsplash.com/photo-1613142007386-6e5aabd52b68?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDM0fHx0b3J0b2lzZXxlbnwwfHx8fDE3MDY4NTA3MjZ8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=600 600w, https://images.unsplash.com/photo-1613142007386-6e5aabd52b68?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDM0fHx0b3J0b2lzZXxlbnwwfHx8fDE3MDY4NTA3MjZ8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1000 1000w, https://images.unsplash.com/photo-1613142007386-6e5aabd52b68?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDM0fHx0b3J0b2lzZXxlbnwwfHx8fDE3MDY4NTA3MjZ8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1600 1600w, https://images.unsplash.com/photo-1613142007386-6e5aabd52b68?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDM0fHx0b3J0b2lzZXxlbnwwfHx8fDE3MDY4NTA3MjZ8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2400 2400w" sizes="(min-width: 720px) 720px"><figcaption>Photo by <a href="https://unsplash.com/@sleblanc01?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Stephanie LeBlanc</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></figcaption></figure><p>I&apos;m not exactly sure</p>]]></description><link>https://steve-adams.me/arc/</link><guid isPermaLink="false">65bc7837617e353603b336e7</guid><dc:creator><![CDATA[Steve Adams]]></dc:creator><pubDate>Fri, 02 Feb 2024 05:20:34 GMT</pubDate><content:encoded><![CDATA[<p>I&apos;m old and inflexible, like an ancient tortoise or dried out clay, but when I discovered the <a href="https://arc.net/">Arc web browser</a> I was an instant convert. Literally instant. I haven&apos;t used Safari, Chrome, or Firefox since.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://images.unsplash.com/photo-1613142007386-6e5aabd52b68?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDM0fHx0b3J0b2lzZXxlbnwwfHx8fDE3MDY4NTA3MjZ8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" class="kg-image" alt loading="lazy" width="5740" height="3827" srcset="https://images.unsplash.com/photo-1613142007386-6e5aabd52b68?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDM0fHx0b3J0b2lzZXxlbnwwfHx8fDE3MDY4NTA3MjZ8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=600 600w, https://images.unsplash.com/photo-1613142007386-6e5aabd52b68?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDM0fHx0b3J0b2lzZXxlbnwwfHx8fDE3MDY4NTA3MjZ8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1000 1000w, https://images.unsplash.com/photo-1613142007386-6e5aabd52b68?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDM0fHx0b3J0b2lzZXxlbnwwfHx8fDE3MDY4NTA3MjZ8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1600 1600w, https://images.unsplash.com/photo-1613142007386-6e5aabd52b68?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDM0fHx0b3J0b2lzZXxlbnwwfHx8fDE3MDY4NTA3MjZ8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2400 2400w" sizes="(min-width: 720px) 720px"><figcaption>Photo by <a href="https://unsplash.com/@sleblanc01?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Stephanie LeBlanc</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></figcaption></figure><p>I&apos;m not exactly sure what they got so right that I switched so easily. Here&apos;s my favourite stuff:</p><ul><li>The front-and-centre search bar is nice. The suggestions in the bar are useful.</li><li>The vertical tabs and <a href="https://resources.arc.net/hc/en-us/articles/19228419623447-Folders-Stash-Similar-Tabs-Together">folders</a> layout is something I didn&apos;t expect to love, but I can see people disliking it too. I know it isn&apos;t new-new, but the implementation is excellent. The folder implementation is awesome.</li><li><a href="https://resources.arc.net/hc/en-us/articles/19335393146775-Split-View-View-Multiple-Tabs-at-Once">Split views</a> come in handy a lot more than I thought they would.</li><li>The ability to quickly switch between <a href="https://resources.arc.net/hc/en-us/articles/19228064149143-Spaces-Distinct-Browsing-Areas">&quot;spaces&quot;</a> is awesome. Another not-totally-new thing which is done so well that it feels new.</li><li><a href="https://resources.arc.net/hc/en-us/articles/19235387524503-Little-Arc-Quick-Lookups-Instant-Triaging">Little Arc</a>, a minimal window version of the browser, is a really nice lightweight accompaniment to the primary browser when you only need to quickly peek at a link from within another app.</li><li><a href="https://resources.arc.net/hc/en-us/articles/19228855311127-Auto-Archive-Clean-as-you-go">Autoarchive</a> saves me from myself. I rarely even remember what it cleared away. I don&apos;t need to see what I mistakenly thought was relevant yesterday. If I can&apos;t remember it today, it doesn&apos;t matter. Get rid of it. Thanks, Arc.</li><li>It looks great. It&apos;s minimal without sacrificing anything at all. That&apos;s hard to do. They have some talented people working there.</li></ul><p>Maybe it&apos;s the sum of all of these parts. I think what impresses me most is how intuitive and easy these features were to find despite the UI being fairly trimmed back by default. I didn&apos;t need to dig deep, I haven&apos;t had to wonder how to do anything, and when I&apos;ve encountered new features I didn&apos;t need to think too hard to start using them. They&apos;re bringing fresh form and function to a stale software category.</p><p>Something else that inspires a lot of confidence and appreciation is how hard they work on it. Updates are frequent, and they&apos;re often meaningful updates. They communicate clearly. It&apos;s a browser that feels like someone cares about it. It isn&apos;t a utility manufactured exclusively to get people to use Google Search.</p><p>I don&apos;t often like software enough to write about it, but here I am. What an awesome take on the web browser. I hope they succeed and keep at it.</p><p>If you&apos;re an arc user and you think YouTube shorts are trash like I do, here&apos;s a great &quot;boost&quot; from the Browser Company themselves: <a href="https://arc.net/boost/C05314CD-FE01-43E2-8D5C-D4B15BB4879B">No YouTube Shorts Plz</a>.</p>]]></content:encoded></item><item><title><![CDATA[TypeScript Benchmarking with mitata]]></title><description><![CDATA[See how you can keep tabs on performance with mitata: a super slick, Rust-based benchmarking library.]]></description><link>https://steve-adams.me/typescript-benchmarking-mitata/</link><guid isPermaLink="false">648caa8b9d3656469e5365cb</guid><category><![CDATA[benchmarking]]></category><category><![CDATA[typescript]]></category><category><![CDATA[mitata]]></category><dc:creator><![CDATA[Steve Adams]]></dc:creator><pubDate>Thu, 16 Feb 2023 11:42:00 GMT</pubDate><content:encoded><![CDATA[<p>Way back, <a href="https://steve-adams.me/summing-the-largest-values-javascript-array/">I wrote about benchmarking</a> some sloppy interview code using jsperf.com (now <a href="https://jsperf.app">jsperf.app</a>). The gist of it is that I wrote a naive array searching algorithm for finding the two largest numbers in an array, and upon discovering just how bad my solution was, I decided to benchmark it to properly stomp on my self esteem a little bit. The contrast between the first solution and the second was surprising and incredibly insightful, and since then I&apos;ve loved leaning on benchmarks to learn more and hone in on better solutions.</p><p>These days I do most of my benchmarking in Go and Rust because it&apos;s implemented in their standard libraries so well; there&apos;s no reason not to do it. I&apos;m not as diligent when it comes to TypeScript, so I wanted to revisit that old benchmark and try to devise a convention I&apos;d actually use, regardless of which runtime I&apos;m using.</p><p>Vitest offers an experimental <code>bench</code> command (which would be great to use because I use Vitest all the time) but it&apos;s fairly unstable still and uses tinybench under the hood, which I&apos;m not as happy with as mitata. Deno and Bun have integrated mitata into their standard library which is great to see, but I don&apos;t typically use them. So, I&apos;m going to set up a bare bones solution without any special tooling or conventions.</p><h2 id="caveats">Caveats</h2><p>It looks like mitata hasn&apos;t been updated for a while now, and while it&apos;s stable and works very well, there are some things some people might believe should be ironed out. There are some relatively basic features that would be great to see, like a reporter API or a timeout argument for groups or single benchmarks. Even so, it&apos;s well-featured for how simple and nice it is to use, and I&apos;m not going to let that get in the way.</p><p>Although people tend to be resistant to the idea, each of these features can be implemented in a benchmark running abstraction, and my impression is that this might be what the creator of mitata thinks is the correct solution.</p><h2 id="setup">Setup</h2><h3 id="packagejson"><code>package.json</code></h3><p>I like to write tests to ensure what I&apos;m benchmarking is working properly and as expected, so I&apos;m including Vitest; you can feel free to exclude it. I&apos;m also including prettier out of habit and it&apos;s safe to leave out as well.</p><p>To run the benchmarks which will be written in TypeScript, I&apos;m using <code>typescript</code>, <code>glob</code>, and <code>ts-node</code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-json">{
  ...
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;tsc --p tsconfig.build.json&quot;,
    &quot;test&quot;: &quot;vitest --reporter=verbose&quot;,
    &quot;benchmark&quot;: &quot;ts-node scripts/benchmark.ts&quot;,

  },
  &quot;devDependencies&quot;: {
    &quot;glob&quot;: &quot;^10.2.7&quot;,
    &quot;mitata&quot;: &quot;^0.1.6&quot;,
    &quot;prettier&quot;: &quot;^2.8.8&quot;,
    &quot;ts-node&quot;: &quot;^10.9.1&quot;,
    &quot;typescript&quot;: &quot;^5.1.3&quot;,
    &quot;vitest&quot;: &quot;^0.32.2&quot;
  }
}
</code></pre><figcaption><code>package.json</code></figcaption></figure><h3 id="tsconfigjson"><code>tsconfig.json</code></h3><p>The TypeScript configuration is typical except for a <code>ts-node</code> entry. This is used to modify the compilation for running scripts with <code>ts-node</code>, which we don&apos;t necessarily <em>have</em> to do for this example but under real-world circumstances I expect you would. Likely the most modern feature you need is top-level await, making it so you can await the <code>run</code> function exported from <code>mitata</code>. You can <a href="https://typestrong.org/ts-node/docs/configuration/">learn more about this <code>ts-node</code> convention here</a>. I&apos;ve also added a specific secondary config for builds, which avoids compiling any benchmarking or test files and provides an output destination:</p><figure class="kg-card kg-code-card"><pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;module&quot;: &quot;es2022&quot;,
    &quot;moduleResolution&quot;: &quot;node&quot;,
    &quot;target&quot;: &quot;es2017&quot;
  },
  &quot;include&quot;: [&quot;src&quot;],

  &quot;ts-node&quot;: {
    &quot;esm&quot;: true,
    &quot;experimentalSpecifierResolution&quot;: &quot;node&quot;,
    &quot;compilerOptions&quot;: {
      &quot;module&quot;: &quot;es2022&quot;,
      &quot;target&quot;: &quot;es2022&quot;
    }
  }
}
</code></pre><figcaption><code>tsconfig.json</code></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-json">{
  &quot;extends&quot;: &quot;./tsconfig.json&quot;,
  &quot;compilerOptions&quot;: {
    &quot;outDir&quot;: &quot;./javascript&quot;
  },
  &quot;exclude&quot;: [&quot;**/*.test.ts&quot;, &quot;**/*.bench.ts&quot;]
}</code></pre><figcaption><code>tsconfig.build.json</code></figcaption></figure><h3 id="the-benchmarking-script">The Benchmarking Script</h3><p>I&apos;ve created a <code>./scripts</code> directory with a file called <code>benchmark.ts</code>. This file uses <code>glob</code> to find all of our benchmarks and execute them:</p><figure class="kg-card kg-code-card"><pre><code class="language-typescript">import { glob } from &apos;glob&apos;;
import * as path from &apos;path&apos;;

const executeFile = async (file: string) =&gt; {
  const absolutePath = path.resolve(file);
  return await import(absolutePath).then((module) =&gt; module);
};

try {
  const files = await glob(&apos;**/*.bench.ts&apos;);

  for (const file of files) {
    await executeFile(file);
  }
} catch (e) {
  console.error(e);
}
</code></pre><figcaption><code>benchmark.ts</code></figcaption></figure><p>This is rudimentary at this point, but it&apos;s loaded with potential. Although <code>mitata</code> always outputs results to stdout for you to view in the terminal, you&apos;ll see later that it also provides a way for us to read and, if we choose to, write results from the benchmarks here.</p><h2 id="writing-a-benchmark">Writing a Benchmark</h2><p>This part is satisfyingly easy. In its most basic form, all you need to do is end a file&apos;s name with <code>.bench.ts</code>, wrap what you&apos;re measuring in <code>bench()</code> and then <code>run()</code> it:</p><figure class="kg-card kg-code-card"><pre><code class="language-typescript">import { run, bench } from &apos;mitata&apos;;

const helloTemplate = (name: string) =&gt; `hello ${name}!`;
const helloConcat = (name: string) =&gt; &apos;hello &apos; + name;

bench(&apos;hello (template string)&apos;, () =&gt; helloTemplate(&apos;world&apos;));
bench(&apos;hello (concatenation)&apos;, () =&gt; helloConcat(&apos;world&apos;));

await run();</code></pre><figcaption><code>hello.bench.ts</code></figcaption></figure><p>Now we can use the <code>bench</code> script from <code>package.json</code> to run this benchmark:</p><pre><code class="language-bash">$ yarn bench

benchmark                    time (avg)             (min &#x2026; max)       p75       p99      p995
--------------------------------------------------------------- -----------------------------
hello (template string)  434.19 ps/iter  (284.9 ps &#x2026; 902.06 ns)  366.8 ps   1.56 ns   2.27 ns
hello (concatenation)      7.62 ns/iter   (5.85 ns &#x2026; 190.93 ns)   7.45 ns  20.89 ns  31.52 ns</code></pre><h3 id="runs-options"><code>run</code>&apos;s Options</h3><p>In the example above I&apos;m using <code>run</code> with no options passed in, but there are several to choose from:</p><figure class="kg-card kg-code-card"><pre><code class="language-typescript">export function run(options?: {
  avg?: boolean,
  colors?: boolean,
  min_max?: boolean,
  collect?: boolean,
  percentiles?: boolean,
  json?: number | boolean,
}): Promise&lt;Report&gt;;</code></pre><figcaption>Taken from mitata&apos;s cli.d.ts types</figcaption></figure><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Option</th>
<th>Default</th>
<th>Explainer</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>avg</code></td>
<td><code>true</code></td>
<td>show the <code>time (avg)</code> column (shows average time per iteration)</td>
</tr>
<tr>
<td><code>min_max</code></td>
<td><code>true</code></td>
<td>show the <code>min...max</code> column (fastest (<code>min</code>) and slowest (<code>max</code>) iteration times)</td>
</tr>
<tr>
<td><code>percentiles</code></td>
<td><code>true</code></td>
<td>show the <code>p75</code>, <code>p99</code>, <code>p995</code> columns</td>
</tr>
<tr>
<td><code>collect</code></td>
<td><code>false</code></td>
<td>collect returned values into an array during benchmark</td>
</tr>
<tr>
<td><code>colors</code></td>
<td><code>true</code></td>
<td>enable colors in <strong>non-json</strong> output</td>
</tr>
<tr>
<td><code>json</code></td>
<td><code>false</code></td>
<td>output results as json</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p><em>Note: I haven&apos;t figured out how <code>collect</code> does anything meaningfully different, but the rest are fairly straight forward.</em></p><h3 id="runs-return-value"><code>run</code>&apos;s Return Value</h3><p>You might have noticed <code>run</code> returns a <code>Promise&lt;Report&gt;</code>; the report object looks like this:</p><figure class="kg-card kg-code-card"><pre><code class="language-typescript">interface Report {
  cpu: string;
  runtime: string;

  benchmarks: {
    name: string;
    time: number;
    fn: () =&gt; any;
    async: boolean;
    warmup: boolean;
    baseline: boolean;
    group: string | null;

    error?: {
      stack: string;
      message: string;
    };

    stats?: {
      n: number;
      avg: number;
      min: number;
      max: number;
      p75: number;
      p99: number;
      p995: number;
      p999: number;
      jit: number[];
    };
  }[];
}</code></pre><figcaption>Interestingly, <code>p999</code> is excluded from the percentile columns</figcaption></figure><p>The <code>Report</code> object struck me as the perfect way to store data about benchmarks after they complete. While the run&apos;s results will always output to stdout, you can use that as feedback while the actual results are piped into files for later use.</p><p>I haven&apos;t quite figured out how I want to use the reports yet, but I&apos;d love to create a convention for identifying functions and their changes over time, and tracking their performance along with each diff. This could help point out major performance improvements or degradations during pull requests or other stages of development, but it wouldn&apos;t require any special effort from developers &#x2014; you&apos;d just need to write the benchmarks.</p><h3 id="benchmarking-sumtwolargestnumbers">Benchmarking sumTwoLargestNumbers</h3><p>Like the example above, this is about as easy. I&apos;m starting with the two implementations I had in my post from 2017:</p><figure class="kg-card kg-code-card"><pre><code class="language-typescript">export type TwoOrMoreNumbers = [number, number, ...number[]];

/**
 * Sums the two largest numbers in an array of numbers with an O(n) algorithm.
 */
export const sumTwoLargestNumbers = (numbers: TwoOrMoreNumbers): number =&gt; {
  let largest: number, secondLargest: number;
  const length = numbers.length;

  if (length &lt; 2) {
    throw new Error(&apos;Expected an array with at least 2 elements.&apos;);
  }

  // Assign our starting values
  largest = numbers[0];
  secondLargest = numbers[1];

  // Ensure we&apos;re starting with largest and secondLargest properly arranged
  if (largest &lt; secondLargest) {
    largest = numbers[1];
    secondLargest = numbers[0];
  }

  // Loop through the numbers
  for (let i = 2; i &lt; length; i++) {
    // If the new number is greater than largest, assign it to largest and
    // pass largest&apos;s value to secondLargest.
    if (numbers[i] &gt; largest) {
      secondLargest = largest;
      largest = numbers[i];
    }
    // If the new number isn&apos;t greater than largest, check if it&apos;s still
    // greater than secondLargest.
    else if (numbers[i] &gt; secondLargest) {
      secondLargest = numbers[i];
    }
  }

  return largest + secondLargest;
};

/**
 * Sums the two largest numbers in an array of numbers using an n*log(n) algorithm (picking from quicksort).
 */
export const sumTwoLargestNumbersSort = (numbers: TwoOrMoreNumbers): number =&gt; {
  // Is the array long enough to sum?
  if (numbers.length &lt; 2) {
    throw new TypeError(&apos;Expected an array with at least 2 elements.&apos;);
  }

  // Sort the array large -&gt; small
  numbers = numbers.sort(function (a, b) {
    return b - a;
  });

  return numbers[0] + numbers[1];
};
</code></pre><figcaption><code>sumTwoLargestNumbers.ts</code></figcaption></figure><p></p><p>Then to write the benchmarks, I&apos;m grouping each assessment with both implementations run with the same data:</p><figure class="kg-card kg-code-card"><pre><code class="language-typescript">import { run, bench, group } from &apos;mitata&apos;;

import {
  TwoOrMoreNumbers,
  sumTwoLargestNumbers,
  sumTwoLargestNumbersSort,
} from &apos;./index&apos;;

const randomNumber = () =&gt; Math.floor(Math.random() * 40);
const makeArrayOfLength = (length: number): TwoOrMoreNumbers =&gt; [
  1,
  2,
  ...Array.from({ length }, randomNumber),
];

const smallArray = makeArrayOfLength(3);
const mediumArray = makeArrayOfLength(100);
const largeArray = makeArrayOfLength(1000);

group(&apos;sumTwoLargestNumbers: Small arrays&apos;, () =&gt; {
  bench(&apos;for loop&apos;, () =&gt; sumTwoLargestNumbers(smallArray));
  bench(&apos;sort and pick&apos;, () =&gt; sumTwoLargestNumbersSort(smallArray));
});

group(&apos;sumTwoLargestNumbers: Medium arrays&apos;, () =&gt; {
  bench(&apos;for loop&apos;, () =&gt; sumTwoLargestNumbers(mediumArray));
  bench(&apos;sort and pick&apos;, () =&gt; sumTwoLargestNumbersSort(mediumArray));
});

group(&apos;sumTwoLargestNumbers: Large arrays&apos;, () =&gt; {
  bench(&apos;for loop&apos;, () =&gt; sumTwoLargestNumbers(largeArray));
  bench(&apos;sort and pick&apos;, () =&gt; sumTwoLargestNumbersSort(largeArray));
});

await run();
</code></pre><figcaption><code>sumTwoLargestNumbers.bench.ts</code></figcaption></figure><p>Now we can run our benchmarking script and see the results:</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">$ yarn benchmark
yarn run v1.22.19
$ ts-node scripts/benchmark.ts
cpu: Intel(R) Core(TM) i5-8210Y CPU @ 1.60GHz
runtime: node v18.16.0 (x64-darwin)

benchmark          time (avg)             (min &#x2026; max)       p75       p99      p995
----------------------------------------------------- -----------------------------
&#x2022; sumTwoLargestNumbers: Small arrays
----------------------------------------------------- -----------------------------
for loop         8.37 ns/iter   (6.97 ns &#x2026; 285.34 ns)   8.38 ns  22.48 ns  34.41 ns
sort and pick  254.06 ns/iter   (182.44 ns &#x2026; 1.36 &#xB5;s) 265.35 ns  590.1 ns 604.48 ns

summary for sumTwoLargestNumbers: Small arrays
  for loop
   30.36x faster than sort and pick

&#x2022; sumTwoLargestNumbers: Medium arrays
----------------------------------------------------- -----------------------------
for loop       221.24 ns/iter   (178.14 ns &#x2026; 2.25 &#xB5;s) 215.12 ns 603.46 ns 752.25 ns
sort and pick    2.41 &#xB5;s/iter     (2.13 &#xB5;s &#x2026; 3.33 &#xB5;s)   2.57 &#xB5;s   3.33 &#xB5;s   3.33 &#xB5;s

summary for sumTwoLargestNumbers: Medium arrays
  for loop
   10.88x faster than sort and pick

&#x2022; sumTwoLargestNumbers: Large arrays
----------------------------------------------------- -----------------------------
for loop         1.92 &#xB5;s/iter     (1.79 &#xB5;s &#x2026; 2.82 &#xB5;s)   1.93 &#xB5;s   2.82 &#xB5;s   2.82 &#xB5;s
sort and pick   20.91 &#xB5;s/iter    (17.04 &#xB5;s &#x2026; 1.45 ms)  21.34 &#xB5;s  41.15 &#xB5;s  66.25 &#xB5;s

summary for sumTwoLargestNumbers: Large arrays
  for loop
   10.89x faster than sort and pick
&#x2728;  Done in 6.94s.</code></pre><figcaption>A 10x improvement is still huge!</figcaption></figure><p>Incredible, right? What a huge difference between the two. The difference is smaller than it was 6 years ago, and my best guess at this point is that it&apos;s due to JIT optimizations.</p><h2 id="wrapping-up">Wrapping Up</h2><p>mitata seems great so far. There are a few quality of life features I&apos;d like to see, such as quieting it&apos;s writing to <code>console.log</code> &#x2014; sometimes I&apos;d like to take over there, outputting what I want to see instead. Even so, in a pinch it&apos;s still possible to either a) ignore default outputs or b) write your outputs to a file and pipe mitata&apos;s outputs into the void. Given how simple and effective it is, I&apos;m really happy with it as it is.</p><p>In the future I&apos;ll hopefully write a bit about building around mitata to create useful tools for tracking performance over time, but I&apos;ll need to put some thought into making that useful and portable.</p><p>Feel free to <a href="https://github.com/steveadams/sum-two-largest-numbers/tree/v1.0.0">take a look at the code on github</a>.</p>]]></content:encoded></item><item><title><![CDATA[Practical Examples of Authentication in Meteor 1.0]]></title><description><![CDATA[A step-by-step guide to customize authentication via the accounts-ui package in Meteor 1.0.]]></description><link>https://steve-adams.me/practical-examples-of-authentication-in-meteor-1-0/</link><guid isPermaLink="false">648c92fe9d3656469e536541</guid><category><![CDATA[meteor]]></category><category><![CDATA[javascript]]></category><dc:creator><![CDATA[Steve Adams]]></dc:creator><pubDate>Mon, 13 Nov 2017 17:52:00 GMT</pubDate><content:encoded><![CDATA[<p>My first dive into Meteor.js has been great. I&apos;m starting to hit that point though (as you do with a full stack framework) where the default behaviours don&apos;t suit requirements. In this case my client doesn&apos;t like the default behaviours and appearance of the <code>accounts-ui</code> package.</p><p><code>accounts-base</code> and <code>accounts-ui</code> are awesome packages that happen to make a heap of assumptions about how you&apos;re going to reason about your user&apos;s authentication and session management using a system called Accounts. That&apos;s alright though, because <code>accounts-ui</code>isn&apos;t necessarily intended to be the de facto way to provide access to the API of Accounts. It&apos;s there to get newcomers started, to help more experienced developers quickly prototype their ideas, and probably only occasionally remain a permanent fixture of the UI.</p><p>Here&apos;s a look at how we can replace the functionality of <code>accounts-ui</code>with custom behaviours and appearances.</p><h3 id="get-your-packages">Get Your Packages</h3><p>Before you can get going with this, you&apos;ll need to ensure you&apos;ve pulled in both <code>accounts-base</code> and <code>accounts-password</code>. To do that, start up your terminal, change directories to your meteor project, and run these commands:</p><p><code>meteor add accounts-base</code></p><p><code>meteor add accounts-password</code></p><p>These are all you need for an impressive and comprehensive accounts management system in your app.</p><h3 id="meteorloginwithpassword">Meteor.loginWithPassword()</h3><p>Logging in with a username/email and password is the most common way your user will interact with Accounts. It&apos;s straight forward and relatively easy to interact with. These are the steps we&apos;ll need to take to get it working:</p><ol><li>Provide a form for the user.</li><li>Set up a listener for the form&apos;s submit event.</li><li>Collect the form values as arguments for <code>loginWithPassword()</code>.</li></ol><p>When calling <code>loginWithPassword</code>, you provide two, or optionally 3 arguments. First is the email or username, then the password. Finally you can provide a callback to handle the result of the login attempt.</p><p>The following examples assume you&apos;re using the iron:router package for the routing behaviours, but it isn&apos;t required - The Accounts system is totally decoupled from your application logic.</p><h4 id="step-1-build-the-template">Step 1: Build the Template</h4><p>To get started, we just need a template. Given you can literally put this <em><em>anywhere</em></em> in a meteor app, do whatever suits you here. I keep my accounts and authentication-related files in <code>client/accounts/</code>. You can style this template any way you&apos;d like - It has no bearing on the functionality.</p><pre><code class="language-html">&lt;!-- client/accounts/login.html --&gt;

&lt;template name=&quot;Login&quot;&gt;
    &lt;form id=&quot;login&quot;&gt;
        &lt;label for=&quot;login-username&quot;&gt;User Name&lt;/label&gt;
        &lt;input type=&quot;text&quot; id=&quot;login-username&quot;&gt;

        &lt;label for=&quot;login-password&quot;&gt;Password&lt;/label&gt;
        &lt;input type=&quot;password&quot; id=&quot;login-password&quot;&gt;

        &lt;input type=&quot;submit&quot; value=&quot;Login&quot;&gt;

        &lt;p id=&quot;form-messages&quot;&gt;&lt;/p&gt;
    &lt;/form&gt;
&lt;/template&gt;
</code></pre><h4 id="step-2-set-up-the-event">Step 2: Set Up The Event</h4><p>Once our template is present, Meteor&apos;s ready to listen to events inside of it. We can follow the Meteor convention here and drop the event into the <code>Template.Login.events</code> hash.</p><pre><code class="language-javascript">/* client/accounts/login.js */

Template.Login.events({
    &apos;submit #login&apos;: function(event, template) {
        // Log in logic
    });

    return false;
});
</code></pre><h4 id="step-3-provide-the-authentication-logic">Step 3: Provide the Authentication Logic</h4><p>What will our event do? Not much, really - Accounts does most of the heavy lifting here.</p><ol><li>First we collect our username and password data from the form.</li><li>Then we call <code>Meteor.loginWithPassword()</code> with our credentials and a callback.</li><li>Inside of that callback we determine if the user authenticated and respond accordingly.</li></ol><pre><code class="language-javascript">/* client/accounts/login.js */

Template.Login.events({
    &apos;submit #login&apos;: function(event, template) {
        // 1. Collect the username and password from the form
        var username = template.find(&apos;#login-username&apos;).value,
            password = template.find(&apos;#login-password&apos;).value;

        // 2. Attempt to login.
        Meteor.loginWithPassword(username, password, function(error) {
            // 3. Handle the response
            if (Meteor.user()) {
                // Redirect the user to where they&apos;re loggin into. Here, Router.go uses
                // the iron:router package.
                Router.go(&apos;dashboard&apos;);
            } else {
                // If no user resulted from the attempt, an error variable will be available
                // in this callback. We can output the error to the user here.
                var message = &quot;There was an error logging in: &lt;strong&gt;&quot; + error.reason + &quot;&lt;/strong&gt;&quot;;

                template.find(&apos;#form-messages&apos;).html(message);
            }

            return;
        });

        return false;
    }
});
</code></pre><p><code>error.reason</code> provides a string clarifying the reason for the error, i.e. <em><em>&apos;User not found&apos;</em></em> or <em><em>&apos;Incorrect password&apos;</em></em>.</p><p>So now what if your user wants to log out?</p><h3 id="meteorlogout">Meteor.logout()</h3><p>This works predictably - It logs out the user currently associated with the client&apos;s session. My approach here was to listen on the entire site&apos;s layout for the click of any element with the class <code>logout</code>, and upon logging out, redirect to the login page. In the callback you provide, a <code>Meteor.Error()</code> object will be present if there was an error, so you can handle that condition if you&apos;d like to.</p><p>As a side note, this is how you can set a default layout for your templates using iron:router which makes setting events or helpers a lot more DRY.</p><pre><code class="language-javascript">/* client/routes/router.js */

// Set a default layout template for all routes.
Router.configure({
  layoutTemplate: &apos;Layout&apos;
});

// As opposed to explicitly setting it with each route...
Router.route(&apos;/my/route&apos;, function () {
  this.layout(&apos;Layout&apos;);
  this.render(&apos;MyPage&apos;);
}, {
  name: &apos;my.page&apos;
});
</code></pre><p>Once we&apos;ve done that, we can set a <code>Template.Layout.events</code> method which handles all clicks of an anchor with the class <code>logout</code>. It can go in the navigation, in a dropdown menu, within some help text, whatever you like so long as the page uses the correct layout. If you choose to make logging out specific to a template, you can use the exact same code within a different template&apos;s events hash.</p><pre><code class="language-javascript">/* client/layout/events.js */

Template.Layout.events({
  &apos;click a.logout&apos;: function() {
    Meteor.logout(function() {
      // Redirect to login
      Router.go(&apos;/login&apos;);
    });

    return;
  }
});
</code></pre><p>That is the most basic interaction with Accounts, but there&apos;s plenty more you can do. Up until this point, all of the examples make the assumption your application already has users. But what if you don&apos;t? How do you go about creating users, or creating a custom convention for creating users?</p><h3 id="creating-users-with-accountscreateuser">Creating Users with Accounts.createUser()</h3><p>This feature is slightly more nuanced than the previous ones. Its behaviour isn&apos;t consistent across the client and server, so the context of execution matters. It also allows quite a few callbacks to help with things like validation or altering the user before sending the data to the server.</p><h3 id="on-the-client">On the Client</h3><p>When you create a user on the client, Meteor will automatically log you in as that user. If you&apos;ve used <code>accounts-ui</code>, you probably noticed that the &apos;Create account&apos; link on <code>{{&gt; loginButtons}}</code> which provides a &apos;Password (again)&apos; field will immediately log you in as that user once you submit the form. Because of this behaviour, it requires a username <em><em>and</em></em>a password so the user can authenticate immediately, and then authenticate again at a later date.</p><p>The idea here is that in the most basic use case, someone can arrive at your app, enter credentials, and start using the app immediately as an authenticated user. There are options available to tailor that process, though. Accounts has support for verification emails for example, or you can implement your own approval process.</p><h3 id="on-the-server">On the Server</h3><p>The server is less strict and doesn&apos;t require a password. This can be set arbitrarily at a later date using <code>Accounts.setPassword(userId, newPassword)</code>. You can also allow a user to choose their own password at a later date by triggering <code>Accounts.sendEnrollmentEmail(userId)</code>, which is highly configurable... But I won&apos;t go into that here.</p><h3 id="creation-from-the-client">Creation from the Client</h3><p>In my own case, I only needed to create users from the client using a form which covered the minimum requirements and provided an optional form for a user profile. Similar to authenticating, all we need here is:</p><ol><li>A template with a form</li><li>A listener for the template&apos;s form submission event</li><li>Logic to use the form data with <code>Accounts.createUser()</code></li></ol><h4 id="step-1-the-template-and-form">Step 1: The Template and Form</h4><p>This template has a form with a field for the username, email, password, and profile values. Yours could be just about anything - The profile object in Accounts doesn&apos;t really care what exists there, and validation is entirely up to you.</p><pre><code class="language-markup">&lt;!-- client/accounts/create-user.html --&gt;

&lt;template name=&quot;CreateUser&quot;&gt;
  &lt;form id=&quot;create-user&quot;&gt;

    &lt;fieldset&gt;
      &lt;legend&gt;Credentials&lt;/legend&gt;
      &lt;label for=&quot;create-user-username&quot;&gt;User Name&lt;/label&gt;
      &lt;input type=&quot;text&quot; id=&quot;create-user-username&quot; placeholder=&quot;SteamDonkey2014&quot;&gt;

      &lt;label for=&quot;create-user-email&quot;&gt;Email&lt;/label&gt;
      &lt;input type=&quot;text&quot; id=&quot;create-user-email&quot; placeholder=&quot;you@domain.tld&quot;&gt;

      &lt;label for=&quot;create-user-password&quot;&gt;Password&lt;/label&gt;
      &lt;input type=&quot;password&quot; id=&quot;create-user-password&quot; placeholder=&quot;Password&quot;&gt;

      &lt;label for=&quot;create-user-password-confirm&quot;&gt;Confirm Password&lt;/label&gt;
      &lt;input type=&quot;password&quot; id=&quot;create-user-password-confirm&quot; placeholder=&quot;Confirm Password&quot;&gt;
    &lt;/fieldset&gt;

    &lt;fieldset&gt;
      &lt;legend&gt;Profile (Optional)&lt;/legend&gt;
      &lt;label for=&quot;create-user-name&quot;&gt;Name&lt;/label&gt;
      &lt;input type=&quot;text&quot; id=&quot;create-user-name&quot;&gt;

      &lt;label for=&quot;create-user-astro&quot;&gt;Astrological Symbol&lt;/label&gt;
      &lt;input type=&quot;text&quot; id=&quot;create-user-astro&quot;&gt;

      &lt;label for=&quot;create-user-newsletter&quot;&gt;Subscribe to Newsletter&lt;/label&gt;
      &lt;input type=&quot;checkbox&quot; id=&quot;create-user-newsletter&quot;&gt;
    &lt;/fieldset&gt;

    &lt;input type=&quot;submit&quot; value=&quot;Sign Up!&quot;&gt;
  &lt;/form&gt;
&lt;/template&gt;
</code></pre><h4 id="step-2-event-prep">Step 2: Event Prep</h4><p>Like before, setting up the event is simple.</p><pre><code class="language-javascript">/* client/accounts/users.js */

Template.CreateUser.events({
  &apos;submit #create-user&apos;: function(event, template) {
      // Code goes here
    });

    return false;
  }
});
</code></pre><h4 id="step-3-event-logic">Step 3: Event Logic</h4><p>We&apos;ll need a bit of boilerplate to get the data from the form, but once we&apos;ve done that we&apos;re ready to fire off the data and see what happens. If your data is good and the user is created, you should be logged in as the user you just created.</p><pre><code class="language-javascript">/* client/accounts/users.js */

Template.CreateUser.events({
  &apos;submit #create-user&apos;: function(event, template) {
    var user;

    // Collect data and validate it.

    // You can go about getting your data from the form any way you choose, but
    // in the end you want something formatted like so:
    user = {
      username: formUsername,
      password: formPassword,
      email: formEmail,
      profile: {
        name: formName,
        // etc...
      }
    }

    // Post the user to the server for creation
    Accounts.createUser(user, function (error) {
      if (error) {
        // :(
        console.log(error);
      }
    });

    return false;
  }
});
</code></pre><p>Worth noting is that if you have validation requirements beyond what Meteor enforces and you aren&apos;t performing validation before triggering <code>createUser</code>, you can run validation logic before a user is created using <code>Accounts.validateNewUser()</code>. If you return true from this method, Meteor will proceed with trying to create the user. If you return false, the process is aborted and <code>Accounts.createUser()</code> will return in the optional callback with an error. You can set the error reason by throwing your own <code>Meteor.Error()</code>, like so:</p><pre><code class="language-javascript">/* client/validation/user.js */

// Validate new users
Accounts.validateNewUser(function (user) {
  // Ensure user name is long enough
  if (user.username.length &lt; 5) {
    throw new Meteor.Error(403, &apos;Your username needs at least 5 characters&apos;);
  }

  var passwordTest = new RegExp(&quot;(?=.{6,}).*&quot;, &quot;g&quot;);
  if (passwordTest.test(user.password) == false) {
    throw new Meteor.Error(403, &apos;Your password is too weak!&apos;);
  }

  return true;
});
</code></pre><p>Remember <code>error.reason</code> in the login process? This is how you access your custom messages when throwing errors in <code>Meteor.Error</code>. If you&apos;d like, you can let Meteor generate a default error by simply calling <code>Meteor.Error</code> with no arguments.</p><p>Finally, if you want to do work on users before they&apos;re persisted to the database, there&apos;s another Accounts method called <a href="https://web.archive.org/web/20180916154925/http://docs.meteor.com/#/full/accounts_oncreateuser"><code>onCreateUser()</code></a>which allows you to push the user object through a callback before it hits the server. It also allows you to throw an error to abort creation, but its purpose isn&apos;t validation, so if errors occur they should be for other reasons.</p><h2 id="whats-next">What&apos;s Next?</h2><p>To really round this out, we still need a couple of things. Users should be able to reset their passwords and recover their passwords for convenience and security.</p><h3 id="reset-a-forgotten-password">Reset a Forgotten Password</h3><p>I&apos;ll assume by now that you&apos;re comfortable with Meteor conventions and I&apos;ll outline these tasks quickly, showing the templates and then corresponding code.</p><pre><code class="language-markup">&lt;!-- client/accounts/forgot-reset-password.html --&gt;

&lt;template name=&quot;RecoverPassword&quot;&gt;
  {{#if resetPassword}}
    &lt;form id=&quot;set-new-password&quot;&gt;
      &lt;label for=&quot;new-password&quot;&gt;New Password&lt;/label&gt;
      &lt;input type=&quot;text&quot; id=&quot;new-password&quot; placeholder=&quot;Try not to forget this one.&quot;&gt;

      &lt;input type=&quot;submit&quot; value=&quot;Set New Password&quot;&gt;

      &lt;p id=&quot;form-messages&quot;&gt;&lt;/p&gt;
    &lt;/form&gt;
  {{else}}
    &lt;form id=&quot;forgot-password&quot;&gt;
      &lt;label for=&quot;user-email&quot;&gt;Email&lt;/label&gt;
      &lt;input type=&quot;text&quot; id=&quot;user-email&quot; placeholder=&quot;Email&quot;&gt;

      &lt;input type=&quot;submit&quot; value=&quot;Get Reset Password Instructions&quot;&gt;

      &lt;p id=&quot;form-messages&quot;&gt;&lt;/p&gt;
    &lt;/form&gt;
  {{/if}}
&lt;/template&gt;
</code></pre><p><br><code>Accounts.forgotPassword()</code> only requires and email, so this template is dead simple. Once we&apos;ve wired this thing up, Meteor will send an email to the given email address if it&apos;s valid.</p><pre><code class="language-javascript">/* client/accounts/recover-password.js */

// Ensure we have the token to pass into the template when it&apos;s present
if (Accounts._resetPasswordToken) {
  Session.set(&apos;resetPasswordToken&apos;, Accounts._resetPasswordToken);
}

Template.RecoverPassword.helpers({
  resetPassword: function() {
    return Session.get(&apos;resetPasswordToken&apos;);
  }
});

Template.RecoverPassword.events({
  &apos;submit #forgot-password&apos;: function(event, template) {
    var email = template.find(&apos;#user-email&apos;),
      message;

    // You will probably want more robust validation than this!
    if (email) {
      // This will send a link to the address which, upon clicking, prompts the
      user to enter a new password.
      Accounts.forgotPassword(email);
      message = &apos;Sent a reset password link to &apos; + email + &apos;.&apos;;
    } else {
      message = &apos;Please enter a valid email address.&apos;
    }

    // Inform the user.
    template.find(&apos;#form-messages&apos;).html(message);

    return false;
  },
  &apos;submit #set-new-password&apos;: function (event, template) {
    // Proper decoupled validation would be much nicer than this
    var password = template.find(&apos;#new-password&apos;).value,
      passwordTest = new RegExp(&quot;(?=.{6,}).*&quot;, &quot;g&quot;);

    // If the password is valid, we can reset it.
    if (passwordTest.test(password)) {
      Accounts.resetPassword(
        Session.get(&apos;resetPasswordToken&apos;),
        password,
        function (error) {
          if (err) {
            template.find(&apos;#form-messages&apos;).html(&apos;There was a problem resetting your password.&apos;);
          } else {
            // Get rid of the token so the forms render properly when they come back.
            Session.set(&apos;resetPasswordToken&apos;, null);
          }
        })
      });
    } else {
      // Looks like they blew it
      template.find(&apos;#form-messages&apos;).html(&apos;Your password is too weak!&apos;);
    }

    return false;
  }
});
</code></pre><p><br>And there you have it - Password recovery. Finally, for routine password updates for users, it&apos;s as simple this:</p><pre><code class="language-html">&lt;!-- client/accounts/change-password.html --&gt;

&lt;template name=&quot;ChangePassword&quot;&gt;
    &lt;form id=&quot;change-password&quot;&gt;
        &lt;label for=&quot;current-password&quot;&gt;Current Password&lt;/label&gt;
        &lt;input type=&quot;text&quot; id=&quot;current-password&quot; placeholder=&quot;Current Password&quot;&gt;

        &lt;label for=&quot;new-password&quot;&gt;New Password&lt;/label&gt;
        &lt;input type=&quot;text&quot; id=&quot;new-password&quot; placeholder=&quot;New Password&quot;&gt;

        &lt;label for=&quot;new-password-repeated&quot;&gt;Repeat New Password&lt;/label&gt;
        &lt;input type=&quot;text&quot; id=&quot;new-password-repeated&quot; placeholder=&quot;Repeat New Password&quot;&gt;

        &lt;input type=&quot;submit&quot; value=&quot;Update Password&quot;&gt;

        &lt;p id=&quot;form-messages&quot;&gt;&lt;/p&gt;
    &lt;/form&gt;
&lt;/template&gt;
</code></pre><p>You don&apos;t <em><em>have</em></em> to force the user to repeat their new password, but it&apos;s certainly a good idea. If anything it just prevents using the forgot password process again if they typed something wrong, at only a minor inconvenience. Finally, here&apos;s all we need to do to change it:</p><pre><code class="language-javascript">/* client/accounts/change-password.js */

Template.RecoverPassword.events({
    &apos;submit #change-password&apos;: function(event, template) {
        var currentPassword,
            newPassword,
            newPasswordRepeated;

        currentPassword = template.find(&apos;#current-password&apos;);
        newPassword = template.find(&apos;#new-password&apos;);
        newPasswordRepeated = template.find(&apos;#new-password-repeated&apos;);

        // You will want to validate your passwords better than this
        if (newPassword !== newPasswordRepeated) {
            template.find(&apos;#form-messages&apos;).html(&quot;The new passwords don&apos;t match!&quot;);

            return false;
        }

        Accounts.changePassword(currentPassword, newPassword, function(error) {
            if (error) {
                message = &apos;There was an issue: &apos; + error.reason;
            } else {
                message = &apos;You reset your password!&apos;
            }
        });

        // Inform the user.
        template.find(&apos;#form-messages&apos;).html(message);

        return false;
    }
});
</code></pre><p>Anyway, that&apos;s plenty to get your feet wet doing manual account management with Meteor. There&apos;s so much more you can do to customize your app, and the best way to find out how is <a href="http://docs.meteor.com">reading the docs!</a> With this foundation, the rest of it is a breeze.</p>]]></content:encoded></item><item><title><![CDATA[Summing the Largest Numbers in a JavaScript Array]]></title><description><![CDATA[A simple yet insightful look into turning an n*log(n) algorithm into an O(n) algorithm without much effort at all.]]></description><link>https://steve-adams.me/summing-the-largest-values-javascript-array/</link><guid isPermaLink="false">648ca65f9d3656469e536562</guid><category><![CDATA[javascript]]></category><category><![CDATA[algorithms]]></category><category><![CDATA[benchmarking]]></category><dc:creator><![CDATA[Steve Adams]]></dc:creator><pubDate>Sat, 21 Oct 2017 07:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Earlier today I ran into a problem posed as an interview question. At a glance it seems simple, but it&apos;s an open ended question. Like any good assessment, it leaves room for programmers to express different solutions and degrees of aptitude.</p><p>The problem went a bit like this:</p><!--kg-card-begin: html--><blockquote class="kg-blockquote-alt">Write a function that efficiently takes an array of integers and returns the sum of the two largest integers in the array. The array can be made up of any valid integers, and the length is unknown (but could be very large).</blockquote><!--kg-card-end: html--><p>The problem could be solved with either PHP or JavaScript, and I chose JavaScript.</p><p>My first thought was that this could be done two ways (that I know of) which I could implement properly in the test scenario. One is to sort the array in descending numeric order and pluck the top 2 elements. The other was to use a <code>for</code> loop to run through the array and continuously pull larger values until I hit the end and had the top 2 remaining. What&apos;s better, and why? What&apos;s the context of the usage cases? Does it need to scale?</p><p>Well, the problem states the arrays <em><em>&apos;could be very large&apos;</em></em>. Since I had a (self imposed) time limit I decided to implement something:</p><ul><li>Concise</li><li>Reasonably fast</li><li>Easy to understand</li></ul><p>This would be trivial using <code>Array.prototype.sort()</code>.</p><h2 id="the-initial-solution">The Initial Solution</h2><pre><code class="language-javascript">/**
 * Sums the two largest numbers in an array of numbers.
 *
 * @param  {Array} numbers An Array of Numbers
 *
 * @return {Number}        The sum of the two largest Numbers
 */
function sumTwoLargestNumbers(numbers) {
    // Ensure the array is sorted large -&gt; small
    numbers = numbers.sort(function (a, b) {
        return b - a;
    });

    return numbers[0] + numbers[1];
};

sumTwoLargestNumbers([20, 5, 13, 7, 200]);
// &gt; 220
</code></pre><p>It&apos;s consice and clean, and it appears to work, so what&apos;s wrong with it? Well, I missed something pretty important:</p><pre><code>return numbers[0] + numbers[1];
</code></pre><p>I&apos;m adding <code>numbers[0]</code> and <code>numbers[1]</code> without ensuring those indexes will be there. That&apos;s an easy fix by adding a condition like so:</p><pre><code class="language-javascript">/**
 * Sums the two largest numbers in an array of numbers.
 *
 * @param  {Array}  numbers An Array of Numbers
 *
 * @return {Number}        The sum of the two largest Numbers
 */
function sumTwoLargestNumbers(numbers) {
    // Is the array long enough to sum?
    if (numbers.length &lt; 2) {
      throw new Error(&apos;Expected an array with at least 2 elements.&apos;);
    }

    // Sort the array large -&gt; small
    numbers = numbers.sort(function (a, b) {
        return b - a;
    });


    return numbers[0] + numbers[1];
};

sumTwoLargestNumbers([20, 5, 13, 7, 15]);
// &gt; 35 (20 + 15)

sumTwoLargestNumbers([5]);
// &gt; Error: Expected an array with at least 2 elements.
</code></pre><p>That seems better, but there&apos;s still another problem to address.</p><h3 id="inefficiency-with-arrayprototypesort">Inefficiency With <code>Array.prototype.sort()</code></h3><p>This appears to be a nice solution because it is, for the developer, highly practical to write, maintain, and use. If you&apos;d only ever use this function on small arrays I think you could stick with it!</p><p>The thing is, being concise and easy to understand here is hiding a lot of algorithmic complexity. I haven&apos;t reduced the amount of work done by writing less code. Instead I&apos;ve increased it by outsourcing the workload to a pretty heavy algorithm that isn&apos;t really intended to be used for this problem. It&apos;s doing work that the function doesn&apos;t imply it should do or even needs to do; sorting the array is pointless outside of convenience. This method only simplifies my job by offloading work to an algorithm which isn&apos;t doing the <em><em>right</em></em> work for our task. That seems like bad programming, and in the scope of this assessment, it is.</p><h2 id="the-second-solution">The Second Solution</h2><p>Later in the day, the person who posed the initial test question asked why I chose sorting over using a <code>for</code> loop. I didn&apos;t have a great answer apart from what is overtly beneficial about the sort solution. It&apos;s easy, it&apos;s clean, and it works.</p><p>Needless to say it became pretty clear that I should reconsider my approach. I wasn&apos;t entirely wrong to choose sort, but I also wasn&apos;t right either. I needed a better answer and a better understanding of the problem. I sat down and wrote out the following <code>for</code> loop solution in around 20 minutes. My sorting function took a total of about 15, for comparison &#x2014; not as much time saved as I would have guessed.</p><pre><code class="language-javascript">/**
 * Sums the two largest numbers in an array of numbers.
 *
 * @param  {Array}  numbers An Array of Numbers
 *
 * @return {Number}        The sum of the two largest Numbers
 */
function sumTwoLargestNumbers(numbers) {
    var largest,
        secondLargest,
        length = numbers.length,
        // Start i at 2 so we skip the first indexes we&apos;ve already used for
        // largest and secondLargest
        i = 2;

    // Is the array long enough to sum?
    if (length &lt; 2) {
        throw new Error(&apos;Expected an array with at least 2 elements.&apos;);
    }

    // Assign our starting values
    largest = numbers[0];
    secondlargest = numbers[1];

    // Ensure we&apos;re starting with largest and secondLargest properly arranged
    if (largest &lt; secondLargest) {
        largest = numbers[1];
        secondLargest = numbers[0];
    }

    // Loop through the numbers
    for (let i = 0; i &lt; length; i++) {
        // If the new number is greater than largest, assign it to largest and
        // pass largest&apos;s value to secondLargest.
        if (numbers[i] &gt; largest) {
            secondLargest = largest;
            largest = numbers[i];
        }
        // If the new number isn&apos;t greater than largest, check if it&apos;s still
        // greater than secondLargest.
        else if (numbers[i] &gt; secondLargest) {
            secondLargest = numbers[i];
        }
    }

    return largest + secondLargest;
};

sumTwoLargestNumbers([20, 5, 13, 7, 15]);
// &gt; 35 (20 + 15)

sumTwoLargestNumbers([5]);
// &gt; Error: Expected an array with at least 2 elements.
</code></pre><p>It turns out the code isn&apos;t that much larger or hard to read. My concerns about writing an excessively verbose solution were unfounded.</p><p>The logic from the top down is straight forward and we get the same behaviours as we did with sort. Now we also have the benefit of only looking at smaller numbers two times at most here: <code>if (numbers[i] &gt; largest)</code>, and then here: <code>else if (numbers[i] &gt; secondLargest)</code>. Our worst case scenario for performance is drastically better than the sort solution.</p><h3 id="on-vs-on-log-n"><code>O(n)</code> vs. <code>O(n log n)</code></h3><p>When we iterate through our array with a <code>for</code> loop, we have the benefit of knowing that if summing an array of 100 elements takes 5ms, then summing 500 elements should take something like 25ms. This is because the complexity of the task doesn&apos;t increase at all. Our work load has increased proportionally so if we have five times the work to do, we can expect our execution time to increase by around five times. This is <code>O(n)</code>, and it&apos;s nice to have when possible.</p><p>When we use <code>Array.prototype.sort()</code>, we&apos;re passing the workload to the browser&apos;s implemented sorting algorithm. As I understand, <em><em>most</em> </em>browsers use the <a href="https://web.archive.org/web/20190130165942/http://en.wikipedia.org/wiki/Merge_sort">merge sort</a> algorithm which has a complexity of <code>O(n log n)</code>. It&apos;s guaranteed to be significantly slower than using a <code>for</code> loop in my function.</p><h3 id="what-are-the-implications">What Are the Implications?</h3><p>Unless this function is meant to sum relatively small sets of values, you shouldn&apos;t use the sort approach.</p><p>That&apos;s not to say native sorting is inherently slower than using <code>for</code> in all cases. In my case, it&apos;s only faster to use <code>for</code> because we&apos;re avoiding a so much unnecessary work by not sorting the rest of the array. All we do is continuously flip through our elements and remember the biggest ones we saw. That&apos;s a simple task. Sorting the entire list is not a simple task, on the other hand.</p><p>I&apos;m sure the sort algorithm is implemented extremely well. It&apos;s just the wrong tool for the job.</p><h3 id="profiling-our-solutions">Profiling Our Solutions</h3><!--kg-card-begin: html--><div class="kg-card kg-callout-card kg-callout-card-blue">
    <div class="kg-callout-emoji">&#x1F44B;&#x1F3FC;</div>
    <div class="kg-callout-text">
        <p><strong>Jun 16, 2023:</strong></p>
        <p>I&apos;ve written a <a href="https://steve-adams.me/typescript-benchmarking-mitata/" title="TypeScript Benchmarking with mitata">new post</a> revisiting these benchmarks because the originals on jsperf.com are gone now, and they never made it to jsperf.app. So, I put some on this site for posterity while learning a new benchmarking library.</p>
    </div>
</div><!--kg-card-end: html--><p>I decided to take my experiment to <a href="https://jsperf.app/sort-vs-for-in-array-summing">jsperf</a> (now defunct &#x1FAE0;) and see what the difference is. It&apos;s not a perfect test as it could be benefited by more samples and input variations... But its initial results are telling. The sort method is evidently <strong><strong>~97%</strong></strong> slower.</p><p>With a smaller array, perhaps with 5 to 10 elements, we&apos;d probably see results with it being closer to 65% slower. This is on account of the sort algorithm&apos;s workload not increasing proportionally as the <code>for</code> loop&apos;s workload does; the performance will decrease as the workload increases.</p><p>That&apos;s probably acceptable for small tasks in the client. But the test above uses only 816 elements - What if you needed to sum 10,000 elements this way, and you have to do it frequently? It&apos;s clear that the <code>for</code> loop would be essential for getting acceptable performance from this type of function.</p><h2 id="lesson-learned">Lesson Learned</h2><p>Don&apos;t be afraid to get your hands dirty, even if the problem seems too simple to warrant a more complex solution. It&apos;s always worth considering or even pursuing other options.</p>]]></content:encoded></item></channel></rss>