Nono.MA

JANUARY 27, 2023

Leading zeros are extra zeros to the left of a number when you want to have a regular amount of digits in a set of numbers. For instance, 0001, 0002, and 0003 is a good formatting if you think you'll get to have thousands of entries, as you can stay at four digits up to 9999.

# Define your number
number = 1

two_digits = f'{number:02d}'
# 01

four_digits = f'{number:04d}'
# 0001

We use the Python formatting helper {my_number:04d} to enforce a minimum set of digits in our number variable. This means you can use it to set the value of a string or to create or print a longer string with that number, not necessarily having to store its value.

a_number = 42
print(f'The number is {a_number:06d}.')
# The number is 000042.

print(f'The number is {512:06d}.')
# The number is 000512.

JANUARY 26, 2023

The e flag/option of pip "installs a project in editable mode (i.e. setuptools “develop mode”) from a local project path or a VCS url."

pip install -e .

-e, --editable <path/url>

As described in the expanded command flag, -e stands for editable.

JANUARY 20, 2023

Today I learned you can use the plus (+) operator to concatenate or extend lists in Python.

Say you have two lists.

list_a = [1, 2, 3]
list_b = ['Nono', 'MA']

And that you want to create a continuous list with the contents of both, which would look something like [1, 2, 3, 'Nono', 'MA'].

You can simple add both lists to obtain that result.

>>> combined_list = [1, 2, 3] + ['Nono', 'MA']
>>> combined_list
[1, 2, 3, 'Nono', 'MA']

Of course, it doesn't too much sense in this example because we're explicitly defining the lists and we could define a combined list directly.

combined_list = [1, 2, 3, 'Nono', 'MA']

But it can be useful when we actually need to add lists, for instance to concatenate the results of glob file listing operations.

>>> from glob import glob
>>> files_a = glob('a/*')
>>> files_a
['a/file.txt', 'a/image.jpg']
>>> files_b = glob('b/*')
>>> files_b
['b/data.json', 'b/profile.jpeg']
>>> all_files = files_a + files_b
>>> all_files
['a/file.txt', 'a/image.jpg', 'b/data.json', 'b/profile.jpeg']

DECEMBER 28, 2022

Iterating through a list.

for i in 1 2 3
do
  echo $i
done
# 1
# 2
# 3

Iterating through a list generated with a sequence.

for i in $(seq 1 2 10)
do
  echo $i
done
# 1
# 3
# 5
# 7
# 9

seq 1 2 10 creates a list of numbers from 1 to 10 in steps of 2.

DECEMBER 16, 2022

According to OpenAI, "embeddings are numerical representations of concepts converted to number sequences, which make it easy for computers to understand the relationships between those concepts."

They introduced a new text and code embeddings API endpoint in January 25, 20221 capable of measuring the relatedness of text strings.

Here's a list of common uses of text embeddings, as listed in OpenAI's documentation.

  • Search (where results are ranked by relevance to a query string)
  • Clustering (where text strings are grouped by similarity)
  • Recommendations (where items with related text strings are recommended)
  • Anomaly detection (where outliers with little relatedness are identified)
  • Diversity measurement (where similarity distributions are analyzed)
  • Classification (where text strings are classified by their most similar label)

I look forward to testing this API on my writing to see how well it recommends, classifies, and clusters my mini-essays.

DECEMBER 15, 2022


An overview of esbuild, a JS bundler up to 100x faster than its competitors.


See transcript ›

NOVEMBER 28, 2022


Creating grids with native HTML5 and the "display: flex" CSS property.


See transcript ›

NOVEMBER 21, 2022


How to encode an image dataset to reduce its dimensionality and visualize it in the 2D space.


See transcript ›

NOVEMBER 18, 2022

Here's a way to encode a Laravel site request as JSON to log it via Laravel's logging mechanism, using the Log class from the illuminate/support package1.

// Log parameters in a get request
Route::get('a-view', function(Request $request) {
  \Log::info(json_encode(request()->server()));
  return view('your.view');
});

