I wrote a script to list (and optionally delete) the image files it finds in a directory that are not referenced any where. I specified a single sub-directory at a time. If you do the entire project it may take long to complete.
#!/bin/bash
PROJECT_DIR="$PWD"
IMAGE_DIR="$PWD"
DELETE_FLAG=false
# Check for -d (delete) flag
if [[ $1 == "-d" ]]; then
DELETE_FLAG=true
fi
# Find all image files
find $IMAGE_DIR -type f \( -iname \*.png -o -iname \*.jpg -o -iname \*.jpeg -o -iname \*.gif -o -iname \*.webp -o -iname \*.svg -o -iname \*.ico \) | while read img_file; do
# Extract the basename of the image file
img_basename=$(basename "$img_file")
# Search for it in the project, excluding .zip files and .git directories
result=$(grep -ril --exclude=*.zip --exclude-dir=.git "$img_basename" "$PROJECT_DIR")
# If not found, print it or delete it based on flag
if [ -z "$result" ]; then
echo "Unused image: $img_file"
if $DELETE_FLAG; then
rm "$img_file"
echo "Deleted: $img_file"
fi
fi
done
Be sure to edit the code to include the image file types that you want to target. Make the file executable before trying to use it:
chmod +x find_unused_images.sh
Run the script (without deletion)
./find_unused_images.sh
To run the script with the delete functionality:
./find_unused_images.sh -d
In an early version, my script was leaving behind files that appeared to be completely unreferenced in the code base (I checked manually by searching the project via IDE). To troubleshoot, I ran grep from the command line:
grep -ril "experienceLogo2.png" "$PWD"
It turned out that those files were referenced in a zip file and in git objects (and therefore considered not unused). I fixed this bug by adding the flags –exclude=*.zip –exclude-dir=.git to my grep command in find_unused_images.sh.
I had lots of unused stock images and old designs that felt good to purge. Now with generative image AI, I know I could create assets easily as I need them in the future.
This code can be changed to search for any kind of file. Here’s an updated version I used to see if some old Bootstrap files were being used any where:
#!/bin/bash
PROJECT_DIR="$PWD"
FILE_DIR="$PWD/css"
DELETE_FLAG=false
# Check for -d (delete) flag
if [[ $1 == "-d" ]]; then
DELETE_FLAG=true
fi
# Find all image files
# find $FILE_DIR -type f \( -iname \*.png -o -iname \*.jpg -o -iname \*.jpeg -o -iname \*.gif -o -iname \*.webp -o -iname \*.svg -o -iname \*.ico \) | while read my_file; do
find $FILE_DIR -type f \( -iname \*.css -o -iname \*.js \) | while read my_file; do
# Extract the basename of the image file
file_basename=$(basename "$my_file")
# Search for it in the project, excluding .zip files and .git directories and .xml directories
result=$(grep -ril --exclude=*.zip --exclude-dir=.git --exclude=*.xml "$file_basename" "$PROJECT_DIR")
# If not found, print it or delete it based on flag
if [ -z "$result" ]; then
echo "Unused file: $my_file"
if $DELETE_FLAG; then
rm "$my_file"
echo "Deleted: $my_file"
fi
fi
done
This didn’t work perfectly. I had to add an exclusion condition for .xml files because my WordPress blog archive files were being highlighted in the search. (EOD, I ended up zipping the few .xml archive files anyway. I also keep them on an S3 bucket, but I enjoy redundancy.)
The file name “bootstrap.css” was being found in its own file.
This, at least, gave me enough confidence to just delete the files manually. I can’t call this a perfect tool (and I don’t think it would scale well), but it is a utility for a practical use-case.
I saw other examples of it acting funny. For instance, I had an old stock photo file named ‘5.jpg’. It was coming up as being used in the project because, for some reason, that string was found in another image file – ‘jimmy.webp’.
This post is all about how I help clients with their existing websites. If you are a fellow web developer reading this, use it for ideas on how to serve more people. My mission is to help businesses achieve more through their web presence.
If your curious about how I can help you with your website, send me a message. Below are some ways I will serve you.
Content updates & maintenance
I try to be a company’s go-to guy that can be called whenever anything comes up with their website.
If someone has a website, even if it is a static brochure site, it will eventually need help. It could be small content updates to reflect inevitable changes to the business. Or, one day something goes wrong. The website suddenly doesn’t work at all. Maybe someone notices something broken, be it major or minor. I want them to think of me and know that I can help.
There’s some businesses that regularly make content updates themselves using a CMS (like WordPress, Wix, Shopify, etc.). They could need occasional help with customizations, CMS or plugin update issues, and more. I let clients know that when issues arise, I’m just a text message away.
Audit & enhancement
A client usually requests this service in the form of a complaint: “My website is too slow”, “I’m not getting enough sales/conversions/leads from my website”. My response is to do a formal audit and analysis. The result includes recommendations for improvement and an estimate (time/price) for implementation.
Other times, clients do not even know that there is a potential risk (security issues) or unrealized upside (UX issues) that needs attention. Providing those insights with empathy and transparency helps businesses see that value. Below is a list of audit types I offer:
Security: Is your website secure? Does it use https? Is WordPress up-to-date? Is your site vulnerable to being taken over by hackers?
Accessibility: Is your website usable for people with disabilities? Many businesses don’t realize that this is a legal requirement under the ADA.
SEO: Technical SEO, optimization, and more
Design UI/UX: Does your website look like it’s stuck in the past? How does it perform on different mobile devices? Is there brand consistency? Is the user experience the best that it can be? Maybe it is slow and bloated from plugins/integrations and needs a refresh.
Once we figure out what needs attention, I can create a plan and strategy to enhance your website. We can take it one step and a time, and focus on what will have the greatest impact for you and your business. You can read more about my process for web development with freelance clients in another post.
Preemptive audits to win new clients
I approach clients (existing or perspective) with a value proposition from a place of wanting to give. This is especially true for small businesses that appear to be leaving honey 🍯 on the table.
My process for engaging a new client, especially cold ones, is to review their website and make note of any obvious improvements. One example is using a generic email address (ExampleBusiness@gmail.com) instead of their own domain. Others include broken 404 links and bad mobile UX.
Google Chrome’s built in Lighthouse feature helps me to highlight low performance in existing websites of potential clients.
Screaming Frog SEO Spider is another utility that I use to identify (and pitch) improvements to new client web projects.
Training & education
Sometimes, managers and stakeholders want to know how things work or how to make some changes themselves. That’s why I offer technical training, education, and tutoring.
Your business might have a designer or marketer that is ready to add some tech skills to the mix. I can help with that too. Contact me about the personalized tech training that I offer.
Disaster recovery & best practices
When a tech disaster happens you need to have a plan for recovery. Do you have backups? What is the RPO and RTO for your organization when “the business is on fire”? Work with me to be prepared in these situations. I apply proven strategies and make sure your digital presence is resilient.
A moment of preparation is worth a week of remediation. Sailing ahead of the storm is possible by following best practices How does your organization manage passwords and credentials? What apps do your employees use? Knowing the right questions is the only way to build valuable answers.
This post documents a solution for the following error I encountered in a React TypeScript project that was using Redux:
No overload matches this call.
Overload 1 of 2, '(props: ProviderProps<Action> | Readonly<ProviderProps<Action>>): Provider<Action>', gave the following error.
Type '{ children: Element; store: Store<{ readonly [$CombinedState]?: undefined; } & { repos: ReposState; }, Action>; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<Provider<Action>> & Readonly<ProviderProps<Action>>'.
Property 'children' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<Provider<Action>> & Readonly<ProviderProps<Action>>'.
Overload 2 of 2, '(props: ProviderProps<Action>, context: any): Provider<Action>', gave the following error.
React is a JavaScript framework used to build web apps. Redux is an open-source library used to manage the state of an application, and is often used along with React JS.
In this context, the Redux <Provider> component is used to pass application state along to its children components. It is best practice to wrap the entirety of your app in the <Provider> component, and pass the store object into it as an argument (property):
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import { Provider } from 'react-redux';
import App from './App';
import { store } from './state';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
In my project, this was producing an error on the <Provider> component that said “No overload matches this call.” I knew this must have something to with the argument(s) being passed along.
At first I thought it might have something to do with my store object, but that was not it. I was able to resolve this roadblock by explicitly passing a ProviderProps object into the Provider component.
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import { Provider, ProviderProps } from 'react-redux';
import App from './App';
import { store } from './state';
const providerProps: ProviderProps = {
store: store,
};
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<Provider {...providerProps}>
<App />
</Provider>
</React.StrictMode>
);
This worked because the store object is explicitly typed in the ProviderProps object. This ensures that it matches the type expected by the Provider component. Ultimately, it was a TypeScript error. TypeScript was not able to infer the store type correctly.
A common coding challenge that you may encounter will ask you to write a function that returns the Fibonacci sequence, up to the nth number (which is provided as an argument). Below is a solution, along with some commentary about the topic. This challenge is a great exercise in problem solving.
Fibonacci numbers
The sequence starts with 0 and 1, and the next number in the sequence is always the sum of the previous two numbers. The first few terms of the sequence are:
Since this series follows a known pattern, we can write code to generate it up to a specified position.
In mathematics, the Fibonacci sequence is a sequence in which each number is the sum of the two preceding ones. Numbers that are part of the Fibonacci sequence are known as Fibonacci numbers.
These numbers were first described in ancient Indian mathematics. They are named after the Italian mathematician Leonardo of Pisa, also known as Fibonacci, who introduced the sequence to Western European mathematics in his 1202 book Liber Abaci. Fibonacci numbers are also strongly related to the golden ratio. In computer science, these numbers can be applied to programming algorithms related to search, data structures, and graphs.
Recursion
In programming, recursion is an important concept. Recursion is when a function calls itself. Writing code that generates the Fibonacci sequence requires recursion. Here is an example in JavaScript:
function fibonacci(n) {
if (n < 2){
return 1;
}else{
return fibonacci(n-2) + fibonacci(n-1);
}
}
console.log(fibonacci(7)); // Returns 21
If you ask this function for 0 or 1 terms, it will simply return 1. It is the condition that will ultimately stop the recursion. Anything above that, it will have to call itself recursively. What exactly is happening there? This Stack Overflow thread does a great job stepping though the logic and illustrating each iteration.
When I decorate the code from above with console log statements and collect the results into an array, it becomes obvious:
function fibonacci(n) {
console.log("Iterating on n = " + n)
if (n <= 1) {
console.log("n is: "+n)
return [0, 1].slice(0, n + 1);
} else {
const fib = fibonacci(n - 1);
console.log("fib is: "+fib)
console.log("iterating on " + n)
fib.push(fib[fib.length - 1] + fib[fib.length - 2]);
return fib;
}
}
You can see it iterates all the way down, starting at the desired argument of term numbers, and ending with 1. Once it gets to the bottom, it starts going back up. On each subsequent round, it adds a new number to the result array by summing the previous two numbers.
I asked ChatGPT to rewrite this code for me, and this is how it was cleaned up:
function fibonacci(n) {
if (n <= 1) {
return [0, 1].slice(0, n + 1);
} else {
var sequence = fibonacci(n - 1);
sequence.push(sequence[sequence.length - 1] + sequence[sequence.length - 2]);
return sequence;
}
}
This function has an exponential time complexity. It can be very slow for larger values of n.
Memoization and Cacheing
This challenge is often followed with a request to make it “more performant”. I mentioned above that our solution has “exponential time complexity”. Its algorithmic efficiency can be described as O(2^n) in Big O notation.
Every call (while n is greater than 1) results in two additional recursive calls, one with n-1 and another with n-2. Each of those calls then results in two more recursive calls, and so on, resulting in a binary tree of recursive calls.
The performance of this code can be improved by implementing memoization. Memoization is when we store the results of our function call. On subsequent calls, our application can look up answers it previously calculated without have to do the work again.
function fibonacci2(n, memo = {}) {
if (n in memo) {
console.log("we used the memo on: " + n)
return memo[n];
}
if (n <= 1) {
return [0, 1].slice(0, n + 1);
} else {
const fib = fibonacci2(n - 1, memo);
fib.push(fib[fib.length - 1] + fib[fib.length - 2]);
memo[n] = fib;
return fib;
}
}
memo = {}
console.log(fibonacci2(7, memo));
console.log(fibonacci2(15, memo));
On each iteration we save the results to an array called memo. When we use the function, we first check to see if the answer we want is already stored in our cache. Our course, the work still needs to be done the first time around, but never again – making computation much faster in the future. You can see that my 2nd call can start where the results of the previous call left off:
By using memoization, this implementation of the Fibonacci function has a time complexity of O(n). Another way to improve the performance of our code, without using memoization, is to use a loop:
function fibonacci(n) {
var sequence = [0, 1];
for (var i = 2; i <= n; i++) {
sequence.push(sequence[i-1] + sequence[i-2]);
}
return sequence;
}
The choice between memoization or a loop depends on the specific requirements and constraints of the use-case. While the time complexity of a loop-based solution is still O(n), it can be faster than the memoization approach for small or medium-sized values of n. If simplicity and readability are a higher priority, or if memory usage is a concern, a loop-based solution may be a better option.
Memoization is considered a “dynamic programming” technique. Dynamic programming involves solving sub-problems once and storing the results for future reference, which can significantly improve the efficiency of the overall solution.
On this website (this very one you are reading) I have a resume page. On it, I display a timeline styled with CSS. I use the border-radius property to create circles for the years on that timeline. The years connect between <div> elements that represent my work experience.
It looks great (at least, I think so). Except on mobile. Specifically, on Android running Chrome. The circle appears oblongly squished.
I could not recreate this bug on my laptop, even when I used Chrome Developer Tools to simulate mobile. Not being able to reproduce a bug consistently is a very frustrating experience.
USB debugging
I would have to use remote debugging via Chrome DevTools. The first step was to enable “Developer Options” on my Android device. I was using a S21 Galaxy Ultra. On this device I navigated to “Settings” -> “About Phone” -> “Software information”. Then I triple tapped the “Build number” and received a toast message “Developer mode activated”. This feels familiar, as it has been a similar process on previous Android devices that I have owned. I’d post a picture of my software information screen, but am worried that some of the data could be used maliciously by strangers on the internet.
“Developer options” was revealed in my “Settings” app. From there, I could turn on “USB debugging”
Now, I could connect my Android to my MacBook. From my MacBook, I opened Chrome and navigated to chrome://inspect/#devices
As long as my Android also had Chrome running, I could see my device listed with any open browser tabs. Clicking “inspect” opened a new window on my laptop that mirrored my cellphone’s screen. When I scrolled on one, it reflected on the other.
CSS Solution
I could inspect elements from the developer tools panel. This was exactly what I needed to debug the problem. And, the fix ended up being pretty easy. Here’s the original, relevant CSS:
The issue was ultimately with the width and height properties. The circles look correct on my desktop browser because the width and height rendered to nearly identical – 64px by 66px. To get a circle with CSS you need an element with the same height and width (the greater the difference, the less even and more stretched the circle appears), and then set the border-radius to 50%. On Android, that same element was being rendered as 64px by 85px. This was most probably happening because the height was not being explicitly defined.
As a remedy, I hard-coded the width and height properties. I changed the unit of measurement to pixels for both.
This fixed the circle from being stretched. But, now the text was not centered that way that it should be.
That was happening because of the line-height property. Since it was set without a unit of measure, most browsers would interpret it to mean a multiplier of the current font-size. And, the font-size was also using a relative unit of measure, em. Right away, that was a code smell. It was a browser issue, where along the way values were not being calculated as expected.
On mobile, the font-size was being calculated to 20.8px. On desktop, the font-was was being calculated to 16px. I fixed this by changing the line-height value to an absolute unit of measure:
A palindrome is a word (or a string of characters) that can be read identically either forwards or backwards. Examples include:
racecar
madam
mom
level
civic
kayak
rotavator
The logic for determining a palindrome is simple. Take the input, reverse it, and then compare it to the original. PHP even has a built-in operation, strrev(), to reverse strings. We can write a function to judge if an input string is a palindrome:
During an interview, for a role as a Software Engineer, I was asked “Given a string, determine if you can make it a palindrome by removing at most 1 character.”
To solve that challenge, I can loop through the string while increasing the value of a counter variable. On each iteration, I’d remove a single character (whose index is determined by the count), reverse the result, and then compare it.
I use PHP’s substr() function to get the each half of “the new string with one character removed.” The first division starts from the beginning (zero index) and continues up until the counter determined position (zero on the first loop, one on the second, and so on). The second part starts one step past the iterative count, and finishes with the string’s end. This results is the original input with a single letter deleted.
To illustrate how this works, I printed out the concatenation each time. You can see that the program continues until the result is a palindrome.
Although this works, reversing the string each time is costly. It reduces the algorithmic efficiency, making the solution “not scalable.” How can we decide if a string is a palindrome without reversing it?
Validating a palindrome using recursion
Judging a string to be a palindrome can be done using recursion. In programming, recursion is when a function calls itself.
Before we worry about removing any characters like we did above, we need a new function to verify a palindrome without reversing it:
function determinePalindromeRecursively($value){
if ((strlen($value) < 2)){
// echo "true \n";
return true;
}else{
if (substr($value,0,1) == substr($value,(strlen($value) - 1),1)){
echo substr($value,1,strlen($value) - 2) . "\n";
return determinePalindromeRecursively(substr($value,1,strlen($value) -2));
}else{
// echo " Not a Palindrome";
return false;
}
}
}
This method compares the first and last characters of the input. If they match, our code will remove them and pass the updated $value back around to itself, recursively. This continues until we get down to a single letter, or less – when we know that the original string was a palindrome.
To get the first character, we tell the substr() method to take the $value, start at the beginning (zero index), and collect a single element: substr($value,0,1)
To get the last character, we tell the substr() method to take the $value, start at the end (the string’s length minus one), and collect a single element: substr($value,(strlen($value) – 1),1)
To remove both the first and last letters, we tell substr() to start just past the first element (represented by an index of 1), and to collect the string’s length worth of characters minus two.
Notice that on each recursive loop, the string loses the front and back symbols.
Now, remember the original challenge: “Given a string, determine if you can make it a palindrome by removing at most 1 character.”
All that is left is to use our recursive function in tandem with removing a single character per loop.
Give it a try, refactor my code, and see if you can solve this problem in a different way. There are other computer science puzzles about palindromes that you can apply this logic towards. Have fun!
Programming and meditation have a lot in common. Both demand focus and concentration and can enhance your ability to think clearly and solve problems.
When you program, you are constantly thinking critically and solving problems. You must be able to debug your code when something goes wrong, pay close attention to the little things, and think rationally. This can be mentally difficult since it calls for intense focus and concentration.
Focus and concentration can be enhanced through the practice of meditation. It can entail sitting in quiet while concentrating on your breath or a mantra, which is a particular word or phrase. This can assist with distraction removal and strengthen your capacity for sustained attention.
I started meditating early in my career as a programmer. Having had an interest in Buddhism, the tangental practice of meditation was appealing as a self-care ritual. I didn’t anticipate the impact its benefits would have on my skill, motivation, and focus. These necessary attributes compounded as my practice grew.
Meditation can help reduce stress and anxiety. This is important for programmers, as the work can be challenging. The ability to stay calm and focused in the face of stress can be invaluable in the programming world.
I had always been a workhorse, spending hours upon hours coding and debugging. Meditating for just a few minutes a day helped me to clear my mind and focus on the task at hand. I discovered I could sleep better and was less irritable.
As I continued to meditate, I found that my productivity increased, and I was able to tackle problems that previously seemed insurmountable with ease. My coding was more precise and effective. I was able to think more imaginatively and provide fresh ideas for problems.
I was so impressed with the benefits of meditation that I began to teach it to my colleagues. They also discovered that it made people more productive and happier at work.
Writing as a Meditation Practice
In another post, Reading Habits for Programmers, I discuss the benefits of practicing focus to improve coding skills. I mention that “ifreading benefits your coding ability, then meditation does too”. I would go as far as to say that reading is a form of meditation. Any time we intentionally shift our focus from scattered to laser-like, we are practicing mindfulness. Note-taking and blogging is a great way to do that too.
Writing about how the world seems through your own particular lens fosters mindfulness and appreciation. It keeps your mind grounded in this realm. Journaling is an important ritual that I practice daily, but it is hard to take note of and digest everything that happens in our lives. Doing it here is a way that I can highlight the stuff that has truly touched me.
To get philosophical about it, I think that what’s really out there and what we experience might be very different things. Like how a the experience of a computer screen showing a beautifully drawn graphical user interface never hints at the reality of computer boards directing electrical circuits to represent the information we see. Our notion of reality is a digital simulation instigated by the sensory input interpreted through our senses.
Walking meditation (kinhin) is another form that has helped get my through challenging times. Solutions, both to engineering problems as well as life obstacles, reveal themselves mid-stride. Stress relief can be induced by physical activity. Fresh air and blood flow are good for the brain. Light exercise is the necessary distraction that gives our mind space to breathe.
Mindfulness
Our senses keep us plugged into reality. They instantly create a connection to the present. Paying attention is the only way to experience this moment in life. If you’re feeling distracted, panicked, or out-of-touch with reality, try taking time to intentionally notice the things that stimulate you.
Setting out a short period of time each day to sit quietly and concentrate on your breathing might serve as a simple way to include meditation & mindfulness into your regular schedule. There are numerous styles of meditation, so it’s worthwhile to try out different techniques to determine which ones suit you.
Though they may appear unrelated, programming and meditation have more in common than you may imagine. Making meditation & mindfulness a daily habit can help you focus better, manage stress and anxiety, and become a better programmer.
Even under the richest of experiential excitement, without mindfulness there is no participation with, nor illusion of, reality. If you mindlessly read a book, you might as well have not read it. If you day-dream during a movie, you may know as much about the plot as someone who has never saw it. The same is true for life in its entirety. Don’t miss out – pay attention.
You can train yourself to get better at paying attention. The better you get at it, the more you will experience the richness of vibrancy of this world. The two best techniques I have found for this is: meditation and reading.
For a recent side project I was tasked with enhancing an existing HTML table. That table displayed search results. The records were dynamic, populated by an AJAX call after the “search” button was pressed. One of the requests was to let users click on the column headers to sort the table. Each click would organize the data, toggling ascending and descending, based on the column values.
My first idea was to use a front-end library. I love abstractions, and hate reinventing the wheel. I’ve used the DataTables jQuery plug-in before, and thought it might be a good fit. All I had to do was include two CDN file references – one for CSS styles and another for JavaScript functionality. After that, I could select the table by ID and call a single function:
This added quick and easily out-of-the-box functionality, with bells and whistles.
It seemed great, but the extras that it added, such as pagination and front-end search were unnecessary, and actually got in the way of the design specification. Those might be easy enough to clean up by passing options into the method call, or even with a bit of custom CSS, but still seemed like overkill.
Further, the DataTables library would occasionally throw errors about an “incorrect column count”, depending on what results my back-end provided. That was because the data model, and possible front-end actions for this app were more complex that you might think. The HTML wasn’t strictly semantic and the information wasn’t just tabular.
The more I thought about it, the more I felt that plain vanilla JavaScript should be enough to get the job done. And, since we’re living in the future, I decided to ask an AI chat-bot called ChatGPT.
I asked “How can I use jQuery to sort an HTML table by a specific column?”
It told me that “You can use the sort method of JavaScript arrays combined with the map method to sort an HTML table by a specific column,” and gave me a specific code example!
I was very impressed. Here is the JavaScript code it provided:
<script>
$(document).ready(function() {
var table = $('#myTable tbody tr').get();
table.sort(function(a, b) {
var A = $(a).children('td').eq(1).text();
var B = $(b).children('td').eq(1).text();
if(A < B) {
return -1;
}
if(A > B) {
return 1;
}
return 0;
});
$.each(table, function(index, row) {
$('#myTable').children('tbody').append(row);
});
});
</script>
I added this code to a click-handler in my app, after adjusting the element selectors. Although it worked (kind of), it did not operate quite as I expected. It only performed the sort on a single column, and did not alternate the order on each click.
I continued to ask the chat-bot more questions, making refinements to the functionality. I wanted the code to toggle between ascending and descending on each click. Also, I figured it could be nice to avoid jQuery completely and just use basic JS.
Eventually, it told me “To toggle between ascending and descending order when sorting the table, you can keep track of the current sorting order for each column in a separate array”. Below, you can see the full code implementation:
<style>
table {
border-collapse: collapse;
width: 100%;
}
th, td {
text-align: left;
padding: 8px;
border-bottom: 1px solid #ddd;
}
tr:nth-child(even) {
background-color: #f2f2f2;
}
th {
background-color: #4CAF50;
color: white;
cursor: pointer;
}
td:first-child {
font-weight: bold;
}
td:nth-child(3), td:nth-child(4) {
text-transform: capitalize;
}
#search-input {
padding: 8px;
margin-bottom: 12px;
width: 100%;
box-sizing: border-box;
border: 2px solid #ccc;
border-radius: 4px;
font-size: 16px;
}
#search-input:focus {
outline: none;
border-color: #4CAF50;
}
</style>
<input type="text" id="search-input" placeholder="Search breeds...">
<button>Search</button>
<table id="dog-table">
<thead>
<tr>
<th>Breed</th>
<th>Origin</th>
<th>Size</th>
<th>Temperament</th>
</tr>
</thead>
<tbody>
<tr>
<td>Labrador Retriever</td>
<td>Canada</td>
<td>Large</td>
<td>Friendly, outgoing, and active</td>
</tr>
<tr>
<td>German Shepherd</td>
<td>Germany</td>
<td>Large</td>
<td>Loyal, confident, and courageous</td>
</tr>
<tr>
<td>Poodle</td>
<td>France</td>
<td>Small to Large</td>
<td>Intelligent, elegant, and proud</td>
</tr>
<tr>
<td>Bulldog</td>
<td>England</td>
<td>Medium</td>
<td>Determined, friendly, and calm</td>
</tr>
<tr>
<td>Beagle</td>
<td>England</td>
<td>Small to Medium</td>
<td>Cheerful, determined, and friendly</td>
</tr>
</tbody>
</table>
<script>
// Get the table element
const table = document.querySelector('table');
// Get the header row and its cells
const headerRow = table.querySelector('thead tr');
const headerCells = headerRow.querySelectorAll('th');
// Get the table body and its rows
const tableBody = table.querySelector('tbody');
const tableRows = tableBody.querySelectorAll('tr');
// Initialize sort order for each column
let sortOrders = Array.from(headerCells).map(() => null);
// Attach a click event listener to each header cell
headerCells.forEach((headerCell, index) => {
headerCell.addEventListener('click', () => {
// Extract the column index of the clicked header cell
const clickedColumnIndex = index;
// Toggle the sort order for the clicked column
if (sortOrders[clickedColumnIndex] === 'asc') {
sortOrders[clickedColumnIndex] = 'desc';
} else {
sortOrders[clickedColumnIndex] = 'asc';
}
// Sort the rows based on the values in the clicked column and the sort order
const sortedRows = Array.from(tableRows).sort((rowA, rowB) => {
const valueA = rowA.cells[clickedColumnIndex].textContent;
const valueB = rowB.cells[clickedColumnIndex].textContent;
const sortOrder = sortOrders[clickedColumnIndex];
const compareResult = valueA.localeCompare(valueB, undefined, { numeric: true });
return sortOrder === 'asc' ? compareResult : -compareResult;
});
// Rebuild the table in the sorted order
tableBody.append(...sortedRows);
});
});
</script>
Using predictive language models as a coding assistant is very helpful. I can’t wait to see what other uses we find for this technology, especially as it gets better.
The benefits of reading are well documented and enumerated. If you are a programmer, reading helped get you to were you are. Of course, as with any intellectual pursuit, consuming prosaic knowledge is a pre-requisite to success.
Having the time to read, think, and write is a luxury. It’s a habit that that many people claim they can’t afford. And ironically, those are the people that could benefit from it the most. Like strength training, it is something I’ve habituated myself to do daily.
Reading makes you a better software engineer, not because of the information ingested, but because of the byproduct mental skill built as a result. Reading, regardless of the subject matter, is strength training for your mind. It is a neighbor to meditation and mindfulness in regards to brain health.
And, to make the connection explicit: if reading benefits your coding ability, then meditation does too. I’m not the only one that thinks that meditation will make you a better programmer. Reading, and writing, can be meditative pursuits that afford the benefits enjoyed by mystics and monks, engineers and enigmatics*. Consuming knowledge is, in many ways, a programmer’s primary function. Let’s delve into the realms of reading and writing that act as superpowers in molding our intellect and efficiency.
Reading
Literacy seems to date back nearly five-thousand years. It might be even older. I wouldn’t be surprised if the timeline of civilization and humanity turns out to be much longer than what is currently accepted by historians and scholars. Reading and writing are low-tech, non-electrical, super powers that define and augment what it means to be a modern human. Like meditation, reading changes the brain’s physical structure.
Reading is a skill. Even if you “know how to read”, true literacy is highly perishable. Amber Peterson from The NCTE writes, “literacy is the way that we interact with the world around us”. Following the video-game, simulation theory, analogy – being literate is the opposite of being an “NPC” in real life.
This type of literacy flexes same brain muscles as mindfulness. It is the kind of mindfulness that allows us to actually experience life, or what Sam Harris calls “waking up”. Even if you live a very long time, if you were a mindlessly zombie the entire ride, then it might as well not even have happened.
Filmmaker Stephen Apkon is quoted as saying “True literacy is always a two-way transaction. We don’t just consume; we produce. We don’t just read; we write.”
Spoken communication already feels magical. From an alien perspective, it seems I can sing sounds to export my thoughts and ideas into another person’s mind. Writing, then, is an evolution of this transcendent practice that allows brain data to be store, shipped, and unzipped without a livestream.
Listening
Audiobooks unlock a new way to consume written words. Many people find it easier than reading. I think that is because it requires less focus. If I listened to a book, can I say that I have “read” that book? My opinion is “yes”, but does that mean that I can say that I have also “read” a podcast? Do we need new terminology to better describe our world?
I can get through a publication more quickly by listening. But, does that forsake the benefits of increased focus that I discussed above? Entrepreneur Naval Ravikant tweeted, “Listening to books instead of reading them is like drinking your vegetables instead of eating them.”
I love that analogy. It’s still good for you, but there is clearly something missing. And, how Faustianly modern it is to prefer a more processed option in favor of palatability. In another tweet, Naval says, “Reading is more efficient when at rest. Audio is more efficient when in motion.”
I agree, and reserve Audiobooks for when I am moving – walking, exercising at the gym, or even driving. Brian Tracy, author and top business-speaker, said “You can become one of the smartest and most knowledgeable people in your field by turning traveling time into learning time; by turning your car into a mobile classroom,”
I first discovered this concept of a “university on wheels” around 2008 after listening to one of my first-ever Audiobooks, “The Phoenix Transformation“. Around that time I had just finished college, and was trying to figure out what to do with my life. My primary source of income was delivering pizza, so I was on the road a lot, alone in my car.
This was before smartphones were popular, and as a poor young person, I could not afford a stand-alone MP3 player. I was able to download books and other audio programs (Tony Robbins changed my life), and burn them onto blank CD-R disks. A single book might be ten CDs long. If I heard something impactful or if there was an exercise prompt by the author, I would pull over and scribble my thoughts down onto a blank “guest check” pad, sparking the origins of my hypergraphia.
Tips, Tricks, and Recommendations
I recommend reading and writing every day. I like to have two or three physical books that I switch between at home. The Kindle app is my electronic option for when I’m out and have a few minutes to kill. My Audible account keeps multiple audiobooks queued that I can switch between at the gym or on long walks. Here is a list of the books that I listened to in 2020:
Read multiple books, and start new ones before you’ve finished them. Don’t worry about putting a book down and never picking it up again: Life is too short to finish books that you don’t like. Audible allows audiobook returns and refunds credits for reuse.
There’s so much content available, it is hard to decide where to direct your focus. As a rule, I try to not read any new books – meaning, I won’t read material that has been recently published. As a soft rule, I like books that have been released at least five years ago.
I avoid read popular books and best-sellers. I’m excited by obscure material containing alternative, even radical, ideas. If you only read the same books as everyone else, you will only have the same ideas that everyone else is having.
Reading will make you a better writer and content producer. Niall Ferguson mentions that quality books tend to have a thousand-to-one ratio; meaning the author has read about a thousand words on the subject for every one word written.
Niall continues about the compounding benefits of reading: “You’ve got to get that reading speed up early, and then you just have to read and read and read. And it is cumulative, not only in the sense that you get better at reading, but in a fascinating way the knowledge that you imbibe from books is cumulative.”
I am a slow reader. And, I am okay with that. I spend lots of time, in between sentences, thinking and contemplating. My advice is to not try reading faster, but instead to read for longer amounts of time. Like anything worth pursuing, it is about putting the hours in. “Reading is the quiet time in which you reflect and learn,” says author Ryan Holiday.
Writing
The best reading habit that I can recommend to you is to write while you read. A professor once told me: If you don’t have a pen in your hand, you’re not really studying. It ensures that you stay focused and engaged, and trains your mind to not wander. I have mentioned before that writing is “calisthenics for the brain“.
Digital devices, like the Kindle, make it easy to highlight text and take notes. After each page, I like to jot down a synopsis or any tangental thoughts that were sparked in the past few minutes. I don’t read fast. I record and define any unfamiliar words, and keep lists of unique phrases and idioms.
If I really like a new word, I’ll rewrite it and what it means, multiple times. Sometimes, I’ll rewrite sentences that just sound nice, or have a musical quality to them. Then, I’ll try to author my own in a similar style.
Taking notes is a challenge when I listen to audiobooks. Audible provides a feature allowing listeners to bookmark audio clips, and add text comments. Periodically, I’ll audit my collection and transcribe my favorites into a journal.
Journaling To Be A Better Writer (And Programmer)
Private journaling is vital to my process of writing publicly (blogging). I write a lot of stuff that I never publish. And that is the point – it is a personal practice that makes everything public-facing better. That idea is inspired by Kevin Kelly who has said, “I write primarily to find out what I’ve been thinking, and I don’t know until I write it”. The common wisdom is that you should think first, and then write – but to me it is obvious that the reverse is true. Spilling lots of ideas down onto a page is how I get started.
Note taking, making lists, and other kinds of journaling are powerful tools for being prolific. Learning how to make and take good notes requires practice. Simple bullet point lists are an easy way to start. Morning journal pages serve as a long-form translation of the lists I scribble mixed with a stream-of-consciousness narration. I try to write something every single day – no zero days. That’s how I get the noise out of my head. It crystalizes the nebulas storm that rages within my “monkey mind” – a concept I borrow from Tim Ferriss.
The magic always happens when I go back to old scrap and put the editor hat on. I have multiple physical notebooks, and use Evernote on my digital devices. I jot down thoughts, copy ideas while I read, write down new word definitions, and try to fill an entire page with free-flow journaling each day. Organizing all of these sources has become a series of techniques I use to keep finding inspiration.
Digesting the messy ball of words is aided by adding lots of visual cues. I use different colored pens, add drawings and doodles. I lay index cards out on an empty space and take a photo. Volume is the important variable in this quality algorithm. Getting as much down on paper as you can is always the best first step to getting value out of the writing process.
A coder’s diary
Taking notes about the coding challenges you’ve solved and the technical knowledge you’ve learned cements it. Even if you never read your notes again, the act of taking pen to paper will deepen the grooves in your mental records. You should consider keeping a blog for this reason. Having an archive of your experiences expounded, with the ability to search keywords, is invaluable. I include code examples, screenshots, relevant links and quotes, and a story to add context.
That compliments my main point: writing prose will ultimately make you a better programmer. Coding is a discipline nearer to writing then it ever will be to mathematics. That seems counterintuitive to the uninitiated.
This blog acts as my technical log that I can back reference when I encounter a familiar problem. As time passes, and new projects take hold of your attention, it’s easy to forget how you did something, even a few months ago. If you also have a similar blog about tech send me a link – I’d love to read through it!
Note Taking & Processing
There is an art and skill to note taking. It is a neighbor to journaling. When I have the urge and energy to do work, but I am not sure where to begin, I hit my notebooks as step number one.
My process has evolved. I start with physical writing as my primary note taking method. I use a notebook and I use index cards. Eventually (at least months, sometimes longer), I revisit hand-written notes and “process” them. That “process” involves re-reading and commenting on them in their existing form. I move the important (and positive) things to Evernote. I have organized my Evernote in multiple Notebooks. My pipeline looks like: index cards -> notebook -> Evernote -> blog
I use Twitter as a public diary for single sentences, ideas, and quotes that won’t fit any where else.
Since this post is a WIP (work-in-progress), I’ll leave unpolished notes below. That way I can continue refining my thoughts on this subject as time goes on.
– The psychological benefit of writing out negative feelings, and later destroying the paper. (“Burn after writing”)
– Looking back on goals. Reviewing old budgets, and feeling gratitude for where I am now
– Doodling and drawing skills
Writer Kurt Vonnegut said about his process: “When I write, I feel like an armless, legless man with a crayon in his mouth”