I am about a quarter of the video in, and so far, it's like hearing my thoughts exactly about Ruby. I am going to consider recommending this UA-cam video (Ruby is Great) to my software engineering team after I finish watching it. Thank you!
You've made incidental remarks in past videos about database migrations and about how relational databases won't scale. A video on those topics would be interesting. And something about application architecture and software design in general would be good, too!
Hey Steve, great video. Would love to hear your views on APL / J / K family of languages (array programming), They are famed for their succinctness especially in quant/trading companies
I posted this comment on topic suggestions in an earlier video, but since you requested it, I don't mind posting it again: 1. Do you actively think of design patterns before you code things? I never seem to do a good job recognizing them myself until after I’ve coded something. 2. How do you determine how high-level a web API should be? I’d love to hear you talk about API design. 3. How important has it been for you, historically, to truly enjoy the application or domain that you’re coding in? 4. Who are your favorite tech writers on the internet? Both in the past, and today. 5. Who are your favorite musicians? What are your favorite albums and songs? I know this is Tech Talk, but since you’re someone who clearly has played an instrument a lot, I think it might be nice to break it up once in a while and talk about something non-technical. 6. What code are you still proud of today? For me, even after a lot of years it feels like a pretty small set of stuff. 7. Has doing this show changed your writing so far? Is it weird to be presenting stuff out loud like this a lot more now instead of writing? And are you ever planning on turning anything from the show into written content? I guess that was three sorta-related questions. :) EDIT: Also, hopefully you will have looked at en.wikipedia.org/wiki/List_of_language_reforms_of_English before coming up with your own effort. :) And Lojban is my personal favorite "let's make a better language" effort: mw.lojban.org/papri/Lojban EDIT 2: Shopify is investing in Ruby too, look at their engineering blog for "Shopify Invests in Research for Ruby at Scale".
I'd love to hear your insights on Elixir/Erlang language(s). Especially how do you see it's concurrency model. Great talks btw, I absolutely love them!
My suggestion for a topic is "Desktop Development in Ruby" (or "Desktop Development" in general) with and without the fully free and open-source Glimmer framework (won a Fukuoka Ruby 2022 Special Award by Matz himself, the creator of Ruby), which embodies all the Ruby benefits you spoke of in this video, even more than Rails dare I say. That lowers the barrier of entry to Ruby significantly by providing new software engineers with the ability to display GUI with minimal code, thus greatly encouraging them to learn Ruby.
Hey Steve. I’d really like your thoughts on how to properly document architecture/projects. What kind of artifacts do you create? How do you document complex interactions between systems? How do you organize them, and ensure they stay current over time?
I've been toying around with Clojure since watching your Lisp video and was hoping to have a solution written in it, but alas, I don't have enough of a command over its concepts. I'm looking forward to checking out your Elisp solution. In the meantime, here's one I put together in C. #include #include /* * Solves farmer, dog, chicken, grain riddle. * * State represented by single byte. * 4 bits for left bank, 4 bits for right bank. */ static uint8_t visited_states[256] = {0}; static uint8_t moves[256] = {0}; static uint8_t num_moves = 0; void print_moves(void) { int i; for (i = 0; i < num_moves; ++i) { switch (moves[i]) { case 0b10001000: printf("carry nothing "); break; case 0b01000100: printf("carry dog "); break; case 0b00100010: printf("carry chicken "); break; case 0b00010001: printf("carry grain "); break; default: printf("unkown carry "); break; } } } void solve(uint8_t state) { int i; uint8_t saved_state; uint8_t swap_mask = 0b10001000; uint8_t check_mask = (state & 0b10000000) ? 0b10000000 : 0b00001000; if (state == 0x0f) { print_moves(); return; } else if (visited_states[state]) { return; } visited_states[state] = 1; state ^= swap_mask; /* Always swap farmer. */ saved_state = state; moves[num_moves++] = swap_mask; solve(state); num_moves--; for (i = 0; i < 3; ++i) { swap_mask >>= 1; check_mask >>= 1; if (state & check_mask) { moves[num_moves++] = swap_mask; state = saved_state ^ swap_mask; solve(state); num_moves--; } } } int main(int argc, char** argv) { /* Set failure states as visited to quickly skip over. */ visited_states[0b01111000] = 1; visited_states[0b10000111] = 1; visited_states[0b01101001] = 1; visited_states[0b10010110] = 1; visited_states[0b00111100] = 1; visited_states[0b11000011] = 1; solve(0xf0); return 0; }
Great episode, Steve! I love listening to a long winding story that goes into rich historic details. Take us on a journey! Questions for potential episodes: -What do you think has yet to be solved by a programming language? What is still missing in terms of expressivity? Is something like Idris, with dependent types and interactive "proof solving", the future? - Will AI driven "coding assistants" like github's co-pilot be the future of programming environments? Are we about to lose our jobs?
I just watched episode 27 and it sounds so much better in comparison. You're clearly talking into the back of the microphone here. Condenser microphones have a cardioid pattern which, by design, rejects sound from the back. Unfortunately, the front and back look almost identical. Simply rotating the microphone 180 degrees should improve the audio quality by a landslide.
I want to hear your thoughts about remote work! Maybe From a management perspective? Ugh I hate it, but a lot of people like it. I love the leadership talks! Also, studies show that things tend to be rough for humans after they retire. Take care of yourself!
Clojure folks really like "babashka" for scripting, maybe its worth a try for your use case. Topic suggestion, If you have time, I would like to hear your thought on design phase of software development. Thank you.
Right at the end of the video you answered one of the questions that came to mind: why not a Lisp? It seems like a Lisp dialect with a few convenient builtins for shell tasks would rival Ruby for elegance and readable but succinctness. As it happens, I start a new job at a Ruby shop in a week, after 17 years on mostly Java.
My Emacs-Lisp solution wound up being a bit verbose. Lisp really requires you to build a DSL for each problem, so it's a better fit for larger problems than this one. The farmer problem is a one-off test of how naturally succinct each language is. That said, one could probably write a shorter one in CL or maybe Scheme.
Here is an episode I would like to hear: "How to get your first tech job." As I am preparing for my entry into the field, I alternate between hearing things like "software engineering jobs are everywhere" and "I graduated with a degree and I have been looking for a job for a year with no luck, it's too competitive." I am interested to know what you think about the people who can't find work and how we can avoid that situation. You could also talk about tips for preparing for the search, how to make yourself stand out, where to look, how to look etc.
The puzzle in the description is a classic, but it still might be worth clearly specifying the losing states of the system above. As written there's nothing about the problem that constrains the set of valid moves, so it relies on people making pretty big assumptions or having heard this before. (For those who are confused: the reason it's not trivial is that the dog is supposed to eat the chicken, or the chicken eat the grain, if they are left alone on the opposite bank.)
@@SteveYegge Hah, sorry about that. Writing up a solution to the problem actually distracted me from the video, and I forgot to go back to see if it was mentioned there.
Like another commenter, I posted a link (pastebin in my case) but the comment seems to have been deleted. So, apologies for length, here's some python. Let's see what youtube does to my indentation... I like the way it turned out - in general I see a lot of classes that don't need to exist but in this case I think it actually makes sense. Opinions welcome. It likely won't run if your python's too old, due to the use of some modern typing features. It works on my install (3.9.7) but not 3.8.10. from collections import deque from dataclasses import dataclass from typing import Optional @dataclass class Position: farmer_on_left: bool left_bank: set right_bank: set history: list[str] @staticmethod def initial(): return Position(False, set(), {"dog", "chicken", "grain"}, []) def wins(self): return self.farmer_on_left and len(self.left_bank) == 3 def loses(self): b = self.bank_without_farmer() return ("dog" in b and "chicken" in b) or ("chicken" in b and "grain" in b) def bank_without_farmer(self): return self.right_bank if self.farmer_on_left else self.left_bank def bank_with_farmer(self): return self.left_bank if self.farmer_on_left else self.right_bank def moving(self, item: Optional[str]): new_position = Position( # farmer always switches places not self.farmer_on_left, self.left_bank.copy(), self.right_bank.copy(), self.history.copy(), ) if item: source = new_position.bank_without_farmer() destination = new_position.bank_with_farmer() assert item in source source -= {item} destination |= {item} # append unconditionally for the sake of the None case (when the farmer # takes a trip without an item) new_position.history.append(item) return new_position def next_positions(self): source = self.bank_with_farmer() return [self.moving(item) for item in (source | {None})] def solve() -> [Optional[str]]: positions = deque([Position.initial()]) while positions: pos = positions.popleft() if pos.wins(): return pos.history for p in pos.next_positions(): if not p.loses(): positions.append(p) for item in solve(): if item: print(f"carry {item}") else: print("carry nothing")
I made a Stevey's Tech Talks discord, but my comment with the invite link gets deleted, even when I try to creatively post it 😄 I can make you an admin-I'm lorendsr on Twitter. Also, re: getting a job, if you might consider a series B startup, Temporal is a durable code execution framework. It allows you to code at a new, higher level of abstraction, where you don't have to worry about your process dying or many distributed systems concerns like retries, timeouts, timers, task queues, transactional outboxes, scaling, fault tolerance, or even persisting state. There's a great YT video on its internal architecture if you're interested: "Designing a Workflow Engine from First Principles"
Great episode! I’d love it if you did a future episode on how to employ interface design and abstraction to combat complexity in gnarly codebases. I bet you’d have lots of great stories given your impressive breadth of experience! Btw a book I recommend on the topic is “A Philosophy of Software Design” by John Ousterhout, who’s known as the author of Tcl and the Raft consensus algorithm. It’s super short and opinionated and I found it highly illuminating. Have you read it?
I think I'm the first to post a horrible Java version like the one you talked about, suitable for pasting into someplace like Replit. It takes more steps than it should because I don't sort the current state before dupe checking, but not too many extra steps. The empty hand case isn't in a separate function because you kinda talked trash about that Steve, haha. I'm probably doing stupid things in here so I welcome feedback! import java.util.HashSet; import java.util.Set; class Main { public static Set previousStates = new HashSet(); public static void main(String[] args) { String solution = currentRiverStep("CDFG|"); for (int i = 0; i < solution.length(); i++) { switch (solution.charAt(i)) { case 'C' -> System.out.println("Carry chicken"); case 'D' -> System.out.println("Carry dog"); case 'G' -> System.out.println("Carry grain"); case ' ' -> System.out.println("Carry nothing"); } } } private static String currentRiverStep(String currentState) { String[] riversides = currentState.split("\\|", -1); if (riversides[0].length() == 0) return ""; for (String riverside : riversides) { if (!riverside.contains("F") && riverside.contains("C") && (riverside.contains("D") || riverside.contains("G")) ) return null; } int farmerRiversideIndex; if (riversides[0].contains("F")) { farmerRiversideIndex = 0; riversides[0] = riversides[0].replace("F", ""); riversides[1] += "F"; } else { farmerRiversideIndex = 1; riversides[0] += "F"; riversides[1] = riversides[1].replace("F", ""); } String[] farmerRiversideThings = riversides[farmerRiversideIndex].split(""); for (String riversideThing : farmerRiversideThings) { String newRiverState; if (farmerRiversideIndex == 0) { newRiverState = riversides[0].replace(riversideThing, "") + "|" + riversides[1] + riversideThing; } else // farmerRiversideIndex == 1 { newRiverState = riversides[0] + riversideThing + "|" + riversides[1].replace(riversideThing, ""); } if (!previousStates.contains(newRiverState)) { previousStates.add(newRiverState); String solutionString = currentRiverStep(newRiverState); if (solutionString != null) { return riversideThing + solutionString; } } } // Handle empty hand case String newRiverState = riversides[0] + "|" + riversides[1]; if (!previousStates.contains(newRiverState)) { previousStates.add(newRiverState); String solutionString = currentRiverStep(newRiverState); if (solutionString != null) { return " " + solutionString; } } return null; } }
Oh if only you could see the reams of code that had to die so that these few lines could live... 😆. Things I learned along the way: * If you just look back one move so that you don't end up repeating it (for instance, shifting the chicken back and forwards infinitely) then you can still end up in an infinite loop of six moves that repeat forever - bzzzt! * If you just look at half the previous moves, say the ones with the animals that are left behind, with no absolute reference to the left or right bank, then it won't work either because the chicken gets left on its own on the right, and then later on the left - bzzzt! * Python using pipe instead of plus for set addition is pretty annoying, syntactically. * Having to cast back to a tuple when using reversed is pretty annoying. As is the fact that it's a global func and not on the collection itself (like you mention with dir). And also the tense. * Having to use a frozenset because otherwise it barfs with normal (lexically supported!) sets in dict keys is annoying (at least when mixing with normal sets the result is a frozenset, saving some visual noise). * It actually has a bug where it can move the farmer first. Oops. It's a dead-end though so it just backtracks and tries another way. It properly should reject that move when it tries it, though. * Unsure whether I went too Perl Golf on it; in reality I'd probably make a name for the 0 index of banks, and probably make it more explicit that the this_bank and that_bank are alternating places between stack levels, by passing variables with names explicitly. I'd also add some doc that explains the datatype of log, and also explains why it has to offset the printing by one position (start has no move in the value). Then there's the aspect that the farmer in the data is a way of easily representing an empty trip without hacking in an extra empty state in the code, which then has be be mapped when it comes to display. And, I dunno, is it obvious from the code that we are switching the banks each recursive call, but then undoing that switch only on the return trip in order that the log has absolute positions (left in 0, right in 1). I am not sure that "if len(log) % 2 == 0" really captures this, and I feel that if I came back to this code in a year or whatever that I'd be pretty annoyed that this higher-level aspect wasn't represented more explicitly. There's a limit after which you're veering into IOCC territory. But perhaps there's a nice clean way to make this obvious without extra doc in Ruby 😉. In Python I think I'd basically put this entire bullet point in the doc block.
I am about a quarter of the video in, and so far, it's like hearing my thoughts exactly about Ruby. I am going to consider recommending this UA-cam video (Ruby is Great) to my software engineering team after I finish watching it. Thank you!
I'd love to here your thoughts on the two holy debates: OOP vs Functional programming and Strongly vs. weakly typed languages
He did that like a decade ago.
You've made incidental remarks in past videos about database migrations and about how relational databases won't scale. A video on those topics would be interesting. And something about application architecture and software design in general would be good, too!
Peter Seibel who wrote the "Practical Common LIsp" book you recommended ended up at a senior role at a Ruby shop
Hey Steve, great video. Would love to hear your views on APL / J / K family of languages (array programming), They are famed for their succinctness especially in quant/trading companies
I posted this comment on topic suggestions in an earlier video, but since you requested it, I don't mind posting it again:
1. Do you actively think of design patterns before you code things? I never seem to do a good job recognizing them myself until after I’ve coded something.
2. How do you determine how high-level a web API should be? I’d love to hear you talk about API design.
3. How important has it been for you, historically, to truly enjoy the application or domain that you’re coding in?
4. Who are your favorite tech writers on the internet? Both in the past, and today.
5. Who are your favorite musicians? What are your favorite albums and songs? I know this is Tech Talk, but since you’re someone who clearly has played an instrument a lot, I think it might be nice to break it up once in a while and talk about something non-technical.
6. What code are you still proud of today? For me, even after a lot of years it feels like a pretty small set of stuff.
7. Has doing this show changed your writing so far? Is it weird to be presenting stuff out loud like this a lot more now instead of writing? And are you ever planning on turning anything from the show into written content? I guess that was three sorta-related questions. :)
EDIT: Also, hopefully you will have looked at en.wikipedia.org/wiki/List_of_language_reforms_of_English before coming up with your own effort. :) And Lojban is my personal favorite "let's make a better language" effort: mw.lojban.org/papri/Lojban
EDIT 2: Shopify is investing in Ruby too, look at their engineering blog for "Shopify Invests in Research for Ruby at Scale".
I'd love to hear your insights on Elixir/Erlang language(s). Especially how do you see it's concurrency model. Great talks btw, I absolutely love them!
Hey Steve, I'm really enjoying the content of this channel. An episode about databases would be awesome.
I really enjoyed your Incompleteness Theorem.
My suggestion for a topic is "Desktop Development in Ruby" (or "Desktop Development" in general) with and without the fully free and open-source Glimmer framework (won a Fukuoka Ruby 2022 Special Award by Matz himself, the creator of Ruby), which embodies all the Ruby benefits you spoke of in this video, even more than Rails dare I say. That lowers the barrier of entry to Ruby significantly by providing new software engineers with the ability to display GUI with minimal code, thus greatly encouraging them to learn Ruby.
Hey Steve. I’d really like your thoughts on how to properly document architecture/projects. What kind of artifacts do you create? How do you document complex interactions between systems? How do you organize them, and ensure they stay current over time?
I've been toying around with Clojure since watching your Lisp video and was hoping to have a solution written in it, but alas, I don't have enough of a command over its concepts. I'm looking forward to checking out your Elisp solution. In the meantime, here's one I put together in C.
#include
#include
/*
* Solves farmer, dog, chicken, grain riddle.
*
* State represented by single byte.
* 4 bits for left bank, 4 bits for right bank.
*/
static uint8_t visited_states[256] = {0};
static uint8_t moves[256] = {0};
static uint8_t num_moves = 0;
void
print_moves(void)
{
int i;
for (i = 0; i < num_moves; ++i) {
switch (moves[i]) {
case 0b10001000:
printf("carry nothing
");
break;
case 0b01000100:
printf("carry dog
");
break;
case 0b00100010:
printf("carry chicken
");
break;
case 0b00010001:
printf("carry grain
");
break;
default:
printf("unkown carry
");
break;
}
}
}
void
solve(uint8_t state) {
int i;
uint8_t saved_state;
uint8_t swap_mask = 0b10001000;
uint8_t check_mask = (state & 0b10000000) ? 0b10000000 : 0b00001000;
if (state == 0x0f) {
print_moves();
return;
} else if (visited_states[state]) {
return;
}
visited_states[state] = 1;
state ^= swap_mask; /* Always swap farmer. */
saved_state = state;
moves[num_moves++] = swap_mask;
solve(state);
num_moves--;
for (i = 0; i < 3; ++i) {
swap_mask >>= 1;
check_mask >>= 1;
if (state & check_mask) {
moves[num_moves++] = swap_mask;
state = saved_state ^ swap_mask;
solve(state);
num_moves--;
}
}
}
int
main(int argc, char** argv)
{
/* Set failure states as visited to quickly skip over. */
visited_states[0b01111000] = 1;
visited_states[0b10000111] = 1;
visited_states[0b01101001] = 1;
visited_states[0b10010110] = 1;
visited_states[0b00111100] = 1;
visited_states[0b11000011] = 1;
solve(0xf0);
return 0;
}
Great episode, Steve! I love listening to a long winding story that goes into rich historic details. Take us on a journey!
Questions for potential episodes:
-What do you think has yet to be solved by a programming language? What is still missing in terms of expressivity? Is something like Idris, with dependent types and interactive "proof solving", the future?
- Will AI driven "coding assistants" like github's co-pilot be the future of programming environments? Are we about to lose our jobs?
I just watched episode 27 and it sounds so much better in comparison. You're clearly talking into the back of the microphone here. Condenser microphones have a cardioid pattern which, by design, rejects sound from the back. Unfortunately, the front and back look almost identical. Simply rotating the microphone 180 degrees should improve the audio quality by a landslide.
Wow, thank you so much for the diagnosis. Will try it out!
I want to hear your thoughts about remote work! Maybe From a management perspective? Ugh I hate it, but a lot of people like it. I love the leadership talks! Also, studies show that things tend to be rough for humans after they retire. Take care of yourself!
Clojure folks really like "babashka" for scripting, maybe its worth a try for your use case. Topic suggestion, If you have time, I would like to hear your thought on design phase of software development. Thank you.
Right at the end of the video you answered one of the questions that came to mind: why not a Lisp? It seems like a Lisp dialect with a few convenient builtins for shell tasks would rival Ruby for elegance and readable but succinctness.
As it happens, I start a new job at a Ruby shop in a week, after 17 years on mostly Java.
My Emacs-Lisp solution wound up being a bit verbose. Lisp really requires you to build a DSL for each problem, so it's a better fit for larger problems than this one. The farmer problem is a one-off test of how naturally succinct each language is. That said, one could probably write a shorter one in CL or maybe Scheme.
Here is an episode I would like to hear: "How to get your first tech job." As I am preparing for my entry into the field, I alternate between hearing things like "software engineering jobs are everywhere" and "I graduated with a degree and I have been looking for a job for a year with no luck, it's too competitive." I am interested to know what you think about the people who can't find work and how we can avoid that situation. You could also talk about tips for preparing for the search, how to make yourself stand out, where to look, how to look etc.
The puzzle in the description is a classic, but it still might be worth clearly specifying the losing states of the system above. As written there's nothing about the problem that constrains the set of valid moves, so it relies on people making pretty big assumptions or having heard this before. (For those who are confused: the reason it's not trivial is that the dog is supposed to eat the chicken, or the chicken eat the grain, if they are left alone on the opposite bank.)
Ah, I explained it thoroughly in the video but should also do it in the description. Thanks.
@@SteveYegge Hah, sorry about that. Writing up a solution to the problem actually distracted me from the video, and I forgot to go back to see if it was mentioned there.
Any chance we'll get a Grab PT.3? 😂
Like another commenter, I posted a link (pastebin in my case) but the comment seems to have been deleted. So, apologies for length, here's some python. Let's see what youtube does to my indentation...
I like the way it turned out - in general I see a lot of classes that don't need to exist but in this case I think it actually makes sense. Opinions welcome.
It likely won't run if your python's too old, due to the use of some modern typing features. It works on my install (3.9.7) but not 3.8.10.
from collections import deque
from dataclasses import dataclass
from typing import Optional
@dataclass
class Position:
farmer_on_left: bool
left_bank: set
right_bank: set
history: list[str]
@staticmethod
def initial():
return Position(False, set(), {"dog", "chicken", "grain"}, [])
def wins(self):
return self.farmer_on_left and len(self.left_bank) == 3
def loses(self):
b = self.bank_without_farmer()
return ("dog" in b and "chicken" in b) or ("chicken" in b and "grain" in b)
def bank_without_farmer(self):
return self.right_bank if self.farmer_on_left else self.left_bank
def bank_with_farmer(self):
return self.left_bank if self.farmer_on_left else self.right_bank
def moving(self, item: Optional[str]):
new_position = Position(
# farmer always switches places
not self.farmer_on_left,
self.left_bank.copy(),
self.right_bank.copy(),
self.history.copy(),
)
if item:
source = new_position.bank_without_farmer()
destination = new_position.bank_with_farmer()
assert item in source
source -= {item}
destination |= {item}
# append unconditionally for the sake of the None case (when the farmer
# takes a trip without an item)
new_position.history.append(item)
return new_position
def next_positions(self):
source = self.bank_with_farmer()
return [self.moving(item) for item in (source | {None})]
def solve() -> [Optional[str]]:
positions = deque([Position.initial()])
while positions:
pos = positions.popleft()
if pos.wins():
return pos.history
for p in pos.next_positions():
if not p.loses():
positions.append(p)
for item in solve():
if item:
print(f"carry {item}")
else:
print("carry nothing")
I want to see an example answer to the problem Steve describes. Can anyone point me to a solution to the fox, chicken, grain problem? Good video!
I made a Stevey's Tech Talks discord, but my comment with the invite link gets deleted, even when I try to creatively post it 😄 I can make you an admin-I'm lorendsr on Twitter.
Also, re: getting a job, if you might consider a series B startup, Temporal is a durable code execution framework. It allows you to code at a new, higher level of abstraction, where you don't have to worry about your process dying or many distributed systems concerns like retries, timeouts, timers, task queues, transactional outboxes, scaling, fault tolerance, or even persisting state. There's a great YT video on its internal architecture if you're interested: "Designing a Workflow Engine from First Principles"
And we're working on a Ruby runtime! As well as Python and .NET ones. (We already have Go, Java/Kotlin, PHP, and TypeScript.)
Some topic ideas: Manual vs Automated testing, Test frameworks vs custom test code, Team size (why big teams, why small)...
Great episode!
I’d love it if you did a future episode on how to employ interface design and abstraction to combat complexity in gnarly codebases. I bet you’d have lots of great stories given your impressive breadth of experience!
Btw a book I recommend on the topic is “A Philosophy of Software Design” by John Ousterhout, who’s known as the author of Tcl and the Raft consensus algorithm. It’s super short and opinionated and I found it highly illuminating. Have you read it?
For the code submissions, might I suggest we post the solutions by submitting Pull Requests to a public github repo?
I think I'm the first to post a horrible Java version like the one you talked about, suitable for pasting into someplace like Replit. It takes more steps than it should because I don't sort the current state before dupe checking, but not too many extra steps. The empty hand case isn't in a separate function because you kinda talked trash about that Steve, haha. I'm probably doing stupid things in here so I welcome feedback!
import java.util.HashSet;
import java.util.Set;
class Main {
public static Set previousStates = new HashSet();
public static void main(String[] args) {
String solution = currentRiverStep("CDFG|");
for (int i = 0; i < solution.length(); i++)
{
switch (solution.charAt(i))
{
case 'C' -> System.out.println("Carry chicken");
case 'D' -> System.out.println("Carry dog");
case 'G' -> System.out.println("Carry grain");
case ' ' -> System.out.println("Carry nothing");
}
}
}
private static String currentRiverStep(String currentState)
{
String[] riversides = currentState.split("\\|", -1);
if (riversides[0].length() == 0)
return "";
for (String riverside : riversides)
{
if (!riverside.contains("F")
&& riverside.contains("C")
&& (riverside.contains("D") || riverside.contains("G"))
)
return null;
}
int farmerRiversideIndex;
if (riversides[0].contains("F"))
{
farmerRiversideIndex = 0;
riversides[0] = riversides[0].replace("F", "");
riversides[1] += "F";
}
else
{
farmerRiversideIndex = 1;
riversides[0] += "F";
riversides[1] = riversides[1].replace("F", "");
}
String[] farmerRiversideThings = riversides[farmerRiversideIndex].split("");
for (String riversideThing : farmerRiversideThings)
{
String newRiverState;
if (farmerRiversideIndex == 0)
{
newRiverState = riversides[0].replace(riversideThing, "")
+ "|" + riversides[1] + riversideThing;
}
else // farmerRiversideIndex == 1
{
newRiverState = riversides[0] + riversideThing
+ "|" + riversides[1].replace(riversideThing, "");
}
if (!previousStates.contains(newRiverState))
{
previousStates.add(newRiverState);
String solutionString = currentRiverStep(newRiverState);
if (solutionString != null)
{
return riversideThing + solutionString;
}
}
}
// Handle empty hand case
String newRiverState = riversides[0] + "|" + riversides[1];
if (!previousStates.contains(newRiverState))
{
previousStates.add(newRiverState);
String solutionString = currentRiverStep(newRiverState);
if (solutionString != null)
{
return " " + solutionString;
}
}
return null;
}
}
How about golang as a topic, choosing it instead of a language like Java or C# as a primary choice
Suggestion: do a video about algorithms commonly used in Software Engineering.
❤
Here's my Python one.
farmer, dog, chicken, grain = "farmer", "dog", "chicken", "grain"
start = (frozenset({farmer, dog, chicken, grain}), frozenset())
end = tuple(reversed(start))
def search(log, this_bank, that_bank):
if end in log:
return log
else:
for item in this_bank:
banks = (this_bank - {item, farmer}, that_bank | {item, farmer})
log_item = tuple(reversed(banks)) if len(log) % 2 == 0 else banks
if banks[0] in [{dog, chicken}, {chicken, grain}] or log_item in log:
continue
if new_log := search(log | {log_item: item}, *tuple(reversed(banks))):
return new_log
def output(result):
for move in list(result)[1:]:
print("carry", "nothing" if move == "farmer" else move)
output(search({start: None}, start[0], start[1]).values())
Oh if only you could see the reams of code that had to die so that these few lines could live... 😆. Things I learned along the way:
* If you just look back one move so that you don't end up repeating it (for instance, shifting the chicken back and forwards infinitely) then you can still end up in an infinite loop of six moves that repeat forever - bzzzt!
* If you just look at half the previous moves, say the ones with the animals that are left behind, with no absolute reference to the left or right bank, then it won't work either because the chicken gets left on its own on the right, and then later on the left - bzzzt!
* Python using pipe instead of plus for set addition is pretty annoying, syntactically.
* Having to cast back to a tuple when using reversed is pretty annoying. As is the fact that it's a global func and not on the collection itself (like you mention with dir). And also the tense.
* Having to use a frozenset because otherwise it barfs with normal (lexically supported!) sets in dict keys is annoying (at least when mixing with normal sets the result is a frozenset, saving some visual noise).
* It actually has a bug where it can move the farmer first. Oops. It's a dead-end though so it just backtracks and tries another way. It properly should reject that move when it tries it, though.
* Unsure whether I went too Perl Golf on it; in reality I'd probably make a name for the 0 index of banks, and probably make it more explicit that the this_bank and that_bank are alternating places between stack levels, by passing variables with names explicitly. I'd also add some doc that explains the datatype of log, and also explains why it has to offset the printing by one position (start has no move in the value). Then there's the aspect that the farmer in the data is a way of easily representing an empty trip without hacking in an extra empty state in the code, which then has be be mapped when it comes to display. And, I dunno, is it obvious from the code that we are switching the banks each recursive call, but then undoing that switch only on the return trip in order that the log has absolute positions (left in 0, right in 1). I am not sure that "if len(log) % 2 == 0" really captures this, and I feel that if I came back to this code in a year or whatever that I'd be pretty annoyed that this higher-level aspect wasn't represented more explicitly. There's a limit after which you're veering into IOCC territory. But perhaps there's a nice clean way to make this obvious without extra doc in Ruby 😉. In Python I think I'd basically put this entire bullet point in the doc block.
A video on testing, or test-driven development would be interesting
Hm, I thought I had posted a link to my solution, but I went to update the link and I don't see my comment; I guess I'm not allowed to post links...
My solution is in SWI-Prolog, not especially "golfed" (online at paste dot sr dot ht slash ~jamesnvc slash 7e1c33e7b9f6d7b6b71c96d97eaa3084926140a0, pasted below)
:- module(chicken, [solve/0]).
:- use_module(library(record)).
:- record fcdg(farmer:boolean=true, chicken:boolean=true, dog:boolean=true, grain:boolean=true).
initial_state(s(fcdg(true,true,true,true),
fcdg(false,false,false,false))).
final_state(s(fcdg(false,false,false,false),
fcdg(true,true,true,true))).
disallowed_state(fcdg(false, true, true, _)). % no farmer, chicken & dog together
disallowed_state(fcdg(false, true, _, true)). % no farmer, chicken & grain together
valid_state(s(L, R)) :-
forall(disallowed_state(Bad), Bad \= L),
forall(disallowed_state(Bad), Bad \= R).
step(s(L, R), s(L1, R1), Move) :-
fcdg_farmer(L, true), % farmer on left
pick_move(L, Move),
move(Move, L, R, L1, R1),
valid_state(s(L1, R1)).
step(s(L, R), s(L1, R1), Move) :-
fcdg_farmer(R, true), % farmer on right
pick_move(R, Move),
move(Move, R, L, R1, L1),
valid_state(s(L1, R1)).
%! pick_move(+Side:fcdg/4, -Move:atom) is nondet.
pick_move(fcdg(true, true, _, _), chicken).
pick_move(fcdg(true, _, true, _), dog).
pick_move(fcdg(true, _, _, true), grain).
pick_move(fcdg(true, _, _, _), nothing).
%! move(Move, From0, To0, From1, To1) is det.
%
move(nothing, From0, To0, From1, To1) :- !,
set_farmer_of_fcdg(false, From0, From1),
set_farmer_of_fcdg(true, To0, To1).
move(Type, From0, To0, From2, To2) :-
format(atom(Setter), "set_~w_of_fcdg", [Type]),
call(Setter, false, From0, From1),
set_farmer_of_fcdg(false, From1, From2),
call(Setter, true, To0, To1),
set_farmer_of_fcdg(true, To1, To2).
shortest(P1, P2, P):-
length(P1, L1),
length(P2, L2),
( L1 < L2
-> P = P1
; P = P2
).
:- table path_from_to(_, _, lattice(shortest/3)).
path_from_to(S1, S2, Moves) :-
step(S1, S2, Move), Moves = [Move].
path_from_to(S1, S3, Moves) :-
path_from_to(S1, S2, Moves0),
step(S2, S3, Move),
Moves = [Move|Moves0].
print_solution(Moves) :-
reverse(Moves, RMoves),
maplist([M]>>format("carry ~w~n", [M]), RMoves).
solve :-
initial_state(Si),
final_state(Sf),
path_from_to(Si, Sf, Moves),
print_solution(Moves).
I was going to try this but I don't know Prolog well enough, but it was definitely my first thought