2

I have a question that I am stuck with for a couple of days, and I need some help.

I have a function called splitList. The function needs to remove nodes from the original list and move them to a new list only if the previous node and the next node are smaller than the current node.

Some notes:

  • pointer to the original list can change if first node removed
  • first node is removed from the original list and moved to the new list if it is larger than the next node
  • last node is removed from the original list and moved to the new list if it is larger than the previous node
  • if only one node in the original list, it is removed and moved the new list
  • original list can be empty

For example:

Original list: 3->6->1->9->8->4->5

New list from the deleted nodes: 6->9->5

Original list after deletion: 3->1->8->4

Now I have a couple of functions that I have to implement and use, and this what I have done so far:

int deleteFirst(list **lst) // I dont have to use the returned value, but i must return 0 if the list is empty and else 1
{
    // your code:
    list *delNode = *lst;
    if (delNode == NULL)
    {
        printf("List is empty...\n");
        return 0;
    }
    else
    {
        *lst = (*lst)->next;
        delNode->next = NULL;
    }
    return 1;
}

int deleteAfter(list *curr) // same here dont have to use the returned value, but need to return 0 if curr null or point to the last node and else 1
{
    // your code:
    if (curr == NULL)
    {
        return 0;
    }
    else
    {
        list *delItem = curr->next;
        curr->next = delItem->next;
        delItem->next = NULL; 
    }
    return 1;
}

void freeList(list **lst)
{
    // your code:
    while (*lst)
    {
        deleteFirst(lst);
    }
}

Now this is the splitList function, and where I am stuck. I tried to use two pointers, prev and curr. The problem is that I cannot find out how to reconnect the nodes to the new list if it gets deleted. Maybe I don't need to really delete the nodes and only to move them?

int splitList(list **lst, list **new)
{
    // your code:
    int counter = 0;
    list *prev, *curr, *currNew;

    // If the original is empty
    if (*lst == NULL)
    {
        printf("Original list is empty...\n");
        return counter;
    }

    prev = NULL;
    curr = *lst;

    while (curr->next != NULL)
    {
        if (prev == NULL && curr->data > curr->next->data) // If the first element larger than the next element
        {
            deleteFirst(lst);

            if (*new == NULL) // If the new list is empty
            {
                *new = curr;
            }
            else
            {
                currNew = *new;

                while (currNew->next != NULL)
                {
                    currNew = currNew->next;
                }
                currNew->next = curr;
            }
        }
    }
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
ezra
  • 87
  • 1
  • 1
  • 7
  • 3
    You shouldn't delete the node if you're moving it, because that frees the node data. You should just be changing `next` pointers. – Barmar Aug 25 '23 at 19:54
  • 1
    Can the _new_ list have elements in it prior to the call to the split function? I'm assuming not, but asking ... – Craig Estey Aug 25 '23 at 19:55
  • @Barmar so instead of free(delNode), delNode->next = NULL ? – ezra Aug 25 '23 at 19:56
  • 1
    @CraigEstey No its pointing to NULL , no elemenets prior to the call – ezra Aug 25 '23 at 19:57
  • 1
    Yes. And then add it to the end of the `new` list. – Barmar Aug 25 '23 at 19:58
  • you also need `prev->next = curr->next;` – Barmar Aug 25 '23 at 19:59
  • @Barmar ok thanks, so in the while loop in need ``cur = cur->next->next`` ? – ezra Aug 25 '23 at 20:02
  • @ezra First of all show the list definition. – Vlad from Moscow Aug 25 '23 at 20:05
  • 1
    You need to save `cur->next` in another variable because you're going to do `cur->next = NULL` when you remove it from the list. – Barmar Aug 25 '23 at 20:07
  • @VladfromMoscow what do you mean by that ? each list is struct with int data , and pointer next of type list – ezra Aug 25 '23 at 20:11
  • @Barmar understood, thanks – ezra Aug 25 '23 at 20:13
  • On the example, the extracted elements and remaining ones are inverted, or not? By design you shoud move 6 because of 3 and 1, remove 9 because of 1 and 8 being smaller, and remove 5 for being larger than 4 and the last. So the new list would be [ 6 9 5 ] not the remaining list from the original – arfneto Aug 26 '23 at 00:19
  • Also there is sort of an ambiguity here as how to proceeed when following the instructions. if the list is [ 8 7 6 ] and we remove 8 should the new head be considered? – arfneto Aug 26 '23 at 00:21
  • @arfneto i had a mistake in the example , i just fixed it – ezra Aug 26 '23 at 06:42
  • @ezra depending on the way we sweep the list things can change. I believe the description should be more formal. Anyway I will post a version of `splitList`. See if it helps. Please post complete, compilable code. – arfneto Aug 26 '23 at 15:53

3 Answers3

1

The assignment is not easy for beginners like you and me. However we, beginners, should help each other.:)

