<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kishlay Kashyap</title>
    <description>The latest articles on DEV Community by Kishlay Kashyap (@kishlayk).</description>
    <link>https://dev.to/kishlayk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4084843%2F94e35942-1bf4-4956-a793-e6a309744aac.jpg</url>
      <title>DEV Community: Kishlay Kashyap</title>
      <link>https://dev.to/kishlayk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kishlayk"/>
    <language>en</language>
    <item>
      <title>Python's `\w+` Silently Breaks Devanagari Words</title>
      <dc:creator>Kishlay Kashyap</dc:creator>
      <pubDate>Sun, 25 Oct 2026 10:07:54 +0000</pubDate>
      <link>https://dev.to/kishlayk/pythons-w-silently-breaks-devanagari-words-30a5</link>
      <guid>https://dev.to/kishlayk/pythons-w-silently-breaks-devanagari-words-30a5</guid>
      <description>&lt;p&gt;Have you ever used Python's regular expressions to tokenize text and assumed everything was working correctly?&lt;/p&gt;

&lt;p&gt;I did, until I looked closely at what happened to Devanagari text.&lt;/p&gt;

&lt;p&gt;Consider this simple example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;हिन्दी भाषा&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\w+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;हिन्दी&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;भाषा&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But Python actually returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ह&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;न&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;द&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;भ&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ष&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The vowel signs and virama disappear from the matches, splitting the written words into fragments.&lt;/p&gt;

&lt;p&gt;This isn't a display problem. It's a consequence of how Python's regular expressions define a word character.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does this happen?
&lt;/h2&gt;

&lt;p&gt;Python's &lt;code&gt;\w&lt;/code&gt; matches Unicode alphanumeric characters and underscores. However, it does not include every Unicode combining mark.&lt;/p&gt;

&lt;p&gt;Devanagari uses consonants, vowel signs, viramas and other marks to represent written words. Some of these marks are essential to the written form of a word, but they aren't matched by &lt;code&gt;\w&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, the word &lt;code&gt;हिन्दी&lt;/code&gt; contains consonants alongside combining marks. When a regular expression matches only the characters it considers word characters, those marks are excluded.&lt;/p&gt;

&lt;p&gt;The result can be a sequence of fragments rather than the original word.&lt;/p&gt;

&lt;p&gt;This matters if you use regex-based tokenization for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Word counts and vocabulary statistics&lt;/li&gt;
&lt;li&gt;Text preprocessing for NLP&lt;/li&gt;
&lt;li&gt;Search indexing&lt;/li&gt;
&lt;li&gt;Language analysis&lt;/li&gt;
&lt;li&gt;Building datasets for machine learning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your tokenizer silently drops parts of words, the downstream results can be misleading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspect the Unicode characters
&lt;/h2&gt;

&lt;p&gt;Python's built-in &lt;code&gt;unicodedata&lt;/code&gt; module makes it easier to investigate what's happening.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;unicodedata&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;char&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;हिन्दी&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;repr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;char&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;unicodedata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;char&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UNKNOWN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;unicodedata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;char&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unicode categories help explain the behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Lo&lt;/code&gt; — Letter, Other; used for many Indic consonants.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Mc&lt;/code&gt; — Mark, Spacing Combining.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Mn&lt;/code&gt; — Mark, Nonspacing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The consonants in the example belong to the &lt;code&gt;Lo&lt;/code&gt; category, while the vowel signs and virama include combining marks.&lt;/p&gt;

&lt;p&gt;Python's standard &lt;code&gt;re&lt;/code&gt; module doesn't treat every combining mark as a &lt;code&gt;\w&lt;/code&gt; character. That's why the regex can match the consonants while leaving out marks that belong to the same written word.&lt;/p&gt;

&lt;p&gt;The important distinction is that Python isn't failing to recognize Devanagari text. The problem is that &lt;code&gt;\w&lt;/code&gt; doesn't cover every character needed for this kind of tokenization.&lt;/p&gt;

&lt;h2&gt;
  
  
  There's another trap: Unicode normalization
