ncurses set_field_buffer and a two-byte UTF-8 character - ncurses

How can I load the ncurses set_field_buffer with a two-byte UTF-8 character?
Some context. I am attempting to build an ncurses form to capture latitude and longitude numbers in degrees, minutes and seconds. In the spirit of standing on the shoulders of the giants, I have adapted this code: https://gist.github.com/alan-mushi/c8a6f34d1df18574f643. I have also:
Added #include <locale.h> to the code.
Added setlocale(LC_ALL, ""); to the code.
Linked to ncurses, not ncursesw.
The following picture shows how the form turned out.
enter image description here
That letter M after the 137 is the problem. It has to be the degrees sign (°).
I loaded all of the characters shown on the form using set_field_buffer. My code for that is:
set_field_buffer(fields[0], 0, "Site A:");
set_field_buffer(fields[1], 0, "w"); /* Station: n, s, w or e. */
set_field_buffer(fields[2], 0, "137"); /* Degrees */
set_field_buffer(fields[3], 0, "\u00B0"); /* Degrees symbol*/
set_field_buffer(fields[4], 0, "22"); /* Minutes */
set_field_buffer(fields[5], 0, "'"); /* Minutes symbol*/
set_field_buffer(fields[6], 0, "45.92"); /* Seconds */
set_field_buffer(fields[7], 0, "\u0022"); /* Seconds symbol*/
Although field 3 is set for "\u00B0", the letter M shows. Alan Mushi's code allows you to press F2 to see the contents of the buffers. The "\u00B0" is rendered as M-B. That suggests to me that set_field_buffer accepts two-byte UTF-8 characters but does not render them correctly. I cannot find a specification detailing what is allowed.

The call to setlocale has to be before the initscr call. Otherwise, ncurses will not use that information, and you will see the data presented that way:
If the locale is not initialized, the library assumes that characters
are printable as in ISO-8859-1, to work with certain legacy programs.
You should initialize the locale and not rely on specific details of
the library when the locale has not been setup.
The bytes used for \u00b0 are \302 and \260 (octal). If you link with ncurses rather than ncursesw, you will of course see an odd display, since the (narrow) ncurses library knows nothing about UTF-8. It relies upon the <ctype.h> macros such as isprint to tell it whether a character is printable in the current locale. For GNU libc (and some others), those macros return false for all of the codes from \177 to \377, making ncurses display those as a printable form described in the keyname manpage.
Printable characters are displayed as themselves, e.g., a one-character string containing the key.
Control characters are displayed in the ^X notation.
DEL (character 127) is displayed as ^?.
Values above 128 are either meta characters (if the screen has not
been initialized, or if meta(3x) has been called with a TRUE parameter), shown in the M-X notation, or are displayed as themselves.
In the latter case, the values may not be printable; this follows
the X/Open specification.
Though not emphasized in the manual page, unctrl and keyname give the same result for those characters.
The addch manpage alludes to this (mentioning ^X because it is the most often encountered):
If ch is any other control character, it is drawn in ^X notation.
Calling winch after adding a control character does not return the
character itself, but instead returns the ^-representation of the control character.
Now, using ncursesw (because the manual page indicates this is required for a locale using "wide characters" such as Unicode):
--- fields_magic.c.orig 2020-07-08 17:49:58.000000000 -0400
+++ fields_magic.c 2020-07-08 17:58:01.650425188 -0400
## -4,15 +4,16 ##
* How to run:
* gcc -Wall -Werror -g -pedantic -o test fields_magic.c -lform -lncurses
*/
-#include <ncurses/ncurses.h>
-#include <ncurses/form.h>
+#include <curses.h>
+#include <form.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
+#include <locale.h>
static FORM *form;
-static FIELD *fields[5];
+static FIELD *fields[15];
static WINDOW *win_body, *win_form;
/*
## -55,7 +56,7 ##
for (i = 0; fields[i]; i++) {
printw("%s", trim_whitespaces(field_buffer(fields[i], 0)));
- if (field_opts(fields[i]) & O_ACTIVE)
+ if (field_opts(fields[i]) & (int) O_ACTIVE)
printw("\"\t");
else
printw(": \"");
## -102,10 +103,11 ##
wrefresh(win_form);
}
-int main()
+int main(void)
{
int ch;
+ setlocale(LC_ALL, "");
initscr();
noecho();
cbreak();
## -126,10 +128,13 ##
fields[4] = NULL;
assert(fields[0] != NULL && fields[1] != NULL && fields[2] != NULL && fields[3] != NULL);
- set_field_buffer(fields[0], 0, "label1");
- set_field_buffer(fields[1], 0, "val1");
- set_field_buffer(fields[2], 0, "label2");
- set_field_buffer(fields[3], 0, "val2");
+ set_field_buffer(fields[0], 0, "w"); /* Station: n, s, w or e. */
+ set_field_buffer(fields[1], 0, "137"); /* Degrees */
+ set_field_buffer(fields[2], 0, "\u00B0"); /* Degrees symbol*/
+ set_field_buffer(fields[3], 0, "22"); /* Minutes */
+ set_field_buffer(fields[4], 0, "'"); /* Minutes symbol*/
+ set_field_buffer(fields[5], 0, "45.92"); /* Seconds */
+ set_field_buffer(fields[6], 0, "\""); /* Seconds symbol*/
set_field_opts(fields[0], O_VISIBLE | O_PUBLIC | O_AUTOSKIP);
set_field_opts(fields[1], O_VISIBLE | O_PUBLIC | O_EDIT | O_ACTIVE);
That "\u0022" is not a valid character string (the compiler can tell you that).