For starters the function splitList has the return type int

int splitList(list** lst, list** new)

but returns nothing.

Secondly the function should not issue any message as it is doing.

// If the original is empty
if (*lst == NULL)
{
    printf("Original list is empty...\n");
    return counter;
}

It is the caller of the function will decide whether to output a message. Before calling the function the caller can check whether the source list is empty or not. So the above if statement should be removed from the function.

The condition in the while statement

while (curr->next != NULL)

contradicts the description of the task where there is said

if only one node in the original list its removed and moved the new list

This if statement

 if (prev == NULL && curr->data > curr->next->data) 

contradicts another requirement from the description of the task

The function need to remove nodes from the original list and move it to a new list only if the previous node and the next node are smaller than the current node.

This call of the function deleteFirst

deleteFirst(lst);

does not make sense because it just deallocates the memory occupied by the node that should be moved in the destination list:

// ...
else
{
    *lst = (*lst)->next;
    free(delNode);
}

This while loop

currNew = *new;

while (currNew->next != NULL)
{
    currNew = currNew->next;
}

is inefficient.

The data member next of the moved node shall be set to NULL.

I can suggest the following function implementation shown in the demonstration program below.

#include <stdio.h>
#include <stdlib.h>

typedef struct list
{
    int data;
    struct list *next;
} list;

void clear( list **lst )
{
    while (*lst)
    {
        list *current = *lst;
        *lst = ( *lst )->next;
        free( current );
    }
}

size_t assign( list **lst, const int a[], size_t n )
{
    clear( lst );

    size_t count = 0;

    while (n-- && ( *lst = malloc( sizeof( list ) ) ) != NULL)
    {
        ( *lst )->data = *a++;
        ( *lst )->next = NULL;

        lst = &( *lst )->next;

        ++count;
    }

    return count;
}

FILE *display( const list *lst, FILE *fp )
{
    for (; lst != NULL; lst = lst->next)
    {
        fprintf( fp, "%d -> ", lst->data );
    }

    fputs( "null", fp );

    return fp;
}

size_t splitList( list **lst1, list **lst2 )
{
    size_t count = 0;

    if (*lst1 != NULL)
    {
        while (*lst2) lst2 = &( *lst2 )->next;

        for (list *prev = NULL; *lst1 != NULL; )
        {
            if (( prev == NULL || prev->data < ( *lst1 )->data ) &&
                ( ( *lst1 )->next == NULL || ( *lst1 )->next->data < ( *lst1 )->data ))
            {
                list *moved = *lst1;

                *lst1 = ( *lst1 )->next;

                moved->next = NULL;

                *lst2 = moved;

                lst2 = &( *lst2 )->next;

                ++count;
            }

            prev = *lst1;

            if (*lst1 != NULL) lst1 = &( *lst1 )->next;
        }
    }

    return count;
}

