473,785 Members | 2,326 Online
Bytes | Software Development & Data Engineering Community
+ Post

Home Posts Topics Members FAQ

Counting poker hands

I was reading http://en.wikipedia.org/wiki/Poker_probability which has a
good description of how to count the frequency of different types of
poker hands using a mathematical approach. A sample Python program is
given in the discussion page for doing it that way. I wanted to take a
different approach and actually generate all the possible hands,
counting the number of each type.

It's quite do-able on today's hardware, with 5-card (2,598,960
combinations) taking only one second and 7-card (133,784,560
combinations) taking around 60 seconds with my program on my notebook.

Does anyone have any suggestions for ways to improve my program,
especially about how to make it run faster? I'd like to get it down to
10 seconds or 5 seconds rather than 60, if that's possible. Profiling
suggests the vast majority of the run time is spent in the 'classify'
function.

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

#define N 7

/* N is the number of cards to deal. The best five-card poker hand
will be selected out of the N cards. Set N to 5 to get the
frequencies for 5-card poker hands as follows:

Straight flush 40
Four of a kind 624
Full house 3,744
Flush 5,108
Straight 10,200
Three of a kind 54,912
Two pair 123,552
One pair 1,098,240
No pair 1,302,540
Total 2,598,960

Or set N to 7 to get the frequences for 7-card poker hands
(as in Texas Hold'em) as follows:

Straight flush 41,584
Four of a kind 224,848
Full house 3,473,184
Flush 4,047,644
Straight 6,180,020
Three of a kind 6,461,620
Two pair 31,433,400
One pair 58,627,800
No pair 23,294,460
Total 133,784,560

Or set N to any other positive number and you should get
appropriate results, given enough time and as long as no
overflow occurs.

*/

#define RANK(card) ((card) % 13)
#define SUIT(card) ((card) / 13)

char *ranks[] = {"A", "2", "3", "4", "5", "6", "7", "8", "9", "T", "J",
"Q", "K"};
char *suits[] = {"S", "H", "D", "C"};

/* inc function increments hand to the next combination
of N cards from 52. If already at the last combination,
returns 1 to indicate end, otherwise returns 0.

Algorithm from nextComb function in combEnum.c at
http://reptar.uta.edu/NOTES5314/combEnum.c
*/

int inc(int *hand)
{
int i;

for(i = 0; i < N; i++)
{
if(hand[i] != 52 - N + i) break;
}
if(i == N) return 1;

for(i = N - 1; hand[i] == 52 - N + i; i--);

hand[i]++;

for(i++; i < N; i++)
hand[i] = hand[i - 1] + 1;

return 0;
}

enum class {
STRAIGHT_FLUSH,
FOUR,
FULL_HOUSE,
FLUSH,
STRAIGHT,
THREE,
TWO_PAIR,
ONE_PAIR,
NO_PAIR,
NUM_CLASSES
};

char *class_names[] = {
"Straight flush: ",
"Four of a kind: ",
"Full house: ",
"Flush: ",
"Straight: ",
"Three of a kind: ",
"Two Pair: ",
"One Pair: ",
"No Pair: "
};

void print_hand(int *hand, enum class cl)
{
int i;
for(i = 0; i < N; i++)
{
if(hand[i] < 0 || hand[i] >= 52)
printf("%d ", hand[i]);
else
printf("%s%s ", ranks[RANK(hand[i])], suits[SUIT(hand[i])]);
}
printf(" %s\n", class_names[cl]);
}

int comp(const void *v1, const void *v2)
{
const int *a = v1, *b = v2;
return *a < *b ? -1 : *a *b;
}
/* Even when there is both a straight and a flush in the cards,
they may not be over the same 5-card hand. This function
is called only when there is both a straight and a flush in
the cards, which is fairly rarely, to confirm whether or not
it is actually a straight flush. It operates by sorting the
cards in numerical order (which is suit-major, ensuring that
same-suited cards are together), then checking for a run of
5 adjacent cards each one greater than the last.

The variable 'state' represents the number of adjacent
straight-flush cards seen so far in the current run. It is
incremented when the current card is one greater than the
previous card, and of the same suit.

The hand is considered to contain a straight flush when state
reaches 4, or when state reaches 3 on a king and there is
an ace of the current suit.
*/