Thank you for your comment. In fact the call to setlocale(LC_ALL, "") is before initscr() and the locale on my Debian Bullseye installation is set to UTF-8. I compile the code linked to ncurses and form.
As a sanity check, I'd be grateful if you would run my code and verify that the \u00B0 character does not render correctly.
My code follows:
#include <ncurses.h>
#include <form.h>
#include <cassert> // Was assert.h See https://stackoverflow.com/questions/60127743/changes-not-showing-in-field-in-ncurses
#include <cstdlib> // Was stdlib.h
#include <string.h>
#include <ctype.h>
#include <locale.h>
static FORM *form;
static FIELD *fields[11];
static WINDOW *win_body, *win_form;
/*
* This is useful because ncurses fill fields blanks with spaces.
*/
static char* trim_whitespaces(char *str) {
char *end;
// trim leading space
while(isspace(*str))
str++;
if(*str == 0) // all spaces?
return str;
// trim trailing space
end = str + strnlen(str, 128) - 1;
while(end > str && isspace(*end))
end--;
// write new null terminator
*(end+1) = '\0';
return str;
}
static void driver(int ch) {
int i;
switch (ch) {
case KEY_F(2):
// Or the current field buffer won't be sync with what is displayed
form_driver(form, REQ_NEXT_FIELD);
form_driver(form, REQ_PREV_FIELD);
move(LINES-3, 2);
for (i = 0; fields[i]; i++) {
printw("%s", trim_whitespaces(field_buffer(fields[i], 0)));
if (field_opts(fields[i]) & O_ACTIVE)
printw("\"\t");
else
printw(": \"");
}
refresh();
pos_form_cursor(form);
break;
case KEY_DOWN:
form_driver(form, REQ_NEXT_FIELD);
form_driver(form, REQ_END_LINE);
break;
case KEY_UP:
form_driver(form, REQ_PREV_FIELD);
form_driver(form, REQ_END_LINE);
break;
case KEY_LEFT:
form_driver(form, REQ_PREV_CHAR);
break;
case KEY_RIGHT:
form_driver(form, REQ_NEXT_CHAR);
break;
// Delete the char before cursor
case KEY_BACKSPACE:
case 127:
form_driver(form, REQ_DEL_PREV);
break;
// Delete the char under the cursor
case KEY_DC:
form_driver(form, REQ_DEL_CHAR);
break;
default:
form_driver(form, ch);
break;
}
wrefresh(win_form);
}
int main() {
int ch;
setlocale(LC_ALL, "");
initscr();
noecho();
cbreak();
keypad(stdscr, TRUE);
win_body = newwin(24, 80, 0, 0);
assert(win_body != NULL);
box(win_body, 0, 0);
win_form = derwin(win_body, 20, 78, 3, 1);
assert(win_form != NULL);
box(win_form, 0, 0);
mvwprintw(win_body, 1, 2, "Press F1 to quit and F2 to print fields content");
fields[0] = new_field(1, 7, 0, 0, 0, 0); // Site A:
fields[1] = new_field(1, 1, 0, 9, 0, 0); // Station: n, s, w or e.
fields[2] = new_field(1, 3, 0, 12,0, 0); // Degrees
fields[3] = new_field(1, 1, 0, 15,0, 0); // Degrees symbol
fields[4] = new_field(1, 2, 0, 17,0, 0); // Minutes
fields[5] = new_field(1, 1, 0, 19,0, 0); // Minutes symbol
fields[6] = new_field(1, 5, 0, 21,0, 0); // Seconds
fields[7] = new_field(1, 1, 0, 26,0, 0); // Seconds symbol
fields[8] = new_field(1, 10, 2, 0, 0, 0);
fields[9] = new_field(1, 40, 2, 15, 0, 0);
fields[10] = NULL;
assert(fields[0] != NULL && fields[1] != NULL && fields[2] != NULL && fields[3] != NULL && fields[4] != NULL && fields[5] != NULL && fields[6] != NULL && fields[7] != NULL && fields[8] != NULL && fields[9] != NULL);
set_field_buffer(fields[0], 0, "Site A:");
set_field_buffer(fields[1], 0, "w"); // Station: n, s, w or e.
set_field_buffer(fields[2], 0, "137"); // Degrees
set_field_buffer(fields[3], 0, "\u00B0"); // Degrees symbol
set_field_buffer(fields[4], 0, "22"); // Minutes
set_field_buffer(fields[5], 0, "'"); // Minutes symbol
set_field_buffer(fields[6], 0, "45.92"); // Seconds
set_field_buffer(fields[7], 0, "\u0022"); // Seconds symbol
set_field_buffer(fields[8], 0, "Site B:");
set_field_buffer(fields[9], 0, "");
set_field_opts(fields[0], O_VISIBLE | O_PUBLIC | O_AUTOSKIP);
set_field_opts(fields[1], O_VISIBLE | O_PUBLIC | O_EDIT | O_ACTIVE | O_BLANK | O_STATIC); // Station: n, s, w or e.
set_field_opts(fields[2], O_VISIBLE | O_PUBLIC | O_EDIT | O_ACTIVE | O_BLANK | O_STATIC); // Degrees
set_field_opts(fields[3], O_VISIBLE | O_PUBLIC | O_AUTOSKIP); // Degrees symbol
set_field_opts(fields[4], O_VISIBLE | O_PUBLIC | O_EDIT | O_ACTIVE | O_BLANK | O_STATIC); // Minutes
set_field_opts(fields[5], O_VISIBLE | O_PUBLIC | O_AUTOSKIP); // Minutes symbol
set_field_opts(fields[6], O_VISIBLE | O_PUBLIC | O_EDIT | O_ACTIVE | O_BLANK | O_STATIC); // Seconds
set_field_opts(fields[7], O_VISIBLE | O_PUBLIC | O_AUTOSKIP); // Seconds symbol
set_field_opts(fields[8], O_VISIBLE | O_PUBLIC | O_AUTOSKIP);
set_field_opts(fields[9], O_VISIBLE | O_PUBLIC | O_EDIT | O_ACTIVE);
set_field_back(fields[1], A_UNDERLINE); // Station: n, s, w or e.
set_field_back(fields[2], A_UNDERLINE); // Degrees
set_field_back(fields[4], A_UNDERLINE); // Minutes
set_field_back(fields[6], A_UNDERLINE); // Seconds
set_field_back(fields[9], A_UNDERLINE);
form = new_form(fields);
assert(form != NULL);
set_form_win(form, win_form);
set_form_sub(form, derwin(win_form, 18, 76, 1, 1));
post_form(form);
refresh();
wrefresh(win_body);
wrefresh(win_form);
while ((ch = getch()) != KEY_F(1))
driver(ch);
unpost_form(form);
free_form(form);
free_field(fields[0]);
free_field(fields[1]);
free_field(fields[2]);
free_field(fields[3]);
free_field(fields[4]);
free_field(fields[5]);
free_field(fields[6]);
free_field(fields[7]);
free_field(fields[8]);
free_field(fields[9]);
free_field(fields[10]);
delwin(win_form);
delwin(win_body);
endwin();
return 0;
}