int main( void )
{
    const int a[] = { 3, 6, 1, 9, 8, 4, 5 };

    list *lst1 = NULL;

    assign( &lst1, a, sizeof( a ) / sizeof( *a ) );

    fputc( '\n', display( lst1, stdout ) );

    list *lst2 = NULL;

    splitList( &lst1, &lst2 );

    fputc( '\n', display( lst1, stdout ) );
    fputc( '\n', display( lst2, stdout ) );

    clear( &lst1 );
    clear( &lst2 );
}

The program output is

3 -> 6 -> 1 -> 9 -> 8 -> 4 -> 5 -> null
3 -> 1 -> 8 -> 4 -> null
6 -> 9 -> 5 -> null
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
0

I have function called splitList . The function need to remove nodes from the original list and move it to a new list only if the previous node and the next node are smaller than the current node.

If I understood it ok then a linked list will be in fact split.

  • if original list is empty then we will have 2 empty lists
  • we have a rule for head node: if it is larger than the 2nd one then i will be moved
  • we have a rule for tail node: if it is larger than the previous one then it will be moved
  • we have a rule for a single node input list: this node will be moved

About the example and the rules

The example should state formally how should the list be scanned in order to get the expected results. As an example, if the lis is 3 1 9 8 5 as the 1 9 8 trio is scanned the 9 is moved. But now the list has 3 1 8 5 and then the 8 should also go...

About he provided code

Please post something we can compile. A full piece of code.

This is hardly he first linked list related program I believe. So you must have the list implementation fully separated from the code of the problem here.

I will left below a complete example of that. Let us see if it helps

The (provided) Example

We have a 7-node list [ 3 6 1 9 8 4 5 ] that, according to the rules, will be split in a 4-node and a 3-node list with

  • [ 3 1 8 4 ]
  • [ 6 9 5 ]

splitList() it the target function

I will rename it to split_list() just because I expected java functions named splitList but C functions named split_list

precondition: we need a linked list implementation

Today, hours back I posted one here, and since you posted no code for that it is easier to use this one I posted.

Here are the functions for a List, and the List itself:

#include <stdio.h>
#include <stdlib.h>

typedef struct s_n
{
   int         number;
   struct s_n* next;
   struct s_n* prev;
} Node;

typedef struct
{
   unsigned size;
   Node*    head;
   Node*    tail;
} List;

List* create();
List* destroy(List* toGo);
int   add(int data, List* list);
int   delete(int one, List* list);
int   display(List* list, const char* title);
Node* find(int one, List* list);

find() will not be used here. The others work as expected. I believe that you have a similar implementation, or any one. No one is tasked to write a split list function before being tasked to write code for a simple linked list for numbers.

Anyway I will post the complete code at the end.

back to split_list()

This is the prototype:

    List* split_list(List* original);

Note that the original list is modified, so the returned List is not the only result of split_list() execution.

build_list() a helper function

This 4-line one helps to create a list in one line

List* builder(unsigned size, int V[])
{
    List* nl = create();
    if (nl == NULL) return NULL;
    for (unsigned i = 0; i < size; i += 1) add(V[i], nl);
    return nl;
};

As in this code:

    my_list = builder(7, (int[]){3, 6, 1, 9, 8, 4, 5});

That returns the list of the example.

a possible split_list()

List* split_list(List* orig)
{
    if (orig == NULL) return NULL;
    List* sec = create();  // the new list
    // rule 1: if original list is empty then it is done
    if (orig->size == 0) return NULL;
    // rule 2: if there is a single node on list it is moved
    if (orig->size == 1)
    {
        add(orig->head->number, sec);
        delete (orig->head->number, orig);
        return sec;  // that is all
    }
    // rule 3: head node
    if (orig->head->number > orig->head->next->number)
    {
        add(orig->head->number, sec);
        delete (orig->head->number, orig);
    };

    // assuming that there is no re-scanning of the list
    while (orig->size > 3)
    {   // we need at least 3
        Node* p = orig->head->next;  // the second one
        // now we need to compare trios 1-2-3 2-3-4 and so
        // on until the last one ends in the tail
        //  we have 'size' so we need to look 
        //  until the one before the last
        char change = 0;
        for (unsigned ix = 0; ix < orig->size - 2; ix += 1)
        {  // just for readability
            int l = p->prev->number;
            int c = p->number;
            int r = p->next->number;
            // so [ l, c, r ] are the numbers
            // rule 0 here
            if ((c > l) && (c > r))
            {  // then move this one, c
                add(c, sec);
                delete (c, orig);  // change in orig
                change = 1;
                break;  // will redefine pointers
            };
            p = p->next;
        };
        if (change == 0) break; // no extraction in loop
    };
    // rule 5: remove tail node if greater than predecessor
    Node* p = orig->tail;
    if (p->number > p->prev->number)
    {
        add(p->number, sec);
        // the list can have duplicates?
        delete_tail(orig);
    }
    return sec;
}

