Bryce

I'll try saying loud things because I'm a loud person. Stop stop laughing.

Conor

It's just great because I I'm the one that gets to cut this stuff and you know I'm gonna put in saying loud things!

Bryce

Saying loud things. Oh, that's unfortunate, cuz that would have been like perfect research for this episode, right? Yeah, yeah, but that's the thing, it's like it's one search away. Yeah, I found it. I found it. I'm good at searching. I'm good at searching.

Conor

Oh yeah, yeah. This is this is it. Exactly, yeah. And there are two, I recall, yeah, there's two languages that call that actually data structures. Welcome to ABSP, the podcast, episode two, recorded on November 28th, 2020. My name is Connor, and today with my co-host Bryce, we're gonna be talking about what our favorite data structures are. Hey Matt, how's it going?

Bryce

It's pretty good. How are you doing? I'm enjoying uh, you know, the one true American Thanksgiving down here while you Canadian heathens are uh are doing your thing.

Conor

We we had the real Thanksgiving a month ago, so I don't know what you're talking about.

Bryce

You you know that there's like more people in the state that I live in than in your entire country.

Conor

That is that true?

Bryce

Yeah, like I think there's like 35 million Canadians and there's like 40 million Californians.

Conor

Hmm. That is That is sad, but uh on the flip side, British Columbia, our most western province, is larger than the combined geography of your three coastal western states, California, Oregon, and Washington. So you win some, you lose some.

Bryce

I actually I really like British Columbia. Um my mom, my stepdad, and I went up to visit uh uh like Victoria Island and um Vancouver Island. Yeah, uh yes, Vancouver Island. Vancouver, Victoria, which is on Victoria Victoria is the name of the capital that's on Vancouver Island, is that right?

Conor

Yes, that is true.

Bryce

Yeah, yeah. And it was like we actually we did it in October, so and it was like rainy and d and dreary and kind of a little bit like depressing weather, but I had a nice time. I really liked it, and I really like Seattle. Um, I really like that whole that whole vicinity.

Conor

Yeah, Seattle and Vancouver.

Bryce

Reminds me of New England where I grew up.

Conor

Yeah, it's uh I studied in Vancouver and it is Oh you did, that's right. A very, very beautiful uh yeah, for those that I I guess because we're just chatting now, but uh I currently live in Toronto. Um I was in the Bay Area, which is where Bryce and I met for just over a year, but I grew up in British Columbia, um, in the middle of nowhere, and then studied in Vancouver. Which is yeah.

Bryce

What was what was the name what was the name of your town? You once you told me it was the equivalent of the New Jersey of uh of Canada, that was like the armpit of Canada.

Conor

No, no, well, so so the armpit of Canada, uh, that's uh that's a joke that refers to to Hamilton, um, which is in Ontario.

Bryce

Which is where you currently live.

Conor

No, well, uh, yeah, I live in Ontario, but not in Hamilton. Hamilton has got its redeeming qualities, though. It's it's also known as the waterfall capital of Canada, um, because it's got a lot of waterfalls. Um, but my hometown is uh I'm sure most of the listeners have no idea where this is, but it's a small city called Prince George, um, which, yeah, it's uh it's a city of trees and trucks. A lot of a lot of trees and a lot of trucks.

Bryce

Yeah. So I think today we were supposed to talk about our favorite data structures, right?

Conor

Yes, the follow-up. We ended up having to do two episodes on our our favorite algorithms because apparently we can't stay on topic and we start topics and don't finish them. Um but the follow-up to that is what is uh each of our respective favorite data structures or data structure.

Bryce

I have a feeling that this is gonna end up taking two episodes too.

Conor

Potentially. Uh who's going first? Are you are you going first?

Bryce