Related

Simple ncurses app not reacting to arrow keys

I compiled this simple ncurses program and the up down keys are unresponsive.
Any idea why this does not work?
I am using Fedora Linux 5.7.16-200.fc32.x86_64 the default terminal emulator is XTerm(351). I got no errors or warning building ncurses or making the app.
cc -o test test.c -lncurses
/* test.c */
#include <stdlib.h>
#include <stdio.h>
#include <curses.h>
int main(void) {
WINDOW * mainwin, * childwin;
int ch;
/* Set the dimensions and initial
position for our child window */
int width = 23, height = 7;
int rows = 25, cols = 80;
int x = (cols - width) / 2;
int y = (rows - height) / 2;
/* Initialize ncurses */
if ( (mainwin = initscr()) == NULL ) {
fprintf(stderr, "Error initialising ncurses.\n");
exit(EXIT_FAILURE);
}
/* Switch of echoing and enable keypad (for arrow keys) */
noecho();
keypad(mainwin, TRUE);
/* Make our child window, and add
a border and some text to it. */
childwin = subwin(mainwin, height, width, y, x);
box(childwin, 0, 0);
mvwaddstr(childwin, 1, 4, "Move the window");
mvwaddstr(childwin, 2, 2, "with the arrow keys");
mvwaddstr(childwin, 3, 6, "or HOME/END");
mvwaddstr(childwin, 5, 3, "Press 'q' to quit");
refresh();
/* Loop until user hits 'q' to quit */
while ( (ch = getch()) != 'q' ) {
switch ( ch ) {
case KEY_UP:
if ( y > 0 )
--y;
break;
case KEY_DOWN:
if ( y < (rows - height) )
++y;
break;
case KEY_LEFT:
if ( x > 0 )
--x;
break;
case KEY_RIGHT:
if ( x < (cols - width) )
++x;
break;
case KEY_HOME:
x = 0;
y = 0;
break;
case KEY_END:
x = (cols - width);
y = (rows - height);
break;
}
mvwin(childwin, y, x);
}
/* Clean up after ourselves */
delwin(childwin);
delwin(mainwin);
endwin();
refresh();
return EXIT_SUCCESS;
}
The example doesn't repaint the child-window (so nothing seems to happen), and doesn't use cbreak (so nothing happens until you press Return (i.e., newline).
I did this change to see what it does:
> diff -u foo.c.orig foo.c
--- foo.c.orig 2020-08-30 06:00:47.000000000 -0400
+++ foo.c 2020-08-30 06:02:50.583242935 -0400
## -29,6 +29,7 ##
/* Switch of echoing and enable keypad (for arrow keys) */
+ cbreak();
noecho();
keypad(mainwin, TRUE);
## -85,6 +86,7 ##
}
mvwin(childwin, y, x);
+ wrefresh(childwin);
}
Some terminal descriptions may use the same character ControlJ for cursor-down (and get mapped into KEY_ENTER rather than KEY_DOWN—see source code). After allowing for the other two problems, you may be seeing that.

