DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 968,873 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
dev.to staff
dev.to staff

Posted on

Daily Challenge #2 - String Diamond

Welcome to Day 2 of our challenge series. Today, you’ll be using some clean and polished code to create a clean and polished diamond.

Our challenge comes from user @jayeshcp on CodeWars.

Your task is to return a string that displays a diamond shape on the screen using asterisk (β€œ*”) characters.

The shape that the print method will return should resemble a diamond. A number provided as input will represent the number of asterisks printed on the middle line. The line above and below will be centered and will have two less asterisks than the middle line. This reduction will continue for each line until a line with a single asterisk is printed at the top and bottom of the figure.

Return null if input is an even number or a negative number.

Note: JS and Python students must implement diamond() method and return None (Py) or null(JS) for invalid input.

Bonus points awarded for experimenting with any extra features.

Good luck!


Thank you to CodeWars, who has licensed redistribution of this challenge under the 2-Clause BSD License!

Want to propose a challenge for a future post? Email yo+challenge@dev.to with your suggestions!

Top comments (87)

Collapse
 
alvaromontoro profile image
Alvaro Montoro • Edited on

CSS

.diamond {
  --stars: 11;
  width: calc(var(--stars) * 10px);
  height: calc(var(--stars) * 15px);
  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="10px" height="15px" viewBox="0 0 10 15"><text x="1.125" y="15" fill="black">*</text></svg>');
  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
}

Some people will claim that I am cheating with this one; and they are probably right... but it was fun to develop and it kind of works (although only with odd numbers 😭). The idea is:

  • Create a SVG image that shows an asterisk with text.
  • Put that SVG as an inline background image for an element.
  • Clip the element to only show a diamond shape (using clip-path).
  • Define a CSS variable to specify the number of stars in the middle of the diamond.
  • Knowing the size of the SVG image, use the CSS variable to calculate the height and width of the element.

Here there is a working demo on Codepen:

Collapse
 
coreyja profile image
Corey Alexander

Wow amazing one again! I don't think it's cheating at all, just a creative solution to the problem!

Collapse
 
alvaromontoro profile image
Alvaro Montoro

Thanks!
Yesterday's solution was definitely more cheating than this one.

Collapse
 
mrdulin profile image
official_dulin

Amazing! You are a CSS master!

Collapse
 
sajanv88 profile image
Sajan

Good job! :)

Collapse
 
coreyja profile image
Corey Alexander • Edited on

After @andrewbrown shamed us all yesterday for not having test cases, I decided I needed to step up my game and went full TDD with this one!

Rust Solution:

fn concat_char(c: char, n: i32) -> String {
    (0..n).map(|_x| c).collect()
}

fn diamond_line(number_of_asteriks: i32, number_of_padding_spaces: i32) -> String {
    let spaces = concat_char(' ', number_of_padding_spaces);
    let asteriks = concat_char('*', number_of_asteriks);
    format!("{}{}{}\n", spaces, asteriks, spaces)
}

pub fn diamond(size: i32) -> Option<String> {
    if size <= 0 {
        None
    } else if size % 2 == 0 {
        None
    } else {
        let midpoint_index = (size - 1) / 2;

        let output: String = (0..size)
            .map(|line_number| {
                let number_of_padding_spaces = (line_number - midpoint_index).abs();
                let number_of_asteriks = size - number_of_padding_spaces * 2;

                diamond_line(number_of_asteriks, number_of_padding_spaces)
            })
            .collect();
        Some(output)
    }
}

#[cfg(test)]
mod tests {
    use crate::diamond;

    #[test]
    fn it_works_for_even_inputs() {
        assert_eq!(diamond(2), None);
        assert_eq!(diamond(4), None);
        assert_eq!(diamond(60), None);
    }

    #[test]
    fn it_works_for_negative_inputs() {
        assert_eq!(diamond(-2), None);
        assert_eq!(diamond(-5), None);
        assert_eq!(diamond(-11), None);
    }