Well, so I I think I think first we should talk about the word data structure. Um so uh a couple months ago we were creating this new library at NVIDIA called which is now called Koo Collections, and we were trying to figure out what to name this library, which was it was a library of containers, um, or data structures, or collections, whatever your term is for it. Um and we we wanted to pick a name that would be a good name, and like we we uh we couldn't call it Koo Containers because that would be too confusing and ambiguous with um, you know, like Docker and Kubernetes, etc. Um, like there's already like an NVIDIA Docker project, which is, you know, um not that far away from like maybe uh sorry, there's already an NVIDIA container project, um, which is not that far away from a coup collections or coup containers uh project. Um and so we wanted something that was like, you know, not gonna be ambiguous with uh containerization. Um and so we we asked you to do some research on what languages call uh data structures, and uh you came up you looked at like I think 12 or 15 languages, um and it seems like most languages call them collections. So like Java, Python, Kotlin, um only C ⁇ and OCaml call them containers, and only Racket and Clojure call them data structures. And I'm sure there's other languages that weren't in your in the set that you researched, but it seems like collections is the the the most popular term, and that's why we called our library coo collections. Um uh but uh to me there's always been a different distinction uh between these terms. Um not it's like like sorry, there to me there's always been a distinction between these terms. Um that they're not just the same thing. Like mentally, my model is that data structure refers to a particular implementation strategy. Um like a particular way of implementing um uh uh some sort way some sort of container. Whereas container refers to um an abstract interface that has you know certain guarantees. And maybe this is a very C ⁇ way of looking at things, but like as an example, I think of um stdd. colon-map as being a uh a container. Um is like that defines an interface for something. Whereas the way that you might implement a stdd colon-map is with a red b black tree, which is a particular data structure. Um and this this uh this mindset I think is more useful if um you consider things like um uh the container adapters in C, like std stack. Um, where std stack you can plug in multiple different template parameters to change how it's implemented, um uh and then it exposes out this container interface. So that's that's always been the distinction for me, that a container is about the interface it exposes, whereas data structure is the term for the actual you know implementation of it. And uh collections is just never a term that I've ever used, so I've never really thought about it. But I guess it it that term seems to me like it's more closely uh it just seems like an analogy for containers, not an analogy for date for the meaning of data structure that I just defined.

Conor

Yeah, I th I think that's uh I did not until now have that mental model, but that makes complete sense to me. And and for those of our listeners that aren't C developers, the red black tree data structure that Bryce mentioned is exactly, I think, how most of the standard libraries implement uh std map. And if you do have done any C development, you might have seen when you run into template errors uh every once in a while there's like underscore underscore underscore rb colon colon, and the rb in that case like stands for the red black tree. Um but it doesn't need to be implemented that way, so that I think makes complete sense.

Bryce

Yeah. Can you can you think of can you think of other ways that you can implement std map? I know that most of the standard libraries do it with a red black tree, but I think there are other ways that you could do it, right?

Conor

I think any kind of balancing tree that has their elements sorted, right? There's a bunch of them, but um I Yeah, I think that's right. I never took that course in school. Um I I I only have a math degree, and as we've established on previous episodes, I was a very bad student, so but yeah, I think any any any data structure that is sort of self-balancing, or doesn't even be s doesn't even need to be self-balancing as long as the elements stay sorted. Um uh and I I guess stdmap does make guarantees about the time complexity on operations, so I guess you can't actually um if you want to be standard compliant, you can't just use any data structure. Uh you have to use ones that uh adhere to the complexity guarantees that the standard specifies. Um but other than that, yeah, there's there's a bunch of options, I'm sure.

Bryce

Well, and that that gets into an interesting notion from C world, which uh is this idea that um a concept or an interface um isn't just about um syntax, but it's also about semantics. You could have something that provides all of the operations that std map does and even does uh you know, does the same thing in all the cases uh as what std map would do. But if it has a different complexity um for some of the operations, um uh we might say that it doesn't satisfy this you know the the concepts that stdmap satisfies, because those concepts have certain um complexity requirements associated with them. Because uh uh concepts have uh have uh uh semantic requirements as well.

Conor

Yeah. And while you were saying that, I actually did think of so I I'm not actually sure if you can swap out this data structure for the Reblack tree that's currently used. Uh but even if it doesn't work, it refers to a great talk that I saw, I believe it was at the 2019 meeting C or sorry, the 2019 Code Dive uh conference in uh Wrocław, Poland. Um I could get this wrong, but I'm almost positive it was Bjorn Fowler that gave a talk on sort of things that I wish uh I had of known. And he takes basically a single example that is really inefficient and um progressively like modifies things about it and switches uh the structured data structure that he's using. Um and ultimately he ends up with uh something that's implementing a B tree, uh, which is I think similar to a red black tree, the difference being that it takes advantage of uh you know adjacent elements stored in memory so that you can have better like cache coherency. Um and it's a fantastic talk. So even if you can't use a B tree, uh I'll put it in the show notes and everyone should go watch it, even if you're not a C dev.

Bryce

