PPP2 Chapter 17 Exercise 11

Pages: 1... 34567... 16
I was asking whether I should keep using a while-loop or switch to a for-loop (if it'll work),

Why do you think a for() loop is a better option? But the while loops are probably the best choice for this job.

If I don't try that with the m_succ and m_prev pointers, how will I know how many elements there are in the list?

If you don't try what?

Why do you really care how many elements are in the list? The purpose of a list is to allow an "unlimited" number of elements.

I really have a feeling that you don't understand how pointers work, which is why I made the suggestion I did in my last post. It is a sort of test of your knowledge so I can better judge where you're at.


I just want to see if the number of elements is more than 1. That's all.

As for what I wanted to try: the counter variable, that I'd update whenever I encounter a m_succ or m_prev that isn't pointing to nullptr.

As for how pointers work. I do know how they work.

I think I'll do an exercise on doubly-linked lists, focused on inserting new nodes into the list, first. Maybe that'll give me a better idea of what to do here. Though I think I should really put this particular exercise (PPP2 Chapter 17 Exercise 13) on hold until I get hold of a pencil/pen and paper to use.
I just want to see if the number of elements is more than 1. That's all.

Then print the list.


As for what I wanted to try: the counter variable, that I'd update whenever I encounter a m_succ or m_prev that isn't pointing to nullptr.

Okay, then try it if you must. But this is really just throwing more code at the problem and hoping it sticks.

As for how pointers work. I do know how they work.

Prove it.

Though I think I should really put this particular exercise (PPP2 Chapter 17 Exercise 13) on hold until I get hold of a pencil/pen and paper to use.

That's probably a good idea, but have you considered a stick and some dirt as you're paper and pen? Also if paper is in short supply go for a pencil and a good eraser so you can re-use the paper.

I think I'll do an exercise on doubly-linked lists, focused on inserting new nodes into the list, first. Maybe that'll give me a better idea of what to do here.


It might, but only if you write and debug the whole program.



Last edited on
I got it to insert one node into the list at the right place (so that since there was already one element there to start with, after the call to add_ordered() there are two). But I couldn't get it to insert any other elements after that. So it's just Athena and Hera (in that order).

Here's the code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Link *Link::add_ordered(Link *n)
{
	if (n == nullptr)
	{
		return nullptr;
	}

	Link *trav = this;
	if (trav == nullptr)
	{
		return nullptr;
	}

	std::string key = n->m_god.name();

	// for when there's only one element in the list
	if (trav->m_succ == nullptr && trav->m_prev == nullptr)
	{
		if (std::lexicographical_compare(key.begin(), key.end(), trav->m_god.name().begin(), trav->m_god.name().end(), no_case))
		{
			trav = trav->insert(n);
		}
	}
	
	// for when there's more than one element in the list
	while (trav->m_succ != nullptr && trav->m_prev != nullptr)
	{
		if (std::lexicographical_compare(key.begin(), key.end(), trav->m_god.name().begin(), trav->m_god.name().end(), no_case))
		{
			trav = trav->insert(n);
		}
		else
		{
			trav = trav->add(n);
		}
		trav = trav->advance(1);
	}
	return trav;
}


The comments in the code say that add() is for inserting an element after "this" object, and that insert() is for inserting an element before "this" object. That's why I did it like that.

Anyway, about proving that I know how pointers work. Do you want me to treat you like a newbie and give you a lecture?
Hi,

About loops: All 3 types loops can be converted from one to another, because they all have: an initial value; an increment; and an end condition. The for loop is best when an exact number times to loop is known; while loop when a specific condition is required ( like == nullptr)

I said earlier to throw away your current logic, and delete the code from the add_ordered function. You are getting closer with the logic now, but it is still wrong and not specific enough. But your code has reverted back something pretty close to the original. It's not helpful if we continually tell you that you are wrong, but to be fair jlb has been trying really hard to get you to think about how you are doing it, and I am trying to do the same thing.

So start afresh, imagine you are a robot that can only do simple decisions/ actions: is empty; insert; is less than; is greater than ; move next; move previous; . I say this, because as humans we can immediately see what we want to do, but this sometimes clouds our ability to break things down into logical steps.

1. So the first scenario we have is that list is empty. You don't have to write code to prove the list is empty.

2. What is the only action we can sensibly do if the list is empty?

3. Now we have a list with one item in it. You don't have write code to prove there is one or more items in the list.

4. We can already think about what is the condition for inserting into the list, given the value we want to insert , and a value that is already there. while that condition is false, continue through the list until it is true.
5. We need to take into account when we reach the end of the list.

If you think about it, all those 5 steps can actually be combined into 1 conditional made of 2 expressions. Remember we always start our comparisons with the first item in the list.


As I said earlier, this is so simple it is hard. All this business about whether there is 0, 1 or more items in the list was designed to get you to think about things at each step of the process, because your logic only worked when there was 0 or 1 item in the list. It's after you have the basic process, the realization comes of how to combine it all into some thing simple.

You could have had the simple answer given to you a lot earlier, but then you wouldn't have really learnt anything. As in the proverbial : "Give someone a fish" versus "Teach them how to fish". Problem solving is an important thing in coding. Imagine you have a job as a coder: Are you going to ask the person next to you 20 times per day how to fix your problems?

So we have been trying to teach you how to do your own debugging. Usually "Rubber Duck Debugging" (In the process of explaining to the Rubber Duck, the answer is realized) works, as does actual debugging, but this time we have had to resort to first principles :+)