&lt;/h2&gt;

&lt;p&gt;Two strings can look identical but contain different sequences of Unicode code points.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;unicodedata&lt;/span&gt;

&lt;span class="n"&gt;one&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\u0958&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;two&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\u0915\u093c&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;one&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;two&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;unicodedata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NFC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;one&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;unicodedata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NFC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;two&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;False
True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first string represents &lt;code&gt;क़&lt;/code&gt; using a single code point, while the second represents the same character using &lt;code&gt;क&lt;/code&gt; followed by a nukta.&lt;/p&gt;

&lt;p&gt;The strings differ before normalization, but their normalized forms compare equal.&lt;/p&gt;

&lt;p&gt;Unicode normalization helps make canonically equivalent representations consistent. This can be useful when comparing, storing or processing text from different sources.&lt;/p&gt;

&lt;p&gt;However, &lt;strong&gt;normalization does not solve tokenization by itself&lt;/strong&gt;. Even normalized text can contain combining marks that a particular regular expression doesn't match.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple workaround and its limits
&lt;/h2&gt;

&lt;p&gt;For a basic Devanagari text-processing experiment, we can normalize the input and explicitly include relevant Unicode ranges.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;unicodedata&lt;/span&gt;

&lt;span class="n"&gt;WORD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[\w\u0900-\u0963\u0966-\u096F\u0971-\u097F]+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;unicodedata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NFC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;WORD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;हिन्दी भाषा&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;हिन्दी&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;भाषा&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pattern combines &lt;code&gt;\w&lt;/code&gt; with additional Devanagari code-point ranges so that the relevant marks can be included in a match.&lt;/p&gt;

&lt;p&gt;This demonstrates one way to address the problem, but it isn't a complete tokenizer.&lt;/p&gt;

&lt;p&gt;Unicode ranges can include characters that shouldn't necessarily be part of ordinary words. They can also require adjustments for different languages, scripts and text-processing requirements.&lt;/p&gt;

&lt;p&gt;For a more comprehensive solution, consider a Unicode-aware regular-expression library such as Python's third-party &lt;code&gt;regex&lt;/code&gt; package, which supports Unicode character properties. For serious NLP applications, a tokenizer designed for the target language may be more appropriate.&lt;/p&gt;

&lt;p&gt;The right choice depends on the text you're processing and what you consider a word.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the behavior instead of assuming it
&lt;/h2&gt;

&lt;p&gt;A few simple tests can help catch regressions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_basic_words&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;हिन्दी भाषा&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;हिन्दी&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;भाषा&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_empty_input&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_english_words&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello world&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;world&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_punctuation&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;हिन्दी, भाषा!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;हिन्दी&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;भाषा&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_nukta_form&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;क़&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;क़&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These tests cover basic Devanagari words, empty input, English text, punctuation and a nukta-containing form.&lt;/p&gt;

&lt;p&gt;You can extend them with danda (&lt;code&gt;।&lt;/code&gt;), mixed-language text, unusual whitespace and examples from the language you're working with.&lt;/p&gt;

&lt;p&gt;Passing a handful of tests doesn't prove that a tokenizer handles every possible input. It does, however, make its behavior explicit and gives you a foundation for testing more complex cases.&lt;/p&gt;

&lt;p&gt;For serious NLP work, test against representative text from your target language rather than relying exclusively on a few hand-picked examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final takeaway
&lt;/h2&gt;

&lt;p&gt;Regular expressions are useful, but &lt;code&gt;\w+&lt;/code&gt; is not a universal solution for word tokenization.&lt;/p&gt;

&lt;p&gt;When working with Devanagari or other scripts that use combining marks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inspect the underlying Unicode representation.&lt;/li&gt;
&lt;li&gt;Normalize text where appropriate.&lt;/li&gt;
&lt;li&gt;Understand which characters your regex actually matches.&lt;/li&gt;
&lt;li&gt;Test with real examples from your target language.&lt;/li&gt;
&lt;li&gt;Choose a tokenizer that fits your application's requirements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A regex that works perfectly for English text may behave very differently with another writing system.&lt;/p&gt;