Cool. Uh so uh okay, so so back to our question. What is your what is your favorite uh data structure?

Conor

Am I going first?

Bryce

Yeah, you're going first.

Conor

Alright. Uh so I'm not I as if you've seen any of my talks, I'm much more of an algorithm person than a data structure person. Um but when I thought about this question, uh a quote came to mind um by uh a prolific individual um from sort of the history of computer science, Alan J. Perlis. Uh for those of you that don't know, he was the first recipient of the uh Turing Award in 1966. And um he's famous for sort of a number of reasons, but one of them uh that a lot of people know is that he has this sort of collection of quotes called epigrams on programming. And one of those quotes is the following uh it's better to have a hundred functions operate on one data structure than ten functions operate on ten data structures. Um and this quote uh is actually what like I care more about than like the particular data structure. And uh so this leads me to sort of my favorite data structure in C. Or I guess if this is the case, it's more of a container, not a data structure, but it's std vector. Uh partially because you know, of when Chandler said, you know, always reach for std vector. Basically, half the time it's gonna be more efficient than whatever your data structure you're reaching for, unless if it's like a niche case. And I think um Tony Van Eard, uh another individual in the C community, he also said like anytime you are not using a std vector, you should uh add a comment and say why, um, because std vector is so versatile. Um but yeah, it's it's my favorite data structure, mostly just because it's got you know uh constant time complexity for most things, and then insertion is amortized constant, uh, which is fantastic. Um and when you combine it with that quote, it's even more than just like having a hundred functions, aka r stl algorithms that can work on it. We almost have in C like a hundred functions slash algorithms that operate on 10 data structures. So like in the quote, it's a hundred on one or ten on ten, when uh this sort of is implicitly referring to something known as like the m times n versus the m plus n problem, where uh due to the fact that we have inner iterators as this sort of uh layer between our algorithms and our data structures, uh they're generic, and so a lot of the algorithms work on uh different containers or data structures, if you will, because of the iterators. So we almost have like a hundred operating on ten, uh, and of those ten, std vector is the one I reach for the most.

Bryce

Yeah, and um uh to to explain that a little bit more, um one of the reasons why uh std vector is almost always the right choice is um because of performance. A std vector gives you a contiguous block of memory. And um in particular on you know our sort of our modern contemporary hardware, um that locality is almost always gonna beat out any theoretical uh uh advantages that another data structure might have. Um uh, you know, there are weaknesses um to uh std vector. Um you know, it has to every now and then it has to go and do these reallocations to grow. Um you have to have a good growing algorithm. It can sometimes uh because of that growing algorithm, it sometimes can be memory inefficient. Um uh and like doing insertion in the middle is obviously, you know, uh uh not a very cheap operation, but um you might go and look at using a different data structure that might be better at some of these things, like stddlist, which is a or a doubly linked list. You might think, okay, well, you know, I know I need to do a bunch of insertions in the middle or insertions at the front, so I'll use list in this algorithm instead of vector because I'm um you know I'm anticipating that. Um but with a a doubly linked list, you're doing pointer chasing, right? All the memory is uh disjoint. Um and so because of that, you're you're paying this regular penalty of you're not walking through memory contiguously, which is what modern hardware is meant to do. And so, yes, you might get this benefit with the doubly linked list when you're you know inserting an element in the middle, but you're paying this penalty in every memory access and in your your the the memory access pattern itself. Um and so that often that that overhead of that less efficient memory access pattern um often makes it a wash or makes using the list slower. So so sort of unintuitively, there are some cases where the things that a vector is bad at, um, it will be better than the competition solely because it has this nice property that it's contiguous. And and it being contiguous means it's easier for it to be cache and it's easier for it to be um prefetched. If you look at modern CPUs, that they have special hardware that's designed for streaming through memory. Um that if if you're if you're in a loop and you're accessing um uh an array of memory contiguously, there's like special hardware that's designed to figure out, hey, we're in a loop and we're sequentially accessing this contiguous um uh range of memory, so like we should pre-fetch and cache accordingly. Um and so yeah, so that's why oftentimes the sort of naive data structure of vector is the right answer as comp as uh opposed to something more complex. And it's it's I really like that uh the first thing you mentioned about this idea that you know sort of less data structures might be better. Um uh that that that oftentimes you want more algorithms to operate on less data structures. Um I think a lot of times people question why doesn't C ⁇ 's standard library have um a richer set of data structures like some other languages. And I think it's largely about that philosophy that that this idea that you don't necessarily need more complex data structures, um and that those tend to be very specialized for particular use cases. Um instead what you need is a core set of really solid data structures and then a set of composable algorithms that build on top of them. Yeah. One of the key things about the design of the C standard library is um separation of concerns. You know, w for data structures in particular, we don't have the with the exception of std string, we don't have these big classes that have a billion different algorithms as member functions. Instead, we have fairly simple data structures that expose abstractions like iterators and ranges, and then we have a separate set of algorithms um that consume iterators and ranges.