// Log parameters in a get request and redirect
Route::get('redirect', function(Request $request) {
  \Log::info(json_encode(request()->server()));
  return redirect('/some/page');
});

  1. The service provider of Laravel's Log class is Illuminate\Support\Facades\Log

NOVEMBER 16, 2022

Here's how to translate 3d points in Python using a translation matrix.


To translate a series of points in three dimensions in Cartesian space (x, y, z) you first need to "homogenize" the points by adding a value to their projective dimension—which we'll set to one to maintain the point's original coordinates, and then multiply our point cloud using NumPy's np.matmul method by a transformation matrix constructed from a (4, 4) identity matrix with three translation parameters in its bottom row (tx, ty, tz).

Steps

Here's a breakdown of the steps.

  • Import the NumPy Python library
  • Define a point cloud with Cartesian coordinates (x, y, z)
  • Convert the points to homogeneous coordinates (x, y, z, w)
  • Define our translation parameters (tx, ty, tz)
  • Construct the translation matrix
  • Multiply the homogenized point cloud by the transformation matrix with NumPy's np.matmul

Code

# translate.py
import numpy as np

# Define a set of Cartesian (x, y, z) points
point_cloud = [
    [0, 0, 0],
    [1, 0, 0],
    [0, 1, 0],
    [0, 0, 1],
    [1, 1, 1],
    [1, 2, 3],
]

# Convert to homogeneous coordinates
point_cloud_homogeneous = []
for point in point_cloud:
    point_homogeneous = point.copy()
    point_homogeneous.append(1)
    point_cloud_homogeneous.append(point_homogeneous)

# Define the translation
tx = 2
ty = 10
tz = 100

# Construct the translation matrix
translation_matrix = [
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 1, 0],
    [tx, ty, tz, 1],
]

# Apply the transformation to our point cloud
translated_points = np.matmul(
    point_cloud_homogeneous,
    translation_matrix)

# Convert to cartesian coordinates
translated_points_xyz = []
for point in translated_points:
    point = np.array(point[:-1])
    translated_points_xyz.append(point)

# Map original to translated point coordinates
# (x0, y0, z0) → (x1, y1, z1)
for i in range(len(point_cloud)):
    point = point_cloud[i]
    translated_point = translated_points_xyz[i]
    print(f'{point} → {list(translated_point)}')

NOVEMBER 12, 2022

If you try to serialize a NumPy array to JSON in Python, you'll get the error below.

TypeError: Object of type ndarray is not JSON serializable

Luckily, NumPy has a built-in method to convert one- or multi-dimensional arrays to lists, which are in turn JSON serializable.

import numpy as np
import json

# Define your NumPy array
arr = np.array([[100,200],[300,400]])

# Convert the array to list
arr_as_list = arr.tolist()

# Serialize as JSON
json.dumps(arr_as_list)
# '[[100, 200], [300, 400]]'

NOVEMBER 9, 2022


How to use TensorFlow inside of a Docker container.


See transcript ›

NOVEMBER 7, 2022


How to sort a Vue.js view by different attributes and toggle different view modes.


See transcript ›

NOVEMBER 2, 2022

You can get tomorrow's date in TypeScript with the Date class.

// Create a date
const tomorrow = new Date()

// Set date to current date plus 1 day
tomorrow.setDate(tomorrow.getDate() + 1)
// 2022-11-03T09:55:29.395Z

You could change that + 1 to the time delta you want to go backward or into the future.

// Create a date for Jan 2, 2020
const aDate = new Date(Date.parse("2020-01-02"))

// Go back in time three days
aDate.setDate(aDate.getDate() - 3)
new Date(aDate)
// 2019-12-30T00:00:00.000Z

// Go back in time three days
aDate.setDate(aDate.getDate() - 3)
new Date(aDate)
// 2019-12-27T00:00:00.000Z

// Go forward in time forty days
aDate.setDate(aDate.getDate() + 40)
new Date(aDate)
2020-02-05T00:00:00.000Z

OCTOBER 31, 2022


How to hide un-compiled Vue templates while loading.


See transcript ›

OCTOBER 28, 2022


How to build a website with the Next.js React framework and TypeScript.

# TL;DR
npx create-next-app@latest --ts
cd my-app
npm run build
npm start
# ready - started server on 0.0.0.0:3000, url: http://localhost:3000

