• Activity
  • Votes
  • Comments
  • New
  • All activity
  • Showing only topics in ~comp with the tag "programming". Back to normal view / Search all groups
    1. Typesetting Markdown Blog: What Next?

      Some of you have read the Typesetting Markdown blog series (https://dave.autonoma.ca/blog/). The plan was to finish the last two parts with Annotated Text (basically markup for Markdown) and...

      Some of you have read the Typesetting Markdown blog series (https://dave.autonoma.ca/blog/). The plan was to finish the last two parts with Annotated Text (basically markup for Markdown) and Figure Drawing (MetaPost); however, people have asked for a post on Markdown to EPUB, others have asked for high-quality PDF theme templates using ConTeXt, and some have requested rendering Markdown into HTML.

      Within the realm of Markdown, digital documentation, typesetting with ConTeXt, R, externalized interpolated strings, and bash scripting, what would interest you for the next post in the series?

      (Please flip through the blog series to see the topics that have been covered.)

      3 votes
    2. Which language would you pick to completely rewrite BSD, Linux, etc.?

      It'd my understanding that C has stuck around in the UNIX world for so long, nearly half a century, mostly due to the inertia of legacy code. If you could snap your fingers and magically port/fork...

      It'd my understanding that C has stuck around in the UNIX world for so long, nearly half a century, mostly due to the inertia of legacy code.

      If you could snap your fingers and magically port/fork the entire stack of open source codebases to the language of your choice, which would you pick and why?

      20 votes
    3. Topic Requests: What subject would you like to see covered in more depth?

      For those who haven't seen my essay-length posts in the past, I occasionally like to delve into explaining different programming concepts, particularly with regards to making your code easier to...

      For those who haven't seen my essay-length posts in the past, I occasionally like to delve into explaining different programming concepts, particularly with regards to making your code easier to manage. Sometimes this has to do with how you structure you code and projects, and at others it has to do with how you think about the problems you're solving. I've been in the mood to write up on yet another programming subject, but nothing in particular has stood out to me lately during the course of my work.

      With that in mind, I figured I would take a different approach and see if anyone here had some specific requests for content they would like to see. Requests from all levels of experience are welcome!

      (And for those who are itching to do a write-up on any of the requests that appear here, feel free to call dibs!)


      Edit

      For those who want to take a dive into my previous submissions, you can now find them in the new wiki entry created by @cfabbro or directly via the programming.code_quality_tips tag here.

      8 votes
    4. Code Quality Tip: The importance of understanding correctness vs. accuracy.

      Preface It's not uncommon for a written piece of code to be both brief and functionality correct, yet difficult to reason about. This is especially true of recursive algorithms, which can require...

      Preface

      It's not uncommon for a written piece of code to be both brief and functionality correct, yet difficult to reason about. This is especially true of recursive algorithms, which can require some amount of simulating the algorithm mentally (or on a whiteboard) on smaller problems to try to understand the underlying logic. The more you have to perform these manual simulations, the more difficult it becomes to track what exactly is going on at any stage of computation. It's also not uncommon that these algorithms can be made easier to reason about with relatively small changes, particularly in the way you conceptualize the solution to the problem. Our goal will be to take a brief tour into what these changes might look like and why they are effective at reducing our mental overhead.


      Background

      We will consider the case of the subset sum problem, which is essentially a special case of the knapsack problem where you have a finite number of each item and each item's value is equal to its weight. In short, the problem is summarized as one of the following:

      • Given a set of numbers, is there a subset whose sum is exactly equal to some target value?

      • Given a set of numbers, what is the subset whose sum is the closest to some target value without exceeding it?

      For example, given the set of numbers {1, 3, 3, 5} and a target value of 9, the answer for both of those questions is {1, 3, 5} because the sum of those numbers is 9. For a target value of 10, however, the first question has no solution because no combination of numbers in the set {1, 3, 3, 5} produces a total of 10, but the second question produces a solution of {1, 3, 5} because 9 is the closest value to 10 that those numbers can produce without going over.


      A Greedy Example

      We'll stick to the much simpler case of finding an exact match to our target value so we don't have to track what the highest value found so far is. To make things even simpler, we'll consider the case where all numbers are positive, non-zero integers. This problem can be solved with some naive recursion--simply try all combinations until either a solution is found or all combinations have been exhausted. While more efficient solutions exist, naive recursion is the easiest to conceptualize.

      An initial assessment of the problem seems simple enough. Our solution is defined as the set of array elements whose total is equal to our target value. To achieve this, we loop through each of the elements in the array, try combinations with all of the remaining elements, and keep track of what the current total is so we can compare it to our target. If we find an exact match, we return an array containing the matching elements, otherwise we return nothing. This gives us something like the following:

      function subsetSum($target_sum, $values, $total = 0) {
          // Base case: a total exceeding our target sum is a failure.
          if($total > $target_sum) {
              return null;
          }
      
          // Base case: a total matching our target sum means we've found a match.
          if($total == $target_sum) {
              return array();
          }
      
          foreach($values as $index=>$value) {
              // Recursive case: try combining the current array element with the remaining elements.
              $result = subsetSum($target_sum, array_slice($values, $index + 1), $total + $value);
      
              if(!is_null($result)) {
                  return array_merge(array($value), $result);
              }
          }
      
          return null;
      }
      

      Your Scope is Leaking

      This solution works. It's functionally correct and will produce a valid result every single time. From a purely functional perspective, nothing is wrong with it at all; however, it's not easy to follow what's going on despite how short the code is. If we look closely, we can tell that there are a few major problems:

      • It's not obvious at first glance whether or not the programmer is expected to provide the third argument. While a default value is provided, it's not clear if this value is only a default that should be overridden or if the value should be left untouched. This ambiguity means relying on documentation to explain the intention of the third argument, which may still be ignored by an inattentive developer.

      • The base case where a failure occurs, i.e. when the accumulated total exceeds the target sum, occurs one stack frame further into the recursion than when the total has been incremented. This forces us to consider not only the current iteration of recursion, but one additional iteration deeper in order to track the flow of execution. Ideally an iteration of recursion should be conceptually isolated from any other, limiting our mental scope to only the current iteration.

      • We're propagating an accumulating total that starts from 0 and increments toward our target value, forcing us to to track two different values simultaneously. Ideally we would only track one value if possible. If we can manage that, then the ambiguity of the third argument will be eliminated along with the argument itself.

      Overall, the amount of code that the programmer needs to look at and the amount of branching they need to follow manually is excessive. The function is only 22 lines long, including whitespace and comments, and yet the amount of effort it takes to ensure you're understanding the flow of execution correctly is pretty significant. This is a pretty good indicator that we probably did something wrong. Something so simple and short shouldn't take so much effort to understand.


      Patching the Leak

      Now that we've assessed the problems, we can see that our original solution isn't going to cut it. We have a couple of ways we could approach fixing our function: we can either attempt to translate the abstract problems into tangible solutions or we can modify the way we've conceptualized the solution. With that in mind, let's take a second crack at this problem by trying the latter.

      We've tried taking a look at this problem from a top-down perspective: "given a target value, are there any elements that produce a sum exactly equal to it?" Clearly this perspective failed us. Instead, let's try flipping the equation: "given an array element, can it be summed with others to produce the target value?"

      This fundamentally changes the way we can think about the problem. Previously we were hung up on the idea of keeping track of the current total sum of the elements we've encountered so far, but that approach is incompatible with the way we're thinking of this problem now. Rather than incrementing a total, we now find ourselves having to do something entirely different: if we want to know if a given array element is part of the solution, we need to first subtract the element from the problem and find out if the smaller problem has a solution. That is, to find if the element 3 is part of the solution for the target sum of 8, then we're really asking if 3 + solutionFor(5) is valid.

      The new solution therefore involves looping over our array elements just as before, but this time we check if there is a solution for the target sum minus the current array element:

      function subsetSum($target_sum, $values) {
          // Base case: the solution to the target sum of 0 is the empty set.
          if($target_sum === 0) {
              return array();
          }
      
          foreach($values as $index=>$value) {
              // Base case: any element larger than our target sum cannot be part of the solution.
              if($value > $target_sum) {
                  continue;
              }
      
              // Recursive case: do the remaining elements create a solution for the sub-problem?
              $result = subsetSum($target_sum - $value, array_slice($values, $index + 1));
      
              if(!is_null($result)) {
                  return array_merge(array($value), $result);
              }
          }
      
          return null;
      }
      

      A Brief Review

      With the changes now in place, let's compare our two functions and, more importantly, compare our new function to the problems we assessed with the original. A few brief points:

      • Both functions are the same exact length, being only 22 lines long with the same number of comments and an identical amount of whitespace.

      • Both functions touch the same number of elements and produce the same output given the same input. Apart from a change in execution order of a base case, functionality is nearly identical.

      • The new function no longer requires thinking about the scope of next iteration of recursion to determine whether or not an array element is included in the result set. The base case for exceeding the target sum now occurs prior to recursion, keeping the scope of the value comparison nearest where those values are defined.

      • The new function no longer uses a third accumulator argument, reducing the number of values to be tracked and removing the issue of ambiguity with whether or not to include the third argument in top-level calls.

      • The new function is now defined in terms of finding the solutions to increasingly smaller target sums, making it easier to determine functional correctness.

      Considering all of the above, we can confidently state that the second function is easier to follow, easier to verify functional correctness for, and less confusing for anyone who needs to use it. Although the two functions are nearly identical, the second version is clearly and objectively better than the original. This is because despite both being functionally correct, the first function does a poor job at accurately defining the problem it's solving while the second function is clear and accurate in its definition.

      Correct code isn't necessarily accurate code. Anyone can write code that works, but writing code that accurately defines a problem can mean the difference between understanding what you're looking at, and being completely bewildered at how, or even why, your code works in the first place.


      Final Thoughts

      Accurately defining a problem in code isn't easy. Sometimes you'll get it right, but more often than not you'll get it wrong on the first go, and it's only after you've had some distance from you original solution that you realize that you should've done things differently. Despite that, understanding the difference between functional correctness and accuracy gives you the opportunity to watch for obvious inaccuracies and keep them to a minimum.

      In the end, even functionally correct, inaccurate code is worth more than no code at all. No amount of theory is a replacement for practical experience. The only way to get better is to mess up, assess why you messed up, and make things just a little bit better the next time around. Theory just makes that a little easier.

      17 votes
    5. Challenge: defuse this fork bomb

      On lobste.rs I found link to an article from Vidar Holen, the author of shellcheck. He made a fork bomb that is really interesting. Here's the bomb: DO NOT RUN THIS. eval $(echo...

      On lobste.rs I found link to an article from Vidar Holen, the author of shellcheck. He made a fork bomb that is really interesting. Here's the bomb:

      DO NOT RUN THIS.

      eval $(echo "I<RA('1E<W3t`rYWdl&r()(Y29j&r{,3Rl7Ig}&r{,T31wo});r`26<F]F;==" | uudecode)
      

      This may look pretty obvious, but it's harder than you think. I fell for it. twice. Can you find out how this bomb works?

      Warning: executing the bomb will slow down your computer and will force you to restart.
      You can limit impact of the fork bomb by setting FUNCNEST.

      export FUNCNEST=3
      

      Have fun!

      12 votes
    6. What are the minimal features every good blog should have?

      I've been learning Laravel, and familiarizing myself with the framework by coding up a blogging website. Right now, it's minimally functional, and I'd like to add some more features to it. Since...

      I've been learning Laravel, and familiarizing myself with the framework by coding up a blogging website. Right now, it's minimally functional, and I'd like to add some more features to it. Since this is my first project with Laravel the code is a mess, and it's just about time for me to rewrite the whole thing. Before starting that, I'd like to have a better idea of what my final product should be. I don't want to recreate WordPress in Laravel, but I do want to have something I wouldn't spit at. Basically a project that would be good as a resume builder if I ever needed one.

      So far, my website allows users to...

      • register for an account, log in/out, update their email address and display name
      • create posts with a WISIWYG editor
      • upload files
      • create profiles
      • and manipulate everything through CRUD.

      What do you think the minimal features a blogging platform needs to have to be "complete" and usable as a stand-alone system?

      13 votes
    7. Programming Challenge: Text compression

      In an effort to make these weekly, I present a new programming challenge. The challenge this week is to compress some text using a prefix code. Prefix codes associate each letter with a given bit...

      In an effort to make these weekly, I present a new programming challenge.

      The challenge this week is to compress some text using a prefix code. Prefix codes associate each letter with a given bit string, such that no encoded bitstring is the prefix of any other. These bit strings are then concatenated into one long integer which is separated into bytes for ease of reading. These bytes can be represented as hex values as well. The provided prefix encoding is as follows:

      char value char value
      ' ' 11 'e' 101
      't' 1001 'o' 10001
      'n' 10000 'a' 011
      's' 0101 'i' 01001
      'r' 01000 'h' 0011
      'd' 00101 'l' 001001
      '~' 001000 'u' 00011
      'c' 000101 'f' 000100
      'm' 000011 'p' 0000101
      'g' 0000100 'w' 0000011
      'b' 0000010 'y' 0000001
      'v' 00000001 'j' 000000001
      'k' 0000000001 'x' 00000000001
      'q' 000000000001 'z' 000000000000

      Challenge

      Your program should accept a lowercase string (including the ~ character), and should output the formatted compressed bit string in binary and hex. Your final byte should be 0 padded so that it has 8 bits as required. For your convenience, here is the above table in a text file for easy read-in.

      Example

      Here is an example:

      $> tildes ~comp
      10010100 10010010 01011010 10111001 00000010 11000100 00110000 10100000
      94 92 5A B9 02 C4 30 A0
      

      Bonuses

      1. Print the data compression ratio for a given compression, assuming the original input was encoded in 8 bit ASCII (one byte per character).
        2. Output the ASCII string corresponding to the encoded byte string in addition to the above outputs.
      2. @onyxleopard points out that many bytes won't actually be valid ASCII. Instead, do as they suggested and treat each byte as an ordinal value and print it as if encoded as UTF-8.
      3. An input prefixed by 'D' should be interpreted as an already compressed string using this encoding, and should be decompressed (by inverting the above procedure).

      Previous Challenges (I am aware of prior existing ones, but it is hard to collect them as they were irregular. Thus I list last week's challenge as 'Week 1')
      Week 1

      13 votes
    8. Programming Challenge: Dice Roller

      Its been a while since we did one of these, which is a shame. Create a program that takes is an input of the type: "d6 + 3" or "2d20 - 5", and return a valid roll. The result should display both...

      Its been a while since we did one of these, which is a shame.

      Create a program that takes is an input of the type: "d6 + 3" or "2d20 - 5", and return a valid roll.
      The result should display both the actual rolls as well as the final result. The program should accept any valid roll of the type 'xdx'
      Bonuses:

      • Multiplication "d6 * 3"
      • Division "d12 / 6"
      • Polish notation "4d6 * (5d4 - 3)"

      As a side note, it would be really cool if weekly programming challenges became a thing

      33 votes
    9. How do you structure larger projects?

      I'll be writing a relatively large piece of scientific code for the first time, and before I begin I would at least like to outline how the project will be structured so that I don't run into...

      I'll be writing a relatively large piece of scientific code for the first time, and before I begin I would at least like to outline how the project will be structured so that I don't run into headaches later on. The problem is, I don't have much experience structuring large projects. Up until now most of the code I have written as been in the form of python scripts that I string together to form an ad-hoc pipeline for analysis, or else C++ programs that are relatively self contained. My current project is much larger in scope. It will consist of four main 'modules' (I'm not sure if this is the correct term, apologies if not) each of which consist of a handful of .cpp and .h files. The schematic I have in mind for how it should look is something like:

      src
       ├──Module1 (Initializer)
       │         ├ file1.cpp
       │         ├ file1.h
       │         │...
       │         └ Makefile
       ├───Module2 (solver)
       │          ├ file1.cpp
       │          ├ file1.h
       │          │...
       │          └ Makefile
       ├───Module3 (Distribute)
       │          ├ file1.cpp
       │          └Makefile 
       └ Makefile
      

      Basically, I build each self-contained 'module', and use the object files produced there to build my main program. Is there anything I should keep in mind here, or is this basically how such a project should be structured?

      I imagine the particularly structure will be dependent on my project, but I am more interested in general principles to keep in mind.

      14 votes
    10. Do you enjoy programming outside of work?

      I have found this to be a semi controversial topic. Its almost becoming a required point for getting a new job to have open source work that you can show. Some people just enjoy working on...

      I have found this to be a semi controversial topic. Its almost becoming a required point for getting a new job to have open source work that you can show. Some people just enjoy working on programming side projects and others don't want to do any more after they leave the office.

      Whats your opinion on this? Do you work on any side projects? Do you think its reasonable for interviewers to look for open source work when hiring?

      16 votes
    11. Coding Challenge - Design network communication protocol

      Previous challenges It's time for another coding challenge! This challenge isn't mine, it's this challenge (year 5, season 3, challenge 3) by ČVUT FIKS. The task is to design a network...

      Previous challenges

      It's time for another coding challenge!

      This challenge isn't mine, it's this challenge (year 5, season 3, challenge 3) by ČVUT FIKS.

      The task is to design a network communication protocol. You're sending large amount of bits over the network. The problem is that network is not perfect and the message sometimes arrives corrupted. Design a network protocol, that will guarantee that the decoded message will be exactly same as the message that was encoded.

      MESSAGE => (encoding) => message corrupted => (decoding) => MESSAGE
      

      Corruption

      Transmitting the message might corrupt it and introduce errors. Each error in a message (there might be more than one error in a single message) will flip all following bits of the message.

      Example:

      011101 => 011|010
      

      (| is place where an error occured).

      There might be more than one error in a message, but there are some rules:

      • Minimum distance between two errors in a single message is k

      • Number of bits between two errors is always odd number

      According to these rules, describe a communication protocol, that will encode a message, and later decode message with errors.

      Bonus

      • Guarantee your protocol will work always - even when errors are as common as possible

      • Try to make the protocol as short as possible.

      8 votes
    12. Conceptualizing Data: Simplifying the way we think about complex data structures.

      Preface Conceptual models in programming are essential for being able to reason about problems. We see this through code all the time, with implementation details hidden away behind abstractions...

      Preface

      Conceptual models in programming are essential for being able to reason about problems. We see this through code all the time, with implementation details hidden away behind abstractions like functions and objects so that we can ignore the cumbersome details and focus only on the details that matter. Without these abstractions and conceptual models, we might find ourselves overwhelmed by the size and complexity of the problem we’re facing. Of these conceptual models, one of the most easily neglected is that of data and object structure.


      Data Types Galore

      Possibly one of the most overwhelming aspects of conceptualizing data and object structure is the sheer breadth of data types available. Depending on the programming language you’re working with, you may find that you have more than several dozens of object classes already defined as part of the language’s core; primitives like booleans, ints, unsigned ints, floats, doubles, longs, strings, chars, and possibly others; arrays that can contain any of the objects or primitives, and even other arrays; and several other data structures like queues, vectors, and mixed-type collections, among others.

      With so many types of data, it’s incredibly easy to lose track in a sea of type declarations and find yourself confused and unsure of where to go.


      Tree’s Company

      Let’s start by trying to make these data types a little less overwhelming. Rather than thinking strictly of types, let’s classify them. We can group all data types into one of three basic classifications:

      1. Objects, which contain key/value pairs. For example, an object property that stores a string.
      2. Arrays, which contain some arbitrary number of values.
      3. Primitives, which contain nothing. They’re simply a “flat” data value.

      We can also make a couple of additional notes. First, arrays and objects are very similar; both contain references to internal data, but the way that data is referenced differs. In particular, objects have named keys while arrays have numeric, zero-indexed keys. In a sense, arrays are a special case of objects where the keys are more strictly typed. From this, we can condense the classifications of objects and arrays into the more general “container” classification.

      With that in mind, we now have the following classifications:

      1. Containers.
      2. Primitives.

      We can now generally state that containers may contain other containers and primitives, and primitives may not contain anything. In other words, all data structures are a composition of containers and/or primitives, where containers may accept containers and/or primitives and primitives may not accept anything. More experienced programmers should notice something very familiar about this description--we’re basically describing a tree structure! Primitive types and empty containers act as the leaves in a tree, whereas objects and arrays act as the nodes.


      Trees Help You Breathe

      Okay, great. So what’s the big deal, anyway? We’ve now traded a bunch of concrete data types that we can actually think about and abstracted them away into this nebulous mess of containers and primitives. What do we get out of this?

      A common mistake many programmers make is planning their data types out from the very beginning. Rather than planning out an abstraction for their data and object architecture, it’s easy to immediately find yourself focusing too much on the concrete implementation details.

      Imagine, for example, modeling a user account for an online payment system. A common feature to include is the ability to store payment information for auto-pay, and payment methods typically take the form of some combination of credit/debit cards and bank accounts. If we focus on implementation details from the beginning, then we may find ourselves with something like this in a first iteration:

      UserAccount: {
          username: String,
          password: String,
          payment_methods: PaymentMethod[]
      }
      
      PaymentMethod: {
          account_name: String,
          account_type: Enum,
          account_holder: String,
          number: String,
          routing_number: String?,
          cvv: String?,
          expiration_date: DateString?
      }
      

      We then find ourselves realizing that PaymentMethod is an unnecessary mess of optional values and needing to refactor it. Odds are we would break it off immediately into separate account types and make a note that they both implement some interface. We may also find that, as a result, remodeling the PaymentMethod could result in the need to remodel the UserAccount. For more deeply nested data structures, a single change deeper within the structure could result in those changes cascading all the way to the top-level object. If we have multiple objects, then these changes could propagate to them as well. And what if we decide a type needs to be changed, like deciding that our expiration date needs to be some sort of date object? Or what if we decide that we want to modify our property names? We’re then stuck having to update these definitions as we go along. What if we decide that we don't want an interface for different payment method types after all and instead want separate collections for each type? Then including the interface consideration will have proven to be a waste of time. The end result is that before we’ve even touched a single line of code, we’ve already found ourselves stuck with a bunch of technical debt, and we’re only in our initial planning stages!

      To alleviate these kinds of problems, it’s far better to just ignore the implementation details. By doing so, we may find ourselves with something like this:

      UserAccount: {
          Username,
          Password,
          PaymentMethods
      }
      
      PaymentMethods: // TODO: Decide on this container’s structure.
      
      CardAccount: {
          AccountName,
          CardHolder,
          CardNumber,
          CVV,
          ExpirationDate,
          CardType
      }
      
      BankAccount: {
          AccountName,
          AccountNumber,
          RoutingNumber,
          AccountType
      }
      

      A few important notes about what we’ve just done here:

      1. We don’t specify any concrete data types.
      2. All fields within our models have the capacity to be either containers or primitives.
      3. We’re able to defer a model’s structural definition without affecting the pace of our planning.
      4. Any changes to a particular field type will automatically propagate in our structural definitions, making it trivial to create a definition like ExpirationDate: String and later change it to ExpirationDate: DateObject.
      5. The amount of information we need to think about is reduced down to the very bare minimum.
      6. By deferring the definition of the PaymentMethods structure, we find ourselves more inclined to focus on the more concrete payment method definitions from the very beginning, rather than trying to force them to be compatible through an interface.
      7. We focused only on data representation, ensuring that representation and implementation are both separate and can be handled differently if needed.

      SOLIDifying Our Conceptual Model

      In object-oriented programming (OOP), there’s a generally recommended set of principles to follow, represented by the acronym “SOLID”:

      • Single responsibility.
      • Open/closed.
      • Liskov substitution.
      • Interface segregation.
      • Dependency inversion.

      These “SOLID” principles were defined to help resolve common, recurring design problems and anti-patterns in OOP.

      Of particular note for us is the last one, the “dependency inversion” principle. The idea behind this principle is that implementation details should depend on abstractions, not the other way around. Our new conceptual model obeys the dependency inversion principle by prioritizing a focus on abstractions while leaving implementation details to the future class definitions that are based on our abstractions. By doing so, we limit the elements involved in our planning and problem-solving stages to only what is necessary.


      Final Thoughts

      The consequences of such a conceptual model extend well beyond simply planning out data and object structures. For example, if implemented as an actual programming or language construct, you could make the parsing of your data fairly simple. By implementing an object parser that performs reflection on some passed object, you can extract all of the publicly accessible object properties of the target object and the data contained therein. Thus, if your language doesn’t have a built-in JSON encoding function and no library yet exists, you could recursively traverse your data structure to generate the appropriate JSON with very little effort.

      Many of the most fundamental programming concepts, like data structures ultimately being nothing more than trees at their most abstract representation, are things we tend to take for granted and think very little about. By making ourselves conscious of these fundamental concepts, however, we can more effectively take advantage of them.

      Additionally, successful programmers typically solve a programming problem before they’ve ever written a single line of code. Whether or not they’re conscious of it, the tools they use to solve these problems effectively consist largely of the myriad conceptual models they’ve collected and developed over time, and the experience they’ve accumulated to determine which conceptual models need to be utilized to solve a particular problem.

      Even when you have a solid grasp of your programming fundamentals, you should always revisit them every now and then. Sometimes there are details that you may have missed or just couldn’t fully appreciate when you learned about them. This is something that I’m continually reminded of as I continue on in my own career growth, and I hope that I can continue passing these lessons on to others.

      As always, I'm absolutely open to feedback and questions!

      15 votes
    13. Programming Challenge - Find path from city A to city B with least traffic controls inbetween.

      Previous challenges Hi, it's been very long time from last Programming Challenge, and I'd like to revive the tradition. The point of programming challenge is to create your own solution, and if...

      Previous challenges

      Hi, it's been very long time from last Programming Challenge, and I'd like to revive the tradition.

      The point of programming challenge is to create your own solution, and if you're bored, even program it in your favourite programming language. Today's challenge isn't mine. It was created by ČVUT FIKS (year 5, season 2, challenge #4).

      You need to transport plans for your quantum computer through Totalitatia. The problem is, that Totalitatia's government would love to have the plans. And they know you're going to transport the computer through the country. You'll receive number N, which denotes number of cities on the map. Then, you'll get M paths, each going from one city to another. Each path has k traffic controls. They're not that much effective, but the less of them you have to pass, the better. Find path from city A to city B, so the maximum number of traffic controls between any two cities is minimal. City A is always the first one (0) and city B is always the last one (N-1).

      Input format:

      N
      M
      A1 B1 K1
      A2 B2 K2
      ...
      

      On the first two lines, you'll get numbers N (number of cities) and M (number of paths). Than, on next M lines, you'll get definition of a path. The definition looks like 1 2 6, where 1 is id of first city and 2 is id of second city (delimited by a space). You can go from city 1 to city 2, or from city 2 to city 1. The third number (6) is number of traffic controls.

      Output format:

      Single number, which denotes maximum number of traffic controls encountered on one path.

      Hint: This means, that path that goes via roads with numbers of traffic controls 4 4 4 is better than path via roads with numbers of traffic controls 1 5 1. First example would have output 4, the second one would have output 5.

      Example:

      IN:

      4
      5
      0 1 3
      0 2 2
      1 2 1
      1 3 4
      2 3 5
      

      OUT:

      4
      

      Solution: The optimal path is either 0 2 1 3 or 0 1 3.

      Bonus

      • Describe time complexity of your algorithm.
      • If multiple optimal paths exist, find the shortest one.
      • Does your algorithm work without changing the core logic, if the source city and the target city is not known beforehand (it changes on each input)?
      • Do you use special collection to speed up minimum value search?

      Hints

      Special collection to speed up algorithm

      13 votes