Conor

Yeah, so there's there's a bunch of things I want to uh comment on. So the the first one where you were talking about pointer chasing, um, that ties in perfectly to the talk that I mentioned by Bjorn Fowler, in that um one of the very first things in his talk that he mentions is that uh he didn't really have a notion of you know the difference between data structures and pointer chasing, and like ones that have you know uh contiguous memory and others that don't. And he said that like a good rule of thumb, you know, this isn't always true, but just like a his mental model that he has is whenever you are uh chasing a pointer, you can basically consider that a cache miss, um, which it exactly ties into what you're thinking. It leads to this counterintuitive, well, like, oh, I'm reaching for a data structure that has you know constant insertion time, which brings me to the the second thing. I said, you know, uh a std vector has uh amortized constant insertion. I should have been more specific in saying that it's specifically that's referring to pushback, the method pushback that inserts at the end of the array. Uh an insertion into the middle is gonna have linear time complexity. Um but it that ties in exactly to what you said is that it leads to this counterintuitive, well, I'm reaching for a data structure that supposedly has constant insertion versus linear insertion for a std vector. Um but due to the cache miss, it's gonna erode at some of the you know, quote unquote uh complexity uh gain that you're getting from switching to uh a data structure that doesn't have you know contiguous memory like a std vector.

Bryce

Um and Yeah, well, and and you know part of that is is yeah, we sp uh I think a lot of people in a lot of traditional literature spends a lot of time looking at um time complexity of algorithms, right? Whereas in in practice, you do need to think about what the constants are. You know, okay, you you know, this thing has um oh one insertion, but w what is the constant? You know, what like like what is that um uh uh the actual constant latency or okay, this thing has linear, you know, insertion in the middle, but if I only have a hundred elements, how expensive actually is that? And when you start looking at those constants, that's when you see, oh, I only have an 100 elements, insertion in the middle is just gonna be cheap for me if this thing is always gonna be like that size. So uh and and whereas using the list, all of the operations are gonna have this greater overhead. So I may as well just use the vector and like I sure it's it's linear insertion here, but in some cases that's fine.

Conor

Yeah, that's a it's that's a fantastic point. And it applies not just to um uh data structures, it also applies to algorithms. Like the uh I think it was a couple months ago a coworker had came and asked me, um, I'm trying to do a binary search, but I need to look at two elements at a time. Uh my immediate thought was uh adjacent uh find, um, but that's Not a binary search. And then I said I followed up and I said the binary search equivalent of that is partition point. But then my next question was do we really need a binary search here? Like do you know on average how many elements is going to be in the list or that you're searching over or vector that you're searching over? Because I you have to profile. I've heard a rule of thumb is that if you have less than 80 elements, a linear time algorithm is going to be faster than a binary search algorithm. I don't know where I heard that or if that's true. You know, profile before you're you're making these claims. But his response was, oh, it's it's less than 10. Um in which case I said, yeah, like we don't need a binary search for that, just use a JSON find and call it a day. Um so yeah, it's it's like thinking like like you said, you know, in in academia, you always drop the constants or the coefficients, uh, but in in like real real world, uh those constants matter, and you know, profiling is very important.

Bryce