&lt;p&gt;The lesson isn't to avoid regular expressions. It's to understand their boundaries and verify their behavior before trusting the results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have you encountered a similar Unicode issue in Python? I'd love to hear how you handled it.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>nlp</category>
      <category>programming</category>
      <category>unicode</category>
    </item>
    <item>
      <title>The Line-Equation Method for Pattern-Printing Programs</title>
      <dc:creator>Kishlay Kashyap</dc:creator>
      <pubDate>Wed, 19 Aug 2026 10:56:06 +0000</pubDate>
      <link>https://dev.to/kishlayk/the-line-equation-method-for-pattern-printing-programs-3bob</link>
      <guid>https://dev.to/kishlayk/the-line-equation-method-for-pattern-printing-programs-3bob</guid>
      <description>&lt;p&gt;Every programming course seems to have the same unit: print a triangle of stars, then a pyramid, and, if the syllabus is feeling ambitious, a diamond. The triangles are usually straightforward. You count. The diamond is where things often get messy: four sloping edges, a pile of possible &lt;code&gt;&amp;lt;=&lt;/code&gt; and &lt;code&gt;&amp;gt;=&lt;/code&gt; combinations, and a nested &lt;code&gt;if&lt;/code&gt; that may work for one input and fail for another.&lt;/p&gt;

&lt;p&gt;There is a more systematic way to do it. Instead of guessing the conditions, treat the loop indices as coordinates, treat the edges of the pattern as lines, and derive the boundary the same way you would in coordinate geometry.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Grid as a Coordinate System
&lt;/h2&gt;

&lt;p&gt;A nested loop like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;visits every cell in a grid. Let &lt;code&gt;i&lt;/code&gt; be the row and &lt;code&gt;j&lt;/code&gt; the column. That gives us a coordinate system, with one small difference from the usual Cartesian plane: row 0 is at the top, and &lt;code&gt;i&lt;/code&gt; increases as you move downward.&lt;/p&gt;

&lt;p&gt;Once you look at it that way, the pattern is just a region of the grid bounded by straight lines. Give me two points on one of those edges and I can write its equation.&lt;/p&gt;

&lt;p&gt;The tool for that is the two-point form of a line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(j − j₁) / (i − i₁)  =  (j₂ − j₁) / (i₂ − i₁)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Substitute the two known grid points, simplify the equation, and you end up with a relationship between &lt;code&gt;i&lt;/code&gt; and &lt;code&gt;j&lt;/code&gt;. That is exactly what a nested-loop &lt;code&gt;if&lt;/code&gt; condition needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The simplest case: a diagonal
&lt;/h2&gt;

&lt;p&gt;Take the classic lower-triangle pattern, with one star at the top and a full row at the bottom:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*
* *
* * *
* * * *
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Its slanted boundary runs from &lt;code&gt;(i, j) = (0, 0)&lt;/code&gt; to &lt;code&gt;(n, n)&lt;/code&gt;, where &lt;code&gt;n&lt;/code&gt; is the last row index. Using the two-point formula gives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(j − 0)/(i − 0) = (n − 0)/(n − 0)   →   j = i   →   i = j
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That line divides the grid into two regions. To decide which side belongs to the pattern, pick a point that you already know is inside it. The bottom-left cell &lt;code&gt;(n, 0)&lt;/code&gt; works. Since &lt;code&gt;n &amp;gt; 0&lt;/code&gt;, it satisfies &lt;code&gt;i &amp;gt; j&lt;/code&gt;, so the inclusive condition is &lt;code&gt;i &amp;gt;= j&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is the whole derivation behind the usual lower-triangle condition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two points, the other way
&lt;/h2&gt;