int confirm_straigh t_flush(int *hand)
{
int i, state = 0, tmp[N], hasAce[4] = {0, 0, 0, 0};

for(i = 0; i < N; i++)
{
if(RANK(hand[i]) == 0) hasAce[SUIT(hand[i])] = 1;
}

memcpy(tmp, hand, sizeof tmp);
qsort(tmp, N, sizeof *tmp, comp);

for(i = 1; i < N; i++)
{
if(( tmp[i] == tmp[i - 1] + 1
) && SUIT(tmp[i]) == SUIT(tmp[i - 1]))
{
state++;
}
else
{
state = 0;
}
if(state == 4 || (RANK(tmp[i]) == 12 && state == 3 &&
hasAce[SUIT(tmp[i])])) return 1;
}
return 0;
}

enum class classify(int *hand)
{
int rank_counts[13] = {0};
int suit_counts[4] = {0};
int i, flush = 0, straight = 0, state = 0;
int max_rank_count = 0, sub_rank_count = 0;
for(i = 0; i < N; i++)
{
rank_counts[RANK(hand[i])]++;
suit_counts[SUIT(hand[i])]++;
}
for(i = 0; i < 4; i++)
{
if(suit_counts[i] >= 5) flush = 1;
}
for(i = 0; i < 13; i++)
{
if(rank_counts[i] >= max_rank_count)
{
sub_rank_count = max_rank_count;
max_rank_count = rank_counts[i];
}
else if(rank_counts[i] >= sub_rank_count)
{
sub_rank_count = rank_counts[i];
}
if(rank_counts[i] != 0)
{
state++;
}
else
{
state = 0;
}
if(state == 5) straight = 1;
}
if(state == 4 && rank_counts[0] != 0) straight = 1; /* ace-high */
if(flush && straight && confirm_straigh t_flush(hand)) return
STRAIGHT_FLUSH;
if(max_rank_cou nt >= 4) return FOUR;
if(max_rank_cou nt == 3 && sub_rank_count >= 2) return FULL_HOUSE;
if(flush) return FLUSH;
if(straight) return STRAIGHT;
if(max_rank_cou nt == 3) return THREE;
if(max_rank_cou nt == 2 && sub_rank_count == 2) return TWO_PAIR;
if(max_rank_cou nt == 2) return ONE_PAIR;
return NO_PAIR;
}

void print_counts(lo ng long *counts)
{
int i;
long long total = 0;
for(i = 0; i < NUM_CLASSES; i++)
{
printf("%s%12ll d\n", class_names[i], counts[i]);
total += counts[i];
}
printf("Total: %12lld\n", total);
}

int main(void)
{
int i;
long long counts[NUM_CLASSES] = {0};
int hand[N] = {0};

/* initialise hand to first combination */
for(i = 0; i < N; i++) hand[i] = i;

do
{
counts[classify(hand)]++;
} while(!inc(hand ));
print_counts(co unts);
return 0;
}

--
Simon.
Jan 16 '07 #1
27 5629
Simon Biber <ne**@ralmin.cc writes:
I was reading http://en.wikipedia.org/wiki/Poker_probability which has
a good description of how to count the frequency of different types of
poker hands using a mathematical approach. A sample Python program is
given in the discussion page for doing it that way. I wanted to take a
different approach and actually generate all the possible hands,
counting the number of each type.
[...]
Does anyone have any suggestions for ways to improve my program,
especially about how to make it run faster? I'd like to get it down to
10 seconds or 5 seconds rather than 60, if that's possible. Profiling
suggests the vast majority of the run time is spent in the 'classify'
function.
[...]
#define RANK(card) ((card) % 13)
#define SUIT(card) ((card) / 13)
[...]

I haven't looked at your code in any detail, but just off the top of
my head, you might get some performance improvement (on many
platforms) by dividing by 16 rather than 13, leaving three gaps in the
sequence for each suit. Or you can set up a 52-entry lookup table for
each.

(Certainly the C standard gives no hint that this will either help or
hurt.)

See if your profiler can be persuaded to give you per-line information
rather than per-function information.