This one is possibly correct. But for the example it does not return the same values. But are also my view of the application of the rules :)

main.c for this test

Due to the generic implementation of List the code is a bit simplistic:

int main(void)
{
    List* my_list = NULL;
    List* filter  = NULL;

    my_list = builder(7, (int[]){3, 6, 1, 9, 8, 4, 5});
    display(my_list, "\n\t==> Original list set 1");
    filter = split_list(my_list);
    display(my_list, "\tOriginal items left");
    display(filter, "\tExtracted items");
    my_list = destroy(my_list);
    filter  = destroy(filter);

    return 0;
}

The lists are declared at the top so we can copy and paste the block below to test for other lists...

output for this test


        ==> Original list set 1
List size: 7
  [ 3 6 1 9 8 4 5 ]

        Original items left
List size: 3
  [ 3 1 4 ]

        Extracted items
List size: 4
  [ 6 9 8 5 ]

The sequence as I see is

  • move 6 because of sequence 3 6 1
    • list is 3 1 9 8 4 5
  • skips 3 1 9
  • move 9 due to 1 9 8
    • list is now 3 1 8 4 5
  • skips 3 1 8
  • move 8 due to 1 8 4
    • list is now 3 1 4 5
  • skips 3 1 4
  • skips 1 4 5
  • move 5 since it is tail node and greater than 4

So the list is split in:

- `3 1 4` that is left on original list
- `6 9 8 5` that goes to the second one

Complete code for this example

main.c

This file contains also builder() and split_list()

#include "so_list.h"
List* split_list(List*);
List* builder(unsigned, int[]);

int main(void)
{
    List* my_list = NULL;
    List* filter  = NULL;

    my_list = builder(7, (int[]){3, 6, 1, 9, 8, 4, 5});
    display(my_list, "\n\t==> Original list set 1");
    filter = split_list(my_list);
    display(my_list, "\tOriginal items left");
    display(filter, "\tExtracted items");
    my_list = destroy(my_list);
    filter  = destroy(filter);

    return 0;
}

List* builder(unsigned size, int V[])
{
    List* nl = create();
    if (nl == NULL) return NULL;
    for (unsigned i = 0; i < size; i += 1) add(V[i], nl);
    return nl;
};

List* split_list(List* orig)
{
    if (orig == NULL) return NULL;
    List* sec = create();  // the new list
    // rule 1: if original list is empty then it is done
    if (orig->size == 0) return NULL;
    // rule 2: if there is a single node on list it is moved
    if (orig->size == 1)
    {
        add(orig->head->number, sec);
        delete (orig->head->number, orig);
        return sec;  // that is all
    }
    // rule 3: head node
    if (orig->head->number > orig->head->next->number)
    {
        add(orig->head->number, sec);
        delete (orig->head->number, orig);
    };

    // assuming that there is no re-scanning of the list
    while (orig->size > 3)
    {   // we need at least 3
        Node* p = orig->head->next;  // the second one
        // now we need to compare trios 1-2-3 2-3-4 and so
        // on until the last one ends in the tail
        //  we have 'size' so we need to look 
        //  until the one before the last
        char change = 0;
        for (unsigned ix = 0; ix < orig->size - 2; ix += 1)
        {  // just for readability
            int l = p->prev->number;
            int c = p->number;
            int r = p->next->number;
            // so [ l, c, r ] are the numbers
            // rule 0 here
            if ((c > l) && (c > r))
            {  // then move this one, c
                add(c, sec);
                delete (c, orig);  // change in orig
                change = 1;
                break;  // will redefine pointers
            };
            p = p->next;
        };
        if (change == 0) break; // no extraction in loop
    };
    // rule 5: remove tail node if greater than predecessor
    Node* p = orig->tail;
    if (p->number > p->prev->number)
    {
        add(p->number, sec);
        // the list can have duplicates?
        delete_tail(orig);
    }
    return sec;
}