To be fair though, solving logical problems can be really hard. The worst kind I have encountered is when I actually hadn't done anything wrong, it was the data I was looking at was expressed in a different way to what my code was producing.

Anyway, good Luck !!
closed account (48T7M4Gy)
https://github.com/bewuethr/stroustrup_ppp/blob/master/chapter17/chapter17_ex11.cpp
@TheIdeasMan: I'll try to do what you said, but there's one thing about this whole program that you have wrong: because the Link class doesn't have a default constructor, I can't go without adding one element to the list from the beginning. So when add_ordered() is called, there's already at least one element in the list.

This is the constructor:
1
2
Link(const God &god, Link *p = nullptr, Link *s = nullptr)
		: m_god{ god }, m_prev{ p }, m_succ{ s } { }


And, again, there is no default constructor.

I'll try it again.

@kemort: You should know better than to just show me something like that. I want to ask: Why?
Last edited on
@TheIdeasMan: I'll try to do what you said, but there's one thing about this whole program that you have wrong: because the Link class doesn't have a default constructor, I can't go without adding one element to the list from the beginning. So when add_ordered() is called, there's already at least one element in the list.


But that shouldn't make any difference to the logic in the end:

TheIdeasMan wrote:
If you think about it, all those 5 steps can actually be combined into 1 conditional made of 2 expressions. Remember we always start our comparisons with the first item in the list.


In a different program, it's possible to start with a list that only contains a nullptr, using the same logic as you will use here.
Yeah, thanks. I still can't get insert more than one element with add_ordered(), though. No subsequent call to it does anything, either.

Here's my code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Link *Link::add_ordered(Link *n)
{
	if (n == nullptr)
	{
		return nullptr;
	}

	Link *trav = this;
	if (trav == nullptr)
	{
		return nullptr;
	}

	std::string key = n->m_god.name();
	while (trav->m_succ != nullptr && trav->m_prev != nullptr)
	{
		if (std::lexicographical_compare(key.begin(), key.end(), trav->m_god.name().begin(), trav->m_god.name().end(), no_case))
		{
			trav = trav->insert(n);
			trav = trav->m_prev;
		}
		else
		{
			trav = trav->add(n);
			trav = trav->m_succ;
		}
		trav = trav->m_succ;

		if (trav->m_succ == nullptr)
		{
			if (std::lexicographical_compare(key.begin(), key.end(), trav->m_god.name().begin(), trav->m_god.name().end(), no_case))
			{
				trav = trav->insert(n);
			}
			else
			{
				trav = trav->add(n);
			}
		}
	}
	return trav;
}


If my logic is a little more right, but still wrong in general, what am I still missing?
You can't seem to get your original algorithm out of your head, can you? I honestly thought that I had practically given you the answer already.

So, granted there is already 1 item in the list, that means we can start at step 4 in my algorithm.

Answer me this, and only this: What is the condition for deciding when to stop traversing, in order to insert an item into the the list? It's a really simple relational expression, don't over think it. This is step 4, lets get that right first, then move on after that.
@TheIdeasMan: I'll try to do what you said, but there's one thing about this whole program that you have wrong: because the Link class doesn't have a default constructor, I can't go without adding one element to the list from the beginning. So when add_ordered() is called, there's already at least one element in the list.


Actually you can, no constructor is necessary, if you really understood pointers you would know this.

The following is acceptable in C++ no constructor required.

Link *my_gods;

And note the above code might be very useful in helping you deduce the problems with your code.

what am I still missing?

Have you tried following your code manually trying to add a couple of items to your list? You should know that your program is not working with more than two gods so try manually adding three following your code step by step. When doing this the addresses for the three elements will be 1, 2, and 3.


Last edited on
Well I am off to do some fatigue management ZZZZZzzzzzz.......

Am traveling to my interstate job tomorrow, but may have some time Sunday, if this Topic hasn't concluded by then ...............

Good Luck all !!
@TheIdeasMan: Do you mean the condition "as long as the next and previous pointers are not nullptr (because until then, we haven't reached the end of the list)"? Or are you talking about comparing the name strings in the while-loop's condition?
@jlb: Leaving a variable uninitialized can be trouble, right? Especially if it's a pointer variable, since if you just leave it, it could end up pointing at some sort of garbage value you don't want? It's one of those things you shouldn't do even if you can. Like going out of bounds in an array, vector, or string with the subscript [] operator.