    #[test]
    fn it_works_for_zero() {
        // This is not defined in the spec
        assert_eq!(diamond(0), None);
    }

    #[test]
    fn a_single_asterik_is_a_basic_diamond() {
        let expected_output = "*\n".to_string();
        assert_eq!(diamond(1), Some(expected_output));
    }

    #[test]
    fn it_works_with_a_small_diamond() {
        let expected_output = " * \n***\n * \n".to_string();
        assert_eq!(diamond(3), Some(expected_output));
    }

    #[test]
    fn it_works_with_a_large_diamond() {
        let expected_output = "     *     \n    ***    \n   *****   \n  *******  \n ********* \n***********\n ********* \n  *******  \n   *****   \n    ***    \n     *     \n".to_string();
        assert_eq!(diamond(11), Some(expected_output));
    }
}

Collapse
 
andrewbrown profile image
Andrew Brown πŸ‡¨πŸ‡¦

+1 for Rust
+1 for TDD

Collapse
 
ben profile image
Ben Halpern

Nice!

Collapse
 
vbarinov profile image
Vlad Barinov

Hey! Also JavaScript

function diamond(n){
  if (n <= 0 || n % 2 === 0) {
    return null;
  }

  const repeater = ch => n => ch.repeat(n)
  const spacer = repeater(' ')
  const asterixer = repeater('*')

  let diam = ''
  for (let row = 1; row <= n; row++) {
    const spaces = Math.abs(n - ((2*row) - 1))
    const stars = n - spaces
    diam += `${spacer(spaces / 2)}${asterixer(stars)}\n`
  }

  return diam
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
mon_ste_cat profile image
Monica Stevens

This is my favorite JS answer to this challenge πŸ‘ I like how you used the repeater, keeps things clean & compact.

Collapse
 
auroratide profile image
Timothy Foster • Edited on

Haskell!

import Data.Maybe

diamond :: Int -> Maybe [Char]
diamond n
  | n < 1     = Nothing
  | even n    = Nothing
  | otherwise = Just (concat $ map line rows)
  where
    line stars = replicate (div (n - stars) 2) ' ' ++ replicate stars '*' ++ "\n"
    rows = filter odd ([1..n] ++ reverse [1..(n - 2)])
Collapse
 
kunde21 profile image
Chad Kunde

Go (with bonus diamond of diamonds) playground link


func diamond(center int) (string, error) {
    if (center & 1) == 0 {
        return "", errors.New("center row must be an odd number")
    }
    return strings.Join(makeDiamond(center), "\n"), nil
}

func diamondOfDiamonds(center int) (string, error) {
    if (center & 1) == 0 {
        return "", errors.New("center row must be an odd number")
    }
    dmd := makeDiamond(center)
    outBuf := make([]string, center)
    row := strings.Repeat(" ", center)
    for i := 0; i <= center/2; i++ {
        rowBuf := make([]string, center)
        for j := range rowBuf {
            rowBuf[j] = strings.Repeat(row, center/2-i) + strings.Repeat(dmd[j], 2*i+1) + strings.Repeat(row, center/2-i)
        }
        outBuf[i], outBuf[center-i-1] = strings.Join(rowBuf, "\n"), strings.Join(rowBuf, "\n")
    }
    return strings.Join(outBuf, "\n"), nil
}

func makeDiamond(center int) []string {
    outBuf := make([]string, center)
    row := bytes.Repeat([]byte{' '}, center)
    for l, r := (center / 2), (center / 2); l >= 0; l, r = l-1, r+1 {
        row[l], row[r] = '*', '*'
        outBuf[center/2-l], outBuf[center/2+l] = string(row), string(row)
    }
    return outBuf
}
Collapse
 
coreyja profile image
Corey Alexander

Woah that's cool! I wanted to see what your diamond of diamonds looked like!

Thought I'd paste in the medium sized one, and let people go to the playground for the big one!

            *            
           ***           
          *****          
           ***           
            *            
       *    *    *       
      ***  ***  ***      
     ***************     
      ***  ***  ***      
       *    *    *       
  *    *    *    *    *  
 ***  ***  ***  ***  *** 
*************************
 ***  ***  ***  ***  *** 
  *    *    *    *    *  
       *    *    *       
      ***  ***  ***      
     ***************     
      ***  ***  ***      
       *    *    *       
            *            
           ***           
          *****          
           ***           
            *            

Collapse
 
erosilva profile image
ErΓ΄ • Edited on

JS ❀️

const diamontLayersStructure = (base) => {
    let diamontLayer = '';
    for (let baseIdx = 0; baseIdx < base; baseIdx++) {
        diamontLayer += '*';
    }
    return diamontLayer;
}

const diamont = base => {
    if (base % 2 === 0) {
        return null;
    }
    let diamontLayers = diamontLayersStructure(base);
    let diamontSpacement  = '';
    while (base !== 1) {
        base -= 2;
        diamontSpacement += ' ';
        let diamontNextLayer = diamontLayersStructure(base);
        diamontLayers = `${diamontSpacement}${diamontNextLayer}\n${diamontLayers}`;
        diamontLayers += `\n${diamontSpacement}${diamontNextLayer}`;
    }
    return console.log(diamontLayers);
}
diamont(10); //= null
diamont(11); //= diamont
Collapse
 
v613 profile image
Ceban Dumitru • Edited on

BASH

drawAsteriks () {
  local n=${2};
  local row=${1};
  local numberOfSpaces=$(( (${n}-${row}-(${row}-1))/2 ));
  local numberOfchars=$(( ${n} - ${numberOfSpaces#-}*2 ));
  local spaces=$(printf '%*s' ${numberOfSpaces} '');
  local chars=$(printf '%*s' ${numberOfchars} '' | tr ' ' '+');
  local result=${spaces}${chars}${spaces};
  echo "${result}";
}

if [[ $((${1} % 2 )) = 0 ]]; then
  echo "the argument is even: ${1}";
else 
  for (( i = 1; i <= ${1}; i++ )); do
    drawAsteriks ${i} ${1};
  done;
fi
echo'';
Collapse
 
kaspermeyer profile image
Kasper Meyer

Ruby solution

require "minitest/autorun"

class DiamondGenerator
  def initialize width
    @width = width
  end

  def generate
    return nil if invalid_width?

    number_of_asterisks.map { |n| number_to_asterisks(n) }.join("\n")
  end

  private

    def invalid_width?
      @width.even? or @width.negative?
    end

    def number_to_asterisks number
      ("*" * number).center(@width).rstrip
    end

    def number_of_asterisks
      steps = (1...@width).step(2).to_a
      steps + [@width] + steps.reverse
    end
end

class DiamondGeneratorTest < MiniTest::Test
  def test_valid_diamond
    expected_diamond = <<~DIAMOND.chomp
         *
        ***
       *****
      *******
     *********
    ***********
     *********
      *******
       *****
        ***
         *
    DIAMOND

    assert_equal expected_diamond, DiamondGenerator.new(11).generate
  end

  def test_diamond_with_even_width
    assert_nil DiamondGenerator.new(6).generate
  end

  def test_diamond_with_negative_width
    assert_nil DiamondGenerator.new(-2).generate
  end
end

Collapse
 
highcenburg profile image
Vicente Reyes

Python

def diamond():

    num = 9

    for i in range(1, num+1):
      i = i - (num//2 +1)
      if i < 0:
        i = -i
      print(" " * i + "*" * (num - i*2) + " "*i)
diamond()
Collapse
 
garrett profile image
Garrett / G66 / megabyteGhost

The shortest one by far

Collapse
 
highcenburg profile image
Vicente Reyes

It's also fast.

Collapse
 
ben profile image
Ben Halpern • Edited on

Ruby

def diamond(apex_size)
  shape_array = (1..apex_size).to_a + (1..apex_size -1).to_a.reverse
  shape_array.each { |num| puts diamond_row(apex_size, num) }
end

def diamond_row(apex_size, num)
  "#{" " * (apex_size - num)} #{"*" * ((num * 2 - 1))}"
end
Collapse
 
ben profile image
Ben Halpern

I didn’t follow the fine print so I’m not sure this totally fits the spec now that I’m reading more carefully.

I’ll be more careful tomorrow πŸ˜„

Collapse
 
believer profile image
Rickard Natt och Dag

ReasonML / OCaml

Runnable example: sketch.sh/s/RvRBbbn6iqnEIumBYs7UwE/

type parity =
  | Even
  | Odd;

let testEven = value => value mod 2 == 0 ? Even : Odd;

let rec createDiamond = (~middleStars, ~shape=[], ~i=1, ()) => {
  switch (testEven(middleStars)) {
  | Even => None
  | Odd =>
    switch (middleStars - i) {
    | 0 =>
      Some(
        [List.rev(shape), [String.make(middleStars, '*')], shape]
        |> List.flatten
        |> String.concat("\n"),
      )
    | _ =>
      let padding = (middleStars - i) / 2;
      let row = Bytes.make(middleStars, ' ');

      Bytes.fill(row, padding, i, '*');
      let shape = [Bytes.to_string(row), ...shape];

      createDiamond(~middleStars, ~i=i + 2, ~shape, ());
    }
  };
};

switch (createDiamond(~middleStars=11, ())) {
| None => ()
| Some(d) => print_string(d)
};

Collapse
 
kesprit profile image
kesprit

My solution for Swift :

func diamond(size: Int, character: Character) {

    guard size > 2 else { return }

    var elements = [String]()
    var subsize = size

    while subsize > 2 {
        subsize -= 2
        elements.append(String(repeating: " ", count: (size - subsize) / 2) + String(repeating: character, count: subsize))
    }
    [elements.reversed(),[String(repeating: character, count: size)],elements].forEach { (tab) in
        tab.forEach { (string) in
            print(string)
        }
    }
}
Collapse
 
coolshaurya profile image
Shaurya

Javascript

function diamond(input) {
    const blank = " "
    const full = "*"
    let output = ""

    if (input%2 === 0) {return null;}

    for (let i=1;i<input;i+=2) {
        spacing = blank.repeat((input-i)/2)
        output += spacing + full.repeat(i) + spacing + "\n"
        }

    output += full.repeat(input) + output.split("").reverse().join("")

    console.log(output)
    }

You can change the characters which are considered blank and full to create different effects.

normal output -

   *   
  ***  
 ***** 
*******
 ***** 
  ***  
   *   

with blank="*" and full="@" -

***@***
**@@@**
*@@@@@*
@@@@@@@
*@@@@@*
**@@@**
***@***
Collapse
 
coolshaurya profile image
Shaurya

If you mess with the fill/blank values, you get some nice art -

________shaurya________
_______shauryashauryashaurya_______
______shauryashauryashauryashauryashaurya______
_____shauryashauryashauryashauryashauryashauryashaurya_____
____shauryashauryashauryashauryashauryashauryashauryashauryashaurya____
___shauryashauryashauryashauryashauryashauryashauryashauryashauryashauryashaurya___
__shauryashauryashauryashauryashauryashauryashauryashauryashauryashauryashauryashauryashaurya__
_shauryashauryashauryashauryashauryashauryashauryashauryashauryashauryashauryashauryashauryashauryashaurya_
shauryashauryashauryashauryashauryashauryashauryashauryashauryashauryashauryashauryashauryashauryashauryashauryashaurya
_ayruahsayruahsayruahsayruahsayruahsayruahsayruahsayruahsayruahsayruahsayruahsayruahsayruahsayruahsayruahs_
__ayruahsayruahsayruahsayruahsayruahsayruahsayruahsayruahsayruahsayruahsayruahsayruahsayruahs__
___ayruahsayruahsayruahsayruahsayruahsayruahsayruahsayruahsayruahsayruahsayruahs___
____ayruahsayruahsayruahsayruahsayruahsayruahsayruahsayruahsayruahs____
_____ayruahsayruahsayruahsayruahsayruahsayruahsayruahs_____
______ayruahsayruahsayruahsayruahsayruahs______
_______ayruahsayruahsayruahs_______
________ayruahs________
        11        
       111111       
      1111111111      
     11111111111111     
    111111111111111111    
   1111111111111111111111   
  11111111111111111111111111  
 111111111111111111111111111111 
1111111111111111111111111111111111
 111111111111111111111111111111 
  11111111111111111111111111  
   1111111111111111111111   
    111111111111111111    
     11111111111111     
      1111111111      
       111111       
        11        
        >>>>        
       >>>>>>>>>>>>       
      >>>>>>>>>>>>>>>>>>>>      
     >>>>>>>>>>>>>>>>>>>>>>>>>>>>     
    >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>    
   >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>   
  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  
 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  
   >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>   
    >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>    
     >>>>>>>>>>>>>>>>>>>>>>>>>>>>     
      >>>>>>>>>>>>>>>>>>>>      
       >>>>>>>>>>>>       
        >>>>        
        -->        
       -->-->-->       
      -->-->-->-->-->      
     -->-->-->-->-->-->-->     
    -->-->-->-->-->-->-->-->-->    
   -->-->-->-->-->-->-->-->-->-->-->   
  -->-->-->-->-->-->-->-->-->-->-->-->-->  
 -->-->-->-->-->-->-->-->-->-->-->-->-->-->--> 
-->-->-->-->-->-->-->-->-->-->-->-->-->-->-->-->-->
 >-->-->-->-->-->-->-->-->-->-->-->-->-->-->-- 
  >-->-->-->-->-->-->-->-->-->-->-->-->--  
   >-->-->-->-->-->-->-->-->-->-->--   
    >-->-->-->-->-->-->-->-->--    
     >-->-->-->-->-->-->--     
      >-->-->-->-->--      
       >-->-->--       
        >--        
aaaaaaaaaaaaaaaa-aaaaaaaaaaaaaaaa
aaaaaaaaaaaaaa---aaaaaaaaaaaaaa
aaaaaaaaaaaa-----aaaaaaaaaaaa
aaaaaaaaaa-------aaaaaaaaaa
aaaaaaaa---------aaaaaaaa
aaaaaa-----------aaaaaa
aaaa-------------aaaa
aa---------------aa
-----------------
aa---------------aa
aaaa-------------aaaa
aaaaaa-----------aaaaaa
aaaaaaaa---------aaaaaaaa
aaaaaaaaaa-------aaaaaaaaaa
aaaaaaaaaaaa-----aaaaaaaaaaaa
aaaaaaaaaaaaaa---aaaaaaaaaaaaaa
aaaaaaaaaaaaaaaa-aaaaaaaaaaaaaaaa
*********************   *********************
******************     ******************
***************       ***************
************         ************
*********           *********
******             ******
***               ***

***               ***
******             ******
*********           *********
************         ************
***************       ***************
******************     ******************
*********************   *********************
************************ ************************
lallallallallallallallal lallallallallallallallal
lallallallallallallal   lallallallallallallal
lallallallallallal     lallallallallallal
lallallallallal       lallallallallal
lallallallal         lallallallal
lallallal           lallallal
lallal             lallal
lal               lal

lal               lal
lallal             lallal
lallallal           lallallal
lallallallal         lallallallal
lallallallallal       lallallallallal
lallallallallallal     lallallallallallal
lallallallallallallal   lallallallallallallal
lallallallallallallallal lallallallallallallallal
lalallalallalallalallalallalallalallalal lalallalallalallalallalallalallalallalal
lalallalallalallalallalallalallalal   lalallalallalallalallalallalallalal
lalallalallalallalallalallalal     lalallalallalallalallalallalal
lalallalallalallalallalal       lalallalallalallalallalal
lalallalallalallalal         lalallalallalallalal
lalallalallalal           lalallalallalal
lalallalal             lalallalal
lalal               lalal

lalal               lalal
lalallalal             lalallalal
lalallalallalal           lalallalallalal
lalallalallalallalal         lalallalallalallalal
lalallalallalallalallalal       lalallalallalallalallalal
lalallalallalallalallalallalal     lalallalallalallalallalallalal
lalallalallalallalallalallalallalal   lalallalallalallalallalallalallalal
lalallalallalallalallalallalallalallalal lalallalallalallalallalallalallalallalal
Collapse
 
coolshaurya profile image
Shaurya • Edited on

Did this in rust too:

fn main() {
    let diamond_size = 17;

    println!("{}", gen_diamond(diamond_size));
}

fn gen_diamond(size: u8) -> String {
    if size % 2 == 0 || size < 3 {
        return "i i\nn n\nv p\na u\nl t\ni\nd".to_string();
    }

    let fill = "*";
    let blank = " ";

    let mut output = String::from("");

    let mut i = 1;
    while i <= (size - 2) {
        let blank_space = blank.repeat(usize::from((size - i) / 2));
        let filled_space = fill.repeat(usize::from(i));
        let line = format!("{0}{1}{2}\n", &blank_space, &filled_space, &blank_space);
        output.push_str(&line);
        i += 2
    }
    output = format!(
        "{0}{1}{2}",
        &output,
        fill.repeat(usize::from(size)),
        &output.chars().rev().collect::<String>()
    );

    output.to_string()
}

On invalid input, it returns -

i i
n n
v p
a u
l t
i
d
Collapse
 
orenovadia profile image
orenovadia

I would like to add:
1) Making str.center do some of the dirty work
2) Using ranges (and chain) to iterate up and down instead of keeping a state on num


from itertools import chain

def diamond(n): 
    if n <=0 or n % 2 == 0:
        raise ValueError('n must be odd and positive') 
    for i in chain(range(1, n + 1, 2), reversed(range(1, n, 2))): 
        print(('*' * i).center(n)) 

diamond(1)
diamond(3)
diamond(5)

It might be more readable to have two loops instead of using chain but I like chaining more because I don't need to repeat the print

Thread Thread
 
highcenburg profile image
Vicente Reyes

Is this fast too?

Thread Thread
 
orenovadia profile image
orenovadia

I wouldn't worry about it too much, since print requires IO, it is by far the bottleneck.

For the rest of the utilities that I used: range, chain, and str.center: they are all implemented in C if you are using the standard CPython (should be fast).

To avoid the IO, let's compare the two functions as generators of strings (I replaced the print with yield (('*' * i).center(n)) for both my implementation and Nicks:

In [39]: %timeit tuple(my_diamond(11))                                                                                                                                                                                 
3.59 Β΅s Β± 37.6 ns per loop (mean Β± std. dev. of 7 runs, 100000 loops each)

In [40]: %timeit tuple(nicks_diamond(11))                                                                                                                                                                           
4.66 Β΅s Β± 69.9 ns per loop (mean Β± std. dev. of 7 runs, 100000 loops each)

Seems like mine is slower than Nicks.
However, on my machine, a single print statement takes about 4.17us, which is almost as long as diamond(11) takes without any prints!

Collapse
 
karstencoba profile image
karsten-coba

Gave it a try in Python. Not exactly as required since the diamond is printed on-the-fly, but I think it shows the main idea to create every line one after the other. Could also be stored in a string and then returned...

import sys

def diamond(width):
    padding = width//2

    while padding > -width//2:
        print(" " * abs(padding) + "*" * (width - abs(padding) * 2))
        padding -= 1

if (len(sys.argv) < 2 or int(sys.argv[1]) < 1 or int(sys.argv[1]) % 2 == 0):
    print("Invalid parameter")
else:
    diamond(int(sys.argv[1]))

Take a look at this:

Settings

Go to your customization settings to nudge your home feed to show content more relevant to your developer experience level. πŸ›