How to move a FIELD which is inside a WINDOW

I'm new to stackoverflow.com so I'll do my best to explain my problem :)
I'm currently working on an "arcade game" project for my school and I have made a menu using ncurses where I can choose games / graphical library to use. I have made this menu resizeable so when the terminal shrunk, I move the windows and resize them (layout-like). Now I have to incorporate a text field to enter your name and I want that to be resizeable as well.
I have a FIELD inside a FORM inside a WINDOW in my program and no matter what I do, the window moves but the field stay in place...
I have made a test-program where I try to move a text field using arrow keys. As expected the little box (the window) moves but not the field.
#include <curses.h>
#include <form.h>
void move_window(WINDOW *window, int y, int x)
{
//clear();
wclear(window); // DON'T KNOW WHY IT DOESN'T CLEAR SCREEN
mvprintw(0, 0, "Y[%d] : X[%d]", y, x);
mvwin(window, y - 1, x - 1);
box(window, 0, 0);
wrefresh(window);
//refresh();
}
int main()
{
WINDOW *window;
FIELD *fields[2];
FORM *form;
int x = 20;
int y = 20;
int ch;
initscr();
keypad(stdscr, TRUE);
noecho();
cbreak();
fields[0] = new_field(1, 10, y, x, 0, 0);
fields[1] = NULL;
set_field_opts(fields[0], O_VISIBLE | O_PUBLIC | O_EDIT | O_ACTIVE | O_STATIC);
set_field_back(fields[0], A_UNDERLINE);
window = newwin(3, 12, y - 1, x - 1);
form = new_form(fields);
set_form_win(form, window);
post_form(form);
refresh();
move_window(window, y, x);
while ((ch = getch()) != 27) {
switch (ch) {
case KEY_LEFT:
x--;
move_window(window, y, x);
break;
case KEY_RIGHT:
x++;
move_window(window, y, x);
break;
case KEY_UP:
y--;
move_window(window, y, x);
break;
case KEY_DOWN:
y++;
move_window(window, y, x);
break;
case KEY_BACKSPACE:
case 127:
form_driver(form, REQ_DEL_PREV);
break;
default:
form_driver(form, ch);
break;
}
}
unpost_form(form);
free_form(form);
free_field(fields[0]);
endwin();
return 0;
}
I compile using : gcc ncurse.cpp -lncurses -lform && ./a.out
Can you explain to me what I am doing wrong in this ? I didn't found anything to solve this myself :/
PS : Sorry for my rusty english...