See transcript ›

OCTOBER 27, 2022

I encountered the following error while trying to run a Python script and import TensorFlow Lite 2.10.0 runtime's interpreter, i.e., tflite_runtime.interpreter.

python -c "from tflite_runtime.interpreter import Interpreter; print(Interpreter)"
# ImportError: /lib64/libm.so.6: version `GLIBC_2.27' not found
# (required by /var/lang/lib/python3.8/site-packages/tflite_runtime/_pywrap_tensorflow_interpreter_wrapper.so)

As of October 25, 2022, tflite-runtime versions 2.8.0, 2.9.1, and 2.10.0 return the same error.

The issue was solved by downgrading to tflite-runtime version 2.7.0.

python -c "from tflite_runtime.interpreter import Interpreter; print(Interpreter)"
# <class 'tflite_runtime.interpreter.Interpreter'>

I haven't found a way to make tflite-runtime 2.10.0 work. If you do, please let me know!

OCTOBER 14, 2022

Here's how to download a Dropbox file from a shared Dropbox link using the command-line interface with cURL.

The first thing we need is a shared link from Dropbox of a folder or file we want to download. These links end in dl=0. Swap the zero for a one to force the link to be a download link, i.e., dl=1.

!curl -L \
  -o file.zip \
  https://www.dropbox.com/s/[hash]?dl=1
  • The -L flag tells cURL to follow Dropbox's link redirect
  • file.zip specifies the file name and extension; make sure to use the zip extension if you're downloading a folder or the file's corresponding extension if you're downloading a single file, e.g., jpg or pdf

OCTOBER 13, 2022

Here's how to convert Pix2Pix models trained with TensorFlow to TensorFlow.js for use on the web or with Node.js with @tensorflow/tfjs-node and/or @tensorflow/tfjs-core.

Training a Pix2Pix model with TensorFlow

The first thing we need is to train a Pix2Pix model with TensorFlow. You can watch my video on how to Train and Predict with Pix2Pix.

That workflow will produce an .h5 file1.

Converting a Pix2Pix model to TensorFlow.js format

The .h5 models can be loaded with TensorFlow Core but won't be usable in the JavaScript version of TensorFlow.

For conversion, TensorFlow provides the tfjs-converter library as a "a command line utility that converts Keras and TensorFlow models for use in TensorFlow.js."

Here's a sample command you can use to convert a model.

tensorflowjs_converter \
    --input_format=keras \
    /tmp/model.h5 \
    /tmp/tfjs_model

Installing the tfjs-converter library

Optionally, we can create an Anaconda environment to install these dependencies we can discard after we're done converting our models. Note that, as of October 11, 2022, the tfjs-converter repository specifies Python 3.6.8 as a requirement for the converter to work, but I was able to make it work with Python 3.9.

# Create a new environment (optional)
conda create -n tfjs python=3.9 -y

# Activate conda environment (optional)
conda activate tfjs

# Install tfjs-converter
pip install tensorflowjs

Now we're all set to convert our H5 model to TensorFlow.js.

tensorflowjs_converter \
    --input_format=keras \
    ./model.h5 \
    ./tfjs-model

If you created an Anaconda environment, here's how to dispose of it. You can also keep it for future model conversions.

# Exit the environment
conda deactivate

# Delete conda environment
conda remove -n tfjs --all -y

Cannot install tensorflowjs

You may get the following error in Apple Silicon Macs because the tensorflow dependency package can't be resolved. I created a Google Colab notebook so you can test this on the browser.

