mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2026-02-03 02:24:47 +08:00
misc: restructure contents
This commit is contained in:
94
experimental/design/README.md
Normal file
94
experimental/design/README.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Design Questions
|
||||
|
||||
## Guides
|
||||
|
||||
- [Grokking the System Design Interview](https://www.educative.io/collection/5668639101419520/5649050225344512)
|
||||
- https://github.com/donnemartin/system-design-primer
|
||||
- https://github.com/checkcheckzz/system-design-interview
|
||||
- https://github.com/shashank88/system_design
|
||||
- https://gist.github.com/vasanthk/485d1c25737e8e72759f
|
||||
- http://www.puncsky.com/blog/2016/02/14/crack-the-system-design-interview/
|
||||
- https://www.palantir.com/2011/10/how-to-rock-a-systems-design-interview/
|
||||
- http://blog.gainlo.co/index.php/2017/04/13/system-design-interviews-part-ii-complete-guide-google-interview-preparation/
|
||||
|
||||
## Flow
|
||||
|
||||
#### A. Understand the problem and scope
|
||||
|
||||
- Define the use cases, with interviewer's help.
|
||||
- Suggest additional features.
|
||||
- Remove items that interviewer deems out of scope.
|
||||
- Assume high availability is required, add as a use case.
|
||||
|
||||
#### B. Think about constraints
|
||||
|
||||
- Ask how many requests per month.
|
||||
- Ask how many requests per second (they may volunteer it or make you do the math).
|
||||
- Estimate reads vs. writes percentage.
|
||||
- Keep 80/20 rule in mind when estimating.
|
||||
- How much data written per second.
|
||||
- Total storage required over 5 years.
|
||||
- How much data reads per second.
|
||||
|
||||
#### C. Abstract design
|
||||
|
||||
- Layers (service, data, caching).
|
||||
- Infrastructure: load balancing, messaging.
|
||||
- Rough overview of any key algorithm that drives the service.
|
||||
- Consider bottlenecks and determine solutions.
|
||||
|
||||
Source: https://github.com/jwasham/coding-interview-university#system-design-scalability-data-handling
|
||||
|
||||
## Grading Rubrics
|
||||
|
||||
- Problem Solving - How systematic is your approach to solving the problem step-by-step? Break down a problem into its core components.
|
||||
- Communication - How well do you explain your idea and communicate it with others?
|
||||
- Evaluation - How do you evaluate your system? Are you aware of the trade-offs made? How can you optimize it?
|
||||
- Estimation - How fast does your system need to be? How much space does it need? How much load will it experience?
|
||||
|
||||
## Specific Topics
|
||||
|
||||
- URL Shortener
|
||||
- http://stackoverflow.com/questions/742013/how-to-code-a-url-shortener
|
||||
- http://blog.gainlo.co/index.php/2016/03/08/system-design-interview-question-create-tinyurl-system/
|
||||
- https://www.interviewcake.com/question/python/url-shortener
|
||||
- Collaborative Editor
|
||||
- http://blog.gainlo.co/index.php/2016/03/22/system-design-interview-question-how-to-design-google-docs/
|
||||
- Photo Sharing App
|
||||
- http://blog.gainlo.co/index.php/2016/03/01/system-design-interview-question-create-a-photo-sharing-app/
|
||||
- Social Network Feed
|
||||
- http://blog.gainlo.co/index.php/2016/02/17/system-design-interview-question-how-to-design-twitter-part-1/
|
||||
- http://blog.gainlo.co/index.php/2016/02/24/system-design-interview-question-how-to-design-twitter-part-2/
|
||||
- http://blog.gainlo.co/index.php/2016/03/29/design-news-feed-system-part-1-system-design-interview-questions/
|
||||
- Trending Algorithm
|
||||
- http://blog.gainlo.co/index.php/2016/05/03/how-to-design-a-trending-algorithm-for-twitter/
|
||||
- Facebook Chat
|
||||
- http://blog.gainlo.co/index.php/2016/04/19/design-facebook-chat-function/
|
||||
- Key Value Store
|
||||
- http://blog.gainlo.co/index.php/2016/06/14/design-a-key-value-store-part-i/
|
||||
- http://blog.gainlo.co/index.php/2016/06/21/design-key-value-store-part-ii/
|
||||
- Recommendation System
|
||||
- http://blog.gainlo.co/index.php/2016/05/24/design-a-recommendation-system/
|
||||
- Cache System
|
||||
- http://blog.gainlo.co/index.php/2016/05/17/design-a-cache-system/
|
||||
- E-commerce Website
|
||||
- http://blog.gainlo.co/index.php/2016/08/22/design-ecommerce-website-part/
|
||||
- http://blog.gainlo.co/index.php/2016/08/28/design-ecommerce-website-part-ii/
|
||||
- Web Crawler
|
||||
- http://blog.gainlo.co/index.php/2016/06/29/build-web-crawler/
|
||||
- http://www.makeuseof.com/tag/how-do-search-engines-work-makeuseof-explains/
|
||||
- https://www.quora.com/How-can-I-build-a-web-crawler-from-scratch/answer/Chris-Heller
|
||||
- YouTube
|
||||
- http://blog.gainlo.co/index.php/2016/10/22/design-youtube-part/
|
||||
- http://blog.gainlo.co/index.php/2016/11/04/design-youtube-part-ii/
|
||||
- Hit Counter
|
||||
- http://blog.gainlo.co/index.php/2016/09/12/dropbox-interview-design-hit-counter/
|
||||
- Facebook Graph Search
|
||||
- Design [Lyft Line](https://www.lyft.com/line).
|
||||
- Design a promo code system (with same promo code, randomly generated promo code, and promo code with conditions).
|
||||
- Model a university.
|
||||
- How would you implement Pacman?
|
||||
- Sketch out an implementation of Asteroids.
|
||||
- Implement a spell checker.
|
||||
- Design the rubik cube.
|
||||
- Design a high-level interface to be used for card games (e.g. poker, blackjack etc).
|
||||
107
experimental/design/collaborative-editor.md
Normal file
107
experimental/design/collaborative-editor.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Collaborative Document Editor
|
||||
|
||||
## Variants
|
||||
|
||||
- Design Google docs.
|
||||
- Design a collaborative code editor like Coderpad/Codepile.
|
||||
- Design a collaborative markdown editor.
|
||||
|
||||
## Requirements Gathering
|
||||
|
||||
- What is the intended platform?
|
||||
- Web
|
||||
- What features are required?
|
||||
- Creating a document
|
||||
- Editing a document
|
||||
- Sharing a document
|
||||
- Bonus features
|
||||
- Document revisions and reverting
|
||||
- Searching
|
||||
- Commenting
|
||||
- Chatting
|
||||
- Executing code (in the case of code editor)
|
||||
- What is in a document?
|
||||
- Text
|
||||
- Images
|
||||
- Which metrics should we optimize for?
|
||||
- Loading time
|
||||
- Synchronization
|
||||
- Throughput
|
||||
|
||||
## Core Components
|
||||
|
||||
- Front end
|
||||
- WebSockets/long polling for real-time communication between front end and back end.
|
||||
- Back end services behind a reverse proxy.
|
||||
- Reverse proxy will proxy the requests to the right server.
|
||||
- Split into a few services for different purposes.
|
||||
- The benefit of this is that each service can use different languages that best suits its purpose.
|
||||
- API servers for non-collaborative features and endpoints.
|
||||
- Ruby/Rails/Django for the server that deals with CRUD operations on data models where performance is not that crucial.
|
||||
- WebSocket servers for handling document edits and publishing updates to listeners.
|
||||
- Possibly Node/Golang for WebSocket server which will need high performance as updates are frequent.
|
||||
- Task queue to persist document updates to the database.
|
||||
- ELB in front of back end servers.
|
||||
- MySQL database.
|
||||
- S3 and CDN for images.
|
||||
|
||||
## Data Modeling
|
||||
|
||||
- What kind of database to use?
|
||||
- Data is quite structured. Would go with SQL.
|
||||
- Design the necessary tables, its columns and its relations.
|
||||
- `users`
|
||||
- `id`
|
||||
- `name`
|
||||
- `document`
|
||||
- `id`
|
||||
- `owner_id`
|
||||
- `permissions`
|
||||
- `id`
|
||||
- `name`
|
||||
- `document_permissions`
|
||||
- `id`
|
||||
- `document_id`
|
||||
- `user_id`
|
||||
|
||||
## Collaborative Editing - Client
|
||||
|
||||
- Upon loading of the page and document, the client should connect to the WebSocket server over the WebSocket protocol `ws://`.
|
||||
- Upon connection, perform a time sync with the server, possibly via Network Time Protocol (NTP).
|
||||
- The most straightforward way is to send the whole updated document content to the back end, and all users currently viewing the document will receive the updated document. However, there are a few problems with this approach:
|
||||
- Race condition. If two users editing the document at the same time, the last one to edit will overwrite the changes by the previous user. One workaround is to lock the document when a user is currently editing it, but that will not make it real-time collaborative.
|
||||
- A large payload (the whole document) is being sent to servers and published to users on each change, and the user is likely to already have most of the content. A lot of redundant data being sent.
|
||||
- A feasible approach would be to use operational transforms and send just the action deltas to the back end. The back end publishes the action deltas to the listeners. What is considered an action delta?
|
||||
- (a) Changing a character/word, (b) inserting a character/word/image, (c) deleting a character/word.
|
||||
- With this approach, the payload will contain only small amount of data, such as (a) type of change, (b) character/word, (c) position in document: line/column, (d) timestamp. Why is the timestamp needed? Read on to find out.
|
||||
- Updates can also be throttled and batched, to avoid flooding the web server with requests. For example, if a user inserts a
|
||||
|
||||
## Back End
|
||||
|
||||
The back end is split into a few portions: WebSocket server for receiving and broadcasting document updates, CRUD server for reading and writing non-document-related data, and a task queue for persistence of the document.
|
||||
|
||||
## WebSocket Server
|
||||
|
||||
- Languages and frameworks that support async requests and non-blocking I/O will be suitable for the collaborative editor server. Node and Golang comes to my mind.
|
||||
- However, the WebSocket server is not stateless, so is it not that straightforward to scale horizontally. One approach would be for a Load Balancer to use Redis to maintain a map of the client to the WebSocket server instance IP, such that subsequent requests from the same client will be routed to the same server.
|
||||
- Each document corresponds to a room (more of namespace). Users can subscribe to the events happening within a room.
|
||||
- When a action delta is being received, blast it out to the listeners within the room and add it to the task queue.
|
||||
|
||||
## CRUD Server
|
||||
|
||||
- Provides APIs for reading and writing non-document-related data, such as users, permissions.
|
||||
|
||||
## Task Queue + Worker Service
|
||||
|
||||
- Worker service retrieves messages from the task queue and writes the updated documents to the database in an async fashion.
|
||||
- Batch the actions together and perform one larger write that consists of multiple actions. For example, instead of persisting to the database once per addition of a word, combine these additions and write them into the database at once.
|
||||
- Publish the save completion event to the WebSocket server to be published to the listeners, informing that the latest version of the document is being saved.
|
||||
- Benefit of using a task queue is that as the amount of tasks in the queue goes up, we can scale up the number of worker services to clear the backlog of work faster.
|
||||
|
||||
## Document Persistence
|
||||
|
||||
TODO
|
||||
|
||||
###### References
|
||||
|
||||
- http://blog.gainlo.co/index.php/2016/03/22/system-design-interview-question-how-to-design-google-docs/
|
||||
164
experimental/design/news-feed.md
Normal file
164
experimental/design/news-feed.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# News Feed
|
||||
|
||||
## Variants
|
||||
|
||||
- Design Facebook news feed.
|
||||
- Design Twitter news feed.
|
||||
- Design Quora feed.
|
||||
- Design Instagram feed.
|
||||
|
||||
## Requirements Gathering
|
||||
|
||||
- What is the intended platform?
|
||||
- Mobile (mobile web or native)? Web? Desktop?
|
||||
- What features are required?
|
||||
- CRUD posts.
|
||||
- Commenting on posts.
|
||||
- Sharing posts.
|
||||
- Trending posts?
|
||||
- Tag people?
|
||||
- Hashtags?
|
||||
- What is in a news feed post?
|
||||
- Author.
|
||||
- Content.
|
||||
- Media.
|
||||
- Tags?
|
||||
- Hashtags?
|
||||
- Comments/Replies.
|
||||
- Operations:
|
||||
- CRUD
|
||||
- Commenting/replying to a post.
|
||||
- What is in a news feed?
|
||||
- Sequence of posts.
|
||||
- Query pattern: query for a user's ranked news feed.
|
||||
- Operations:
|
||||
- Append - Fetch more posts.
|
||||
- Delete - I don't want to see this.
|
||||
- Which metrics should we optimize for?
|
||||
- User retention.
|
||||
- Ads revenue.
|
||||
- Fast loading time.
|
||||
- Bandwidth.
|
||||
- Server costs.
|
||||
|
||||
## Core Components
|
||||
|
||||
TODO
|
||||
|
||||
## Data modeling
|
||||
|
||||
- What kind of database to use?
|
||||
- Data is quite structured. Would go with SQL.
|
||||
- Design the necessary tables, its columns and its relations.
|
||||
- `users`
|
||||
- `posts`
|
||||
- `likes`
|
||||
- `follows`
|
||||
- `comments`
|
||||
|
||||
> There are two basic objects: user and feed. For user object, we can store userID, name, registration date and so on so forth. And for feed object, there are feedId, feedType, content, metadata etc., which should support images and videos as well.
|
||||
>
|
||||
> If we are using a relational database, we also need to model two relations: user-feed relation and friend relation. The former is pretty straightforward. We can create a user-feed table that stores userID and corresponding feedID. For a single user, it can contain multiple entries if he has published many feeds.
|
||||
>
|
||||
> For friend relation, adjacency list is one of the most common approaches. If we see all the users as nodes in a giant graph, edges that connect nodes denote friend relation. We can use a friend table that contains two userIDs in each entry to model the edge (friend relation). By doing this, most operations are quite convenient like fetch all friends of a user, check if two people are friends.
|
||||
>
|
||||
> The system will first get all userIDs of friends from friend table. Then it fetches all feedIDs for each friend from user-feed table. Finally, feed content is fetched based on feedID from feed table. You can see that we need to perform 3 joins, which can affect performance.
|
||||
>
|
||||
> A common optimization is to store feed content together with feedID in user-feed table so that we don't need to join the feed table any more. This approach is called denormalization, which means by adding redundant data, we can optimize the read performance (reducing the number of joins).
|
||||
>
|
||||
> The disadvantages are obvious:
|
||||
>
|
||||
> - Data redundancy. We are storing redundant data, which occupies storage space (classic time-space trade-off).
|
||||
> - Data consistency. Whenever we update a feed, we need to update both feed table and user-feed table. Otherwise, there is data inconsistency. This increases the complexity of the system.
|
||||
> - Remember that there's no one approach always better than the other (normalization vs denormalization). It's a matter of whether you want to optimize for read or write.
|
||||
|
||||
## Feed Display
|
||||
|
||||
- The most straightforward way is to fetch posts from all the people you follow and render them sorted by time.
|
||||
- There can be many posts to fetch. How many posts should you fetch?
|
||||
- What are the pagination approaches and the pros and cons of each approach?
|
||||
- Offset by page size
|
||||
- Offset by time
|
||||
- What data should the post contain when you initially fetch them?
|
||||
- Lazy loading approach for loading associated data: media, comments, people who liked the post.
|
||||
- Media
|
||||
- If the post contains media such as images and videos, how should they be handled? Should they be loaded on the spot?
|
||||
- A better way would be to fetch images only when they are about to enter the viewport.
|
||||
- Videos should not autoplay. Only fetch the thumbnail for the video, and only play the video when user clicks play.
|
||||
- If the content is being refetched, the media should be cached and not fetched over the wire again. This is especially important on mobile connections where data can be expensive.
|
||||
- Comments
|
||||
- Should you fetch all the comments for a post? For posts by celebrities, they can contain a few hundred or thousand comments.
|
||||
- Maybe fetch the top few comments and display them under the post, and the user is given the choice to "show all comments".
|
||||
- How does the user request for new content?
|
||||
- Infinite scrolling.
|
||||
- User has to tap next page.
|
||||
|
||||
## Feed Ranking
|
||||
|
||||
- First select features/signals that are relevant and then figure out how to combine them to calculate a final score.
|
||||
- How do you show the relevant posts that the user is interested in?
|
||||
- Chronological - While a chronological approach works, it may not be the most engaging approach. For example, if a person posts 30 times within the last hour, his followers will have their news feed clogged up with his posts. Maybe set a cap on the number of time a person's posts can appear within the feed.
|
||||
- Popularity - How many likes and comments does the post have? Does the user usually like posts by that person?
|
||||
- How do you determine which are the more important posts? A user might be more interested in a few-hour old post from a good friend than a very recent post from an acquaintance.
|
||||
- A common strategy is to calculate a post score based on various features and rank posts by its score.
|
||||
- Prior to 2013, Facebook was using the [EdgeRank](https://www.wikiwand.com/en/EdgeRank) algorithm to determine what articles should be displayed in a user's News Feed.
|
||||
- Edge Rank basically is using three signals: affinity score, edge weight and time decay.
|
||||
- Affinity score (u) - For each news feed, affinity score evaluates how close you are with this user. For instance, you are more likely to care about feed from your close friends instead of someone you just met once.
|
||||
- Edge weight (e) - Edge weight basically reflects importance of each edge. For instance, comments are worth more than likes.
|
||||
- Time decay (d) - The older the story, the less likely users find it interesting.
|
||||
- Affinity score
|
||||
- Various factors can be used to reflect how close two people are. First of all, explicit interactions like comment, like, tag, share, click etc. are strong signals we should use. Apparently, each type of interaction should have different weight. For instance, comments should be worth much more than likes.
|
||||
- Secondly, we should also track the time factor. Perhaps you used to interact with a friend quite a lot, but less frequent recently. In this case, we should lower the affinity score. So for each interaction, we should also put the time decay factor.
|
||||
- A good ranking system can improve some core metrics - user retention, ads revenue, etc.
|
||||
|
||||
## Feed Publishing
|
||||
|
||||
TODO. Refer to http://blog.gainlo.co/index.php/2016/04/05/design-news-feed-system-part-2/.
|
||||
|
||||
## Additional Features
|
||||
|
||||
#### Tagging feature
|
||||
|
||||
- Have a `tags` table that stores the relation between a post and the people tagged in it.
|
||||
|
||||
#### Sharing feature
|
||||
|
||||
- Add a column to `posts` table called `original_post_id`.
|
||||
- What should happen when the original post is deleted?
|
||||
- The shared `posts` have to be deleted too.
|
||||
|
||||
#### Notifications feature
|
||||
|
||||
- When should notifications happen?
|
||||
- Can the user subscribe to only certain types of notifications?
|
||||
|
||||
#### Trending feature
|
||||
|
||||
- What constitutes trending? What signals would you look at? What weight would you give to each signal?
|
||||
- Most frequent hashtags over the last N hours.
|
||||
- Hottest search queries.
|
||||
- Fetch the recent most popular feeds and extract some common words or phrases.
|
||||
|
||||
#### Search feature
|
||||
|
||||
- How would you index the data?
|
||||
|
||||
## Scalability
|
||||
|
||||
- Master-slave replication.
|
||||
- Write to master database and read from replica databases/in-memory data store.
|
||||
- Post contents are being read more than they are updated. It is acceptable to have a slight lag between a user updating a post and followers seeing the updated content. Tweets are not even editable.
|
||||
- Data for real-time queries should be in memory, disk is for writes only.
|
||||
- Pre-computation offline.
|
||||
- Tracking number of likes and comments.
|
||||
- Expensive to do a `COUNT` on the `likes` and `comments` for a post.
|
||||
- Use Redis/Memcached for keeping track of how many likes/comments a post has. Increment when there's new activity, decrement when someone unlikes/deletes the comment.
|
||||
- Load balancer in front of your API servers.
|
||||
- Partitioning the data.
|
||||
|
||||
###### References
|
||||
|
||||
- [Design News Feed System (Part 1)](http://blog.gainlo.co/index.php/2016/03/29/design-news-feed-system-part-1-system-design-interview-questions/)
|
||||
- [Design News Feed System (Part 1)](http://blog.gainlo.co/index.php/2016/04/05/design-news-feed-system-part-2/)
|
||||
- [Etsy Activity Feeds Architecture](https://www.slideshare.net/danmckinley/etsy-activity-feeds-architecture)
|
||||
- [Big Data in Real-Time at Twitter](https://www.slideshare.net/nkallen/q-con-3770885)
|
||||
5
experimental/design/search-engine.md
Normal file
5
experimental/design/search-engine.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Search Engine
|
||||
|
||||
###### References
|
||||
|
||||
- [How Do Search Engines Work?](http://www.makeuseof.com/tag/how-do-search-engines-work-makeuseof-explains/)
|
||||
61
experimental/domain/async-loading/index.html
Normal file
61
experimental/domain/async-loading/index.html
Normal file
@@ -0,0 +1,61 @@
|
||||
<!doctype html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Helvetica', sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<div id="chapters">
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(() => {
|
||||
const DOM = {
|
||||
$chapters: document.getElementById('chapters'),
|
||||
};
|
||||
const URL = 'https://raw.githubusercontent.com/googlesamples/web-fundamentals/gh-pages/fundamentals/getting-started/primers';
|
||||
|
||||
function getJSON(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = new XMLHttpRequest();
|
||||
req.open('GET', url);
|
||||
req.onload = () => {
|
||||
if (req.status === 200) {
|
||||
resolve(JSON.parse(req.response));
|
||||
return;
|
||||
}
|
||||
reject(req.responseText);
|
||||
}
|
||||
req.onerror = (err) => {
|
||||
reject(err);
|
||||
}
|
||||
req.send();
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
getJSON(`${URL}/story.json`).then(story => {
|
||||
const $heading = document.createRange().createContextualFragment(story.heading);
|
||||
DOM.$chapters.before($heading);
|
||||
return Promise.all(story.chapterUrls.map((url, index) => {
|
||||
return getJSON(`${URL}/${url}`);
|
||||
}));
|
||||
}).then(chapters => {
|
||||
const $chapters = document.createDocumentFragment();
|
||||
chapters.forEach(chapter => {
|
||||
$chapters.appendChild(document.createRange().createContextualFragment(chapter.html));
|
||||
});
|
||||
DOM.$chapters.appendChild($chapters);
|
||||
}).catch(err => {
|
||||
console.warn(err);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
7
experimental/domain/databases.md
Normal file
7
experimental/domain/databases.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Databases
|
||||
|
||||
## General
|
||||
|
||||
- How should you store passwords in a database?
|
||||
- http://www.geeksforgeeks.org/store-password-database/
|
||||
- https://nakedsecurity.sophos.com/2013/11/20/serious-security-how-to-store-your-users-passwords-safely/
|
||||
5
experimental/domain/networking.md
Normal file
5
experimental/domain/networking.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Networking
|
||||
|
||||
- Given an IPv4 IP address p and an integer n, return a list of CIDR strings that most succinctly represents the range of IP addresses from p to (p + n).
|
||||
- Describe what happens when you enter a url in the web browser.
|
||||
- Define UDP/TCP and give an example of both.
|
||||
677
experimental/domain/pagination-sorting/data.js
Normal file
677
experimental/domain/pagination-sorting/data.js
Normal file
@@ -0,0 +1,677 @@
|
||||
const data = [
|
||||
{
|
||||
"_id": "591ad7e2d1b6119887420af5",
|
||||
"age": 36,
|
||||
"name": "Salas Mccarthy",
|
||||
"gender": "male",
|
||||
"company": "MAKINGWAY",
|
||||
"email": "salasmccarthy@makingway.com",
|
||||
"phone": "+1 (905) 546-3931"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2918b0ba1231b582d",
|
||||
"age": 29,
|
||||
"name": "Jodi Graham",
|
||||
"gender": "female",
|
||||
"company": "BITREX",
|
||||
"email": "jodigraham@bitrex.com",
|
||||
"phone": "+1 (861) 596-2691"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2bc19d181dcb64d57",
|
||||
"age": 34,
|
||||
"name": "Hollie Hardin",
|
||||
"gender": "female",
|
||||
"company": "ZENSURE",
|
||||
"email": "holliehardin@zensure.com",
|
||||
"phone": "+1 (822) 410-2433"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e224d2fa7216132561",
|
||||
"age": 26,
|
||||
"name": "Bullock Cole",
|
||||
"gender": "male",
|
||||
"company": "ZILPHUR",
|
||||
"email": "bullockcole@zilphur.com",
|
||||
"phone": "+1 (839) 563-3350"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e206c3fb3043ccf8a8",
|
||||
"age": 30,
|
||||
"name": "Peterson Mosley",
|
||||
"gender": "male",
|
||||
"company": "MOBILDATA",
|
||||
"email": "petersonmosley@mobildata.com",
|
||||
"phone": "+1 (974) 547-3461"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e20a180de908b0960e",
|
||||
"age": 34,
|
||||
"name": "Lucille Jackson",
|
||||
"gender": "female",
|
||||
"company": "KINETICUT",
|
||||
"email": "lucillejackson@kineticut.com",
|
||||
"phone": "+1 (845) 443-3594"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2768b0cf4837ecf35",
|
||||
"age": 40,
|
||||
"name": "Cooper Leonard",
|
||||
"gender": "male",
|
||||
"company": "CHORIZON",
|
||||
"email": "cooperleonard@chorizon.com",
|
||||
"phone": "+1 (817) 418-2290"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e270a640cf8f8a3e44",
|
||||
"age": 27,
|
||||
"name": "Iris Shepherd",
|
||||
"gender": "female",
|
||||
"company": "IDEALIS",
|
||||
"email": "irisshepherd@idealis.com",
|
||||
"phone": "+1 (858) 599-3628"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e222ddd244aaba0315",
|
||||
"age": 31,
|
||||
"name": "Oneil Head",
|
||||
"gender": "male",
|
||||
"company": "LIMOZEN",
|
||||
"email": "oneilhead@limozen.com",
|
||||
"phone": "+1 (894) 440-3396"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2d36847b5e648587b",
|
||||
"age": 28,
|
||||
"name": "Swanson Singleton",
|
||||
"gender": "male",
|
||||
"company": "NSPIRE",
|
||||
"email": "swansonsingleton@nspire.com",
|
||||
"phone": "+1 (817) 510-2480"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2b78092f9fb866779",
|
||||
"age": 37,
|
||||
"name": "Sutton Odom",
|
||||
"gender": "male",
|
||||
"company": "GLUKGLUK",
|
||||
"email": "suttonodom@glukgluk.com",
|
||||
"phone": "+1 (961) 424-2812"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e222736aa66a9b35cc",
|
||||
"age": 31,
|
||||
"name": "Chandler Kirk",
|
||||
"gender": "male",
|
||||
"company": "EWAVES",
|
||||
"email": "chandlerkirk@ewaves.com",
|
||||
"phone": "+1 (902) 524-3712"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2ac3f9abecd5175a7",
|
||||
"age": 28,
|
||||
"name": "Gentry Stanley",
|
||||
"gender": "male",
|
||||
"company": "GINK",
|
||||
"email": "gentrystanley@gink.com",
|
||||
"phone": "+1 (878) 592-2331"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2f79787dc605d61fb",
|
||||
"age": 28,
|
||||
"name": "Paulette Guthrie",
|
||||
"gender": "female",
|
||||
"company": "SEQUITUR",
|
||||
"email": "pauletteguthrie@sequitur.com",
|
||||
"phone": "+1 (820) 473-2926"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2ff61553783737bc0",
|
||||
"age": 37,
|
||||
"name": "Lesley Nash",
|
||||
"gender": "female",
|
||||
"company": "MELBACOR",
|
||||
"email": "lesleynash@melbacor.com",
|
||||
"phone": "+1 (849) 414-2115"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e26f9c3fc95fa8e6f1",
|
||||
"age": 22,
|
||||
"name": "Frye Oneil",
|
||||
"gender": "male",
|
||||
"company": "OVERPLEX",
|
||||
"email": "fryeoneil@overplex.com",
|
||||
"phone": "+1 (999) 412-2403"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2eee8b7cd857a29d0",
|
||||
"age": 40,
|
||||
"name": "Christina Zamora",
|
||||
"gender": "female",
|
||||
"company": "REPETWIRE",
|
||||
"email": "christinazamora@repetwire.com",
|
||||
"phone": "+1 (860) 504-3885"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2be93544c81761053",
|
||||
"age": 25,
|
||||
"name": "Earlene Dunn",
|
||||
"gender": "female",
|
||||
"company": "PHARMEX",
|
||||
"email": "earlenedunn@pharmex.com",
|
||||
"phone": "+1 (831) 582-2974"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2cb672de927940ef4",
|
||||
"age": 25,
|
||||
"name": "Hoffman Pittman",
|
||||
"gender": "male",
|
||||
"company": "ENOMEN",
|
||||
"email": "hoffmanpittman@enomen.com",
|
||||
"phone": "+1 (882) 420-3186"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2aabaae50d75c21a3",
|
||||
"age": 35,
|
||||
"name": "Wilson Tran",
|
||||
"gender": "male",
|
||||
"company": "CAXT",
|
||||
"email": "wilsontran@caxt.com",
|
||||
"phone": "+1 (842) 529-3085"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2bdd6104be85bdd87",
|
||||
"age": 22,
|
||||
"name": "Hensley Hawkins",
|
||||
"gender": "male",
|
||||
"company": "LUDAK",
|
||||
"email": "hensleyhawkins@ludak.com",
|
||||
"phone": "+1 (902) 566-3308"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e24796cbdc0eacb6c0",
|
||||
"age": 36,
|
||||
"name": "Gray Schultz",
|
||||
"gender": "male",
|
||||
"company": "EBIDCO",
|
||||
"email": "grayschultz@ebidco.com",
|
||||
"phone": "+1 (910) 532-2845"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e204425cc3b121279a",
|
||||
"age": 25,
|
||||
"name": "Odonnell Livingston",
|
||||
"gender": "male",
|
||||
"company": "ANOCHA",
|
||||
"email": "odonnelllivingston@anocha.com",
|
||||
"phone": "+1 (991) 422-2754"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2997ac59b446d3a30",
|
||||
"age": 36,
|
||||
"name": "Shelton Lindsay",
|
||||
"gender": "male",
|
||||
"company": "DENTREX",
|
||||
"email": "sheltonlindsay@dentrex.com",
|
||||
"phone": "+1 (899) 567-2928"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2646dad2ff456b035",
|
||||
"age": 40,
|
||||
"name": "Mcclain Larson",
|
||||
"gender": "male",
|
||||
"company": "GENMOM",
|
||||
"email": "mcclainlarson@genmom.com",
|
||||
"phone": "+1 (934) 508-3477"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e21e908e847ee6d3ea",
|
||||
"age": 27,
|
||||
"name": "Miranda Branch",
|
||||
"gender": "female",
|
||||
"company": "ROCKYARD",
|
||||
"email": "mirandabranch@rockyard.com",
|
||||
"phone": "+1 (989) 446-2387"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2d7905d2fcaa1ce18",
|
||||
"age": 36,
|
||||
"name": "Valencia Moreno",
|
||||
"gender": "male",
|
||||
"company": "KIOSK",
|
||||
"email": "valenciamoreno@kiosk.com",
|
||||
"phone": "+1 (807) 449-2626"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e245e3c4ce18a92054",
|
||||
"age": 40,
|
||||
"name": "Witt Leblanc",
|
||||
"gender": "male",
|
||||
"company": "ENERSAVE",
|
||||
"email": "wittleblanc@enersave.com",
|
||||
"phone": "+1 (809) 491-3886"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e27f2c70583d1cf34f",
|
||||
"age": 32,
|
||||
"name": "Glover Mccoy",
|
||||
"gender": "male",
|
||||
"company": "BYTREX",
|
||||
"email": "glovermccoy@bytrex.com",
|
||||
"phone": "+1 (928) 470-2426"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e22768012595c933c9",
|
||||
"age": 25,
|
||||
"name": "Billie Dunlap",
|
||||
"gender": "female",
|
||||
"company": "INQUALA",
|
||||
"email": "billiedunlap@inquala.com",
|
||||
"phone": "+1 (903) 415-2136"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e22df8d07559a29904",
|
||||
"age": 21,
|
||||
"name": "Bridget Hill",
|
||||
"gender": "female",
|
||||
"company": "TRIBALOG",
|
||||
"email": "bridgethill@tribalog.com",
|
||||
"phone": "+1 (954) 461-3390"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e201b2bddbc81ae05e",
|
||||
"age": 30,
|
||||
"name": "Dawson Clements",
|
||||
"gender": "male",
|
||||
"company": "EURON",
|
||||
"email": "dawsonclements@euron.com",
|
||||
"phone": "+1 (926) 570-3444"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2c2aeb820e77c1d9a",
|
||||
"age": 31,
|
||||
"name": "Valdez Johnson",
|
||||
"gender": "male",
|
||||
"company": "RETRACK",
|
||||
"email": "valdezjohnson@retrack.com",
|
||||
"phone": "+1 (841) 506-2428"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2370ff047048bd688",
|
||||
"age": 31,
|
||||
"name": "Nichole Dickerson",
|
||||
"gender": "female",
|
||||
"company": "NEOCENT",
|
||||
"email": "nicholedickerson@neocent.com",
|
||||
"phone": "+1 (945) 437-3591"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2b77affba5a8f8603",
|
||||
"age": 34,
|
||||
"name": "Madden Weaver",
|
||||
"gender": "male",
|
||||
"company": "VERBUS",
|
||||
"email": "maddenweaver@verbus.com",
|
||||
"phone": "+1 (810) 598-3145"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e21e692cc8247b1d83",
|
||||
"age": 26,
|
||||
"name": "Larsen Benson",
|
||||
"gender": "male",
|
||||
"company": "ISOSURE",
|
||||
"email": "larsenbenson@isosure.com",
|
||||
"phone": "+1 (890) 435-2219"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2de06284f2d2a8d19",
|
||||
"age": 37,
|
||||
"name": "Barnes Gaines",
|
||||
"gender": "male",
|
||||
"company": "RODEOCEAN",
|
||||
"email": "barnesgaines@rodeocean.com",
|
||||
"phone": "+1 (824) 484-2693"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e20d9d8224dfbd292f",
|
||||
"age": 28,
|
||||
"name": "Pacheco Landry",
|
||||
"gender": "male",
|
||||
"company": "METROZ",
|
||||
"email": "pachecolandry@metroz.com",
|
||||
"phone": "+1 (813) 525-3821"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2c5348da6a981a148",
|
||||
"age": 21,
|
||||
"name": "Richard Alexander",
|
||||
"gender": "male",
|
||||
"company": "GADTRON",
|
||||
"email": "richardalexander@gadtron.com",
|
||||
"phone": "+1 (911) 513-3007"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2ca9ef31ba3b67d4f",
|
||||
"age": 22,
|
||||
"name": "Stone Campos",
|
||||
"gender": "male",
|
||||
"company": "TETAK",
|
||||
"email": "stonecampos@tetak.com",
|
||||
"phone": "+1 (879) 440-3672"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2ff5a9796c8470151",
|
||||
"age": 31,
|
||||
"name": "Walter Randall",
|
||||
"gender": "male",
|
||||
"company": "EQUITAX",
|
||||
"email": "walterrandall@equitax.com",
|
||||
"phone": "+1 (927) 563-3777"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e272c6b6c1241b3a4a",
|
||||
"age": 27,
|
||||
"name": "Copeland Compton",
|
||||
"gender": "male",
|
||||
"company": "MAXIMIND",
|
||||
"email": "copelandcompton@maximind.com",
|
||||
"phone": "+1 (829) 599-2574"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2220c6caa9e2c444d",
|
||||
"age": 30,
|
||||
"name": "Lorie Irwin",
|
||||
"gender": "female",
|
||||
"company": "ELPRO",
|
||||
"email": "lorieirwin@elpro.com",
|
||||
"phone": "+1 (938) 584-2368"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2942d603032f1ebda",
|
||||
"age": 35,
|
||||
"name": "Buck Roth",
|
||||
"gender": "male",
|
||||
"company": "COMTRACT",
|
||||
"email": "buckroth@comtract.com",
|
||||
"phone": "+1 (830) 585-3168"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2c23efccd2fcd5b2e",
|
||||
"age": 31,
|
||||
"name": "Mamie Robinson",
|
||||
"gender": "female",
|
||||
"company": "ZOMBOID",
|
||||
"email": "mamierobinson@zomboid.com",
|
||||
"phone": "+1 (845) 481-3222"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e285109fadbf0f9c01",
|
||||
"age": 33,
|
||||
"name": "Bryan Stone",
|
||||
"gender": "male",
|
||||
"company": "PODUNK",
|
||||
"email": "bryanstone@podunk.com",
|
||||
"phone": "+1 (971) 469-3832"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e25abc328c2cc42283",
|
||||
"age": 37,
|
||||
"name": "Benson Lyons",
|
||||
"gender": "male",
|
||||
"company": "FUTURITY",
|
||||
"email": "bensonlyons@futurity.com",
|
||||
"phone": "+1 (964) 536-3522"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e28fa81611cdf5066a",
|
||||
"age": 37,
|
||||
"name": "Sally Finley",
|
||||
"gender": "female",
|
||||
"company": "ZYTRAC",
|
||||
"email": "sallyfinley@zytrac.com",
|
||||
"phone": "+1 (953) 506-2428"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e271c5882aa5452601",
|
||||
"age": 26,
|
||||
"name": "Callahan Mckee",
|
||||
"gender": "male",
|
||||
"company": "ACCUPRINT",
|
||||
"email": "callahanmckee@accuprint.com",
|
||||
"phone": "+1 (842) 400-3570"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2275e787c5d9eea26",
|
||||
"age": 25,
|
||||
"name": "Ines Hamilton",
|
||||
"gender": "female",
|
||||
"company": "PROSURE",
|
||||
"email": "ineshamilton@prosure.com",
|
||||
"phone": "+1 (976) 485-2663"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2d8790e386b671ec6",
|
||||
"age": 38,
|
||||
"name": "Mcknight Abbott",
|
||||
"gender": "male",
|
||||
"company": "MAXEMIA",
|
||||
"email": "mcknightabbott@maxemia.com",
|
||||
"phone": "+1 (960) 600-3920"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2a1cae1e4f17d8db2",
|
||||
"age": 28,
|
||||
"name": "Tanisha Ross",
|
||||
"gender": "female",
|
||||
"company": "SYNKGEN",
|
||||
"email": "tanishaross@synkgen.com",
|
||||
"phone": "+1 (920) 502-3204"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2f816fd6848bc03a3",
|
||||
"age": 22,
|
||||
"name": "Pam Webb",
|
||||
"gender": "female",
|
||||
"company": "VALREDA",
|
||||
"email": "pamwebb@valreda.com",
|
||||
"phone": "+1 (926) 442-2683"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e208585adb332ac653",
|
||||
"age": 29,
|
||||
"name": "Bobbi Mays",
|
||||
"gender": "female",
|
||||
"company": "PARLEYNET",
|
||||
"email": "bobbimays@parleynet.com",
|
||||
"phone": "+1 (940) 577-3487"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2f4b29cbd1744b14e",
|
||||
"age": 27,
|
||||
"name": "Snider Sandoval",
|
||||
"gender": "male",
|
||||
"company": "GRONK",
|
||||
"email": "snidersandoval@gronk.com",
|
||||
"phone": "+1 (903) 526-2655"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e23a3f54209ec9deba",
|
||||
"age": 33,
|
||||
"name": "Fitzpatrick Weiss",
|
||||
"gender": "male",
|
||||
"company": "MICROLUXE",
|
||||
"email": "fitzpatrickweiss@microluxe.com",
|
||||
"phone": "+1 (894) 494-2135"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e229d21e1b7723c044",
|
||||
"age": 27,
|
||||
"name": "Kimberly Brown",
|
||||
"gender": "female",
|
||||
"company": "SPACEWAX",
|
||||
"email": "kimberlybrown@spacewax.com",
|
||||
"phone": "+1 (832) 481-2926"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e247e285f0c563ac94",
|
||||
"age": 20,
|
||||
"name": "Fisher Kent",
|
||||
"gender": "male",
|
||||
"company": "ORBIN",
|
||||
"email": "fisherkent@orbin.com",
|
||||
"phone": "+1 (962) 523-3956"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e274439c6eb52e99df",
|
||||
"age": 26,
|
||||
"name": "Arlene Carroll",
|
||||
"gender": "female",
|
||||
"company": "ELITA",
|
||||
"email": "arlenecarroll@elita.com",
|
||||
"phone": "+1 (998) 497-3751"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e271852e5766fad809",
|
||||
"age": 22,
|
||||
"name": "Tamra Spence",
|
||||
"gender": "female",
|
||||
"company": "ARTWORLDS",
|
||||
"email": "tamraspence@artworlds.com",
|
||||
"phone": "+1 (963) 516-2492"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e28c9165c4372744f7",
|
||||
"age": 29,
|
||||
"name": "Alice Goodman",
|
||||
"gender": "female",
|
||||
"company": "ZILLIDIUM",
|
||||
"email": "alicegoodman@zillidium.com",
|
||||
"phone": "+1 (829) 577-2972"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2c2a9121f713f15c3",
|
||||
"age": 27,
|
||||
"name": "Clay Washington",
|
||||
"gender": "male",
|
||||
"company": "EXOSTREAM",
|
||||
"email": "claywashington@exostream.com",
|
||||
"phone": "+1 (926) 460-3699"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2c99d41ce0347ac8f",
|
||||
"age": 38,
|
||||
"name": "Navarro Walsh",
|
||||
"gender": "male",
|
||||
"company": "ILLUMITY",
|
||||
"email": "navarrowalsh@illumity.com",
|
||||
"phone": "+1 (896) 564-3270"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e25e7c6ff0785d1337",
|
||||
"age": 22,
|
||||
"name": "Alexandra Hughes",
|
||||
"gender": "female",
|
||||
"company": "TASMANIA",
|
||||
"email": "alexandrahughes@tasmania.com",
|
||||
"phone": "+1 (977) 509-3599"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2acb8a5785b6fd512",
|
||||
"age": 40,
|
||||
"name": "Vonda Oliver",
|
||||
"gender": "female",
|
||||
"company": "NEPTIDE",
|
||||
"email": "vondaoliver@neptide.com",
|
||||
"phone": "+1 (836) 595-2260"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e20543a9fc7bc33515",
|
||||
"age": 36,
|
||||
"name": "Christa Nixon",
|
||||
"gender": "female",
|
||||
"company": "GLASSTEP",
|
||||
"email": "christanixon@glasstep.com",
|
||||
"phone": "+1 (966) 551-2149"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e21b8e73ba4dbf7c9a",
|
||||
"age": 34,
|
||||
"name": "Weber Hurst",
|
||||
"gender": "male",
|
||||
"company": "SLUMBERIA",
|
||||
"email": "weberhurst@slumberia.com",
|
||||
"phone": "+1 (943) 516-3449"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e282b9cb56357b42db",
|
||||
"age": 27,
|
||||
"name": "Rasmussen Kramer",
|
||||
"gender": "male",
|
||||
"company": "MUSIX",
|
||||
"email": "rasmussenkramer@musix.com",
|
||||
"phone": "+1 (891) 553-3264"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2287c587e3a04e80c",
|
||||
"age": 30,
|
||||
"name": "Mayra Beasley",
|
||||
"gender": "female",
|
||||
"company": "BRAINCLIP",
|
||||
"email": "mayrabeasley@brainclip.com",
|
||||
"phone": "+1 (958) 576-2642"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e26245135989367fa8",
|
||||
"age": 20,
|
||||
"name": "Nunez Ortiz",
|
||||
"gender": "male",
|
||||
"company": "KYAGURU",
|
||||
"email": "nunezortiz@kyaguru.com",
|
||||
"phone": "+1 (906) 591-3563"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e23248cee3b84bde40",
|
||||
"age": 39,
|
||||
"name": "Michael Foreman",
|
||||
"gender": "male",
|
||||
"company": "DANCITY",
|
||||
"email": "michaelforeman@dancity.com",
|
||||
"phone": "+1 (848) 400-3539"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e23ae236a1d59d07ee",
|
||||
"age": 36,
|
||||
"name": "Virgie Wheeler",
|
||||
"gender": "female",
|
||||
"company": "XIIX",
|
||||
"email": "virgiewheeler@xiix.com",
|
||||
"phone": "+1 (831) 400-2814"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2d8b9e3c0c31d770d",
|
||||
"age": 31,
|
||||
"name": "Levy Hoover",
|
||||
"gender": "male",
|
||||
"company": "CANDECOR",
|
||||
"email": "levyhoover@candecor.com",
|
||||
"phone": "+1 (843) 428-2780"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e2badb7200ca06a483",
|
||||
"age": 21,
|
||||
"name": "Battle Stanton",
|
||||
"gender": "male",
|
||||
"company": "CALCULA",
|
||||
"email": "battlestanton@calcula.com",
|
||||
"phone": "+1 (861) 522-3951"
|
||||
},
|
||||
{
|
||||
"_id": "591ad7e27fd1a4455ccf406c",
|
||||
"age": 24,
|
||||
"name": "Church Lynn",
|
||||
"gender": "male",
|
||||
"company": "DAISU",
|
||||
"email": "churchlynn@daisu.com",
|
||||
"phone": "+1 (896) 425-3854"
|
||||
}
|
||||
]
|
||||
144
experimental/domain/pagination-sorting/index.html
Normal file
144
experimental/domain/pagination-sorting/index.html
Normal file
@@ -0,0 +1,144 @@
|
||||
<!doctype html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Helvetica', sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>People</h1>
|
||||
<table class="js-table">
|
||||
<tr class="js-table-header">
|
||||
<th data-field="name">Name</th>
|
||||
<th data-field="age">Age</th>
|
||||
<th data-field="email">Email</th>
|
||||
</tr>
|
||||
<tbody class="js-table-body">
|
||||
</tbody>
|
||||
</table>
|
||||
<button class="js-page-button button-prev" data-type="prev">Prev</button>
|
||||
Page <span class="js-current-page"></span> / <span class="js-total-page"></span>
|
||||
<button class="js-page-button button-next" data-type="next">Next</button>
|
||||
|
||||
<script type="text/javascript" src="data.js"></script>
|
||||
<script>
|
||||
(() => {
|
||||
function init() {
|
||||
const DOM = {
|
||||
$pageButtons: document.querySelectorAll('.js-page-button'),
|
||||
$prevButton: document.querySelector('.js-page-button.button-prev'),
|
||||
$nextButton: document.querySelector('.js-page-button.button-next'),
|
||||
$tableHeader: document.querySelector('.js-table-header'),
|
||||
$tableBody: document.querySelector('.js-table-body'),
|
||||
$currentPage: document.querySelector('.js-current-page'),
|
||||
$totalPages: document.querySelector('.js-total-page'),
|
||||
};
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
function initialState() {
|
||||
return {
|
||||
currentPage: 0,
|
||||
totalPages: 0,
|
||||
sortField: 'name',
|
||||
sortOrder: 'asc',
|
||||
data,
|
||||
};
|
||||
}
|
||||
let state = initialState();
|
||||
state.totalPages = Math.ceil(state.data.length / PAGE_SIZE);
|
||||
|
||||
function navigatePages(type) {
|
||||
let newCurrentPage = state.currentPage + (type === 'prev' ? -1 : 1);
|
||||
newCurrentPage = Math.max(0, newCurrentPage);
|
||||
newCurrentPage = Math.min(newCurrentPage, state.totalPages - 1);
|
||||
state.currentPage = newCurrentPage;
|
||||
}
|
||||
|
||||
function setSortField(field) {
|
||||
state.currentPage = 0;
|
||||
if (state.sortField !== field) {
|
||||
state.sortField = field;
|
||||
state.sortOrder = 'asc';
|
||||
} else {
|
||||
state.sortOrder = state.sortOrder === 'asc' ? 'desc' : 'asc';
|
||||
}
|
||||
}
|
||||
|
||||
function attachEventListeners() {
|
||||
// Pagination.
|
||||
DOM.$pageButtons.forEach(el => {
|
||||
el.addEventListener('click', function () {
|
||||
navigatePages(this.getAttribute('data-type'));
|
||||
render();
|
||||
});
|
||||
});
|
||||
|
||||
// Sorting.
|
||||
DOM.$tableHeader.addEventListener('click', function (event) {
|
||||
const el = event.target;
|
||||
const field = el.getAttribute('data-field');
|
||||
if (el.tagName !== 'TH' || !field) {
|
||||
return;
|
||||
}
|
||||
setSortField(field);
|
||||
render();
|
||||
});
|
||||
}
|
||||
|
||||
function render() {
|
||||
DOM.$tableBody.innerHTML = '';
|
||||
|
||||
// Sort data.
|
||||
const sortField = state.sortField;
|
||||
state.data.sort((a, b) => {
|
||||
switch (sortField) {
|
||||
case 'name':
|
||||
case 'email':
|
||||
return a[sortField] > b[sortField] ? 1 : -1;
|
||||
case 'age':
|
||||
return a[sortField] - b[sortField];
|
||||
}
|
||||
});
|
||||
if (state.sortOrder === 'desc') {
|
||||
state.data.reverse();
|
||||
}
|
||||
|
||||
// Create table rows.
|
||||
const pageData = state.data.slice(state.currentPage * PAGE_SIZE, state.currentPage * PAGE_SIZE + PAGE_SIZE);
|
||||
const $tableRowsFragment = document.createDocumentFragment();
|
||||
pageData.forEach(person => {
|
||||
const $tableRow = document.createElement('tr');
|
||||
['name', 'age', 'email'].forEach(field => {
|
||||
const $tableCell = document.createElement('td');
|
||||
$tableCell.textContent = person[field];
|
||||
$tableRow.appendChild($tableCell);
|
||||
});
|
||||
$tableRowsFragment.appendChild($tableRow);
|
||||
});
|
||||
DOM.$tableBody.appendChild($tableRowsFragment);
|
||||
|
||||
// Pagination buttons disabled states.
|
||||
DOM.$currentPage.textContent = state.currentPage + 1;
|
||||
DOM.$totalPages.textContent = state.totalPages;
|
||||
if (state.currentPage === 0) {
|
||||
DOM.$prevButton.setAttribute('disabled', true);
|
||||
} else {
|
||||
DOM.$prevButton.removeAttribute('disabled');
|
||||
}
|
||||
if (state.currentPage === state.totalPages - 1) {
|
||||
DOM.$nextButton.setAttribute('disabled', true);
|
||||
} else {
|
||||
DOM.$nextButton.removeAttribute('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
render();
|
||||
attachEventListeners();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
66
experimental/domain/security.md
Normal file
66
experimental/domain/security.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Security
|
||||
|
||||
## Encryption
|
||||
|
||||
#### Symmetrical Encryption
|
||||
|
||||
- Symmetrical encryption is a type of encryption where the same key is used to encrypt plaintext messages and to decrypt ciphertext.
|
||||
- Symmetrical encryption is usually much less computationally expensive as compared to asymmetric encryption.
|
||||
- Often called "shared secret" encryption, or "secret key" encryption.
|
||||
- To use a symmetric encryption scheme, the sender and receiver must securely share a key in advance. This sharing can be done via asymmetric encryption.
|
||||
|
||||
#### Asymmetric Encryption
|
||||
|
||||
- A pair of keys are required: a **private key** and a **public key**. Public keys can be shared with anyone while private keys should be kept secret and known only to the owner.
|
||||
- A private key can be used to decrypt a message encrypted by the corresponding public key. A successful decryption verifies that the holder possesses the private key.
|
||||
- Also known as public-key cryptography.
|
||||
|
||||
## Public Key Infrastructure
|
||||
|
||||
A public key infrastructure (PKI) is a system for the creation, storage, and distribution of digital certificates which are used to verify that a particular public key belongs to a certain entity. The PKI creates digital certificates which map public keys to entities, securely stores these certificates in a central repository and revokes them if needed.
|
||||
|
||||
###### References
|
||||
|
||||
- https://www.wikiwand.com/en/Public_key_infrastructure
|
||||
|
||||
## SSH
|
||||
|
||||
An SSH session consists of two stages, **Negotiating Encryption** and **User Authentication**.
|
||||
|
||||
#### Negotiating Encryption
|
||||
|
||||
The goal of this stage is for the client and server to agree upon and establish encryption to protect future communication, by generating an identical session key. One possible algorithm to generate the session key is the Diffie–Hellman key exchange scheme. Each party generates a public/private key pair and exchanges the public key. After obtaining an authentic copy of each other's public keys, each party can compute a shared secret offline.
|
||||
|
||||
The basis of this procedure for classic Diffie-Hellman is:
|
||||
|
||||
1. Both parties agree on a large prime number, which will serve as a seed value.
|
||||
1. Both parties agree on an encryption generator (typically AES), which will be used to manipulate the values in a predefined way.
|
||||
1. Independently, each party comes up with another prime number which is kept secret from the other party. This number is used as the private key for this interaction (different than the private SSH key used for authentication).
|
||||
1. The generated private key, the encryption generator, and the shared prime number are used to generate a public key that is derived from the private key, but which can be shared with the other party.
|
||||
1. Both participants then exchange their generated public keys.
|
||||
1. The receiving entity uses their own private key, the other party's public key, and the original shared prime number to compute a shared secret key.
|
||||
1. Although this is independently computed by each party, using opposite private and public keys, it will result in the same shared secret key.
|
||||
1. The shared secret is then used to encrypt all communication that follows.
|
||||
|
||||
The purpose of the shared secret key is to wrap all further communication in an encrypted tunnel that cannot be deciphered by outsiders.
|
||||
|
||||
#### User Authentication
|
||||
|
||||
The goal of this stage is to authenticate the user and discover whether access to the server should be granted. There are two approaches for authenticating, either by using passwords, or SSH key pairs.
|
||||
|
||||
For password authentication, the server simply prompts the client for the password of the account they are attempting to login with. The password is sent through the negotiated encryption, so it is secure from outside parties.
|
||||
|
||||
Authentication using SSH key pairs begins after the symmetric encryption has been established as described in the last section. The procedure happens like this:
|
||||
|
||||
1. The client begins by sending an ID for the key pair it would like to authenticate with to the server.
|
||||
1. The server check's the `authorized_keys` file of the account that the client is attempting to log into for the key ID.
|
||||
1. If a public key with matching ID is found in the file, the server generates a random number and uses the public key to encrypt the number.
|
||||
1. The server sends the client this encrypted message.
|
||||
1. If the client actually has the associated private key, it will be able to decrypt the message using that key, revealing the original number.
|
||||
1. The client combines the decrypted number with the shared session key that is being used to encrypt the communication, and calculates the MD5 hash of this value.
|
||||
1. The client then sends this MD5 hash back to the server as an answer to the encrypted number message.
|
||||
1. The server uses the same shared session key and the original number that it sent to the client to calculate the MD5 value on its own. It compares its own calculation to the one that the client sent back. If these two values match, it proves that the client was in possession of the private key and the client is authenticated.
|
||||
|
||||
###### References
|
||||
|
||||
- https://www.digitalocean.com/community/tutorials/understanding-the-ssh-encryption-and-connection-process
|
||||
83
experimental/domain/snake-game/snake-game.md
Normal file
83
experimental/domain/snake-game/snake-game.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Snake Game
|
||||
|
||||
Design a snake game that is to be played in web browser.
|
||||
|
||||
Client: React + Redux
|
||||
|
||||
Rendering:
|
||||
Pixel-based graphics. Depending on the intended resolution, can divide the screen into N \* M pixels. Can dynamically calculate the size of each pixel.
|
||||
|
||||
Fruit: One pixel.
|
||||
Snake body: One pixel width made up of connected pixels.
|
||||
|
||||
Model:
|
||||
|
||||
```js
|
||||
{
|
||||
fruit: {
|
||||
x, y
|
||||
},
|
||||
snake: {
|
||||
points: [(x, y), ...] # head is at index 0
|
||||
direction: north/south/east/west
|
||||
}
|
||||
speed: 500,
|
||||
points: 0
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
function update() {
|
||||
next_loc = points[0] + (x, y) # Depends on the direction
|
||||
if (snake.points.find(next_loc) > 0) {
|
||||
// die
|
||||
}
|
||||
let pts = snake.points;
|
||||
if (!isEqual(next_loc, fruit)) {
|
||||
pts = points.removeLast();
|
||||
} else {
|
||||
generateFruit();
|
||||
points++;
|
||||
}
|
||||
snake.points = [next_loc, ...pts];
|
||||
|
||||
// Boundary checking -> die
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
function generateFruit() {
|
||||
// Cannot generate on my own body.
|
||||
|
||||
// First approach: while on body, generate
|
||||
let next_fruit_location = random_location();
|
||||
while (snake.points.find(next_fruit_location) > 0) {
|
||||
next_fruit_location = random_location();
|
||||
}
|
||||
fruit = next_fruit_location;
|
||||
|
||||
// Second approach: brute force
|
||||
for (let i = 0; i < rows; i++) {
|
||||
for (let j = 0; j < cols; j++) {
|
||||
let point = { x: i, y: j };
|
||||
if (snake.points.find(next_fruit_location) === -1) {
|
||||
fruit = point;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Third approach: brute force with random
|
||||
const available_points = [];
|
||||
for (let i = 0; i < rows; i++) {
|
||||
for (let j = 0; j < cols; j++) {
|
||||
let point = { x: i, y: j };
|
||||
if (snake.points.find(next_fruit_location) === -1) {
|
||||
available_points.push(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
fruit = _.sample(available_points);
|
||||
}
|
||||
|
||||
setInterval(update, speed);
|
||||
```
|
||||
21
experimental/domain/software-engineering.md
Normal file
21
experimental/domain/software-engineering.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Software Engineering
|
||||
|
||||
## What is the difference between an interface and abstract class?
|
||||
|
||||
**Abstract Class**
|
||||
|
||||
- For an abstract class, a method must be declared as abstract. An abstract method doesn't have an implementation.
|
||||
- The Abstract methods can be declared with Access modifiers like public, internal, protected, etc. When implementing these methods in a subclass, you must define them with the same (or a less restricted) visibility.
|
||||
- Abstract classes can contain variables and concrete methods.
|
||||
- A class can Inherit only one Abstract class. Hence multiple inheritance is not possible for an Abstract class.
|
||||
- Abstract is object-oriented. It offers the basic data an 'object' should have and/or functions it should be able to do. It is concerned with the object's basic characteristics: what it has and what it can do. Hence objects which inherit from the same abstract class share the basic characteristics (generalization).
|
||||
- Abstract class establishes "is a" relation with concrete classes.
|
||||
|
||||
**Interface**
|
||||
|
||||
- For an interface, all the methods are abstract by default. So one cannot declare variables or concrete methods in interfaces.
|
||||
- All methods declared in an interface must be public.
|
||||
- Interfaces cannot contain variables and concrete methods except constants.
|
||||
- A class can implement many interfaces. Hence multiple interface inheritance is possible.
|
||||
- Interface is functionality-oriented. It defines functionalities an object should have. Regardless what object it is, as long as it can do these functionalities, which are defined in the interface, it's fine. It ignores everything else. An object/class can contain several (groups of) functionalities; hence it is possible for a class to implement multiple interfaces.
|
||||
- Interface provides "has a" capability for classes.
|
||||
178
experimental/domain/tic-tac-toe/index.html
Normal file
178
experimental/domain/tic-tac-toe/index.html
Normal file
@@ -0,0 +1,178 @@
|
||||
<!doctype html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Helvetica', sans-serif;
|
||||
}
|
||||
|
||||
.board-cell {
|
||||
border: 1px solid #666;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
font-size: 32px;
|
||||
height: 100px;
|
||||
line-height: 100px;
|
||||
text-align: center;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.board-cell .content {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Tic Tac Toe</h1>
|
||||
<p>Current player turn: <span class="js-current-player"></span></p>
|
||||
<div class="js-board"></div>
|
||||
<button class="js-reset">Reset</button>
|
||||
<script>
|
||||
// We will spend the next 45 minutes building a single-page web app that implements a Tic-Tac-Toe game. jQuery has been included for you. We'll implement the following features in order:
|
||||
|
||||
// 1. Render a 3x3 board. You can hardcode some X and O values within the cell for starters.
|
||||
// 2. Implement the add symbol functionality that adds a X or O into a cell whenever the player clicks on it.
|
||||
// 3. Rotate between the players whenever a move is made. Update the current player display.
|
||||
// 4. Check for end game conditions after each move and display the winner if any.
|
||||
// horizontally, vertically, diagonally
|
||||
// 5. After a winner has been determined, disable further moves on the board.
|
||||
// 6. Add a button to reset the game state.
|
||||
(() => {
|
||||
function init() {
|
||||
const DOM = {
|
||||
$currentPlayer: document.querySelector('.js-current-player'),
|
||||
$board: document.querySelector('.js-board'),
|
||||
$resetButton: document.querySelector('.js-reset'),
|
||||
};
|
||||
const SIZE = 3;
|
||||
function initialState() {
|
||||
return {
|
||||
boardModel: Array(SIZE).fill(null).map(_ => Array(SIZE).fill(null)),
|
||||
players: ['X', 'O'],
|
||||
currentPlayer: 0,
|
||||
gameEnded: false,
|
||||
turn: 0,
|
||||
};
|
||||
}
|
||||
let state = initialState();
|
||||
|
||||
function renderBoard() {
|
||||
DOM.$currentPlayer.textContent = state.players[state.currentPlayer];
|
||||
// Assuming SIZE > 0.
|
||||
DOM.$board.innerHTML = '';
|
||||
for (let i = 0; i < SIZE; i++) {
|
||||
const $row = document.createElement('div');
|
||||
$row.classList.add('board-row');
|
||||
for (let j = 0; j < SIZE; j++) {
|
||||
const $cell = document.createElement('div');
|
||||
$cell.classList.add('board-cell');
|
||||
$cell.setAttribute('data-i', i);
|
||||
$cell.setAttribute('data-j', j);
|
||||
const $content = document.createElement('span');
|
||||
$content.classList.add('content');
|
||||
$content.textContent = state.boardModel[i][j];
|
||||
$cell.appendChild($content);
|
||||
$row.appendChild($cell);
|
||||
}
|
||||
DOM.$board.appendChild($row);
|
||||
}
|
||||
}
|
||||
|
||||
function checkWinning(board, player) {
|
||||
// Check horizontal.
|
||||
for (let i = 0; i < SIZE; i++) {
|
||||
if (board[i].every(cell => cell === player)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check vertical.
|
||||
for (let j = 0; j < SIZE; j++) {
|
||||
let verticalAllPlayer = true;
|
||||
for (let i = 0; i < SIZE; i++) {
|
||||
if (board[i][j] !== player) {
|
||||
verticalAllPlayer = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (verticalAllPlayer) {
|
||||
return verticalAllPlayer;
|
||||
}
|
||||
}
|
||||
|
||||
// Check diagonal South-East.
|
||||
let diagonalAllPlayer = true;
|
||||
for (let i = 0; i < SIZE; i++) {
|
||||
if (board[i][i] !== player) {
|
||||
diagonalAllPlayer = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (diagonalAllPlayer) {
|
||||
return diagonalAllPlayer;
|
||||
}
|
||||
|
||||
// Check diagonal North-East.
|
||||
diagonalAllPlayer = true;
|
||||
for (let i = SIZE - 1, j = 0; i >= 0; i--, j++) {
|
||||
if (board[i][j] !== player) {
|
||||
diagonalAllPlayer = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (diagonalAllPlayer) {
|
||||
return diagonalAllPlayer;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function attachEventListeners() {
|
||||
DOM.$board.addEventListener('click', (event) => {
|
||||
if (state.gameEnded) {
|
||||
return;
|
||||
}
|
||||
if (!event.target.classList.contains('board-cell')) {
|
||||
return;
|
||||
}
|
||||
const $cell = event.target;
|
||||
const i = parseInt($cell.getAttribute('data-i'), 10);
|
||||
const j = parseInt($cell.getAttribute('data-j'), 10);
|
||||
if (state.boardModel[i][j] !== null) {
|
||||
alert('Cell has already been taken!');
|
||||
return;
|
||||
}
|
||||
const player = state.players[state.currentPlayer];
|
||||
state.boardModel[i][j] = player;
|
||||
const winningMove = checkWinning(state.boardModel, player);
|
||||
state.turn++;
|
||||
if (!winningMove) {
|
||||
state.currentPlayer = (state.currentPlayer + 1) % 2;
|
||||
renderBoard();
|
||||
if (state.turn === SIZE * SIZE) {
|
||||
alert('It\'s a draw!');
|
||||
}
|
||||
} else {
|
||||
renderBoard();
|
||||
state.gameEnded = true;
|
||||
alert(`Player ${player} wins!`);
|
||||
}
|
||||
});
|
||||
|
||||
DOM.$resetButton.addEventListener('click', () => {
|
||||
if (confirm('Start a new game?')) {
|
||||
state = initialState();
|
||||
renderBoard();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
renderBoard();
|
||||
attachEventListeners();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
3
experimental/front-end/README.md
Normal file
3
experimental/front-end/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Front-end Job Interview Questions
|
||||
|
||||
Front-end Job Interview Questions have been migrated [here](https://github.com/yangshun/front-end-interview-handbook).
|
||||
18
experimental/interviewers/basics.md
Normal file
18
experimental/interviewers/basics.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Basics
|
||||
|
||||
## Disclaimer
|
||||
|
||||
All these items will change based on your specific company and needs but these items area are the starting point.
|
||||
|
||||
## Items To Consider
|
||||
|
||||
- **Timeliness** - The interviewee should show up on time, but of course things happen and we must all be understanding that things outside of their control may happen. Try to give a few minutes leeway.
|
||||
- **Strengths** - Ask the interviewee what they would consider to be their strengths and maybe rate themselves. This gives you a good idea where to start asking technical questions and sets a baseline for expected knowledge of each subject.
|
||||
- **Keep Things Loose** - This is of course dependent on your industry but try to keep make the interviewee comfortable. Many people get nervous when trying to perform at their best for others and a technical interview is no different. A suggestion is to start with a personal question such as "What are some of your hobbies?" or "What do you like to do for fun?" These types of questions can help relax an interviewee and allows them to perform better.
|
||||
- **Understand The Position** - Understand that a junior level candidate isn't going to have as much knowledge about languages and frameworks as a senior candidate will.
|
||||
- **Save Time For Questions** - The interviewee may have questions for you! Give them the ability to ask. Maybe offer up a few questions if they have none, (ie. "What is the typical day here like for my position?", "What is your favorite part about working at \_\_?")
|
||||
|
||||
## Tech Question Technique
|
||||
|
||||
- **Tools** - Using a text editor such as Sublime or Atom will give the interviewee syntax highlighting but doesn't show compiler errors which can be a help.
|
||||
- **Nitpicking** - Sometimes pseudocode is okay. If testing in C# do you really need the interviewee to write `Console.WriteLine()` or is `Print()` good enough? -**Keep Dialog Open** - Don't leave the interviewee alone or sit quietly by as they attempt to code. Give some subtle hints like "I see you're doing \_\_\_\_, can you think of any other ways to accomplish this?" It's unlikely that the interviewee will be working in a silo should they get the job, is there any reason they should be during the interview?
|
||||
18
experimental/utilities/javascript/binToInt.js
Normal file
18
experimental/utilities/javascript/binToInt.js
Normal file
@@ -0,0 +1,18 @@
|
||||
// Does not handle negative binary numbers.
|
||||
function binToInt(binary) {
|
||||
let res = 0;
|
||||
for (let i = 0; i < binary.length; i++) {
|
||||
res = res * 2 + +binary[i];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
console.log(binToInt('0') === parseInt('0', 2) && parseInt('0', 2) === 0);
|
||||
console.log(binToInt('1') === parseInt('1', 2) && parseInt('1', 2) === 1);
|
||||
console.log(binToInt('10') === parseInt('10', 2) && parseInt('10', 2) === 2);
|
||||
console.log(binToInt('11') === parseInt('11', 2) && parseInt('11', 2) === 3);
|
||||
console.log(binToInt('101') === parseInt('101', 2) && parseInt('101', 2) === 5);
|
||||
console.log(
|
||||
binToInt('1100011') === parseInt('1100011', 2) &&
|
||||
parseInt('1100011', 2) === 99,
|
||||
);
|
||||
26
experimental/utilities/javascript/binarySearch.js
Normal file
26
experimental/utilities/javascript/binarySearch.js
Normal file
@@ -0,0 +1,26 @@
|
||||
function binarySearch(arr, target) {
|
||||
let left = 0;
|
||||
let right = arr.length - 1;
|
||||
while (left <= right) {
|
||||
const mid = left + Math.floor((right - left) / 2);
|
||||
if (arr[mid] === target) {
|
||||
return mid;
|
||||
}
|
||||
if (arr[mid] < target) {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
right = mid - 1;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
console.log(binarySearch([1, 2, 3, 10], 1) === 0);
|
||||
console.log(binarySearch([1, 2, 3, 10], 2) === 1);
|
||||
console.log(binarySearch([1, 2, 3, 10], 3) === 2);
|
||||
console.log(binarySearch([1, 2, 3, 10], 10) === 3);
|
||||
console.log(binarySearch([1, 2, 3, 10], 9) === -1);
|
||||
console.log(binarySearch([1, 2, 3, 10], 4) === -1);
|
||||
console.log(binarySearch([1, 2, 3, 10], 0) === -1);
|
||||
console.log(binarySearch([1, 2, 3, 10], 11) === -1);
|
||||
console.log(binarySearch([5, 7, 8, 10], 3) === -1);
|
||||
43
experimental/utilities/javascript/deepEqual.js
Normal file
43
experimental/utilities/javascript/deepEqual.js
Normal file
@@ -0,0 +1,43 @@
|
||||
function deepEqual(val1, val2) {
|
||||
if (typeof val1 !== typeof val2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Array comparison.
|
||||
if (Array.isArray(val1) && Array.isArray(val2)) {
|
||||
if (val1.length !== val2.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < val1.length; i++) {
|
||||
if (!deepEqual(val1[i], val2[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Object comparison.
|
||||
if (
|
||||
typeof val1 === 'object' &&
|
||||
typeof val2 === 'object' &&
|
||||
val1 !== null &&
|
||||
val2 !== null
|
||||
) {
|
||||
const keys1 = Object.keys(val1),
|
||||
keys2 = Object.keys(val2);
|
||||
if (keys1.length !== keys2.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < keys1.length; i++) {
|
||||
if (!deepEqual(val1[keys1[i]], val2[keys2[i]])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Primitive comparison.
|
||||
return val1 === val2;
|
||||
}
|
||||
|
||||
module.exports = deepEqual;
|
||||
35
experimental/utilities/javascript/graphTopoSort.js
Normal file
35
experimental/utilities/javascript/graphTopoSort.js
Normal file
@@ -0,0 +1,35 @@
|
||||
function graphTopoSort(numberNodes, edges) {
|
||||
const nodes = new Map();
|
||||
const order = [];
|
||||
const queue = [];
|
||||
for (let i = 0; i < numberNodes; i++) {
|
||||
nodes.set(i, {in: 0, out: new Set()});
|
||||
}
|
||||
|
||||
edges.forEach(edge => {
|
||||
const [node_id, pre_id] = edge;
|
||||
nodes.get(node_id).in += 1;
|
||||
nodes.get(pre_id).out.add(node_id);
|
||||
});
|
||||
|
||||
for (let [node_id, value] of nodes.entries()) {
|
||||
if (value.in === 0) {
|
||||
queue.push(node_id);
|
||||
}
|
||||
}
|
||||
|
||||
while (queue.length) {
|
||||
const node_id = queue.shift();
|
||||
for (let outgoing_id of nodes.get(node_id).out) {
|
||||
nodes.get(outgoing_id).in -= 1;
|
||||
if (nodes.get(outgoing_id).in === 0) {
|
||||
queue.push(outgoing_id);
|
||||
}
|
||||
}
|
||||
order.push(node_id);
|
||||
}
|
||||
|
||||
return order.length == numberNodes ? order : [];
|
||||
}
|
||||
|
||||
console.log(graphTopoSort(3, [[0, 1], [0, 2]]));
|
||||
21
experimental/utilities/javascript/intToBin.js
Normal file
21
experimental/utilities/javascript/intToBin.js
Normal file
@@ -0,0 +1,21 @@
|
||||
// Does not handle negative numbers.
|
||||
function intToBin(number) {
|
||||
if (number === 0) {
|
||||
return '0';
|
||||
}
|
||||
let res = '';
|
||||
while (number > 0) {
|
||||
res = String(number % 2) + res;
|
||||
number = parseInt(number / 2, 10);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
console.log(intToBin(0) === (0).toString(2) && (0).toString(2) === '0');
|
||||
console.log(intToBin(1) === (1).toString(2) && (1).toString(2) === '1');
|
||||
console.log(intToBin(2) === (2).toString(2) && (2).toString(2) === '10');
|
||||
console.log(intToBin(3) === (3).toString(2) && (3).toString(2) === '11');
|
||||
console.log(intToBin(5) === (5).toString(2) && (5).toString(2) === '101');
|
||||
console.log(
|
||||
intToBin(99) === (99).toString(2) && (99).toString(2) === '1100011',
|
||||
);
|
||||
17
experimental/utilities/javascript/intervalsIntersect.js
Normal file
17
experimental/utilities/javascript/intervalsIntersect.js
Normal file
@@ -0,0 +1,17 @@
|
||||
// Interval: [start, end].
|
||||
function intervalsIntersect(a, b) {
|
||||
return a[0] < b[1] && b[0] < a[1];
|
||||
}
|
||||
|
||||
console.log(intervalsIntersect([1, 2], [3, 4]) === false);
|
||||
console.log(intervalsIntersect([1, 2], [2, 4]) === false);
|
||||
console.log(intervalsIntersect([1, 2], [1, 4]) === true);
|
||||
console.log(intervalsIntersect([1, 2], [0, 4]) === true);
|
||||
console.log(intervalsIntersect([1, 2], [0, 2]) === true);
|
||||
console.log(intervalsIntersect([1, 2], [0, 1.5]) === true);
|
||||
console.log(intervalsIntersect([3, 4], [1, 2]) === false);
|
||||
console.log(intervalsIntersect([2, 4], [1, 2]) === false);
|
||||
console.log(intervalsIntersect([1, 4], [1, 2]) === true);
|
||||
console.log(intervalsIntersect([0, 4], [1, 2]) === true);
|
||||
console.log(intervalsIntersect([0, 2], [1, 2]) === true);
|
||||
console.log(intervalsIntersect([0, 1.5], [1, 2]) === true);
|
||||
16
experimental/utilities/javascript/intervalsMerge.js
Normal file
16
experimental/utilities/javascript/intervalsMerge.js
Normal file
@@ -0,0 +1,16 @@
|
||||
// Interval: [start, end].
|
||||
// Merges two overlapping intervals into one.
|
||||
function intervalsMerge(a, b) {
|
||||
return [Math.min(a[0], b[0]), Math.max(a[1], b[1])];
|
||||
}
|
||||
|
||||
const deepEqual = require('./deepEqual');
|
||||
|
||||
console.log(deepEqual(intervalsMerge([1, 2], [1, 4]), [1, 4]));
|
||||
console.log(deepEqual(intervalsMerge([1, 2], [0, 4]), [0, 4]));
|
||||
console.log(deepEqual(intervalsMerge([1, 2], [0, 2]), [0, 2]));
|
||||
console.log(deepEqual(intervalsMerge([1, 2], [0, 1.5]), [0, 2]));
|
||||
console.log(deepEqual(intervalsMerge([1, 4], [1, 2]), [1, 4]));
|
||||
console.log(deepEqual(intervalsMerge([0, 4], [1, 2]), [0, 4]));
|
||||
console.log(deepEqual(intervalsMerge([0, 2], [1, 2]), [0, 2]));
|
||||
console.log(deepEqual(intervalsMerge([0, 1.5], [1, 2]), [0, 2]));
|
||||
19
experimental/utilities/javascript/isSubsequence.js
Normal file
19
experimental/utilities/javascript/isSubsequence.js
Normal file
@@ -0,0 +1,19 @@
|
||||
function isSubsequence(s, t) {
|
||||
if (s.length > t.length) {
|
||||
return false;
|
||||
}
|
||||
let matchedLength = 0;
|
||||
for (let i = 0; i < t.length; i++) {
|
||||
if (matchedLength < s.length && s[matchedLength] === t[i]) {
|
||||
matchedLength += 1;
|
||||
}
|
||||
}
|
||||
return matchedLength === s.length;
|
||||
}
|
||||
|
||||
console.log(isSubsequence('abc', 'abcde') === true);
|
||||
console.log(isSubsequence('abd', 'abcde') === true);
|
||||
console.log(isSubsequence('abf', 'abcde') === false);
|
||||
console.log(isSubsequence('abef', 'abcde') === false);
|
||||
console.log(isSubsequence('abcdef', 'abcde') === false);
|
||||
console.log(isSubsequence('a', 'abcde') === true);
|
||||
22
experimental/utilities/javascript/matrixClone.js
Normal file
22
experimental/utilities/javascript/matrixClone.js
Normal file
@@ -0,0 +1,22 @@
|
||||
function matrixClone(matrix, defaultValue) {
|
||||
return matrix.map(row => {
|
||||
return defaultValue === undefined
|
||||
? row.slice(0)
|
||||
: Array(row.length).fill(defaultValue);
|
||||
});
|
||||
}
|
||||
|
||||
const deepEqual = require('./deepEqual');
|
||||
|
||||
// Test clone.
|
||||
const a = [[1, 2], [1, 4]];
|
||||
console.log(deepEqual(matrixClone(a), [[1, 2], [1, 4]]));
|
||||
a[0][0] = 4;
|
||||
console.log(deepEqual(matrixClone(a), [[1, 2], [1, 4]]) === false);
|
||||
console.log(deepEqual(matrixClone([[1]]), [[1]]));
|
||||
|
||||
// Test clone with default value.
|
||||
console.log(deepEqual(matrixClone([[1, 2], [1, 4]], 1), [[1, 1], [1, 1]]));
|
||||
console.log(
|
||||
deepEqual(matrixClone([[1, 2], [1, 4]], null), [[null, null], [null, null]]),
|
||||
);
|
||||
12
experimental/utilities/javascript/matrixTranspose.js
Normal file
12
experimental/utilities/javascript/matrixTranspose.js
Normal file
@@ -0,0 +1,12 @@
|
||||
function matrixTranspose(matrix) {
|
||||
return matrix[0].map((col, i) => matrix.map(row => row[i]));
|
||||
}
|
||||
|
||||
const deepEqual = require('./deepEqual');
|
||||
|
||||
console.log(deepEqual(matrixTranspose([[1]]), [[1]]));
|
||||
console.log(deepEqual(matrixTranspose([[1, 2]]), [[1], [2]]));
|
||||
console.log(deepEqual(matrixTranspose([[1, 2], [1, 4]]), [[1, 1], [2, 4]]));
|
||||
console.log(
|
||||
deepEqual(matrixTranspose([[1, 2, 3], [4, 5, 6]]), [[1, 4], [2, 5], [3, 6]]),
|
||||
);
|
||||
30
experimental/utilities/javascript/matrixTraverse.js
Normal file
30
experimental/utilities/javascript/matrixTraverse.js
Normal file
@@ -0,0 +1,30 @@
|
||||
function traverse(matrix) {
|
||||
const DIRECTIONS = [[0, 1], [0, -1], [1, 0], [-1, 0]];
|
||||
const rows = matrix.length;
|
||||
const cols = matrix[0].length;
|
||||
const visited = matrix.map(row => Array(row.length).fill(false));
|
||||
function dfs(i, j) {
|
||||
if (visited[i][j]) {
|
||||
return;
|
||||
}
|
||||
visited[i][j] = true;
|
||||
DIRECTIONS.forEach(dir => {
|
||||
const row = i + dir[0],
|
||||
col = j + dir[1];
|
||||
// Boundary check.
|
||||
if (row < 0 || row >= rows || col < 0 || col >= cols) {
|
||||
return;
|
||||
}
|
||||
// Valid neighbor check.
|
||||
if (matrix[row][col] !== 1) {
|
||||
return;
|
||||
}
|
||||
dfs(row, col);
|
||||
});
|
||||
}
|
||||
for (let i = 0; i < rows; i++) {
|
||||
for (let j = 0; j < cols; j++) {
|
||||
dfs(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
62
experimental/utilities/javascript/mergeSort.js
Normal file
62
experimental/utilities/javascript/mergeSort.js
Normal file
@@ -0,0 +1,62 @@
|
||||
function mergeSort(arr) {
|
||||
if (arr.length < 2) {
|
||||
// Arrays of length 0 or 1 are sorted by definition.
|
||||
return arr;
|
||||
}
|
||||
|
||||
const left = arr.slice(0, Math.floor(arr.length / 2));
|
||||
const right = arr.slice(Math.floor(arr.length / 2), Math.floor(arr.length));
|
||||
|
||||
return merge(mergeSort(left), mergeSort(right));
|
||||
}
|
||||
|
||||
function merge(arr1, arr2) {
|
||||
const merged = [];
|
||||
let i = 0,
|
||||
j = 0;
|
||||
|
||||
while (i < arr1.length && j < arr2.length) {
|
||||
if (arr1[i] <= arr2[j]) {
|
||||
merged.push(arr1[i]);
|
||||
i++;
|
||||
} else if (arr2[j] < arr1[i]) {
|
||||
merged.push(arr2[j]);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
merged.push(...arr1.slice(i), ...arr2.slice(j));
|
||||
return merged;
|
||||
}
|
||||
|
||||
const deepEqual = require('./deepEqual');
|
||||
|
||||
console.log(deepEqual(mergeSort([]), []));
|
||||
console.log(deepEqual(mergeSort([1]), [1]));
|
||||
console.log(deepEqual(mergeSort([2, 1]), [1, 2]));
|
||||
console.log(deepEqual(mergeSort([7, 2, 4, 3, 1, 2]), [1, 2, 2, 3, 4, 7]));
|
||||
console.log(deepEqual(mergeSort([1, 2, 3, 4, 5, 0]), [0, 1, 2, 3, 4, 5]));
|
||||
console.log(
|
||||
deepEqual(mergeSort([10, 9, 8, 7, 6, 5, 4, 3, 2, 1]), [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
]),
|
||||
);
|
||||
console.log(
|
||||
deepEqual(mergeSort([98322, 3242, 876, -234, 34, 12331]), [
|
||||
-234,
|
||||
34,
|
||||
876,
|
||||
3242,
|
||||
12331,
|
||||
98322,
|
||||
]),
|
||||
);
|
||||
13
experimental/utilities/javascript/treeEqual.js
Normal file
13
experimental/utilities/javascript/treeEqual.js
Normal file
@@ -0,0 +1,13 @@
|
||||
function treeEqual(node1, node2) {
|
||||
if (!node1 && !node2) {
|
||||
return true;
|
||||
}
|
||||
if (!node1 || !node2) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
node1.val == node2.val &&
|
||||
treeEqual(node1.left, node2.left) &&
|
||||
treeEqual(node1.right, node2.right)
|
||||
);
|
||||
}
|
||||
10
experimental/utilities/javascript/treeMirror.js
Normal file
10
experimental/utilities/javascript/treeMirror.js
Normal file
@@ -0,0 +1,10 @@
|
||||
function treeMirror(node) {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
let temp = node.left;
|
||||
node.left = node.right;
|
||||
node.right = temp;
|
||||
treeMirror(node.left);
|
||||
treeMirror(node.right);
|
||||
}
|
||||
68
experimental/utilities/python/binary_search.py
Normal file
68
experimental/utilities/python/binary_search.py
Normal file
@@ -0,0 +1,68 @@
|
||||
def binary_search(arr, target):
|
||||
left = 0;
|
||||
right = len(arr) - 1
|
||||
while left <= right:
|
||||
mid = left + (right - left) // 2;
|
||||
if arr[mid] == target:
|
||||
return mid
|
||||
elif arr[mid] < target:
|
||||
left = mid + 1
|
||||
else:
|
||||
right = mid - 1
|
||||
return -1
|
||||
|
||||
def bisect_left(arr, target):
|
||||
"""Returns the leftmost position that `target` should
|
||||
go to such that the sequence remains sorted."""
|
||||
left = 0
|
||||
right = len(arr)
|
||||
while left < right:
|
||||
mid = (left + right) // 2
|
||||
if arr[mid] < target:
|
||||
left = mid + 1
|
||||
else:
|
||||
right = mid
|
||||
return left
|
||||
|
||||
def bisect_right(arr, target):
|
||||
"""Returns the rightmost position that `target` should
|
||||
go to such that the sequence remains sorted."""
|
||||
left = 0
|
||||
right = len(arr)
|
||||
while left < right:
|
||||
mid = (left + right) // 2
|
||||
if arr[mid] > target:
|
||||
right = mid
|
||||
else:
|
||||
left = mid + 1
|
||||
return left
|
||||
|
||||
print(binary_search([1, 2, 3, 10], 1) == 0)
|
||||
print(binary_search([1, 2, 3, 10], 2) == 1)
|
||||
print(binary_search([1, 2, 3, 10], 3) == 2)
|
||||
print(binary_search([1, 2, 3, 10], 10) == 3)
|
||||
print(binary_search([1, 2, 3, 10], 9) == -1)
|
||||
print(binary_search([1, 2, 3, 10], 4) == -1)
|
||||
print(binary_search([1, 2, 3, 10], 0) == -1)
|
||||
print(binary_search([1, 2, 3, 10], 11) == -1)
|
||||
print(binary_search([5, 7, 8, 10], 3) == -1)
|
||||
|
||||
print(bisect_left([1, 2, 3, 3, 10], 1) == 0)
|
||||
print(bisect_left([1, 2, 3, 3, 10], 2) == 1)
|
||||
print(bisect_left([1, 2, 3, 3, 10], 3) == 2) # First "3" is at index 2
|
||||
print(bisect_left([1, 2, 3, 3, 10], 10) == 4)
|
||||
|
||||
# These return a valid index despite target not being in array.
|
||||
print(bisect_left([1, 2, 3, 3, 10], 9) == 4)
|
||||
print(bisect_left([1, 2, 3, 3, 10], 0) == 0) # Insert "0" at front
|
||||
print(bisect_left([1, 2, 3, 3, 10], 11) == 5) # Insert "5" at back
|
||||
|
||||
print(bisect_right([1, 2, 3, 3, 10], 1) == 1)
|
||||
print(bisect_right([1, 2, 3, 3, 10], 2) == 2)
|
||||
print(bisect_right([1, 2, 3, 3, 10], 3) == 4) # Last "3" is at index 3, so insert new "3" at index 4
|
||||
print(bisect_right([1, 2, 3, 3, 10], 10) == 5)
|
||||
|
||||
# These return a valid index despite target not being in array.
|
||||
print(bisect_right([1, 2, 3, 3, 10], 9) == 4)
|
||||
print(bisect_right([1, 2, 3, 3, 10], 0) == 0) # Insert "0" at front
|
||||
print(bisect_right([1, 2, 3, 3, 10], 11) == 5) # Insert "5" at back
|
||||
19
experimental/utilities/python/char_prime_map.py
Normal file
19
experimental/utilities/python/char_prime_map.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# For mapping a lowercase character to a prime number.
|
||||
# Useful for checking whether two strings are anagram or permutations of each other.
|
||||
primes = {
|
||||
'a': 2, 'b': 3, 'c': 5, 'd': 7, 'e': 11, 'f': 13,
|
||||
'g': 17, 'h': 19, 'i': 23, 'j': 29, 'k': 31, 'l': 37,
|
||||
'm': 41, 'n': 43, 'o': 47, 'p': 53, 'q': 59, 'r': 61,
|
||||
's': 67, 't': 71, 'u': 73, 'v': 79, 'w': 83, 'x': 89,
|
||||
'y': 97, 'z': 101, ' ': 103,
|
||||
}
|
||||
|
||||
import functools
|
||||
|
||||
def mul(seq):
|
||||
return functools.reduce(lambda a, b: a * b, seq, 1)
|
||||
|
||||
def prime_value_of_string(string):
|
||||
return mul([primes[c] for c in string])
|
||||
|
||||
print(prime_value_of_string('abcde'))
|
||||
45
experimental/utilities/python/graph_dfs.py
Normal file
45
experimental/utilities/python/graph_dfs.py
Normal file
@@ -0,0 +1,45 @@
|
||||
def graph_dfs(matrix):
|
||||
rows, cols = len(matrix), len(matrix[0])
|
||||
visited = set()
|
||||
directions = ((0, 1), (0, -1), (1, 0), (-1, 0))
|
||||
def dfs(i, j):
|
||||
if (i, j) in visited:
|
||||
return
|
||||
visited.add((i, j))
|
||||
# Traverse neighbors.
|
||||
for direction in directions:
|
||||
next_i, next_j = i + direction[0], j + direction[1]
|
||||
if 0 <= next_i < rows and 0 <= next_j < cols: # Check boundary.
|
||||
# Add any other checking here ^
|
||||
dfs(next_i, next_j)
|
||||
|
||||
for i in range(rows):
|
||||
for j in range(cols):
|
||||
dfs(i, j)
|
||||
|
||||
# Follow up:
|
||||
# 1) Diagonal cells are considered neighbors
|
||||
# 2) View the matrix like Earth, right boundary is adjacent to the left boundary, top adjacent to left, etc.
|
||||
def graph_dfs_diagonals_and_boundary_wrap(matrix):
|
||||
rows, cols = len(matrix), len(matrix[0])
|
||||
visited = set()
|
||||
# Change 1: Add 4 more diagonal directions.
|
||||
directions = ((0, 1), (0, -1), (1, 0), (-1, 0), (-1, -1), (1, 1), (1, -1), (-1, 1))
|
||||
def dfs(i, j):
|
||||
if (i, j) in visited:
|
||||
return
|
||||
visited.add((i, j))
|
||||
for direction in directions:
|
||||
# Change 2: No more boundary, use modulo to allow traversal that exceed boundaries to wrap around.
|
||||
next_i, next_j = (i + direction[0] + rows) % rows, (j + direction[1] + cols) % cols
|
||||
dfs(next_i, next_j)
|
||||
|
||||
for i in range(rows):
|
||||
for j in range(cols):
|
||||
dfs(i, j)
|
||||
|
||||
graph_dfs([
|
||||
[1, 2, 3, 4],
|
||||
[5, 6, 7, 8],
|
||||
[9, 10, 11, 12],
|
||||
])
|
||||
21
experimental/utilities/python/graph_topo_sort.py
Normal file
21
experimental/utilities/python/graph_topo_sort.py
Normal file
@@ -0,0 +1,21 @@
|
||||
def graph_topo_sort(num_nodes, edges):
|
||||
from collections import deque
|
||||
nodes, order, queue = {}, [], deque()
|
||||
for node_id in range(num_nodes):
|
||||
nodes[node_id] = { 'in': 0, 'out': set() }
|
||||
for node_id, pre_id in edges:
|
||||
nodes[node_id]['in'] += 1
|
||||
nodes[pre_id]['out'].add(node_id)
|
||||
for node_id in nodes.keys():
|
||||
if nodes[node_id]['in'] == 0:
|
||||
queue.append(node_id)
|
||||
while len(queue):
|
||||
node_id = queue.pop()
|
||||
for outgoing_id in nodes[node_id]['out']:
|
||||
nodes[outgoing_id]['in'] -= 1
|
||||
if nodes[outgoing_id]['in'] == 0:
|
||||
queue.append(outgoing_id)
|
||||
order.append(node_id)
|
||||
return order if len(order) == num_nodes else []
|
||||
|
||||
print(graph_topo_sort(3, [[0, 1], [0, 2]]))
|
||||
84
experimental/utilities/python/heap.py
Normal file
84
experimental/utilities/python/heap.py
Normal file
@@ -0,0 +1,84 @@
|
||||
# Implements a min-heap. For max-heap, simply reverse all comparison orders.
|
||||
#
|
||||
# Note on alternate subroutine namings (used in some textbooks):
|
||||
# - _bubble_up = siftdown
|
||||
# - _bubble_down = siftup
|
||||
|
||||
def _bubble_up(heap, i):
|
||||
while i > 0:
|
||||
parent_i = (i - 1) // 2
|
||||
if heap[i] < heap[parent_i]:
|
||||
heap[i], heap[parent_i] = heap[parent_i], heap[i]
|
||||
i = parent_i
|
||||
continue
|
||||
break
|
||||
|
||||
def _bubble_down(heap, i):
|
||||
startpos = i
|
||||
newitem = heap[i]
|
||||
left_i = 2 * i + 1
|
||||
while left_i < len(heap):
|
||||
# Pick the smaller of the L and R children
|
||||
right_i = left_i + 1
|
||||
if right_i < len(heap) and not heap[left_i] < heap[right_i]:
|
||||
child_i = right_i
|
||||
else:
|
||||
child_i = left_i
|
||||
|
||||
# Break if heap invariant satisfied
|
||||
if heap[i] < heap[child_i]:
|
||||
break
|
||||
|
||||
# Move the smaller child up.
|
||||
heap[i], heap[child_i] = heap[child_i], heap[i]
|
||||
i = child_i
|
||||
left_i = 2 * i + 1
|
||||
|
||||
def heapify(lst):
|
||||
for i in reversed(range(len(lst) // 2)):
|
||||
_bubble_down(lst, i)
|
||||
|
||||
def heappush(heap, item):
|
||||
heap.append(item)
|
||||
_bubble_up(heap, len(heap) - 1)
|
||||
|
||||
def heappop(heap):
|
||||
if len(heap) == 1:
|
||||
return heap.pop()
|
||||
min_value = heap[0]
|
||||
heap[0] = heap[-1]
|
||||
del heap[-1]
|
||||
_bubble_down(heap, 0)
|
||||
return min_value
|
||||
|
||||
|
||||
|
||||
# Example usage
|
||||
heap = [3, 2, 1, 0]
|
||||
heapify(heap)
|
||||
print('Heap(0, 1, 2, 3):', heap)
|
||||
heappush(heap, 4)
|
||||
heappush(heap, 7)
|
||||
heappush(heap, 6)
|
||||
heappush(heap, 5)
|
||||
print('Heap(0, 1, 2, 3, 4, 5, 6, 7):', heap)
|
||||
|
||||
sorted_list = [heappop(heap) for _ in range(8)]
|
||||
print('Heap-sorted list:', sorted_list)
|
||||
|
||||
# Large test case, for randomized tests
|
||||
import random
|
||||
|
||||
# Heapify 0 ~ 99
|
||||
heap = list(range(100))
|
||||
random.shuffle(heap)
|
||||
heapify(heap)
|
||||
|
||||
# Push 100 ~ 199 in random order
|
||||
new_elems = list(range(100, 200))
|
||||
random.shuffle(new_elems)
|
||||
for elem in new_elems:
|
||||
heappush(heap, elem)
|
||||
|
||||
sorted_list = [heappop(heap) for _ in range(200)]
|
||||
print(sorted_list == sorted(sorted_list))
|
||||
13
experimental/utilities/python/is_subsequence.py
Normal file
13
experimental/utilities/python/is_subsequence.py
Normal file
@@ -0,0 +1,13 @@
|
||||
def is_subsequence(s, t):
|
||||
"""
|
||||
:type s: str
|
||||
:type t: str
|
||||
:rtype: bool
|
||||
"""
|
||||
if len(s) > len(t):
|
||||
return False
|
||||
matched_s = 0
|
||||
for char in t:
|
||||
if matched_s < len(s) and s[matched_s] == char:
|
||||
matched_s += 1
|
||||
return matched_s == len(s)
|
||||
109
experimental/utilities/python/linked_list.py
Normal file
109
experimental/utilities/python/linked_list.py
Normal file
@@ -0,0 +1,109 @@
|
||||
# Singly-Linked List
|
||||
#
|
||||
# The linked list is passed around as a variable pointing to the
|
||||
# root node of the linked list, or None if the list is empty.
|
||||
|
||||
class LinkedListNode:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
self.next = None
|
||||
|
||||
def linked_list_append(linked_list, value):
|
||||
'''Appends a value to the end of the linked list'''
|
||||
node = linked_list
|
||||
insert_node = LinkedListNode(value)
|
||||
if not node:
|
||||
return insert_node
|
||||
while node.next:
|
||||
node = node.next
|
||||
node.next = insert_node
|
||||
return linked_list
|
||||
|
||||
def linked_list_insert_index(linked_list, value, index):
|
||||
'''Inserts a value at a particular index'''
|
||||
node = linked_list
|
||||
insert_node = LinkedListNode(value)
|
||||
|
||||
# Check if inserting at head
|
||||
if index == 0:
|
||||
insert_node.next = node
|
||||
return insert_node
|
||||
|
||||
# Skip ahead
|
||||
for _ in range(index - 1):
|
||||
node = node.next
|
||||
if not node:
|
||||
raise ValueError
|
||||
insert_node.next = node.next
|
||||
node.next = insert_node
|
||||
return linked_list
|
||||
|
||||
def linked_list_delete(linked_list, value):
|
||||
'''Deletes the first occurrence of a value in the linked list'''
|
||||
node = linked_list
|
||||
|
||||
# Check if deleting at head
|
||||
if node.value == value:
|
||||
return node.next
|
||||
|
||||
# Skip ahead
|
||||
while node.next:
|
||||
if node.next.value == value:
|
||||
node.next = node.next.next
|
||||
return linked_list
|
||||
node = node.next
|
||||
raise ValueError
|
||||
|
||||
def linked_list_delete_index(linked_list, index):
|
||||
'''Deletes the element at a particular index in the linked list'''
|
||||
node = linked_list
|
||||
|
||||
# Check if deleting at head
|
||||
if index == 0:
|
||||
return node.next
|
||||
|
||||
# Skip ahead
|
||||
for _ in range(index - 1):
|
||||
node = node.next
|
||||
if not node:
|
||||
raise ValueError
|
||||
if not node.next:
|
||||
raise ValueError
|
||||
node.next = node.next.next
|
||||
return linked_list
|
||||
|
||||
def linked_list_iter(linked_list):
|
||||
'''Lazy iterator over each node in the linked list'''
|
||||
node = linked_list
|
||||
while node is not None:
|
||||
yield node
|
||||
node = node.next
|
||||
|
||||
|
||||
# Append to back
|
||||
linked_list = None # Start with an empty linked list
|
||||
linked_list = linked_list_append(linked_list, 1)
|
||||
linked_list = linked_list_append(linked_list, 2)
|
||||
linked_list = linked_list_append(linked_list, 4)
|
||||
print([node.value for node in linked_list_iter(linked_list)])
|
||||
|
||||
# Insert by index
|
||||
linked_list = linked_list_insert_index(linked_list, 0, 0) # Front
|
||||
print([node.value for node in linked_list_iter(linked_list)])
|
||||
linked_list = linked_list_insert_index(linked_list, 3, 3) # Back
|
||||
print([node.value for node in linked_list_iter(linked_list)])
|
||||
|
||||
# Delete "3"
|
||||
linked_list = linked_list_delete(linked_list, 3)
|
||||
print([node.value for node in linked_list_iter(linked_list)])
|
||||
|
||||
# Delete by index
|
||||
linked_list = linked_list_delete_index(linked_list, 0)
|
||||
print([node.value for node in linked_list_iter(linked_list)])
|
||||
linked_list = linked_list_delete_index(linked_list, 1)
|
||||
print([node.value for node in linked_list_iter(linked_list)])
|
||||
|
||||
# Delete until empty
|
||||
linked_list = linked_list_delete_index(linked_list, 0)
|
||||
linked_list = linked_list_delete_index(linked_list, 0)
|
||||
print([node.value for node in linked_list_iter(linked_list)])
|
||||
60
experimental/utilities/python/quick_select.py
Normal file
60
experimental/utilities/python/quick_select.py
Normal file
@@ -0,0 +1,60 @@
|
||||
## QuickSelect -- Linear-time k-th order statistic
|
||||
## (i.e. select the k-th smallest element in an unsorted array)
|
||||
## https://en.wikipedia.org/wiki/Quickselect
|
||||
|
||||
def partition(array, start, end, pivot):
|
||||
"""Partitions by a pivot value, which might not necessarily be in the array.
|
||||
This variant is useful when you want to bound your recursion depth by the
|
||||
range of the input values, and not the length of the array."""
|
||||
pivot_index = start
|
||||
for i in range(start, end):
|
||||
if array[i] <= pivot:
|
||||
array[i], array[pivot_index] = array[pivot_index], array[i]
|
||||
pivot_index += 1
|
||||
return pivot_index
|
||||
|
||||
import random
|
||||
def partition_first(array, start, end):
|
||||
"""Selects the first element as pivot. Returns the index where the pivot went to.
|
||||
In this variant, we can guarantee that the pivot will be in its final sorted position.
|
||||
We need this guarantee for QuickSelect."""
|
||||
if start + 1 == end:
|
||||
return start
|
||||
pivot = array[start]
|
||||
pivot_index = start + 1
|
||||
for i in range(start + 1, end):
|
||||
if array[i] <= pivot:
|
||||
array[i], array[pivot_index] = array[pivot_index], array[i]
|
||||
pivot_index += 1
|
||||
# Move pivot to front
|
||||
array[start], array[pivot_index - 1] = array[pivot_index - 1], array[start]
|
||||
return pivot_index - 1
|
||||
|
||||
def quick_select(array, k):
|
||||
"""NOTE: k-th smallest element counts from 0!"""
|
||||
left = 0
|
||||
right = len(array)
|
||||
while True:
|
||||
random_index = random.sample(range(left, right), 1)[0]
|
||||
array[left], array[random_index] = array[random_index], array[left]
|
||||
pivot_index = partition_first(array, left, right)
|
||||
if k == pivot_index:
|
||||
return array[pivot_index]
|
||||
if k < pivot_index:
|
||||
right = pivot_index
|
||||
else:
|
||||
left = pivot_index + 1
|
||||
|
||||
|
||||
|
||||
print(quick_select([0], 0) == 0)
|
||||
print(quick_select([0, 1, 2, 3, 4], 2) == 2)
|
||||
print(quick_select([4, 3, 2, 1, 0], 2) == 2)
|
||||
print(quick_select([1, 3, 4, 2, 0], 2) == 2)
|
||||
|
||||
# Large test case, for randomized tests
|
||||
lst = list(range(1000))
|
||||
for _ in range(10):
|
||||
k = random.randint(0, 999)
|
||||
random.shuffle(lst)
|
||||
print(quick_select(lst, k) == k)
|
||||
41
experimental/utilities/python/rabin_karp_hash.py
Normal file
41
experimental/utilities/python/rabin_karp_hash.py
Normal file
@@ -0,0 +1,41 @@
|
||||
## Rabin-Karp Rolling Hash
|
||||
## Implementation of: https://en.wikipedia.org/wiki/Rabin%E2%80%93Karp_algorithm#Hash_function_used
|
||||
##
|
||||
## This rolling hash function is useful when you need to compute the hash of successive substrings
|
||||
## of text. E.g. note that going from 'abcd' to 'bcde', we drop the 'a' from the back and add an 'e'
|
||||
## on the right. The rolling hash function thus allows us to update the hash in-place O(1) instead of
|
||||
## recomputing the full hash of the substring O(m), where m is the length of the substring.
|
||||
##
|
||||
## NOTE: The implementation below takes in a tuple of integers, to be as general as possible. For use
|
||||
## with strings, simply take the ASCII value of each character before passing into the functions.
|
||||
|
||||
BASE = 101 # Arbitrary prime number
|
||||
|
||||
def rk_hash_init(tpl):
|
||||
'''Initializes the hash with a tuple of integers.'''
|
||||
return sum(n * BASE ** i for i, n in enumerate(reversed(tpl)))
|
||||
|
||||
def rk_hash_update(curr_hash, size, add_n, rem_n):
|
||||
'''Updates the hash by removing an integer from the left and appending
|
||||
an integer to the right.
|
||||
|
||||
curr_hash: The previous hash
|
||||
size: The size of the rolling window
|
||||
add_n: The integer appended to the right
|
||||
rem_n: The integer removed from the left'''
|
||||
return (curr_hash - (rem_n * BASE ** (size - 1))) * BASE + add_n
|
||||
|
||||
|
||||
|
||||
abc_hash = rk_hash_init(tuple(map(ord, 'abc'))) # Init the hash with 'abc'
|
||||
print('abc:', abc_hash)
|
||||
bcd_hash_1 = rk_hash_update(abc_hash, 3, ord('d'), ord('a')) # Add a 'd' to the right, remove an 'a' from the left
|
||||
print('bcd 1:', bcd_hash_1)
|
||||
|
||||
zbc_hash = rk_hash_init(tuple(map(ord, 'zbc'))) # Init the hash with 'zbc'
|
||||
print('zbc:', zbc_hash)
|
||||
bcd_hash_2 = rk_hash_update(zbc_hash, 3, ord('d'), ord('z')) # Add a 'd' to the right, remove a 'z' from the left
|
||||
print('bcd 2:', bcd_hash_2)
|
||||
|
||||
# Notice that both hash values are the same despite arriving via different paths
|
||||
print(bcd_hash_1 == bcd_hash_2)
|
||||
8
experimental/utilities/python/tree_equal.py
Normal file
8
experimental/utilities/python/tree_equal.py
Normal file
@@ -0,0 +1,8 @@
|
||||
def tree_equal(node1, node2):
|
||||
if not node1 and not node2:
|
||||
return True
|
||||
if not node1 or not node2:
|
||||
return False
|
||||
return node1.val == node2.val and \
|
||||
tree_equal(node1.left, node2.left) and \
|
||||
tree_equal(node1.right, node2.right)
|
||||
6
experimental/utilities/python/tree_mirror.py
Normal file
6
experimental/utilities/python/tree_mirror.py
Normal file
@@ -0,0 +1,6 @@
|
||||
def tree_mirror(node):
|
||||
if not node:
|
||||
return
|
||||
node.left, node.right = node.right, node.left
|
||||
tree_mirror(node.left)
|
||||
tree_mirror(node.right)
|
||||
62
experimental/utilities/python/tree_traversal.py
Normal file
62
experimental/utilities/python/tree_traversal.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# Various iterative ways of traversing a tree.
|
||||
def inorder_traversal(root):
|
||||
"""
|
||||
:type root: TreeNode
|
||||
:rtype: List[int]
|
||||
"""
|
||||
if not root:
|
||||
return []
|
||||
result = []
|
||||
stack = [root]
|
||||
while len(stack) > 0:
|
||||
curr_node = stack.pop()
|
||||
if curr_node.left:
|
||||
stack.append(curr_node)
|
||||
stack.append(curr_node.left)
|
||||
curr_node.left = None
|
||||
else:
|
||||
result.append(curr_node.val)
|
||||
if curr_node.right:
|
||||
stack.append(curr_node.right)
|
||||
return result
|
||||
|
||||
def preorder_traversal(root):
|
||||
"""
|
||||
:type root: TreeNode
|
||||
:rtype: List[int]
|
||||
"""
|
||||
if not root:
|
||||
return []
|
||||
result = []
|
||||
stack = [root]
|
||||
while len(stack) > 0:
|
||||
curr_node = stack.pop()
|
||||
result.append(curr_node.val)
|
||||
if curr_node.right:
|
||||
stack.append(curr_node.right)
|
||||
if curr_node.left:
|
||||
stack.append(curr_node.left)
|
||||
return result
|
||||
|
||||
def postorder_traversal(root):
|
||||
"""
|
||||
:type root: TreeNode
|
||||
:rtype: List[int]
|
||||
"""
|
||||
if not root:
|
||||
return []
|
||||
result = []
|
||||
stack = [root]
|
||||
while len(stack) > 0:
|
||||
curr_node = stack.pop()
|
||||
if curr_node.left:
|
||||
stack.append(curr_node)
|
||||
stack.append(curr_node.left)
|
||||
curr_node.left = None
|
||||
elif curr_node.right:
|
||||
stack.append(curr_node)
|
||||
stack.append(curr_node.right)
|
||||
curr_node.right = None
|
||||
else:
|
||||
result.append(curr_node.val)
|
||||
return result
|
||||
80
experimental/utilities/python/trie.py
Normal file
80
experimental/utilities/python/trie.py
Normal file
@@ -0,0 +1,80 @@
|
||||
class Trie(object):
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize your data structure here.
|
||||
"""
|
||||
self.d = {}
|
||||
|
||||
def insert(self, word):
|
||||
"""
|
||||
Inserts a word into the trie.
|
||||
:type word: str
|
||||
:rtype: void
|
||||
"""
|
||||
curr = self.d
|
||||
for char in word:
|
||||
if char not in curr:
|
||||
curr[char] = {}
|
||||
curr = curr[char]
|
||||
curr['#'] = {} # Using an empty dict rather than a boolean value makes recursive traversal easier.
|
||||
|
||||
def search(self, word):
|
||||
"""
|
||||
Returns if the word is in the trie.
|
||||
:type word: str
|
||||
:rtype: bool
|
||||
"""
|
||||
curr = self.d
|
||||
for char in word:
|
||||
if char in curr:
|
||||
curr = curr[char]
|
||||
else:
|
||||
return False
|
||||
return '#' in curr
|
||||
|
||||
def startsWith(self, prefix):
|
||||
"""
|
||||
Returns if there is any word in the trie that starts with the given prefix.
|
||||
:type prefix: str
|
||||
:rtype: bool
|
||||
"""
|
||||
curr = self.d
|
||||
for char in prefix:
|
||||
if char in curr:
|
||||
curr = curr[char]
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
def searchRegex(self, word):
|
||||
"""
|
||||
Returns if the word is in the data structure. A word could contain the dot character '.' to represent any one letter.
|
||||
:type word: str
|
||||
:rtype: bool
|
||||
"""
|
||||
def traverse(node, index):
|
||||
if len(word) == index:
|
||||
return '#' in node
|
||||
char = word[index]
|
||||
if char == '.':
|
||||
for key in node.keys():
|
||||
if traverse(node[key], index+1):
|
||||
return True
|
||||
return False
|
||||
else:
|
||||
if char not in node:
|
||||
return False
|
||||
return traverse(node[char], index + 1)
|
||||
return traverse(self.d, 0)
|
||||
|
||||
# Example
|
||||
trie = Trie()
|
||||
trie.insert('hello')
|
||||
print(trie.search('hello') == True)
|
||||
print(trie.startsWith('hello') == True)
|
||||
print(trie.startsWith('hel') == True)
|
||||
print(trie.search('world') == False)
|
||||
print(trie.startsWith('wor') == False)
|
||||
print(trie.searchRegex('..llo') == True)
|
||||
print(trie.searchRegex('..llx') == False)
|
||||
print(trie.searchRegex('..') == False)
|
||||
50
experimental/utilities/python/union_find.py
Normal file
50
experimental/utilities/python/union_find.py
Normal file
@@ -0,0 +1,50 @@
|
||||
## Union-Find data structure
|
||||
## https://en.wikipedia.org/wiki/Disjoint-set_data_structure
|
||||
|
||||
parents = [0, 1, 2, 3, 4, 5, 6] # parent[i] is the parent of i
|
||||
weights = [1, 1, 1, 1, 1, 1, 1]
|
||||
|
||||
def find_root(parents, p):
|
||||
'''Average: O(log n)'''
|
||||
root = p
|
||||
while parents[root] != root:
|
||||
root = parents[root]
|
||||
# Flatten tree
|
||||
while parents[p] != p:
|
||||
parents[p], p = root, parents[p]
|
||||
return root
|
||||
|
||||
def union(parents, p, q):
|
||||
'''Average: O(log n)'''
|
||||
p = find_root(parents, p)
|
||||
q = find_root(parents, q)
|
||||
# Link the smaller node to the larger node
|
||||
if weights[p] > weights[q]:
|
||||
parents[q] = p
|
||||
weights[p] += weights[q]
|
||||
else:
|
||||
parents[p] = q
|
||||
weights[q] += weights[p]
|
||||
|
||||
|
||||
|
||||
# Start with all elements separate
|
||||
# -> [0], [1], [2], [3], [4], [5], [6]
|
||||
print(find_root(parents, 2) == 2)
|
||||
|
||||
# Merge 1, 2, 3 and 4, 5, 6
|
||||
# -> [0], [1, 2, 3], [4, 5, 6]
|
||||
union(parents, 1, 2)
|
||||
union(parents, 2, 3)
|
||||
union(parents, 4, 5)
|
||||
union(parents, 4, 6)
|
||||
|
||||
# Roots of 1, 2, 3 and 4, 5, 6 are the same
|
||||
print(find_root(parents, 0))
|
||||
print(list(find_root(parents, i) for i in (1, 2, 3)))
|
||||
print(list(find_root(parents, i) for i in (4, 5, 6)))
|
||||
|
||||
# Merge 2, 4
|
||||
# -> [0], [1, 2, 3, 4, 5, 6]
|
||||
union(parents, 2, 4)
|
||||
print(list(find_root(parents, i) for i in (1, 2, 3, 4, 5, 6)))
|
||||
Reference in New Issue
Block a user