But as for that code helping me out with my problem, could you please clarify at least what I should look for? You don't have to give me the answer, just a hint about what I should look for in it.

Also, the condition for the loop. Is it okay to keep going until I hit a "next" pointer that's pointing at nullptr? Or would that be a bad idea?
> Leaving a variable uninitialized can be trouble, right?

Yes, in most situations.

At least in one case, needlessly initialising a variable with an arbitrary value could lead to more trouble. If the object is a scalar (say int), and its initial value is to come from input to the program (stdin, file etc.), leaving it uninitialised at the point of declaration would be a good idea; it allows the compiler to warn us if we try to use it before input has been accepted into it.

For a recent example, see this thread: http://www.cplusplus.com/forum/beginner/222338/
If the programmer had initialised the variable as part of its declaration, the compiler could not have pointed out that there is a serious logical error in the program.


> Especially if it's a pointer variable

Yes. It is a good idea to always explicitly initialise raw pointers.
Last edited on
Yeah, it's better to leave a variable uninitialized when you want to use it to take input. That's true. But sometimes the compiler still warns you that it's uninitialized.

Anyway, I'd appreciate an answer to my questions. I'm only asking for hints. I'm not the type to understand from (too) subtle hints like the ones TheIdeasMan tried to give.
@jlb: Leaving a variable uninitialized can be trouble, right?

My point was that you don't need a constructor when dealing with pointers, even if you initialize it to a nullptr you don't need a constructor. The constructor will not be triggered until you try to allocate or assign memory to the pointer.


But as for that code helping me out with my problem, could you please clarify at least what I should look for?

Since you probably still haven't done any manual (without computer) simulations you need to do something to see what is happening. What can you do to see the state of your variables, if you don't know how to use a debugger?

just a hint about what I should look for in it.

I've already given you several hints that seem to have gone over your head, or just plain ignored. For example:

Didn't you read the code?


Not really, I just skimmed the code. I don't see a reason to thoroughly study the code if you're describing it wrong. Besides after about a half dozen lines I had already noticed several problems.[/quote]

And your code at that point:
1
2
3
4
5
6
Link *Link::add_ordered(Link *n)
{
	Link *trav = this;
	while (trav != nullptr)
	{
		const std::string key = n->m_god.name();	// string being used to compare; is the name string of the "god" to be inserted 


If you didn't understand what I meant in that bolded section you should have asked.

Next you seem to have ignored my suggestion from my post of Sept 28 10:33 am.

I'm going to suggest you make a few further changes to main() so that maybe you can see what is going on.

First, let's get rid of the dynamic memory and just use plain non-pointer instances of the three gods, in the following order, Thor, Odin, Zeus. Once you do that print out the following information (you may want to create a function to make this easier) the address of the instance, the name of the god, the previous pointer and the next pointer. This print should turn out something like the following:


You need to do some work so that you can see what is happening in your code. Notice those highlighted portions of the above post. Have you even tried any of those suggestions?

I doubt it, but you never fail to question minutia, like whether or not to initialize variables and other small non-relevant issues.



Last edited on
Well, yes, I didn't understand what you meant when you said that you'd noticed several problems in my code before. The reason I didn't wasn't because I didn't want to know, but more because I wasn't sure if I should ask that. I felt it was something I needed to figure out on my own.

As for why I didn't make that change to main() that you suggested on Sept. 28 at 10:33 am. When I asked about whether or not I should use "new" in add_ordered(), you said there was no need because main() already does that. So I was just wondering if I'd need to use "new" in add_ordered() if I took it out of main(). But I don't think I should use regular variables (i.e. non-pointer variables) for the Link objects since all of the functions that return a Link object do so by returning a pointer to a Link. I'd have to change those functions' return values to plain Links if I were to make that change to main().

And I already have a print_all() function that prints the information held by a Link object, so do I really need another printing function?

As for the code of mine that you posted: is that the place you're saying I'm wrong at? Or at least one of the places I'm wrong? If so, I'm guessing the main problem is in Line 6 in that snippet. But I don't know what.

Anyway, what variables' values do I need to test out? I'll try to use a debugger for this, since I at least know how to watch values changing in a debugger with breakpoints.
Last edited on
Somehow I have the suspect that you overthink this problem. When adding the node there are two options. If the name of the insert god is greater than the current god insert it after. If the name of the insert god is smaller than the current god insert it before.
I assume ascending order.
If the names are equal handle the error.
Maybe just focus on one case like inserting after - you need to create a special test project for that.
I did include code for that simple thing (sans the error handling for when the strings are equal). But I still can't seem to put the node in there. How am I overthinking it? My problem seems to be that I don't understand a part of it. What places should I breakpoint at and what variables and objects should I add to the watch list (in the debugger)? I really think I should try using it now.
Pages: 1... 34567... 16