ERROR: Cannot install tensorflowjs==0.1.0, tensorflowjs==0.1.1, tensorflowjs==0.1.2, tensorflowjs==0.2.0, tensorflowjs==0.2.1, tensorflowjs==0.3.0, tensorflowjs==0.3.1, tensorflowjs==0.4.0, tensorflowjs==0.4.1, tensorflowjs==0.4.2, tensorflowjs==0.5.0, tensorflowjs==0.5.2, tensorflowjs==0.5.4, tensorflowjs==0.5.6, tensorflowjs==0.5.7, tensorflowjs==0.6.0, tensorflowjs==0.6.1, tensorflowjs==0.6.2, tensorflowjs==0.6.4, tensorflowjs==0.6.5, tensorflowjs==0.6.7, tensorflowjs==0.8.0, tensorflowjs==0.8.5, tensorflowjs==0.8.6, tensorflowjs==1.0.1, tensorflowjs==1.1.2, tensorflowjs==1.2.1, tensorflowjs==1.2.10, tensorflowjs==1.2.10.1, tensorflowjs==1.2.2, tensorflowjs==1.2.2.1, tensorflowjs==1.2.3, tensorflowjs==1.2.6, tensorflowjs==1.2.9, tensorflowjs==1.3.1, tensorflowjs==1.3.1.1, tensorflowjs==1.3.2, tensorflowjs==1.4.0, tensorflowjs==1.5.2, tensorflowjs==1.6.0, tensorflowjs==1.7.2, tensorflowjs==1.7.3, tensorflowjs==1.7.4, tensorflowjs==1.7.4.post1, tensorflowjs==2.0.0, tensorflowjs==2.0.1, tensorflowjs==2.0.1.post1, tensorflowjs==2.1.0, tensorflowjs==2.3.0, tensorflowjs==2.4.0, tensorflowjs==2.5.0, tensorflowjs==2.6.0, tensorflowjs==2.7.0, tensorflowjs==2.8.0, tensorflowjs==2.8.1, tensorflowjs==2.8.2, tensorflowjs==2.8.3, tensorflowjs==2.8.4, tensorflowjs==2.8.5, tensorflowjs==3.0.0, tensorflowjs==3.1.0, tensorflowjs==3.11.0, tensorflowjs==3.12.0, tensorflowjs==3.13.0, tensorflowjs==3.14.0, tensorflowjs==3.15.0, tensorflowjs==3.17.0, tensorflowjs==3.18.0, tensorflowjs==3.19.0, tensorflowjs==3.2.0, tensorflowjs==3.20.0, tensorflowjs==3.21.0, tensorflowjs==3.3.0, tensorflowjs==3.4.0, tensorflowjs==3.5.0, tensorflowjs==3.6.0, tensorflowjs==3.7.0, tensorflowjs==3.8.0 and tensorflowjs==3.9.0 because these package versions have conflicting dependencies.

  1. An H5 file is a data file saved in the Hierarchical Data Format (HDF). FileInfo

OCTOBER 1, 2022

I've had conda's initialization code in my .zshrc file for a long time. I've used it on my former Intel and M1 Macs, but it was just recently that I migrated my code to a new M1 Max Apple Silicon Mac. When I start a new Terminal window, I see how a Python process takes up to 5–10 seconds to finalize before the Terminal becomes responsive. I'm used to hitting CMD + N for a new window or CMT + T for a new tab and starting to type immediately. But this issue breaks my workflow and keeps me hanging for a few seconds per new window (!).

Here's my initialization code, auto-generated by Anaconda.

# >>> conda initialize >>>
!! Contents within this block are managed by 'conda init' !!
__conda_setup="$('/Users/nono/anaconda3/bin/conda' 'shell.zsh' 'hook' 2> /dev/null)"
if [ $? -eq 0 ]; then
    eval "$__conda_setup"
else
    if [ -f "/Users/nono/anaconda3/etc/profile.d/conda.sh" ]; then
        . "/Users/nono/anaconda3/etc/profile.d/conda.sh"
    else
        export PATH="/Users/nono/anaconda3/bin:$PATH"
    fi
fi
unset __conda_setup
# <<< conda initialize <<<

This issue also happens with Miniforge on Macbooks, as seen in this GitHub issue titled Slow zsh startup on MacBook Pro 14-inch (M1 Pro). In my case, it's not the M1 Pro but the M1 Max. So the issue seems independent of specific chips but may be an Apple-Silicon-only problem.

Others may see this in M1, M1 Pro, M1 Max, M1 Ultra, and M2 chips and the incoming M2 Pro, M2 Max, and M2 Ultra.