How to return a int converted to char array back to main for displaying it

My doubts are as follows :
1 : how to send 'str' from function 'fun' , So that i can display it in main function.
2 : And is the return type correct in the code ?
2 : the current code is displaying some different output.
char * fun(int *arr)
{
char *str[5];
int i;
for(i=0;i<5;i++)
{
char c[sizeof(int)] ;
sprintf(c,"%d",arr[i]);
str[i] = malloc(sizeof(c));
strcpy(str[i],c);
}
return str;
}
int main()
{
int arr[] = {2,1,3,4,5},i;
char *str = fun(arr);
for(i=0;i<5;i++)
{
printf("%c",str[i]);
}
return 0;
}
how to send 'str' from function 'fun' , So that i can display it in main function.
This is the way:
char* str = malloc( size );
if( str == NULL ) {
fprintf( stderr,"Failed to malloc\n");
}
/* Do stuff with str, use str[index],
* remember to free it in main*/
free(str);
And is the return type correct in the code ?
No, Probably char** is the one you need to return.
the current code is displaying some different output.
Consider explaining what/why do you want to do ? The way you have written, seems completely messed up way to me. You're passing array of integer but not its length. How is the fun() supposed to know length of array? Another problem is array of pointers in fun().
You can't write a int to a char (See the both size). So I used char array instead.
However, I'm not sure if this is what you want to do (might be a quick and dirty way of doing it):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char**
fun(int *arr, int size)
{
char **str = malloc( sizeof(char*)*size );
if( str == NULL ) {
fprintf( stderr, "Failed malloc\n");
}
int i;
for(i=0;i<5;i++) {
str[i] = malloc(sizeof(int));
if( str == NULL ) {
fprintf( stderr, "Failed malloc\n");
}
sprintf(str[i],"%d",arr[i]);
}
return str;
}
int
main()
{
int arr[] = {2,1,3,4,5},i;
char **str = fun(arr, 5);
for(i=0;i<5;i++) {
printf("%s\n",str[i]);
free(str[i]);
}
free(str);
return 0;
}
I made these changes to your code to get it working:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char **fun(int *arr)
{
char **str = malloc(sizeof(char *) * 5);
int i;
for(i = 0; i < 5; i++) {
if ((arr[i] >= 0) && (arr[i] <= 9)) {
char c[2] ;
sprintf(c, "%d", arr[i]);
str[i] = (char *) malloc(strlen(c) + 1);
strcpy(str[i],c);
}
}
return str;
}
int main()
{
int arr[] = {2, 1, 3, 4, 5}, i;
char **str = fun(arr);
for(i = 0; i < 5; i++) {
printf("%s", str[i]);
free(str[i]);
}
printf("\n");
free(str);
return 0;
}
Output
21345
I added a check to make sure that arr[i] is a single digit number. Also, returning a pointer to a stack variable will result in undefined behavior, so I changed the code to allocate an array of strings. I don't check the return value of the malloc calls, which means this program could crash due to a NULL pointer reference.
This solution differs from the others in that it attempts to answer your question based on the intended use.
how to send 'str' from function 'fun' , So that i can display it in main function.
First, you need to define a function that returns a pointer to array.
char (*fun(int arr[]))[]
Allocating variable length strings doesn't buy you anything. The longest string you'll need for 64bit unsigned int is 20 digits. All you need is to allocate an array of 5 elements of 2 characters long each. You may adjust the length to suit your need. This sample assumes 1 digit and 1 null character. Note the allocation is done only once. You may choose to use the length of 21 (20 digits and 1 null).
For readability on which values here are related to the number of digits including the terminator, I'll define a macro that you can modify to suit your needs.
#define NUM_OF_DIGITS 3
You can then use this macro in the whole code.
char (*str)[NUM_OF_DIGITS] = malloc(5 * NUM_OF_DIGITS);
Finally the receiving variable in main() can be declared and assigned the returned array.
char (*str)[NUM_OF_DIGITS] = fun(arr);
Your complete code should look like this:
Code
char (*fun(int arr[]))[]
{
char (*str)[NUM_OF_DIGITS] = malloc(5 * NUM_OF_DIGITS);
int i;
for(i=0;i<5;i++)
{
snprintf(str[i],NUM_OF_DIGITS,"%d",arr[i]); //control and limit to single digit + null
}
return str;
}
int main()
{
int arr[] = {24,1,33,4,5},i;
char (*str)[NUM_OF_DIGITS] = fun(arr);
for(i=0;i<5;i++)
{
printf("%s",str[i]);
}
free(str);
return 0;
}
Output
2413345
With this method you only need to free the allocated memory once.