&lt;p&gt;Now flip the triangle so that it gets narrower as you move downward. The boundary runs from &lt;code&gt;(0, n)&lt;/code&gt; to &lt;code&gt;(n, 0)&lt;/code&gt;, which is the anti-diagonal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(j − n)/(i − 0) = (0 − n)/(n − 0)   →   j − n = −i   →   i + j = n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test the top-left corner &lt;code&gt;(0,0)&lt;/code&gt;. There, &lt;code&gt;i + j = 0&lt;/code&gt;, which is less than &lt;code&gt;n&lt;/code&gt;. So the triangle containing that corner is described by &lt;code&gt;i + j &amp;lt;= n&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Choose the opposite side of the line instead and you get the complementary triangle, &lt;code&gt;i + j &amp;gt;= n&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So with just two line equations, we already have two basic pattern families.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real target: a diamond
&lt;/h2&gt;

&lt;p&gt;A diamond is just four of those sloping boundaries combined. Put it inside a &lt;code&gt;(2n + 1) × (2n + 1)&lt;/code&gt; grid by using the midpoint of each side of the bounding square as a vertex: &lt;code&gt;(0, n)&lt;/code&gt;, &lt;code&gt;(n, 0)&lt;/code&gt;, &lt;code&gt;(n, 2n)&lt;/code&gt;, &lt;code&gt;(2n, n)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4ngil850ceafgfwc1mo0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4ngil850ceafgfwc1mo0.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now apply the two-point formula to each edge:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Edge&lt;/th&gt;
&lt;th&gt;Through&lt;/th&gt;
&lt;th&gt;Equation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;top-left&lt;/td&gt;
&lt;td&gt;(0, n) → (n, 0)&lt;/td&gt;
&lt;td&gt;i + j = n&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;top-right&lt;/td&gt;
&lt;td&gt;(0, n) → (n, 2n)&lt;/td&gt;
&lt;td&gt;j − i = n&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bottom-left&lt;/td&gt;
&lt;td&gt;(n, 0) → (2n, n)&lt;/td&gt;
&lt;td&gt;i − j = n&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bottom-right&lt;/td&gt;
&lt;td&gt;(n, 2n) → (2n, n)&lt;/td&gt;
&lt;td&gt;i + j = 3n&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The equations give us the boundaries. We still need to decide which side of each line belongs to the diamond.&lt;/p&gt;

&lt;p&gt;There is a simple way to do that. Pick a point that is definitely inside the shape. The centre &lt;code&gt;(n, n)&lt;/code&gt; is convenient.&lt;/p&gt;

&lt;p&gt;At that point, &lt;code&gt;i + j = 2n&lt;/code&gt;, which is greater than &lt;code&gt;n&lt;/code&gt; and less than &lt;code&gt;3n&lt;/code&gt;. Also, &lt;code&gt;j − i = 0&lt;/code&gt; and &lt;code&gt;i − j = 0&lt;/code&gt;, both less than &lt;code&gt;n&lt;/code&gt;. Those comparisons tell us which direction to use for each inequality:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four lines, four comparisons, one diamond. There is no need to guess at the conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the complement for free
&lt;/h2&gt;

&lt;p&gt;Now suppose we want the opposite region: the diamond-shaped gap, with the corners of the grid filled instead.&lt;/p&gt;

&lt;p&gt;It is tempting to flip every comparator and keep the &lt;code&gt;and&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# prints nothing
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That produces an empty grid. Not a nearly empty one. No cell satisfies the condition for any positive &lt;code&gt;n&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The arithmetic is fine. The logic is the problem.&lt;/p&gt;

&lt;p&gt;The filled diamond is an &lt;code&gt;and&lt;/code&gt; of four conditions, so its exact complement follows from De Morgan's law: negate the whole expression, which means flipping each condition and replacing &lt;code&gt;and&lt;/code&gt; with &lt;code&gt;or&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the actual complement. Every cell outside the diamond matches it, and no cell inside does.&lt;/p&gt;

&lt;p&gt;Run the two patterns together for &lt;code&gt;n=3&lt;/code&gt; and they partition the 49-cell grid into 25 cells and 24 cells. They do not overlap, and no cell is left unclassified. That is simply &lt;code&gt;P&lt;/code&gt; and &lt;code&gt;not-P&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A common alternative is to relax the flipped comparators back to non-strict comparisons:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a different shape from the strict complement above. It is not just another way of writing the same logic.&lt;/p&gt;