// https://stackoverflow.com/questions/76980148/
// move-node-from-one-linked-list-to-another-in
// -certain-conditions

so_list.h() header file

#include <stdio.h>
#include <stdlib.h>

typedef struct s_n
{
    int         number;
    struct s_n* next;
    struct s_n* prev;
} Node;

typedef struct
{
    unsigned size;
    Node*    head;
    Node*    tail;
} List;

List* create();
List* destroy(List* toGo);
int   add(int data, List* list);
int   delete(int one, List* list);
int   delete_tail(List* list);
int   display(List* list, const char* title);
Node* find(int one, List* list);

so_list.c: simple implementation for list

#include "so_list.h"

List* create()
{
    List* one = malloc(sizeof(List));
    if (one == NULL) return NULL;
    one->size = 0;
    one->head = NULL;
    one->tail = NULL;
    return one;
}

int delete(int data, List* list)
{  // delete the 1st node that contains 'data'
    if (list == NULL) return -1;
    if (list->size == 1)
    {   // now it is empty
        free(list->head);
        list->head = list->tail = NULL;
        list->size              = 0;
        return 0;  // ok
    }
    Node* p = list->head; // there are more than 1
    for (unsigned i = 0; i < list->size; i += 1)
    {
        if (p->number == data)
        {  // this is the one
            if (p == list->head)
            {   // delete head element
                list->head->next->prev = p->next;
                list->head             = p->next;
                if (list->size == 2) list->tail = list->head;
            }
            else
            {
                if (p == list->tail)
                {   // delete last
                    list->tail = p->prev;
                    p->prev->next = NULL;
                    if (list->size == 2)
                        list->head = list->tail;
                }
                else
                {   // p is in the middle of the list
                    p->prev->next = p->next;
                    p->next->prev = p->prev;
                }
            }
            list->size -= 1;
            free(p);
            return 0;  // ok
        }
        p    = p->next;  // go ahead
    }
    return -2;  // not found
};

List* destroy(List* gone)
{
    if (gone == NULL) return NULL;
    for (unsigned i = 0; i < gone->size; i += 1)
    {
        Node* p = gone->head->next;
        free(gone->head);
        gone->head = p;
    }
    free(gone);
    return NULL;
};

int add(int data, List* list)
{  // insert at last element
    if (list == NULL) return -1;
    Node* p   = malloc(sizeof(Node));
    p->number = data;  // data copied to node
    p->next   = NULL;  // inserted at tail
    if (list->size == 0)
    {
        list->head = list->tail = p;
        list->size              = 1;
        p->prev                 = NULL;
        return 0;
    }
    list->tail->next = p;
    p->prev          = list->tail;
    list->tail       = p;
    list->size += 1;  // count it in
    return 0;
}

int delete_tail(List* list)
{  
    if (list == NULL) return -1;
    if (list->size == 0) return -2;
    if (list->size == 1)
    {  // now it is empty
        free(list->head);
        list->head = list->tail = NULL;
        list->size              = 0;
        return 0;  // ok
    }
    Node* p = list->tail;
    list->tail    = p->prev;
    list->size -= 1;
    free(p);
    return 0;  // ok
}

