|
We had to implement a binary search algorithm, which searches for the first missing number from an ordered list. It actually seems to work..code:
C++ code:
|
# ? Sep 25, 2023 16:46 |
|
|
# ? May 26, 2024 14:29 |
|
quote:Don't ask me why the numbers in a list print out like that, I have no idea. Only the first ten elements look valid to me; Looks like either you're either operating on a list of length 50 that you are not initializing past element 10, or, you are operating on a list of length 10 but the function thinks it's of length 50 so you're seeing unrelated/garbage values past the end of the array. edit: glancing at your code a bit more, I think you want your base case (the "do I have the empty range of values to search" to come first; otherwise, I don't think you'll get the right answer when, say, the input list is of length 1). Dijkstracula fucked around with this message at 20:10 on Sep 25, 2023 |
# ? Sep 25, 2023 20:03 |
|
Dijkstracula posted:Only the first ten elements look valid to me; Looks like either you're either operating on a list of length 50 that you are not initializing past element 10, or, you are operating on a list of length 10 but the function thinks it's of length 50 so you're seeing unrelated/garbage values past the end of the array. The test functions for algorithms were provided by school, but they are quite obtuse and have no comments at all. The test functions apparently generate n sized lists with a random generator. Lists can be 10, 100 or 1000 long and the tester prints up to first 50 numbers. I was happy enough when my binary search function passed the tests. The code which school has provided to students in various courses has quite often been something that I would not write or distribute. It’s rarely pleasant to read, but I guess they have their reasons.
|
# ? Sep 25, 2023 20:11 |
|
I've got Opinions on your instructor's decisions there, but I also had Opinions on your other class's instructor's decisions, so I guess at least there's consistency
|
# ? Sep 25, 2023 20:14 |
|
The numbers after 20 look like they are uninitialized values. You need to pass the correct length to the function that is printing the array. The task itself is super weird and artificial. I guess that the trick is that you want to keep descending left as long as it's not "full"? That's the vital information you should mention in the last comment. Your function was a more confusing that it should for me because I expected a binary search to be more in the following canonical form: code:
At the risk of solving someone's homework: hint 1: Your code only works if you keep descending to the left until the last step. If there is a right followed by left it will not find the right result. hint 2: You keep looking at arr[0], don't solution: Your left/right condition should be something like `arr[right] - arr[left] == right - left` if I am not mistaken. edit: Am I an idiot? I think this might work because you need the first non-consecutive. edit 2: try running an example that has the missing element in the second half of the list Beef fucked around with this message at 21:49 on Sep 25, 2023 |
# ? Sep 25, 2023 20:15 |
|
Beef posted:edit: Am I an idiot? I think this might work because you need the first non-consecutive. When I'm stuck writing artificial things like this I like doing the Jon Bentley thing and manually writing out things that have to be true at each step of the computation (which, because you're doing thing recursively, is at the start of each function call), something like code:
Dijkstracula fucked around with this message at 20:39 on Sep 25, 2023 |
# ? Sep 25, 2023 20:23 |
|
Dijkstracula posted:I've got Opinions on your instructor's decisions there, but I also had Opinions on your other class's instructor's decisions, so I guess at least there's consistency Then I thought it was comparing *two* sorted lists, where one has had one item removed, and looking for what the removed item is, which would be a valid use for a binary search provided duplicate entries are not allowed (because everything to the right of target a[i]!=b[i], and everything to the left a[i]==b[i]). Then I saw that was not the case and I thought for a moment "oh but if you find a missing at 50% and look left and find another at 25% then you don't have to look at the ones in between so there is a saving after all". Then I realized that no, you still have to look at the 25% to the left (if there's no others missing in there), and if you just did a linear scan from the left you'd look at those and then stop at the first so it would be shorter by one lookup in this case, and there is no case where the binary search reads fewer items than the linear search from left for this algorithm, jesus, is this instructor *intentionally* making nonsense problems, is his entire course like a piece of performance art commenting on blockchain and AI?
|
# ? Sep 25, 2023 23:38 |
|
Ihmemies posted:Does that make sense? It had to be implemented recursively, and I saw examining the next index from left as the only option for base case. It took me many hours to get it working. Finally I realized I can actually use printf to "see" what it does instead of just using the debugger. I haven't done a particularly deep read but far as I can tell there's nothing horribly wrong with what you've done, it should divide and conquer well enough. I think the termination condition might possibly not be what your professor is looking for but it's not wrong, although you should probably be aware of what you could do instead. Notes in general: - There's an additional precondition that you're not mentioning, which is that it's necessary that the sorted array contains no duplicates. I'm guessing this is stated as part of the assignment since I don't see how you could solve it with a binary search otherwise, just noting it. - You shouldn't need to check if there's a missing element within the current range on every iteration, since you already verified that there is when you selected which half of the array to recurse through. You can test if a missing value exists once on the initial call and then either return NO_VALUE_MISSING immediately or proceed with the binary search assuming that when you terminate there will be a missing value. - You can indeed find a different termination condition. It's possible to write the search in such a way that you terminate when the range is empty, and then return fromArray[left] + 1 which will be guaranteed to be the smallest missing value. Recursing until the range is empty is arguably more "canonical" and in line with how binary searches usually work. You would lose the potential early exit. Which is "better" depends on other things like what sort of input you'd expect to get, if the loop code is more/less efficient with better/worse branch behavior, etc. It's a toy example so there's no real world data to provide a real world answer. roomforthetuna posted:Then I realized that no, you still have to look at the 25% to the left (if there's no others missing in there), and if you just did a linear scan from the left you'd look at those and then stop at the first so it would be shorter by one lookup in this case, and there is no case where the binary search reads fewer items than the linear search from left for this algorithm, jesus, is this instructor *intentionally* making nonsense problems, is his entire course like a piece of performance art commenting on blockchain and AI? Nah, binary search works fine here. The proposed algorithm effectively splits the array into two half-sized subarrays, tests (in constant time) if there's a missing number in the left sublist, and only visits the right sublist if there is no missing number on the left. Problem size is halved at each step so the time complexity is reduced from linear to logarithmic as usual. In that sense it's a perfectly okay -- if very artificial -- teaching problem. You have to assume that the list contains no duplicates as a precondition in addition to the listed stuff but, fine, it's a toy problem. Xerophyte fucked around with this message at 00:20 on Sep 26, 2023 |
# ? Sep 26, 2023 00:18 |
|
Xerophyte posted:Nah, binary search works fine here. The proposed algorithm effectively splits the array into two half-sized subarrays, tests (in constant time) if there's a missing number in the left sublist,
|
# ? Sep 26, 2023 01:15 |
|
Dijkstracula posted:When I'm stuck writing artificial things like this I like doing the Jon Bentley thing and manually writing out things that have to be true at each step of the computation enough with the Dafny evangelism
|
# ? Sep 26, 2023 01:48 |
|
Subjunctive posted:enough with the Dafny evangelism
|
# ? Sep 26, 2023 02:46 |
|
Ihmemies posted:We had to implement a binary search algorithm, which searches for the first missing number from an ordered list. Just FYI, this isn’t binary search. Binary search is a specific divide-and-conquer algorithm for trying to find a given value in a sorted array. The algorithm in your assignment is a divide-and-conquer algorithm, and we could hand-wavily describe it as a kind of search, and it involves a sorted array, but it’s not “binary search”. It’s like “record player”. If you think about the term from first principles, it could mean all kinds of devices, but in reality it means a specific thing. Just another way your teacher is being weirdly confusing.
|
# ? Sep 26, 2023 06:11 |
|
Whole task was:code:
rjmccall posted:Just FYI, this isn’t binary search. Binary search is a specific divide-and-conquer algorithm for trying to find a given value in a sorted array. The algorithm in your assignment is a divide-and-conquer algorithm, and we could hand-wavily describe it as a kind of search, and it involves a sorted array, but it’s not “binary search”. It is probably me being confusing. We had to develop a function which used Beef posted:
Thanks. The deadline for homework was on last Sunday, so I asked only after the deadline for returning the homework was passed. We were provided a Qt project in a zip file with some premade test functions. So we had to only implement the specific functions with iteration, then with recursion. The tester did the rest. I did not really understand why it started printing weird numbers like 19, 406632848, 165920, 749745312, 540, I just assumed it does what it's supposed to do. I guess I will have to look at the tester code in more detail, and try to understand what it does, and try to coax it to put the missing numbers to any possible place, not just inside the first 10 numbers or so. Maybe after that I'd better understand the faults in my solution, when the tester tests it with more cases. Ihmemies fucked around with this message at 10:59 on Sep 29, 2023 |
# ? Sep 29, 2023 10:45 |
|
Okay, yeah, the assignment is using the terms correctly. The right way to approach problems like this is to step back from the code for a second and think about what you know at each step of the algorithm. (The fancy way to say this is that you’re defining your invariants.) In this case, we know these things from the problem statement:
Since we never modify the array, these things stay true the entire time. We want to find the first element that is not the previous element + 1. If you look at a few examples, you’ll see that there’s another way of thinking about this: there’s a prefix of the array where each element increases by 1, and you want to find the end of it. The first element is always part of that prefix (unless the array is empty), but we don’t know anything about the rest of the array, and if the prefix runs all the way to the last element, there are no missing elements. As we recurse, we’ll be looking at smaller and smaller slices of the array, but if we generalize those three things, they seem like useful things to keep knowing:
If we can keep those three invariants, then whenever we complete the recursion, we’ll have the final answer. This is a really nice property — it means that when we recurse, we can always immediately return the answer we get. (That also means we could do this without recursion if we were allowed to.) There’s one last property we want to make sure we keep when we recurse. We do need to make sure we’re always looking at a smaller problem, or else the recursion might be infinite. So whenever we make a recursive call, we want to know that the new (end - start) is smaller than the current (end - start). This isn’t strictly speaking an invariant, but it’s a rule that’s going to guarantee we make progress. Now we can think about the code and think about how we can make the problem smaller at each step while still maintaining these invariants and making progress. Finding the first element that isn’t the previous element + 1 is normally the sort of condition that would require a linear search through the array. But the guarantees of sorting and uniqueness give us a shortcut, because now we can test if the prefix extends to some element by just testing if the difference between the indexes matches the difference between the values. The exact relationship is super important, and it’s really easy to get it “off by one”, so I suggest writing out an example array on paper or something, with the indexes next to the elements, and making sure your formula works. Then the basic structure of your solution is essentially: 1. Figure out how long the slice of the array is. 2. Deal with the base cases (lengths <= 1). 3. Otherwise, find an index halfway through the array. 4. Check if that’s still part of the prefix. 5. Recurse, making sure you maintain all the invariants above with the new information you just got, and making sure that the new array is smaller. rjmccall fucked around with this message at 18:11 on Sep 29, 2023 |
# ? Sep 29, 2023 18:08 |
|
I kind of suck at compilation units and hoped somebody could explain why testing for a header guard elsewhere sometimes doesn't work. Like, let's say:code:
code:
|
# ? Oct 2, 2023 21:25 |
|
I think it’s going to be hard to answer that without an example, but this thread is full of wizards
|
# ? Oct 2, 2023 22:13 |
|
Are you sure you’re using #ifdef when it fails and not just #if? #if __BUTT_H__ would evaluate to 0.
|
# ? Oct 2, 2023 22:45 |
|
chglcu posted:Are you sure you’re using #ifdef when it fails and not just #if? #if __BUTT_H__ would evaluate to 0. Ehh this is the kind of thing I do and I thought I even triple verified it, but you make me so anxious so I'll have to just look again at it. Edit: I gave up and logged in to mess around and very much if an #ifndef (in this situation) and it puked. I think the problem in this particular case is that the header in file was creating originally with some of the stuff missing and I can still bump into that in one of the permutations. So maybe I'll come back in six months when I do this again and find out what I screwed up that time. Rocko Bonaparte fucked around with this message at 01:47 on Oct 3, 2023 |
# ? Oct 3, 2023 01:09 |
|
Usually when I see something unintuitive when I’m not following the standard pattern, it’s because the headers are cyclically including each other.
|
# ? Oct 3, 2023 02:42 |
|
Rocko Bonaparte posted:I kind of suck at compilation units and hoped somebody could explain why testing for a header guard elsewhere sometimes doesn't work. Like, let's say: This is unlikely to be the reason, but: technically this program is ill-formed since you're not allowed to use guards of the form __BUTT_H__. Identifiers that...
Violation of that particular rule is endemic and if your actual guard identifier is reasonably unique it will likely not be a problem. If your guard is closer to #ifdef __VECTOR_H__ then something exploding is more plausible.
|
# ? Oct 3, 2023 08:39 |
|
Use "#pragma once" y'all
|
# ? Oct 3, 2023 09:58 |
|
Xerophyte posted:This is unlikely to be the reason, but: technically this program is ill-formed since you're not allowed to use guards of the form __BUTT_H__. Identifiers that... You can also do it to yourself without reserved indentifiers when two of your header files use UTIL_H
|
# ? Oct 3, 2023 11:06 |
|
Beef posted:Use "#pragma once" y'all I'm not sure if #pragma once works with some of my headers. They're using the trick of requiring an implementation define in one location to allow implementation in the header. Mostly use that for generated headers holding const arrays (serialized image data, etc) I can't use that pragma elsewhere because not all of my compilers support it. I do use it sometimes in headers for platform code on platforms that support it, but I don't have a lot of platform code that doesn't have shared headers.
|
# ? Oct 3, 2023 14:18 |
|
You are actually using a compiler that doesn't support #pragma once? Is it an ancient version of a compiler?
|
# ? Oct 3, 2023 15:58 |
|
Nalin posted:You are actually using a compiler that doesn't support #pragma once? Is it an ancient version of a compiler? Yes, compilers I'm actively using (lsic86ww) and compilers I'm attempting to use but am having issues with (toshiba TC900). I don't think the fork of turbo c I could use instead of lsic supports it either, but that only runs on 16 bit dos; I have a 32 bit version of lsic, which is quite nice comparatively. I think turbo c's code generation might be better though..
|
# ? Oct 3, 2023 17:18 |
|
Xerophyte posted:This is unlikely to be the reason, but: technically this program is ill-formed since you're not allowed to use guards of the form __BUTT_H__. What if it's Linux kernel crap? They got their own stuff going on so I dunno if they are playing by their own game there.
|
# ? Oct 3, 2023 17:53 |
|
The other problem with #pragma once is that if your build includes multiple copies of a file -- not uncommon even in sane scenarios, like when using multiple libraries that share the same public header-only dependency that they expose as part of their API and similar -- then pragma once may happily include both copies and possibly explode your build with multiply defined symbols. This said I still use it liberally in all my hobby projects. I avoid it in the public headers of libraries. When I write include guards my identifiers are generally on the form PROJECTNAME_DIRECTORY_SUBDIRECTORY_FILENAME_H_. This is annoyingly verbose, but compliant and at least very unlikely to ever conflict. I too have seen #ifdef UTIL_H_, and shuddered in fear. Rocko Bonaparte posted:What if it's Linux kernel crap? My linux kernel knowledge is nonexistant so, uh, I'unno.
|
# ? Oct 3, 2023 18:00 |
|
Yeah, for private headers - either part of an application, or headers in a library that aren't exposed - if #pragma once doesn't work you're doing something stupid and should just stop doing that. Public headers for a library should make a reasonable effort to work even if things are a bit weird, though, and header guards aren't actually very much work.
|
# ? Oct 3, 2023 19:13 |
|
Xerophyte posted:The other problem with #pragma once is that if your build includes multiple copies of a file -- not uncommon even in sane scenarios, like when using multiple libraries that share the same public header-only dependency that they expose as part of their API and similar -- then pragma once may happily include both copies and possibly explode your build with multiply defined symbols. This said I still use it liberally in all my hobby projects. I avoid it in the public headers of libraries. Why even use a real name, just generate a random string and grep for it in your code lol
|
# ? Oct 4, 2023 02:43 |
|
Plorkyeran posted:Yeah, for private headers - either part of an application, or headers in a library that aren't exposed - if #pragma once doesn't work you're doing something stupid and should just stop doing that. Public headers for a library should make a reasonable effort to work even if things are a bit weird, though, and header guards aren't actually very much work. I've run into those niche cases multiple times in large corporate software (the sort of places to have tens or hundreds of people dedicated to maintaining your build pipelines), but aside from that it's not too much of an issue to switch if needed anyway. Just worth keeping in mind there are cases where it will break something that works with include guards.
|
# ? Oct 4, 2023 05:19 |
|
Sweeper posted:Why even use a real name, just generate a random string and grep for it in your code lol I used to have an Emacs macro generating header guards from UUIDs. (Nowadays it just uses #pragma once.)
|
# ? Oct 4, 2023 21:28 |
|
Zopotantor posted:I used to have an Emacs macro generating header guards from UUIDs. (Nowadays it just uses #pragma once.) There is some delicious irony in using (gensym) to create a unique preprocessor variable name for a header include guard.
|
# ? Oct 4, 2023 21:51 |
|
I'd like some general way to return values asynchronously in C++. Currently we either park a thread or, occasionally, take a callback when e.g. making an HTTP request. But I'd prefer to return something rather than nest callbacks endlessly. Something like a Future type. Is there anything like that built into the standard library? Should I consider coroutines? Is there a focused lil library that everyone uses? This is recent-ish clang (Android NDK and iOS SDK) and we're on C++20 (I could probably bump that if necessary).
|
# ? Oct 4, 2023 22:39 |
|
std::promise has been around since C++11, might be worth looking into?
|
# ? Oct 4, 2023 22:46 |
|
std::future is probably what you want.
|
# ? Oct 4, 2023 22:47 |
|
Alternatively, embrace coroutines.
|
# ? Oct 4, 2023 22:58 |
|
I got the impression that std::future and std::promise block threads, which I was hoping to avoid. Is there some other way to use them like "when this future's done, run this code"? Coroutines look like what I want, just complicated. I'll give them a shot.
|
# ? Oct 4, 2023 23:29 |
|
I think the main motivation for a promise is to have something run after something else finishes and has a value to spit out, but I have never used them in C++. It's kind of strange here! I figure you'd pass a function to the promise and "chain" it.
|
# ? Oct 5, 2023 00:02 |
|
Futures represent values that may not be present yet. You don’t “dereference” them until you need to use the value, in which case you want to block until it’s present because you need the value to proceed. It sounds like what you want is just a background thread, maybe, that blocks on a thing and then operates on the result. When you say “return asynchronously”, what value is being returned, and what is consuming it?
|
# ? Oct 5, 2023 00:12 |
|
|
# ? May 26, 2024 14:29 |
|
Subjunctive posted:Futures represent values that may not be present yet. You don’t “dereference” them until you need to use the value, in which case you want to block until it’s present because you need the value to proceed. Sounds like they want something like a JavaScript Promise, where they can include a chunk of code that gets executed when the promise completes. So, yeah, a background thread that blocks on the future and executes some code.
|
# ? Oct 5, 2023 00:28 |