Yeah. So um it's funny that uh that you said std vector because uh w when we talked about algorithms, we both we said um, oh, we thought that both of our answers was gonna be uh transform reduce, but then it turned out that in fact that wasn't both of our answers for what our favorite algorithms were. Um and for for this, I sort of figured that both of us would our initial gut reaction would be std vector or a um a dynamically um uh dynamic array would be our first choice. Um uh and it was for you, but I I actually am gonna argue that um there's a that that it's not mine and that a dynamic array is often uh is often too heavyweight. And I actually think that you're gonna agree with me. Um that like I don't think that that a dynamic array like std vector is your favorite algorithm. I think you've just been um I think you've just been tricked into thinking that because the actual container that you need the most frequently doesn't exist in a easy easily accessible form um in C ⁇ . So um let's let's talk about about um uh how we use a dynamic array. So one of the key properties of a dynamic array like std vector is that it's growable. Um uh that is that um after I've created it I need to add more elements to it. But I think in a lot of cases we know ahead of time how many elements we're going to put into that vector. Um and there is a uh a pattern that is uh very common that I see a lot. Um I call it like the default construct and reserve pattern um when you're using a vector, um uh where you will uh default construct the vector, um, and then you'll immediately call reserve on it with the uh uh capacity that you need. Um and for those who may not be aware, what when when we talk about capacity, that's that's different from size. So capacity is how many slots for elements does the vector or the dynamic array have, and size is how many slots are currently in use. And um every time the size grows above the capacity, you have to reallocate um the entire vector's storage and move everything into the new capacity. So typically um capacity is grown with you know some internal algorithm, like you you double capacity every time so that you don't have to constantly be reallocating. But so let me just ask you, Connor, how how many times have you constructed a std vector and then like either immediately called reserve or immediately called resize and then never added, never increased the capacity after that?

Conor

Uh probably more times than I can count. Although I will say, as a small caveat to this, I've discovered a new pattern using thrust fancy iterators, which also exist in Boost as boost iterators, where you can set up like transform iterators and then just call the range constructor and avoid like the resize or the reserve, which I think is a super nice pattern, because then you can also declare your uh data structure container const um by using those fancy iterators, but uh that's only been recent in the last couple months that I've discovered that pattern, and before that, uh yeah, probably more times than I can count.

Bryce

Yeah. Um and and so uh this sort of gets to one of the defects that std vector has in C, which is there is not a good way, one, there's there's there's not a simple way with std vector to reserve on construction. Um there's no constructor that lets you reserve capacity without also allocating elements. So you you can construct us uh a vector with an initial set of elements, but then you have to pay to initialize those elements. So like if I'm if I'm creating a one gigabyte vector of doubles, I could um just call the vector constructor that says um uh I want in in elements that have this initial value, but then I'd have to pay the cost of initializing that whole gigabyte of memory with uh uh this initial value when all I really wanted to do was just reserve that storage, and then later I was gonna push back into the vector. Um uh and that's why you have this common pattern of you know, default construct it and then you call reserve. Um uh and uh there's there's also the case where you want to um get uninitialized elements. And there's actually a C committee proposal um regarding this. For example, let's say you've got like a std string and you want to call a C library function that's going to initialize the constants of that std string. Well, there's not really a good way to do that. Like the only thing you can do is you can uh you can resize the string, which then zero initializes all the characters, and then you can call that that library function or you can copy um into it from some external API. Um uh and that's a little bit unfortunate. And so there's this proposal P1072 that uh that tries to address this for originally for std vector and std string, but now just for std string. Um but uh it it and and it is a slightly hazardous pattern where you want to say, oh yeah, I want I want to can I want to like add elements to this container, but I don't want them to be initialized. Like that's how you get that's that's how you lead yourself um towards uh uh you know memory safety violations and all sorts of unpleasant, uninitialized variable hazards. Um but uh if you really care about performance, it can matter. Um so I'm gonna argue that that that dynamic array is in many cases more than we need because in many cases we know what the capacity is gonna need to be up front and it's never gonna change. Um and so what we really need is we need a a container that's just a C equivalent of um you know a C style array where I want to allocate it, I want to give it a size, um, and I want to have control over whether and how those elements get initialized. Um now there are a few options in the C standard library if you want this uh uh this data structure. So if you know at compile time the size, you can use std array, um, which is uh a compile time sized array with more or less the same interface as vector. Of course, it can't be resized, etc. Um, but I think the more common pattern is that I just want a dynamic like a runtime-sized array. And there's no specific class for this in C, but you can express this with unique pointers um array specialization. Have you ever uh are you familiar with unique pointers array specialization?

Conor

Uh walk us through it.

Bryce