How can I safely and simply read a line of text from a file or stdin?

Given that fgets only sometimes includes a linebreak, and fscanf is inherently unsafe, I would like a simple alternative to read text line-by-line from a file. Is this page a good place to find such a function?
Yes. The following function should satisfy this requirement without creating any damaging security flaws.
/* reads from [stream] into [buffer] until terminated by
* \r, \n or EOF, or [lastnullindex] is reached. Returns
* the number of characters read excluding the terminating
* character. [lastnullindex] refers to the uppermost index
* of the [buffer] array. If an error occurs or non-text
* characters (below space ' ' or above tilde '~') are
* detected, the buffer will be emptied and 0 returned.
*/
int readline(FILE *stream, char *buffer, int lastnullindex) {
if (!stream) return 0;
if (!buffer) return 0;
if (lastnullindex < 0) return 0;
int inch = EOF;
int chi = 0;
while (chi < lastnullindex) {
inch = fgetc(stream);
if (inch == EOF || inch == '\n' || inch == '\r') {
buffer[chi] = '\0';
break;
} else if (inch >= ' ' && inch <= '~') {
buffer[chi] = (char)inch;
chi++;
} else {
buffer[0] = '\0';
return 0;
}
}
if (chi < 0 || chi > lastnullindex) {
buffer[0] = '\0';
return 0;
} else {
buffer[chi] = '\0';
return chi;
}
}