int display(List* list, const char* title)
{
    if (title != NULL) printf("%s", title);
    if (list == NULL)
    {
        printf("\nList does not exist\n");
        return 0;
    }
    printf("\nList size: %u\n  [ ", list->size);
    Node* p = list->head;
    for (unsigned i = 0; i < list->size; i += 1)
    {
        printf("%d ", p->number);
        p = p->next;
    }
    printf("]\n\n");
    return 0;
};

Node* find(int data, List* list)
{  // return address of the first node with 'data'
    // or NULL if not found. Do not consider the
    // 'list` as sorted
    if (list == NULL)
    {
        fprintf(stderr, "\nfind(): List does not exist\n");
        return NULL;
    }
    Node* p = list->head;
    for (unsigned i = 0; i < list->size; i += 1)
    {
        if (p->number == data) return p;
        p = p->next;
    }
    return NULL;
};

copy vs move a Node from one list to another

The code here only copy data, even the moved values are copies.

If it is really essencial to move the nodes from one list to another, keep in mind that find() returns the node address and could be used for a move operation. By the other side, care must be taken with duplicate values in order to move the exact one node and not a clone.

This is the reason why a delete_tail() must exist: delete() deletes the first node with the supplied value. It may not be the tail and instead just a node with the same value.

Maybe it would be too much work for a simple exercise that even misses a formal specification

arfneto
  • 1,227
  • 1
  • 6
  • 13
-1

Let me start by saying that it is unfortunate that you are forced to implement and use the deleteFirst and deleteAfter functions. I understand this is a given, but it must be said that:

  • It is confusing that apparently these functions are not supposed to free the memory of the node that is deleted. The delete word would therefore better be replaced with extract or detach or something along those lines, just to indicate to the caller that the node is still available and can be used to add to another list if desired.

  • It would be nice if there were only one function that would cover both cases. It is quite elegant to have such a function return the head of the list instead of this 0/1 integer. Returning 0 if the list is empty is really not helpful for the caller, who is quite able to test for themselves if the list was empty or not.

There is an issue with your freeList function, because that function certainly needs to really free up the memory. This again highlights that the name of the function deleteFirst is misleading: you still need to free the node's memory. So define it as follows:

void freeList(list **lst) {
    while (*lst) {
        list *temp = *lst;
        deleteFirst(lst);
        free(temp); // Must still free the node.
    }
}

Now to splitList: obviously that function is not yet complete as you only deal with one case. Concerning your question, the idea is to make 2 steps forward after you have "deleted" a node. In other words, prev should reference the successor of the already deleted node.

Some other comments:

  • Don't have currNew start from the target list's head. Instead keep it pointing at the last appended node. This way you don't need to loop over and over again over the target list.

  • Deal with all cases in one place: whether curr is the first, the last or other node. This means your while condition should be on curr, not curr->next: you want to deal with the tail node in the loop too.

Here is how you could do it:

int splitList(list **lst, list **new) {
    int counter = 0;
    list *prev = NULL,
         *curr = *lst,
         *next,
         *currNew = NULL;
    while (curr) { // Also the last node can be moved, so test curr
        next = curr->next; 
        // Deal with all cases together
        if ((!prev || prev->data < curr->data) && 
                    (!next || curr->data > next->data))  {
            if (!prev) {
                deleteFirst(lst); // Use the obligatory function
            } else {
                deleteAfter(prev); // Use the obligatory function
            }
            // Append the detached node (still available!) to target list
            if (!*new) {
                *new = curr;
            } else {
                currNew->next = curr;
            }
            currNew = curr; // Track the tail node in the target list
            counter++;
            if (!next) { // Nothing more to do
                break;
            }
            prev = next; // Make one extra step forward
        } else {
            prev = curr;
        }
        curr = prev->next;
    }
    return counter;
}
trincot
  • 317,000
  • 35
  • 244
  • 286
  • thank you so much, about the delete functions you right it very misleading, and i talked about that with my instructor – ezra Aug 26 '23 at 13:15