Please let me know on Twitter if you find out how to make this initialization faster. In the meantime, I've removed this code and will have to get it back when I use conda, or simply find another way to initialize Anaconda on demand, only in the Terminal instances I want to use it.

SEPTEMBER 21, 2022

Let's create two files with incremental content.

echo "Hello," > a
echo "Hello, Nono\!" > b

Then show their diff in Visual Studio Code.

code --diff a b

A VSCode window comparing the two files we created will open.

SEPTEMBER 10, 2022

List existing keys added to the SSH agent.

ssh-add -l
# The agent has no identities.

Create a new EdDSA key1.

ssh-keygen -t ed25519 -C "your@email.com"
# Generating public/private ed25519 key pair.
# Enter file in which to save the key (/Users/john/.ssh/id_ed25519):               
# Enter passphrase (empty for no passphrase): 
# Enter same passphrase again: 
# Your identification has been saved in /Users/john/.ssh/id_ed25519
# Your public key has been saved in /Users/john/.ssh/id_ed25519.pub
# 
# The key fingerprint is:
# SHA256:CPtr5U4xCPT1Ypssz0L/bIn7+l2gNMVe1Bkh2H8tB6w your@email.com
# 
# The key's randomart image is:
# +--[ED25519 256]--+
# |        .o*B= oo.|
# |         *o++= ..|
# |        =.B .+o  |
# |       o B Eo.o  |
# |        S o  +.  |
# |       . .  + .. |
# |        .   .+.o.|
# |           . ++++|
# |            .+*.*|
# +----[SHA256]-----+

Here's how to copy the public key to the clipboard on macOS Terminal.

cat /Users/john/.ssh/id_ed25519 | pbcopy
# ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMe/yQPuB7k4MO6pL9c+03YHXKc3q/LLvaUED24Vu6P your@email.com

Your public key can be added to services like GitHub or Bitbucket for remote Git pull/push access or Linux machines via SSH.

For instance, you'd add that output as a New SSH Key to GitHub at github.com/settings/keys.


  1. According to Goteleport, "the [key type] choice is between RSA 2048/4096 and Ed25519 and the trade-off is between performance and compatibility. RSA is universally supported among SSH clients while EdDSA performs much faster and provides the same level of security with significantly smaller keys." 

SEPTEMBER 8, 2022

I spent some time looking for a solution to change the location of Visual Studio Code’s settings.json file without any luck.

But I found a solution.

Visual Studio Code’s settings JSON file is always located at the same path.

~/Library/Application\\ Support/Code/User/settings.json

The way I worked around this limitation is by creating a symbolic link from this path to another file. In my case, I place settings.json on a Dropbox folder that is synchronized across my computers.

ln -s \
~/Dropbox/settings/vscode/settings.json \
~/Library/Application\\ Support/Code/User/settings.json

SEPTEMBER 5, 2022

Here's how to round a decimal float number to two decimals in TypeScript.

parseFloat("123.456").toFixed(2);
// Returns 123.46

Of course, you could change toFixed(2) to any number to keep as many decimals as you wanted.

SEPTEMBER 3, 2022

Here's how to convert a string from CamelCase to snake_case in Python with regular expressions.

import re

# Option 1
regex = r'(?<!^)(?=[A-Z])'
re.sub(regex, '_', 'GettingSimple', 0).lower()
# returns 'getting_simple'

# Option 2
pattern = re.compile(r'(?<!^)(?=[A-Z])')
pattern.sub('_', 'nonoMartinezAlonso').lower()
# returns 'nono_martinez_alonso'

See how to Convert from snake_case to camelCase.

SEPTEMBER 2, 2022

Here's how to remove a remote Git branch without using an app or a website user interface, say, GitHub or GitLab.

# Delete a remote Git Branch (assuming your remote is called origin)
git push origin --delete your-branch-name
# To bitbucket.org:nonoesp/some-repo.git
#  - [deleted]         your-branch-name

Remember that you will also have to delete the Git branch from your local repository as follows.

# Delete a local branch
# -d is a shorthand for --delete
git branch -d your-branch-name
# Deleted branch your-branch-name (was f9b622c).