--
Keith Thompson (The_Other_Keit h) ks***@mib.org <http://www.ghoti.net/~kst>
San Diego Supercomputer Center <* <http://users.sdsc.edu/~kst>
We must do something. This is something. Therefore, we must do this.
Jan 17 '07 #2

"Simon Biber" <ne**@ralmin.cc wrote in message
news:45******** **@news.peoplet elecom.com.au.. .

<snip>
void print_counts(lo ng long *counts)
{
int i;
for(i = 0; i < N; i++) hand[i] = i;

do
{
counts[classify(hand)]++;
} while(!inc(hand ));
print_counts(co unts);
return 0;
}
Did this compile for you with a long long? My compiler doesn't like it.

I'm all about poker, so I love the post. LS
Jan 17 '07 #3
Keith Thompson wrote, On 17/01/07 00:46:
Simon Biber <ne**@ralmin.cc writes:
>I was reading http://en.wikipedia.org/wiki/Poker_probability which has
a good description of how to count the frequency of different types of
poker hands using a mathematical approach. A sample Python program is
given in the discussion page for doing it that way. I wanted to take a
different approach and actually generate all the possible hands,
counting the number of each type.

[...]
>Does anyone have any suggestions for ways to improve my program,
especially about how to make it run faster? I'd like to get it down to
10 seconds or 5 seconds rather than 60, if that's possible. Profiling
suggests the vast majority of the run time is spent in the 'classify'
function.

[...]
>#define RANK(card) ((card) % 13)
#define SUIT(card) ((card) / 13)

[...]

I haven't looked at your code in any detail, but just off the top of
my head, you might get some performance improvement (on many
platforms) by dividing by 16 rather than 13, leaving three gaps in the
sequence for each suit.
On my notebook this only gave about a 1 second improvement, from 40s to
39s (gcc with -O2). Just goes to show that first impressions when it
comes to optimisation can be very misleading, as I know you already know.

BTW doing something about the division was my first thought ;-)

If the OP has not enabled the optimiser then doing so would be a far
better option than anything else.
Or you can set up a 52-entry lookup table for
each.
I did not try it but I don't think that would do much better.
(Certainly the C standard gives no hint that this will either help or
hurt.)

See if your profiler can be persuaded to give you per-line information
rather than per-function information.
That is also worth doing, although using the optimiser might mess up any
such statistics.
--
Flash Gordon
Jan 17 '07 #4
Lane Straatman wrote, On 17/01/07 00:52:
"Simon Biber" <ne**@ralmin.cc wrote in message
news:45******** **@news.peoplet elecom.com.au.. .

<snip>
>void print_counts(lo ng long *counts)
{
int i;
for(i = 0; i < N; i++) hand[i] = i;

do
{
counts[classify(hand)]++;
} while(!inc(hand ));
print_counts(co unts);
return 0;
}
Did this compile for you with a long long? My compiler doesn't like it.

I'm all about poker, so I love the post. LS
long long was not in the C89 standard so it will produce a diagnostic
(error or warning) on any compiler in C89 conforming mode. It was,
however, a common extension and has been added to C99, so any C99
compiler (few and far between) and any compiler supporting it as an
extension will accept it when properly poked.
--
Flash Gordon
Jan 17 '07 #5
Flash Gordon wrote, On 17/01/07 01:20:
Keith Thompson wrote, On 17/01/07 00:46:
>Simon Biber <ne**@ralmin.cc writes:
>>I was reading http://en.wikipedia.org/wiki/Poker_probability which has
a good description of how to count the frequency of different types of
poker hands using a mathematical approach. A sample Python program is
given in the discussion page for doing it that way. I wanted to take a
different approach and actually generate all the possible hands,
counting the number of each type.

[...]
>>Does anyone have any suggestions for ways to improve my program,
especially about how to make it run faster? I'd like to get it down to
10 seconds or 5 seconds rather than 60, if that's possible. Profiling
suggests the vast majority of the run time is spent in the 'classify'
function.

[...]
>>#define RANK(card) ((card) % 13)
#define SUIT(card) ((card) / 13)

[...]