&lt;p&gt;The two versions share the boundary lines with the diamond itself. For &lt;code&gt;n=3&lt;/code&gt;, the two printed patterns overlap on 12 cells. When printed separately, though, the non-strict version often looks like the intended pattern because its own boundary stays closed instead of leaving a one-cell gap.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2avc52sef2b8arg3iiv5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2avc52sef2b8arg3iiv5.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The useful part here is bigger than this particular diamond. When a shape is formed by intersecting several half-planes, its complement is formed by taking the union of the flipped conditions. Strict versus non-strict boundaries is a separate decision about which pattern gets the edge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond four lines
&lt;/h2&gt;

&lt;p&gt;None of this depends on having exactly four edges. The same procedure works for any shape made from straight boundaries:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Mark the shape's vertices as grid coordinates.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Connect consecutive vertices and get one line equation for each edge using the two-point formula.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Decide the direction of each inequality by testing a known interior point. Then choose &lt;code&gt;&amp;gt;=&lt;/code&gt; versus &lt;code&gt;&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;=&lt;/code&gt; versus &lt;code&gt;&amp;lt;&lt;/code&gt; depending on whether the boundary itself belongs to the shape.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;and&lt;/code&gt; together all the conditions for one convex piece.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If the target shape is not a single convex piece, split it into several pieces and combine those pieces with &lt;code&gt;or&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For the complement version, apply De Morgan's law to the complete Boolean expression rather than rebuilding the logic from scratch: flip every comparator and replace &lt;code&gt;and&lt;/code&gt; with &lt;code&gt;or&lt;/code&gt; at the corresponding level.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That fifth step is also useful for more complicated patterns, where the target shape cannot be represented as a single convex region and must instead be built from multiple pieces.&lt;/p&gt;

&lt;p&gt;As a quick check, take the diamond and add two horizontal boundaries: &lt;code&gt;i = a&lt;/code&gt; and &lt;code&gt;i = 2n − a&lt;/code&gt;, where &lt;code&gt;0 &amp;lt; a &amp;lt; n&lt;/code&gt;. These cut off the top and bottom points. Keep the four diagonal conditions and add the two new ones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
    &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw3fmxo935hvrhtu09m1f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw3fmxo935hvrhtu09m1f.png" alt=" " width="800" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now there are six conditions, but the method has not changed. Two more lines simply mean two more inequalities. The diamond has become a hexagon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Relation to Existing Approaches
&lt;/h2&gt;

&lt;p&gt;The basic idea of treating a grid position as a coordinate and using line equations to derive pattern conditions is not new. Dilip Kumar's &lt;a href="https://dilipkumar.medium.com/pattern-printing-coding-technique-f6f7f9c8bd9c" rel="noopener noreferrer"&gt;"Pattern printing coding technique"&lt;/a&gt; explicitly uses the &lt;code&gt;(i, j)&lt;/code&gt; grid representation and derives conditions such as &lt;code&gt;i &amp;gt;= j&lt;/code&gt; for simple shapes.&lt;/p&gt;

&lt;p&gt;A lot of other pattern-printing tutorials take a different route and build each shape directly from loops. GeeksforGeeks' &lt;a href="https://www.geeksforgeeks.org/dsa/program-print-diamond-shape/" rel="noopener noreferrer"&gt;diamond-pattern reference&lt;/a&gt; is one example. It constructs the diamond by handling the upper and lower parts separately, then controlling the spaces and stars with nested loops.&lt;/p&gt;

&lt;p&gt;The difference here is mainly in how the condition is derived. Instead of trying combinations until the pattern looks right, you start with the boundary points, derive the line equations, determine the correct half-planes, and combine the resulting inequalities with Boolean logic.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>algorithms</category>
      <category>python</category>
      <category>geometry</category>
    </item>
  </channel>
</rss>