So unique pointer in C is um a uh it is an RAI type that owns some pointer that you, the programmer, have allocated, and it has a um a customizable deleter um parameter which uh is used to clean up this pointer that you've given it um uh when the unique pointer is destroyed. And it's called unique pointer because it is uniquely owning. Umly that unique pointer can can uh uh have access to it. You can't copy the unique pointer, um, you can move the unique pointer though. Um and so this gives us some guarantees about you know uh uh who who has access to this pointer. And in particular, because it's uniquely owned, you don't have to worry about who's gonna clean it up. The answer is always gonna be there's just one person that there's just one instance of this unique pointer, and that's the one that's gonna clean it up. So you don't have to do reference counting or anything like that. But unique pointer is usually uh used for managing the lifetime of like a single uh pointer, but um there's this specialization where if you write like unique pointer um uh chevron template brackets, yeah, Chevron, um, and then like int empty braces, um, square braces, um that will give you this specialization that is intended for arrays, and it will have an array access operator, and by default it will use um uh an array delete. Um and so this is a pretty nice way to proxy or or to to implement just a container that is just I want to allocate this array that has n elements and that number of elements is never gonna change. Um and it's also useful because you fully control the allocation um of these uh elements. Um all that you do is you give a pointer to um unique pointer. It doesn't create the pointer itself, so you can control how you um uh allocate that memory, how you initialize it. So you don't have to you don't have to worry about this case of oh well you know I want to use a std string, but I I want to control you know how these elements get initialized and created. With unique pointer, you have full control. All that it provides you is uh is the uh the safety of the resource acquisition is initialization pattern where when the unique pointer goes out of scope, you know that it's gonna clean up the memory using the deleter. So that's I think something that I reach for very frequently. And um there's a related facility called allocate unique that's proposed been proposed for the past two C standards, but hasn't been very high priority. Um uh and that just gives you a convenience for saying, hey, I have this C allocator, and I want to create a um unique pointer that uh uses this allocator to allocate and deallocate the memory.

Conor

Interesting. Where did uh or if we want to point our listeners at like more resources on this pattern, uh, what would you recommend?

Bryce

Um so we'll put a link in the show notes to the allocate unique um paper. Um in uh in our thrust library, we have an implementation of um allocate unique, and then we have a few different um overloads. We have one for uh you know arrays and um on I use it all the time. Like that's my go-to, um my go-to thing. The paper is P0316. Um and it's it's it doesn't seem like it's a particularly important thing, but I think if you start I think if you start thinking about all the places where you can use a unique pointer, um uh the array version instead of a std vector, you'd be surprised how commonly this comes up.

Conor

Yeah, it sounds like a s a common pattern that potentially a lot of people just aren't reaching for because it's not, you know, stated right next to std array and std vector in sort of the the containers uh docs slash blogs that are written.

Bryce

Um well and it's not perfect because like it doesn't give you iterators, etc. Like we could build a version of this. And um at one point in C's history we sort of did. There was this thing called dyne array that we tried to do, but it it ended poorly. Um that was before my time. I don't remember all of the details, but uh we could do a better job here.

Conor

Yeah, it sounds like an interesting direction for the future of C once we get everything that's supposed to go into 23 and uh the bleed over into 26, that you know this could be a future uh proposal for C. Yeah.

Bryce

What's the thing that you're most excited about that's coming in C23?

Conor

Uh I personally uh, although I I don't think we can make any um guaranteed statements about what will and will not be in C23. Uh but assuming that, you know, everything that's being quote unquote targeted by individuals and uh for C23, I'm most excited about seeing uh the extension to the ranges stuff. Um on top of that, uh I think pattern matching is gonna be awesome. Um executors, you know, everything that's going in, honestly, like library support for coroutines, I think that's gonna open up the world of coroutines to a lot more people. Um but yeah, at the top of the list it's definitely uh the ranges stuff. How about yourself?

Bryce

Um That's hard to say. Um I would probably say I'd probably say executors, but I also think that things like allocate unique and any invocable, which is a better version of std function, um, I think those are gonna have a lot of impact on people's day-to-day lives. So I think that that those will be uh those will be big wins too.

Conor

Alrighty. I think we're once again past our 30-minute uh target time, but uh it's alright.

Bryce

Is that our target time?

Conor

Yeah, yeah, well, you know, 20, 25, 30 minutes. Uh it's it's never a hard limit though. So it's our podcast. We get we get to do what we want.

Bryce

Um I I think we're gonna have to do another one next week because there's uh there's a whole other set of uh of data structures that uh at least I want to talk about. Um and uh yeah, so maybe we'll do part two next week.

Conor

Sounds like a plan. All right. Awesome. Thanks everyone for listening, and uh, we'll catch you guys in the next one.