I haven't looked at your code in any detail, but just off the top of
my head, you might get some performance improvement (on many
platforms) by dividing by 16 rather than 13, leaving three gaps in the
sequence for each suit.

On my notebook this only gave about a 1 second improvement, from 40s to
39s (gcc with -O2). Just goes to show that first impressions when it
comes to optimisation can be very misleading, as I know you already know.
<snip>

Increasing the optimisation from -O2 to -O3 knocked the original version
down to 29 seconds whilst leaving the version with %16 /16 at 38
seconds! So increasing the optimisation level beyond what I normally use
had a *far* greater effect!

For reference, without any optimisation it was about 75 seconds for the
original and 65 with the optimisation.

So the OP should definitely play with optimisation levels in the
compiler as a first step and will probably find it very difficult to
make code changes that increase the performance with the best compiler
optimisation available.

An interesting little exercise though for learning about optimisation.
--
Flash Gordon
Jan 17 '07 #6
Flash Gordon <sp**@flash-gordon.me.ukwri tes:
Keith Thompson wrote, On 17/01/07 00:46:
>Simon Biber <ne**@ralmin.cc writes:
[...]
>>#define RANK(card) ((card) % 13)
#define SUIT(card) ((card) / 13)
[...]
I haven't looked at your code in any detail, but just off the top of
my head, you might get some performance improvement (on many
platforms) by dividing by 16 rather than 13, leaving three gaps in the
sequence for each suit.

On my notebook this only gave about a 1 second improvement, from 40s
to 39s (gcc with -O2). Just goes to show that first impressions when
it comes to optimisation can be very misleading, as I know you already
know.

BTW doing something about the division was my first thought ;-)
[...]

Hypothesis: either your compiler knows a clever way to divide by 13,
or your hardware does division quickly.

(By "hypothesis ", of course, I mean "wild guess unsupported by any
facts or research".)

(gcc, on one x86 platform, implements division by 13 without using a
division instruction; it involves multiplication by 1321528399, which
is close to 2**34/13, but I'm too lazy to analyze the code in any
depth.)

--
Keith Thompson (The_Other_Keit h) ks***@mib.org <http://www.ghoti.net/~kst>
San Diego Supercomputer Center <* <http://users.sdsc.edu/~kst>
We must do something. This is something. Therefore, we must do this.
Jan 17 '07 #7
"Lane Straatman" <in*****@invali d.netwrites:
"Simon Biber" <ne**@ralmin.cc wrote in message
news:45******** **@news.peoplet elecom.com.au.. .

<snip>
>void print_counts(lo ng long *counts)
{
int i;
for(i = 0; i < N; i++) hand[i] = i;

do
{
counts[classify(hand)]++;
} while(!inc(hand ));
print_counts(co unts);
return 0;
}
Did this compile for you with a long long? My compiler doesn't like it.
How exactly did your compiler make its displeasure known? Does it not
recognize the type "long long", or was there some other problem?

--
Keith Thompson (The_Other_Keit h) ks***@mib.org <http://www.ghoti.net/~kst>
San Diego Supercomputer Center <* <http://users.sdsc.edu/~kst>
We must do something. This is something. Therefore, we must do this.
Jan 17 '07 #8
Simon Biber wrote:
I was reading http://en.wikipedia.org/wiki/Poker_probability which has a
good description of how to count the frequency of different types of
poker hands using a mathematical approach. A sample Python program is
given in the discussion page for doing it that way. I wanted to take a
different approach and actually generate all the possible hands,
counting the number of each type.

It's quite do-able on today's hardware, with 5-card (2,598,960
combinations) taking only one second and 7-card (133,784,560
combinations) taking around 60 seconds with my program on my notebook.

Does anyone have any suggestions for ways to improve my program,
especially about how to make it run faster? I'd like to get it down to
10 seconds or 5 seconds rather than 60, if that's possible. Profiling
suggests the vast majority of the run time is spent in the 'classify'
function.
Indeed. Please take a look at:

http://www.pobox.com/~qed/poker.zip

It does both 5 and 7 card stud calculations by monotonically mapping
each hand to a score metric. All exact ties are all calculated
correctly, for example. Also the two few bits of the metric correctly
classify the hand into the rough hand categorization (two of a kind,
flush, etc).

(I was also working on a full general heads-up situation evaluator, but
got side tracked. There are non-loop based methods for accelerating
that but it starts getting really really complicated. I would probably
require several days of working at it to get it.)

--
Paul Hsieh
http://www.pobox.com/~qed/
http://bstring.sf.net/

Jan 17 '07 #9

Simon Biber wrote:
Does anyone have any suggestions for ways to improve my program,
especially about how to make it run faster? I'd like to get it down to
10 seconds or 5 seconds rather than 60, if that's possible. Profiling
suggests the vast majority of the run time is spent in the 'classify'
function.
<snip>
enum class classify(int *hand)
{
int rank_counts[13] = {0};
int suit_counts[4] = {0};
int i, flush = 0, straight = 0, state = 0;
int max_rank_count = 0, sub_rank_count = 0;
for(i = 0; i < N; i++)
{
rank_counts[RANK(hand[i])]++;
suit_counts[SUIT(hand[i])]++;
}
for(i = 0; i < 4; i++)
{
if(suit_counts[i] >= 5) flush = 1;
}
for(i = 0; i < 13; i++)
{
if(rank_counts[i] >= max_rank_count)
{
sub_rank_count = max_rank_count;
max_rank_count = rank_counts[i];
}
else if(rank_counts[i] >= sub_rank_count)
{
sub_rank_count = rank_counts[i];
}
if(rank_counts[i] != 0)
{
state++;
}
else
{
state = 0;
}
if(state == 5) straight = 1;
}
if(state == 4 && rank_counts[0] != 0) straight = 1; /* ace-high */
if(flush && straight && confirm_straigh t_flush(hand)) return
STRAIGHT_FLUSH;
if(max_rank_cou nt >= 4) return FOUR;
if(max_rank_cou nt == 3 && sub_rank_count >= 2) return FULL_HOUSE;
if(flush) return FLUSH;
if(straight) return STRAIGHT;
if(max_rank_cou nt == 3) return THREE;
if(max_rank_cou nt == 2 && sub_rank_count == 2) return TWO_PAIR;
if(max_rank_cou nt == 2) return ONE_PAIR;
return NO_PAIR;
}
1) How about combining inc and classify? Basically, when you increment
one of the cards, you'd adjust rank_counts and suit_counts - it looks
like you're recalculating N of those each time, even if you only change
1 card.

