Add first post in the LeetCode one-liners series

This commit is contained in:
Maciej Pędzich 2023-10-28 20:49:11 +02:00
parent a8fa75ddad
commit 9eadb1441d
2 changed files with 116 additions and 58 deletions

View File

@ -0,0 +1,116 @@
---
title: "leetcode one-liners: remove element"
description: "Solving LeetCode's \"Remove Element\" problem by making clever use of Array.reduceRight and Array.splice"
pubDate: 2023-10-28T09:27:09.772Z
draft: true
categories:
- leetcode one-liners
tags:
- leetcode
- javascript
---
This post serves as a foundation, for what I'd like to become a weekly series, where I try to come up with solution to a given LeetCode problem using only an inline return JavaScript arrow function. This means that I'm limited to using expressions as my functions' bodies, so curly braces with statements minified into a single line are prohibited.
With that out of the way, here's how I've managed to solve [the "Remove Element" puzzle](https://leetcode.com/problems/remove-element/description/).
## the goal
This problem requires us to create a `removeElement` function that accepts two arguments:
1. `nums` - an array of integers in range from 0 to 50 inclusive
2. `val` - an integer in range from 0 to 100 inclusive
The function should remove all occurences of `val` from the original `nums` array and return an integer `k`, which is equivalent to the `nums` array's new length.
## the algorithm
We should initialise our variable `k` to 0 and loop through each item in `nums`, starting from the end of the array. If the current item equals `val`, we should remove it from `nums` array. Otherwise, we should increment `k` by 1. Once we've gone throgh every single item, we can return `k`.
Note that by starting our loop from the last item, we ensure no item gets skipped after the previous number's been removed.
## the code
After many different iterations, the one-liner that turned out to be the most runtime and memory efficient, clocked at 42 milliseconds (beating 95.15% of accepted submissions) and used 41.24 megabytes (beating 96.98% of accepted submissions) respectively. That's not too bad if you ask me.
But anyway, here's what the code looks like:
```js
const removeElement = (nums, val) => nums.reduceRight((k, n, i) => k += 1 - nums.splice(i, +(n === val)).length, 0)
```
If you take advantage of declaring implicit global variables, remove spaces, and take liberties with renaming the main function and its arguments, you can end up with this syntactically-valid beauty:
```js
f=(a,v)=>a.reduceRight((k,n,i)=>k+=1-a.splice(i,+(n===v)).length,0)
```
## the explanation
Let's go back to the former snippet and break it down bit-by-bit.
### reduceRight array method
My `removeElement` function will return the result of `reduceRight` method called on the `nums` array:
```js
const removeElement = (nums, val) => nums.reduceRight(/*...*/)
```
But what does it do and what exactly does it return? This method allows us to iterate through all the items in the array, starting from the last one (or in other words, **the right side** of the array), and **reduce them down** to a single value - hence the name `reduceRight`.
`reduceRight` accepts 2 arguments:
1. Callback function that will be executed for each item in the array. It can take up to 4 parameters:
1. Accumulator, AKA the value we reduce the array down to. From the second iteration onwards, the accumulator's value is set to whatever was returned when the function got called in the previous iteration. Once `reduceRight`'s gone through all the items,
2. Item we're currently looping through
3. Current item's index (optional, but it will come in handy later)
4. The array itself (optional; this argument is not used in our case)
2. The accumulator's initial value. If ommited, the accumulator will be initialised to the first item's value and the loop will begin from the second to last item in the array
Now that we're on the same page regarding `reduceRight`, we can move on to its first argument
### reduceRight's callback function
Here's the callback function on its own:
```js
(k, n, i) => k += 1 - nums.splice(i, +(n === val)).length
```
This is another inline returhn arrow function, where we utilise the `+=` operator to increment our accumulator `k` by `1 - nums.splice(i, +(n === val)).length` and return `k`'s' value after incrementation. In order to understand what it will be incremented by, we must take a closer look at this weird bit after `1 -`.
### splice array method
Splice is a really versatile method, because it allows you to add, replace, and most importantly for us, remove elements from an array. In order to do so, we need to provide it with 2 arguments:
1. Index to start removing items from
2. Number of items to remove
So we pass `i` (the current item's index) as the first argument, but then follow it up with `+(n === val)`... what's up with that?
`n === val` checks if the number we're iterating through equals the value whose all occurences in the `nums` array are supposed to be deleted. By wrapping this expresion in parentheses and prepending a `+`, we can convert the boolean returned by our equality comparison to a number: `1` for `true`, and `0` for `false`.
This means that if `n === val` returns `true`, then the item we're looping through will be removed because the remove count will be set to `1`. Likewise, if `n === val` returns `false`, the delete count will be set to `0`, and thus the item won't get removed.
Finally we take advantage of the `splice` method's return value, which is an array of items that have been removed. Therefore, we can obtain the delete count by reading this array's `length`. So if you come back to the `1 - nums.splice(i, +(n === val)).length` bit, we can now tell what `k` will get incremented by, and thus what our callback function will return.
If the `length` of the array returned by `splice` equals `0` (ie. the current item hasn't got deleted), we increment `k` by `1 - 0`, so `1`. If the length of that array equals `1` (ie. the current item has been removed), we increment `k` by `1 - 1`, so `0`.
### reduceRight's second argument
And last but not least, we've got the second argument that we need to pass to the `reduceRight` method:
```js
nums.reduceRight(/*...*/, 0)
```
By passing `0` as the initial value for our `k`, we ensure no error gets thrown in case `nums` turns out to be an empty array. This return value makes sense, because its length is `0`, and if all items from a non-empty array get removed, its new length will also be `0`.
## the end
Thank you so much if you've made it this far!
While I agree that these examples put form over, hah, function, and that if you get too smart with one-liners the code can become unreadable, I also consider these self-imposed one-liner challenges a fun way to explore and exploit some of the features and quirks JavaScript has to offer.
Take care and stay tuned for the next post in this series!

View File

@ -1,58 +0,0 @@
---
title: spotify playlist archive rewrite
description: Outlining my plan to rewrite the Spotify Playlist Archive website
pubDate: 2023-09-20T13:25:50.531Z
draft: false
categories:
- dev diary
tags:
- vue
- nuxt
- spotify-playlist-archive
- primevue
---
Hey folks! It's time for another series of dev diary posts. In this entry, I'm going to explain how the current website works from the end user's perspective, but also share the overhauled site's tech stack and a list of new features I'd like to implement. With that said, let's just jump into it!
## High-level overview
If you go to [https://spotifyplaylistarchive.com](https://spotifyplaylistarchive.com), you'll be greeted with a search bar that allows you to look up available playlists either via the title or via the playlist's URL (e.g. if you paste [https://open.spotify.com/playlist/2vpAyuy9HOTPjygPl63QuH](https://open.spotify.com/playlist/2vpAyuy9HOTPjygPl63QuH) into the search bar, you'll get a single result for the _House Shift_ playlist).
Once you've picked a playlist from the search results, you're shown a calendar which has snapshot capture dates highlighted in green. If you click on one of these dates, you'll be presented with a snapshot of the playlist captured that day, including the title, description, and of course the tracklist.
You can also:
- Download a snapshot as a JSON file
- Copy track URLs of a snapshot to create a playlist out of said snapshot by pressing `Ctrl/Cmd+V` in Spotify's desktop app window (when the target playlist is open)
- Browse a playlist's stats, such as follower growth over the past week/month, or a track retention ranking table
## Tech stack
This site uses a rather unconventional backend, because its database is [a Git repo hosted on GitHub](https://github.com/mackorone/spotify-playlist-archive), with a GitHub Actions workflow running once every day to find and commit new playlist snapshots to this repository. GitHub's API and `raw.githubusercontent.com` serve as a means of obtaining commit hashes for given snapshots and snapshots themselves respectively, which I'll go into more detail in subsequent articles.
The frontend is powered by Nuxt 3 and PrimeVue, since the site relies quite heavily on interactive UI components for the aforementioned playlist search bar and the snapshot calendar (among others), even though the content (ie. playlists and their snapshots) is static and the same for every visitor.
While I'm looking to stick with these tools for the overhauled site, I'd also like to introduce Lucia Auth into the mix, which brings me to...
## New features
I'm pretty sure you've had this question come up while reading through this post so far:
> How are users supposed to add new playlists to the archive?
The process boils down to forking the repo, and then creating files with playlists' IDs as their names inside `/playlists/registry`, and finally opening a pull request against the main repo. As much as it may sound trivial for someone who's got experience with Git and GitHub, this process requires plenty of extra steps for _non-developers_, such as creating a GitHub account and learning how to perform all the aforementioned actions.
This might have previosuly discouraged the less technically adept folk from contributing their playlists to the archive. In order to make this process more accessible for these users, I'm looking to introduce a _setup wizard_, which should only require them to authenticate via GitHub (hence the addition of Lucia Auth to the tech stack) and provide links to playlists they'd like to add to the registry.
Apart from the setup wizard, I'm also planning to add:
- Comparing two snapshots of a given playlist
- Creating a cumulative playlist (ie. a playlist of all tracks that have ever been added to a given playlist) out of track URLs in the same vein as for a specific snapshot
- Looking up playlists that featured a given track (based on user-provided track URL)
- Various enhancements and refactor of existing features' code
## Wrapping up
Thank you so much for reading this shorter post. I'm looking forward to writing up the first technical breakdown of this project for you.
Take care!