[HN Gopher] Challenging algorithms and data structures every pro... ___________________________________________________________________ Challenging algorithms and data structures every programmer should try Author : whack Score : 188 points Date : 2022-12-24 16:41 UTC (6 hours ago) (HTM) web link (austinhenley.com) (TXT) w3m dump (austinhenley.com) | TheEndIsNigh wrote: | Not exactly challenging, they're covered in a decent undergrad | Algorithms course. | taeric wrote: | Fun list! Other things I'd recommend trying: | Huffman coding Binary decision diagrams Simplex | method General modeling of a problem to SAT | | The last one there is particularly fun. Finding a way to solve a | problem fast by mapping it to a silly large set of variables is | surprisingly fun. | koinedad wrote: | Thanks for the list. First time I coded a Trie I thought it was | super cool. Topological sort is such a useful one as well. | Turing_Machine wrote: | Tries are probably the one I've used the most in the real | world, so I guess if I were making such a list it'd be on the | main one, not in the honorable mention section. I'm not sure | which one I'd remove to make space for it, though. | | I second the recommendation for Jeff Erickson's book. He is an | awesome in-person instructor, too. | TrackerFF wrote: | Call me cynical, but every time I see interesting threads like | these - I can't help but to think that somewhere, a hiring | manager is thinking to himself _" Nice, I'll add these to the | technical (interview) questions"_ | PaulDavisThe1st wrote: | Although it is somewhat embarrassing to admit this, when I wrote | JACK [0] I was unaware of topological sort, mostly due to a lack | of any formal CS education. This was a major error/failing on my | part, because that codebase was more or less made for TS. There | have been few other instances where a lack of formal CS training | has been an impediment, but not knowing that there was a well- | known algorithm for ordering node execution in a directed graph | was ... bad. | | [0] https://jackaudio.org/ | Xeoncross wrote: | > Google asked me two interview problems involving these back | when I was in college. Have I ever needed this in practice? Nope! | | This is the shame of the Big Tech. I've used bloomfilters, tries, | and many other of these multiple times on side projects with | constraints, but would something like this ever make it into a | deployed system at FAANG by all the regular devs? | | No, just use dynamo, spanner or a bigger cluster + more workers, | more queues, add more k8s nodes, plus whatever cloud tech you | should be using to "horizontally scale" and be "distributed". | | Not that there aren't awesome devs at these companies which do | code responsibly, but I don't often see it. | | I can't believe how unperformant the systems I've often seen in | Big Tech are. I want more people to read the article about | fitting the whole level into a single byte[1] and stop using big | budgets as a way to be irresponsible. | | 1: https://news.ycombinator.com/item?id=34095954 | didibus wrote: | They're different problems, you've got programming in the | small, medium and large. | | Computer Science tends to focus on programming in the small to | medium, and software engineering tends to focus more on | programming in the medium to large. | | It's one thing to optimize a small piece of functionality | fully, but it's another thing to put together a product that | has thousands of live users, hundreds of features, multitude of | configuration modes, distributed globally, etc. | | If I ask you to clean a bathroom in 3 hours, and then I ask you | to clean the whole house in 3 hours, your approach to the | cleaning will be very different between the two, and the first | thing that will be cut is your attention to details. | Jensson wrote: | I think the problem with slow user interfaces is that the | programmers who knows how to make things fast usually want to | work on more interesting algorithms instead of optimising UI | interactions. I built model evaluators at Google so I used a | lot of these algorithms, specifically topological sort is | useful in so many places and you can't really make a generic | solution for it to put in a library, and different parsing | methods. When doing this I had to look at how many nanoseconds | different ways to do things costs, because things are run | billions of times on large models in production so it is very | expensive. | | The people on that team were really good at making things run | fast, but I haven't seen that kind of people doing user | interfaces, instead even at Google some internal pages took | minutes to load with barely any data, I could run a batch job | spinning up thousands of servers over terabytes of data in the | time it took just to view my account in that service. The | silver lining is that Google has many alternatives for | everything so I could use something else instead. | | I guess a part of it is that Google pays for server compute, | but not client compute, so your computer running slowly isn't a | big deal compared to their server costs, so the optimizing | programmers gets placed on backends. I did work on message | routers as well, they have to be blazing fast, so I know how to | make network requests fasts, the only reason the UI's are slow | is that the companies aren't prioritizing it. | armchairhacker wrote: | Bloom filters, skiplists, tries, recursive descent, etc. are | actually used in many systems and I think it's important to | understand how they work at a high-level | | Now, implementing one of them on a whiteboard or without access | to internet, knowing all of their intricacies and edge | cases...that is stuff I doubt anyone needs to know except a | very specific type of developer. If i want an optimized trie I | use a library, and when it breaks and i need to fix the low- | level implementation, i look it up online | mythhouse wrote: | > system at FAANG by all the regular devs? | | dynamo, spanner ect were written by devs at FAANG . Do they | have special interview channels for 'regular devs' and another | for people who can write dynamo ? | taeric wrote: | I took the point to be that maybe scale of database engine | can get you passed a lot of nuance. | | Not much different from databases of old. Writing a good | sorting algorithms feels excessive in many applications. The | number of such algorithms that the planner of a database | picks between is rather large. | ahh wrote: | > Do they have special interview channels for 'regular devs' | and another for people who can write dynamo ? | | Not special interviews, but certainly different pieces of | headcount. There are Googlers and there are _Googlers_. Being | the second is a lot more work, but a lot more interesting. | taeric wrote: | I mean, you aren't wrong. But there is more diversity and | challenge in five minutes of many modern games than all of pit | fall. | | Same goes for many of these algorithms. They are amazing, but | also require a simplicity of problem that is hard to pull back | to. Worse, they often solidify the program into something that | is very rigidly defined and not conducive to change. | rrgok wrote: | I like algorithms and data structures. One of the thing I, | personally, find hard about Algos and DS, is that I don't know | when to use which. For example, I could never ever come up with | my own, that the traveling salesman problem can have a good | solution with ant colony optimization or simulated annealing. How | do I learn that skill? I still don't know when to use a graph for | what kind of problem. | | I hope someone can shed some light on this. | TheEndIsNigh wrote: | You need to solve hard problems. Code Jam is an amazing source | for this. Just don't expect to knock them out in 5 minutes. | Pick a hard problem and stay a week with it trying different | ideas and compare the time complexity. | djur wrote: | So far most of the resources I've found for this kind of | thing don't really provide enough guidance in what algorithms | to choose and why, mostly because they're usually designed as | competitions. I'm not interested in competing with other | programmers, I want to learn from them. | Jensson wrote: | You get good at this from a long life of curiosity in the | subject. Basically, if you spent your life testing many | different ways to do things, think thousands of different ways | to compute or parse or view data etc, then you have that huge | knowledge bank to aid you when you see a new problem, so you | compose a few of those things to solve it. | | It is the same process as learning the elementary algebra in | math. You learn a few basic techniques, like moving numbers and | variables from one side to the other, and then you compose | those to solve expressions you haven't seen before. The only | difference is that algorithms is a much larger space, there are | way more techniques and problems there so it takes longer to | get to a point where it is useful to you. | jpitz wrote: | The Algorithm Design Manual, which maybe should have been named | The Algorithm Selection Manual. | | https://www.goodreads.com/book/show/425208.The_Algorithm_Des... | djur wrote: | What I would be interested in is some kind of resource (book, | video series, etc.) where someone presents a reasonably "real- | world" problem, discusses the thought process in choosing an | implementation, and steps through the process of implementing, | testing, and iterating on the solution. It's easy to find sources | for hard problems to solve, and it's easy to find sources for | explanations of algorithms and data structures, but I haven't | found much to bridge the gap. | charlieyu1 wrote: | Thank you. Never really figure out how parser works but maybe I | should try and write one | anigbrowl wrote: | Nicely written. Hits a spot that a lot of instructional and | documentary material misses - telling the reader up front what | common problem each technique solves. | samsquire wrote: | Wikipedia is sometimes enough to implement an algorithm. I | implemented multiversion concurrency control and btrees due to | Wikipedia which do not have pseudocode. If you can implement an | algorithm from an English description that is really useful. It | relies on the algorithm being well explained. | | I also suggest trying to implement a btree, they're really useful | data structures for data retrieval and many database products use | them extensively for indexes. | | With any algorithm, I think there is a critical insight (that | might be different for each person) where the algorithm clicks. | | I didn't understand mergesort properly until I worked on | implementing it but I understood quicksort. When the underlying | mental model of the problem being solved becomes clear, we can | reason about the algorithm and write code to implement what we | want to do. | todd8 wrote: | This is an interesting list. I'd add hashing and hash tables to | the list. There are lots of interesting variations of these as | well. | | I would put hash tables ahead of piece tables because of their | generality. For text editors I've always thought that gap buffers | were so straightforward that ropes and piece tables seemed like | extra unnecessary work. I see that VS Code uses piece tables so | perhaps someone can explain the performance differences to me. | | I've never sat down and benchmarked various implementations of | text buffers. However, it seems to me that memmove(3) on modern | processors would be so fast that moving a buffer gap on even a | 1GB file wouldn't be perceptible, and in return operations over | the whole buffer like string searching or parsing would be much | more efficient in a gap buffer because the program could count on | the buffer being in contiguous memory locations (after closing | the gap). In buffers implemented using a part table or ropes, | every character access involves extra layers of indirection. | Furthermore, gap buffers are going to be more cache friendly for | the way that most editing operations take place in a text editor. | | Perhaps someone that has actually done some comparisons could | enlighten me. | ComputerGuru wrote: | At this point, everyone knows about bloom filters simply because | it's _the_ thing to write about any time someone asks "lesser | known data structures /algorithms?" - it's hard to find a list | that _doesn't_ feature them! | | Still some good entries in that list. I would add skip lists, not | because they're structurally anything special but because they | open your mind to the possibly entirely-new-to-the-reader world | of probabilistic data structures. | | As someone that's been around the block, something I've learned | to be less surprised by is how often simply adding an element of | randomness to a difficult problem can sometimes give you results | comparable to a 10- or 100-times more complicated solution, with | the advantage of being more maintainable, making better use of | the i$, and avoiding a lot of work. (Eg compare a cache dropping | randomly selected items to LFU or LRU caches.) | Jensson wrote: | Randomness is very useful to handle edge cases, quicksort is | the poster child here, it has some edge cases were it runs | extremely slowly but randomising how you apply the algorithm | means that you can ignore them since they are so unlikely. If | you try to use deterministic code then those rare edge cases | often comes up in the real world. | zelphirkalt wrote: | I often find myself thinking: "But how would I do any of these | without reaching for mutation? So that I can later parallelize | them?" And then I come up empty. I still need to work through | "Purely Functional Data Structures" by Okasaki. Hopefully then I | will have an idea, of how to come up with a functional version to | mostly single-core algorithms or at least locking-requiring | algorithms. | | I skipped ahead once and looked at functional red-black trees and | the solution was to involve one more color! How ingenious is | that?! I have no idea, how people got to these solutions. | | In many places algorithms are described implicitly with mutation | assumed. However, this can be very hard to parallelize later and | might hit you in the future. In the times of many cores, this | seems no longer appropriate to me. I wish we spent more time on | learning how to get it done without mutation. | | Another great post that was linked at some point on HN was this: | https://nullprogram.com/blog/2020/04/30/ -- A comparatively | simple change in approach and yet it allows for massive | parallelization without locks! This is the kind of stuff we | should learn more about and then make more use of our many | available cores, moving on from the single core world of | algorithms. | samsquire wrote: | I am deeply interested in parallelism. Would enjoy talking to | you about it. I think the parallelism provided by Haskell is | really interesting. And its software transactional memory. | | I think parallelisation can be generalised but we need to study | it further to understand it better. There must be a set of | primitives that parallelise very well but they've not been | found out yet. Rather than trying to parallelise something that | was invented to be sequential, we parallelise something that is | parallelisable. | | I'm today working on parallelising a total ordered list view | over X sorted lists that are maintained by separate threads or | independent computer servers. My idea is to use N way mergesort | to retrieve up to N items sorted items. Servicing requests is | extremely fast since there is no synchronization needed within | a thread or server but if you need total order, you need to use | the N way mergesort. I'm thinking you have a cluster for | ordered views and a cluster for within-a-thread view. | | My other problem I'm working on is how to paralellise integer | updates. If you have 8000000000 accounts (which DOES fit on a | single machine) but you have more operations overall than a | single machine can process? You could shard by account but I | think there's an approach that shards by integer and per | server. You store a portion of the integer on each machine, | hopefully enough to handle operations. Then when you need to | check if account integer > deduction amount I just need to | learn how to a strongly consistent read cheaply. Essentially we | load balance the integer in the account. | | For example, fibonacci is often used as an example to | parallelise something but it's never implemented in a | parallelisable manner. It just does the same calculation in | different threads. If we knew the formula of fibonacci that | didn't depend on previous iterations, we could parallelise | fibonacci. But I'm not a mathematician so I I'm not sure how to | find out that formula. | Jensson wrote: | Sorting is a standard distributed algorithm, it is basically | quicksort but you move some data between servers between the | steps. The aim is to create ordered buckets of data that you | then send to servers. The first step is to find good pivots, | so sort the data and select data points at the median and | different percentiles from each server, then fan out those | pivot candidates to every server. Now since all servers has | the same pivots they can create the same buckets, and send | the data they have of each bucket to the corresponding | server. The buckets wont have perfect balance, so you | typically want a few times more buckets than you have servers | to ensure that you don't overload one of them, that is the | fast way. Alternatively you can do another fan out step | computing the number of items in each bucket among the | servers, and split large ones into smaller buckets using the | same technique as before, this will always work but requires | a bit more communication between the servers. | | As for distributing heavy computations, Google has done that | to compute your search results since almost the beginning. | Fan out a request to a huge cluster of servers that all have | different parts of their data in ram, then they filter and | return the parts of the data that is relevant to you. It is | totally possible to do it and return a response to the user | in a fraction of a second. Typically it takes a few | milliseconds to send data between servers in a data center, | you can easily fan out a request to a few hundred servers, | let them compute and return something 15 milliseconds later. | It costa bit to run that though, Google can afford it since | search is worth so much per request, so you got figure out if | your usecase is worth that much compute. But it doesn't cost | that much more than running the same work on a single server, | it depends a bit on how well your servers are collocated. | | Btw, frameworks for distributed computing like Apache Spark | are way too slow for this, you have to hand roll those. But | it really isn't that hard to do, there are programming | competitions involving those, a correct solution to these | kind of things takes like 30 minutes to write if you know | what to do and you have done all the boring infrastructure | work to ensure you can contact servers and access the data. | That is why most programmers never work with complex | algorithms, because the boring and tedious work on all the | other parts takes up most of the time and manpower. | convolvatron wrote: | assuming that local access is cheaper than distribution, | wouldn't a merge sort be better? | mrkeen wrote: | Often mutation on a single core is faster than persistence on | multiple cores. | samsquire wrote: | This is the problem I'm working on. | | Independent threads are as independently fast as a single | core and they slow down overall when synchronization is | introduced. Amdahls law means that the cost of sequential | tasks approaches the majority of time compared to the | parallel speed up. | | My design is to shard by thread. Each thread or machine | maintains a sorted shard of the data. Erlang takes a similar | approach. If you want a total order view, then you N way | mergesort. | | I have a lock free algorithm that can do about 1.2 million | synchronizations a second across 11 threads. My lock | benchmark does 3,444,152 3.4 million requests per second with | 11 threads so my lock free algorithm isn't as good as locks. | My lock free algorithm doesn't block though. | | I'm thinking of calibrating message rate by increasing | latency to increase throughput and limit interference for the | lock. If I increase message rate to 2500-25000 messages then | more messages get transferred per synchronization event. | Jensson wrote: | You can parallelize with mutations, just split the data and do | a tiny bit of extra work when the data overlaps. There aren't | that many real world cases where you want to run expensive | computations on highly connected data, and in those cases you | mostly just write GPU code instead of bothering with functional | CPU languages. That is what the engineers who works on the | extreme end of performance does, if you want to learn | functional programming that is efficient and performant then go | and learn GPU programming, the techniques you learn there also | works for many CPU applications, there are decades of top end | industry practices behind those techniques. | melony wrote: | I recommend https://web.stanford.edu/class/cs168/index.html | waynecochran wrote: | I would add KD-Tree and BSP-Tree as generalizations of a binary | search tree in higher dimensions. ___________________________________________________________________ (page generated 2022-12-24 23:00 UTC)