From cb3c5623aeaa65c2a39893f2e5e0ac993d8a3cc3 Mon Sep 17 00:00:00 2001 From: Freezed Date: Thu, 16 Jul 2020 08:41:01 +0200 Subject: [PATCH 1/7] Add specs for `sand_grain_drop.py` --- technical_tests/sand_grain_drop.py | 44 ++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 technical_tests/sand_grain_drop.py diff --git a/technical_tests/sand_grain_drop.py b/technical_tests/sand_grain_drop.py new file mode 100644 index 0000000..a7e2c16 --- /dev/null +++ b/technical_tests/sand_grain_drop.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# coding: utf8 + +""" +Author: freezed 2020-07-16 +Licence: `GNU GPL v3` GNU GPL v3: http://www.gnu.org/licenses/ + +This file is part of [`free_zed/mypsb`](https://gitlab.com/free_zed/mypsb/) +""" + + +def main(pile, n): + """ + This function returns the situation of a sand `pile` after droping `n` + sand grain on top of it. + + + 1. Sand pile is a square table of uneven size (viewed from up) + 1. Sand grain is always dropped on center of pile. + 1. When a cell had 4 grains inside, these grains moves on the near 4th cells + 1. Grains going out the pile are losts + + :Tests: + >>> main([[1,1,1],[1,1,1],[1,1,1]],1) + [[1, 1, 1], [1, 2, 1], [1, 1, 1]] + >>> main([[1,1,1],[1,1,1],[1,1,1]],2) + [[1, 1, 1], [1, 3, 1], [1, 1, 1]] + >>> main([[1,1,1],[1,1,1],[1,1,1]],3) + [[1, 2, 1], [2, 0, 2], [1, 2, 1]] + >>> main([[1,1,1,1,1],[1,1,1,1,1],[1,1,1,1,1],[1,1,1,1,1],[1,1,1,1,1]],1) + [[1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 2, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1]] + >>> main([[1,1,1,1,1],[1,1,1,1,1],[1,1,1,1,1],[1,1,1,1,1],[1,1,1,1,1]],3) + [[1, 1, 1, 1, 1], [1, 1, 2, 1, 1], [1, 2, 0, 2, 1], [1, 1, 2, 1, 1], [1, 1, 1, 1, 1]] + >>> main([[0,0,3,0,0],[0,0,3,0,0],[3,3,3,3,3],[0,0,3,0,0],[0,0,3,0,0]],1) + [[0, 1, 0, 1, 0], [1, 2, 2, 2, 1], [0, 2, 0, 2, 0], [1, 2, 2, 2, 1], [0, 1, 0, 1, 0]] + """ + + return pile + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 9bf58c7cef71e4a84abbfe02c8d14a304fb3aae1 Mon Sep 17 00:00:00 2001 From: Freezed Date: Thu, 16 Jul 2020 09:40:40 +0200 Subject: [PATCH 2/7] Find id for pile center --- technical_tests/sand_grain_drop.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/technical_tests/sand_grain_drop.py b/technical_tests/sand_grain_drop.py index a7e2c16..93d5284 100644 --- a/technical_tests/sand_grain_drop.py +++ b/technical_tests/sand_grain_drop.py @@ -35,7 +35,9 @@ def main(pile, n): [[0, 1, 0, 1, 0], [1, 2, 2, 2, 1], [0, 2, 0, 2, 0], [1, 2, 2, 2, 1], [0, 1, 0, 1, 0]] """ - return pile + center = int((len(pile) - 1) / 2) + + return center if __name__ == "__main__": From 7a8a0945c6ca66c0693e54874df924d531b14b1c Mon Sep 17 00:00:00 2001 From: Freezed Date: Thu, 16 Jul 2020 09:48:31 +0200 Subject: [PATCH 3/7] Drop sand grains without propagation --- technical_tests/sand_grain_drop.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/technical_tests/sand_grain_drop.py b/technical_tests/sand_grain_drop.py index 93d5284..ab34797 100644 --- a/technical_tests/sand_grain_drop.py +++ b/technical_tests/sand_grain_drop.py @@ -37,7 +37,14 @@ def main(pile, n): center = int((len(pile) - 1) / 2) - return center + while n != 0: + + if pile[center][center] < 3: + pile[center][center] += 1 + + n -= 1 + + return pile if __name__ == "__main__": From ff479a6fe2d8d8a312a63bb223aa425c42bc9ad0 Mon Sep 17 00:00:00 2001 From: Freezed Date: Thu, 16 Jul 2020 10:17:18 +0200 Subject: [PATCH 4/7] Change approach global instead local 1st idea was to manage propagation cells after cells. But this looks to generate a chain reaction that I cannot manage in a function. New idea is to manage the whole pile : * add a grain * find cells that reached 4 * change cell & update neighbors * find cells that reached 4 * change cell & update neighbors --- technical_tests/sand_grain_drop.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/technical_tests/sand_grain_drop.py b/technical_tests/sand_grain_drop.py index ab34797..82bf1d3 100644 --- a/technical_tests/sand_grain_drop.py +++ b/technical_tests/sand_grain_drop.py @@ -39,9 +39,7 @@ def main(pile, n): while n != 0: - if pile[center][center] < 3: - pile[center][center] += 1 - + pile[center][center] += 1 n -= 1 return pile From eebd2751fe513e093b4b1666ecb59a0bfa11c75d Mon Sep 17 00:00:00 2001 From: Freezed Date: Thu, 16 Jul 2020 11:19:41 +0200 Subject: [PATCH 5/7] Update only drop cell Structure for pile walking to find cells greater than 3 is set I do not update neighbors cells, then only central value of the pile (where the sand grain is dropped) is at the right value in the end --- technical_tests/sand_grain_drop.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/technical_tests/sand_grain_drop.py b/technical_tests/sand_grain_drop.py index 82bf1d3..7c774f6 100644 --- a/technical_tests/sand_grain_drop.py +++ b/technical_tests/sand_grain_drop.py @@ -35,11 +35,21 @@ def main(pile, n): [[0, 1, 0, 1, 0], [1, 2, 2, 2, 1], [0, 2, 0, 2, 0], [1, 2, 2, 2, 1], [0, 1, 0, 1, 0]] """ - center = int((len(pile) - 1) / 2) + size = len(pile) + center = int((size - 1) / 2) while n != 0: pile[center][center] += 1 + + # find cells > 3 + for x in range(size): + + for y in range(size): + + if pile[x][y] > 3: + pile[x][y] -= 4 + n -= 1 return pile From 2119783dad57354f6814f59214e419c79739aa75 Mon Sep 17 00:00:00 2001 From: Freezed Date: Thu, 16 Jul 2020 11:49:44 +0200 Subject: [PATCH 6/7] Update neighbors cells Propation is right for small iteration and/or pile with low grain rate. For pile with high grain rate, the main while loop is not enough to make propagation in all cells --- technical_tests/sand_grain_drop.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/technical_tests/sand_grain_drop.py b/technical_tests/sand_grain_drop.py index 7c774f6..ac65714 100644 --- a/technical_tests/sand_grain_drop.py +++ b/technical_tests/sand_grain_drop.py @@ -36,10 +36,10 @@ def main(pile, n): """ size = len(pile) + x_max, y_max = size - 1, size - 1 center = int((size - 1) / 2) while n != 0: - pile[center][center] += 1 # find cells > 3 @@ -50,6 +50,19 @@ def main(pile, n): if pile[x][y] > 3: pile[x][y] -= 4 + # update west neighbor + if x < x_max: + pile[x + 1][y] += 1 + # update east neighbor + if x > 0: + pile[x - 1][y] += 1 + # update north neighbor + if y < y_max: + pile[x][y + 1] += 1 + # update south neighbor + if y > 0: + pile[x][y - 1] += 1 + n -= 1 return pile From 0bd7ff72b0784eba2be97c012d553871e866eb67 Mon Sep 17 00:00:00 2001 From: Freezed Date: Thu, 16 Jul 2020 12:06:45 +0200 Subject: [PATCH 7/7] Refactor propagation in a function That's better! I want more test cases to check my algorythm ! --- technical_tests/sand_grain_drop.py | 64 +++++++++++++++++++----------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/technical_tests/sand_grain_drop.py b/technical_tests/sand_grain_drop.py index ac65714..12d2ec6 100644 --- a/technical_tests/sand_grain_drop.py +++ b/technical_tests/sand_grain_drop.py @@ -36,38 +36,56 @@ def main(pile, n): """ size = len(pile) - x_max, y_max = size - 1, size - 1 center = int((size - 1) / 2) while n != 0: pile[center][center] += 1 - - # find cells > 3 - for x in range(size): - - for y in range(size): - - if pile[x][y] > 3: - pile[x][y] -= 4 - - # update west neighbor - if x < x_max: - pile[x + 1][y] += 1 - # update east neighbor - if x > 0: - pile[x - 1][y] += 1 - # update north neighbor - if y < y_max: - pile[x][y + 1] += 1 - # update south neighbor - if y > 0: - pile[x][y - 1] += 1 - + do_propagation(pile) n -= 1 + # All sand grain are dropped, let's finish propagations + while True in [4 in row for row in pile]: + do_propagation(pile) + return pile +def do_propagation(pile): + """ + Walk through the pile to propagate sand grains + + :tests: + >>> test_case = [[0,0,4,0,0],[0,2,0,2,0],[4,0,4,0,4],[0,2,0,2,0],[0,0,4,0,0]] + >>> do_propagation(test_case) + >>> print(test_case) + [[0, 1, 0, 1, 0], [1, 2, 2, 2, 1], [0, 2, 0, 2, 0], [1, 2, 2, 2, 1], [0, 1, 0, 1, 0]] + """ + + size = len(pile) + x_max, y_max = size - 1, size - 1 + + # find cells > 3 + for x in range(size): + + for y in range(size): + + if pile[x][y] > 3: + pile[x][y] -= 4 + + # update west neighbor + if x < x_max: + pile[x + 1][y] += 1 + # update east neighbor + if x > 0: + pile[x - 1][y] += 1 + # update north neighbor + if y < y_max: + pile[x][y + 1] += 1 + # update south neighbor + if y > 0: + pile[x][y - 1] += 1 + + if __name__ == "__main__": import doctest