You could also calculate 'flush' at the same time (another loop
removed), and max_rank_count sometimes (i.e., if you increase a number
to more than max_rank count, but not if you decrement the number that
was previously max_rank_count, as there could be another equal one
somewhere).

2) If you re-order the ifs at the end in a way that is logically
consistent, but that minimizes the expected path through it, you'd be
executing less code on average. For example, right now if you have
NO_PAIR (which is very common), you have to do all the checks. If that
were first (with appropriate logic), your average number of ifs would
decrease.

You could do something like:
switch (max_rank_count ) {
case 1:
logic for straight and straight flush
case 2:
logic for flush and pair and two pair
case 3:
logic for flush and full house and three
etc.
Note that straight implies max_rank_count = 1, so you should only
execute the loop to calculate 'state' and 'straight' in case 1.

This one is a little trickier, so you'll have to be careful not to mess
up the logic.

Good luck.

Michael

Jan 17 '07 #10

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

34
2154
by: Mo Geffer | last post by:
Greetings: I have a question about the output of the sample program in section 1.5.3 Line Counting of K&R, Second Edition. Here's the program: /****************************************/ #include <stdio.h> /* count lines in input */
1
8857
by: Jerry | last post by:
We have a 10-question quiz for kids, each question being a yes or no answer using radio selections. I'd like to keep a current total of yes's and no's at the bottom of the quiz (if the user selects yes to question 1, the total of yes's increases by 1). I've been searching for a while but I'm not sure I'm searching with the right keywords. Can anyone direct me to a source I can review to learn how to do this? Thanks! --
7
13270
by: ibtc209 | last post by:
I just started programming in C, and I need some help with this problem. Your program will read the information about one MiniPoker hand, namely the rank and suit of the hand’s first card, and the rank and suit of its second card. Note that the two cards in a hand may be entered in any order; it’s not necessarily the case that the highest card will appear first, for example. Your program will then determine whether the hand is valid, and...
1
4487
by: Martin Olsen | last post by:
Hi all. I am creating a program which calculates poker odds. The program should look at the visible cards (those on your hand and those on the table) then count the cards needed to improve the hand(eg. how many cards do I need to get a Flush) and then calculate the odds based on those numbers. My problem is that I do not know how I should find the missing cards. One way I could do it is to use programming logic like this
4
8915
by: hardieca | last post by:
Has anyone heard of an open-source .NET engine that calculates the winning and losing percentages of hands? Regards, Chris
15
6481
by: sandy123456 | last post by:
At the moment im trying to write a hand class for a game poker patientnce But when i get to the part having to catergorise the difference of full house straight flush flush four of a kind and straight i got stuck.I need to write boolean methods to return these (stright flush , four of a kind..etc) I can only do a pair and 2 pairs and three of a kind. The following is my code please someone if possible help me thanks import java.util.*; ...
13
2557
by: kinghippo423 | last post by:
Hello Everyone, I did a poker program in Java that essencially finds the strenght of a poker hand created Randomly. My program is doing OK...but I'm pretty sure it can be optimised. This is my results with 1 million hands: Number of hands generated : 1000000 Straight Flush : 0.0012 % In theory : 0.0012%
9
4687
by: teejayem | last post by:
I am looking for some help! I am currently developing a new game for an online community. The game is called Pokino which ishalf bingo and half poker. Wierd eh?! Anyway... The final part of the program needs to evaluate two 5 card poker hands and determine which one out of the two hands wins. I thought rather than create a new class of my own I would see if there was anything
7
5682
by: Extremity | last post by:
Hi, I am taking a intro to C++ course so my knowledge base only limits to areas such as if/else, functions, and recursions. We are creating a program which will determine the probability of Poker Hands, such as Royal Flush, Straight Flush, Four of a Kind and such. However in order to complete this, I need to determine how many different poker hands there are. On pencil and paper, this is relatively easy as its a simple combination problem. ...
0
9647
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However, people are often confused as to whether an ONU can Work As a Router. In this blog post, we’ll explore What is ONU, What Is Router, ONU & Router’s main usage, and What is the difference between ONU and Router. Let’s take a closer look ! Part I. Meaning of...
0
9489
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can effortlessly switch the default language on Windows 10 without reinstalling. I'll walk you through it. First, let's disable language synchronization. With a Microsoft account, language settings sync across devices. To prevent any complications,...
0
10162
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven tapestry of website design and digital marketing. It's not merely about having a website; it's about crafting an immersive digital experience that captivates audiences and drives business growth. The Art of Business Website Design Your website is...
0
9959
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each protocol has its own unique characteristics and advantages, but as a user who is planning to build a smart home system, I am a bit confused by the choice of these technologies. I'm particularly interested in Zigbee because I've heard it does some...
0
8988
agi2029
by: agi2029 | last post by:
Let's talk about the concept of autonomous AI software engineers and no-code agents. These AIs are designed to manage the entire lifecycle of a software development project—planning, coding, testing, and deployment—without human intervention. Imagine an AI that can take a project description, break it down, write the code, debug it, and then launch it, all on its own.... Now, this would greatly impact the work of software developers. The idea...
1
7509
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome a new presenter, Adolph Dupré who will be discussing some powerful techniques for using class modules. He will explain when you may want to use classes instead of User Defined Types (UDT). For example, to manage the data in unbound forms. Adolph will...
0
5528
by: adsilva | last post by:
A Windows Forms form does not have the event Unload, like VB6. What one acts like?
2
3665
muto222
by: muto222 | last post by:
How can i add a mobile payment intergratation into php mysql website.
3
2893
bsmnconsultancy
by: bsmnconsultancy | last post by:
In today's digital era, a well-designed website is crucial for businesses looking to succeed. Whether you're a small business owner or a large corporation in Toronto, having a strong online presence can significantly impact your brand's success. BSMN Consultancy, a leader in Website Development in Toronto offers valuable insights into creating effective websites that not only look great but also perform exceptionally well. In this comprehensive...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.