What is optimal algorithm to make all possible combinations of a string?

I find other similar question too complicated.
I think it means if we are given pot then combinations will be
pot
opt
top
pot
pto
pot
so I wrote the following code:
#include<iostream>
#include<string.h>
using namespace std;
int main(){
char s[10];
char temp;
cin>>s;
char t[10];
for(int i=0;i<3;i++)
{
for(int j=i;j<3;j++)
{
strcpy(t,s);
temp=s[i];
s[i]=s[j];
s[j]=temp;
cout<<s<<"\n";
strcpy(s,t);
}
}
Is there a better way ?
This problem is inherently an O(N!) (factorial) complexity problem. The reason is that for each position of each potential word, there will be a decrementing amount of possibilities of characters that can fill the position, An example with 4 letters a, b, c, and d.
-----------------
Positions: | 0 | 1 | 2 | 3 |
-----------------
In position 0, there are 4 possibilities, a, b, c, or d
Lets fill with a
-----------------
String: | a | | | |
-----------------
Now Position 1 has 3 possibilities of fill letters b, c, or d
Lets fill with b
-----------------
String: | a | b | | |
-----------------
Now Position 2 has 2 possibilities of fill letters c, or d
Lets fill with c
-----------------
String: | a | b | c | |
-----------------
Now Position 1 has only 1 possibility for a fill letter: d
-----------------
String: | a | b | c | d |
-----------------
This is only for 1 string, the complexity comes from (in this case) the potential possibilities that can fill a character location for a given output word, thus:
4 * 3 * 2 * 1 = 4!
This can be extended to any amount of input letters and is exactly N! if there are no repeat letters. This also represents the AMOUNT OF WORDS you should result with.
Code to perform something like this could be (TESTED AND WORKING IN C):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TRUE 1
#define FALSE 0
void printPermutations(int level, const char * inString, char * outString){
unsigned int len = strlen(inString);
char * unusedLetter;
int j;
if( 1 == len ){
printf("%s%s\n", outString, inString);
}else{
unusedLetters = (char *)malloc(sizeof(char) * (len - 1));
for(int startLetter = 0; startLetter < len; startLetter++){
outString[level] = inString[startLetter];
// setup the "rest of string" string
j = 0;
for(int i = 0; i < len; i++){
if( i != startLetter ){
unusedLetter[j] = inString[i];
j++;
}
}
// recursive call to THIS routine
printPermutations(level+1, unusedLetters, outString);
}
}
}
int main(int argc, char * argv[]){
unsigned int len;
char * outString;
if(argc != 2) return 0;
len = strlen(argv[1]);
outString = (char *)malloc(sizeof(char) * (len + 1));
outstring[len] = '\0';
printPermutations(0, argv[1], outString);
return 0;
}
From outside, call this as follows:
projectName abc
sample output from using "abc"
abc
acb
bac
bca
cab
cba
If there are repeat letters lets say a, a, b, c
then there will ALWAYS be repeat words.
With these cases, the amount of UNIQUE result words should be the amount of unique characters factorial, so for the above case it would be 3! not 4!.
The reason for this is that it does not matter WHICH of the a's fills a given spot and thus the uniqueness is given be the amount of unique letters provided. This is also a hard problem, and in ways I would say you should generate ALL N! words first, then run a second algorithm to search for the repeat words and delete. There may be smarter ways of generating the unique words on the fly.
The following solution is O(N!).This takes repetitions into account too :
#include<stdio.h>
void permute(char s[10],char *p);
int count=0;
main(){
char s[10];
int i;
scanf("%s",s);
permute(s,s);
}
//takes into account repetetion
void permute(char s[10],char *p){
char *swap,temp;
if(*(p+1)==0) {
count++;
printf("%4d] %s\n",count,s);
}
else{
for(swap=p;*swap;++swap){
char *same;
for(same=p;*same!=*swap;++same){};
if(same==swap){
temp=*swap;
*swap=*p;
*p=temp;
permute(s,p+1);
*p=*swap;/*restoring the original string*/
*swap=temp;
}
}
}
}

Resources