# Force delete a local branch (regardless of the branch merge status)
# -D is a shorthand for --delete --force
git branch -D your-branch-name
# Deleted branch your-branch-name (was f9b622c).

SEPTEMBER 1, 2022

Here's how to remove a local Git branch.

# Delete a local branch
# -d is a shorthand for --delete
git branch -d your-branch-name
# Deleted branch your-branch-name (was f9b622c).

# Force delete a local branch (regardless of the branch merge status)
# -D is a shorthand for --delete --force
git branch -D your-branch-name
# Deleted branch your-branch-name (was f9b622c).

Remember that you will also have to delete the Git branch from your remote repository as a separate step.

Here's how to remove a remote Git branch without using an app or a website user interface, say, GitHub or GitLab.

# Delete a remote Git Branch (assuming your remote is called origin)
git push origin --delete your-branch-name
# To bitbucket.org:nonoesp/some-repo.git
#  - [deleted]         your-branch-name

AUGUST 31, 2022

I run the following command to clean up unneeded old Homebrew kegs on macOS.

brew cleanup --prune=all

But often get this error.

Error: Could not cleanup old kegs! Fix your permissions on:

Here's the tail of what the command returns when there are permissions issues.

==> This operation has freed approximately 395MB of disk space.
Error: Could not cleanup old kegs! Fix your permissions on:
  /opt/homebrew/Cellar/dnsmasq/2.85
  /opt/homebrew/Cellar/php/8.1.1
  /opt/homebrew/Cellar/php/8.1.1.reinstall
  /opt/homebrew/Cellar/php/8.1.3_1
  /opt/homebrew/Cellar/php/8.1.4.reinstall
  /opt/homebrew/Cellar/php@7.4/7.4.21_1
  /opt/homebrew/Cellar/php@7.4/7.4.27.reinstall
  /opt/homebrew/Cellar/php@7.4/7.4.28_1.reinstall
  /opt/homebrew/Cellar/php@8.0/8.0.14

The fix

brew won't use root permissions for any operations.

This is what you'll see if you try to cleanup with root.

sudo brew cleanup --prune=all                                                   
# Error: Running Homebrew as root is extremely dangerous and no longer supported.
# As Homebrew does not drop privileges on installation you would be giving all
# build scripts full access to your system.

What you have to do is to change the ownership of the problematic directories to your username instead of root.

You can do that with sudo chown -R "$USER":admin /conflicting/dir.

To solve the issues shown in my error above, I ran the following commands.

sudo chown -R "$USER":admin /opt/homebrew/Cellar/dnsmasq/2.85
sudo chown -R "$USER":admin /opt/homebrew/Cellar/php/8.1.1
sudo chown -R "$USER":admin /opt/homebrew/Cellar/php/8.1.1.reinstall
sudo chown -R "$USER":admin /opt/homebrew/Cellar/php/8.1.3_1
sudo chown -R "$USER":admin /opt/homebrew/Cellar/php/8.1.4.reinstall
sudo chown -R "$USER":admin /opt/homebrew/Cellar/php@7.4/7.4.21_1
sudo chown -R "$USER":admin /opt/homebrew/Cellar/php@7.4/7.4.27.reinstall
sudo chown -R "$USER":admin /opt/homebrew/Cellar/php@7.4/7.4.28_1.reinstall
sudo chown -R "$USER":admin /opt/homebrew/Cellar/php@8.0/8.0.14

Then run brew cleanup --prune=all again and everything should work.

AUGUST 29, 2022

You can use the parse_url built-in PHP method to break down a URL into its components.

here's the method itself.

parse_url(string $url, int $component = -1): int|string|array|null|false

And here's how to use it.

parse_url("https://gettingsimple.com/podcast");
// [
//     "scheme" => "https",
//     "host" => "gettingsimple.com",
//     "path" => "/podcast",
// ]

You can then obtain the scheme, host, or path of a URL.

$components = parse_url("https://gettingsimple.com/podcast");

$scheme = $components["scheme"];
// "https"

$host = $components["host"];
// "gettingsimple.com"

$path = $components["path"];
// "/podcast"

Want to see older publications? Visit the archive.

Listen to Getting Simple .