Как мы могли бы реализовать дверь гаража, отслеживая, каково было ее последнее направление движения? - PullRequest
0 голосов
/ 09 февраля 2020

Я выполняю следующее упражнение по программированию: Killer Garage Door . Заявление:

Ситуация

Вы были наняты компанией, производящей электро c гаражные ворота. Несчастные случаи с существующей производственной линией привели к многочисленным поврежденным автомобилям, сломанным конечностям и нескольким убитым домашним животным. Ваша миссия состоит в том, чтобы написать более безопасную версию программного обеспечения их контроллера. Спецификация

Мы всегда начинаем с закрытой двери. Пульт дистанционного управления имеет ровно одну кнопку со следующим поведением:

If the door is closed, a push starts opening the door, and vice-versa
It takes 5 seconds for the door to open or close completely
While the door is moving, one push pauses movement, another push resumes movement in the same direction

Для того, чтобы сделать дверь более безопасной, он был оснащен обнаружением препятствий на основе сопротивления. Когда дверь обнаруживает препятствие, она должна немедленно изменить направление движения. Input

Строка, в которой каждый символ представляет одну секунду со следующими возможными значениями.

'.' No event
'P' Button has been pressed
'O' Obstacle has been detected (supersedes P)

Например, «..P ....» означает, что ничего не происходит в течение двух секунд, затем кнопка нажата, то больше никаких событий. Выходные данные

Строка, где каждый символ представляет одну секунду и указывает положение двери (0, если полностью закрыт, и 5, полностью открыт). Дверь начинает двигаться немедленно, поэтому ее положение изменяется в ту же секунду, что и событие. Пример

.. P ... O ..... в качестве ввода должен выдавать 001234321000 в качестве вывода

Я написал следующий код:

public class Door {
  public static String run(String events) {
    System.out.println("\n\n\nevents: "+events);
    int count = 0;
    StringBuilder result = new StringBuilder();
    boolean movingUp = false;
    boolean movingDown = false;

    for(char c : events.toCharArray()){
      System.out.println("movingUp: "+movingUp);
      System.out.println("movingDown: "+movingDown);
      System.out.println("c: "+c);
      System.out.println("count: "+count);
      System.out.println("result: "+result);
      if(c=='.'){
        if(movingUp){
          result.append(count < 5 ? ++count : 5);
        }else if(movingDown){
          result.append(count > 0 ? --count : 0);
        }else{
          result.append(count);
        }
      }else if(c=='P'){
        if(count==5){
          movingUp = false;
          movingDown = true;
          result.append(count > 0 ? --count : 0);
        }else if(movingUp){
          movingUp = false;
          result.append(count);
        }else if(movingDown){
          movingDown = false;
          result.append(count);
        }else{
          movingUp = true;  
          result.append(count < 5 ? ++count : 5);
        }
      }else if(c=='O'){
        movingUp = false;
        movingDown = true;
        result.append(count > 0 ? --count : 0);
      }
    }
    return result.toString();
  }
}

Мне было интересно, как мы можем справиться, когда есть пауза, а затем дверь возобновляется, чтобы можно было продолжать подниматься или опускаться, как это было до паузы?

Вот некоторые тесты те, которые отмечены знаком →, - это те, в которых поведение паузы или препятствия терпит неудачу из-за того, что в настоящее время код предполагает, что после паузы он должен go вверх, а после препятствия он должен go вниз.

import static org.junit.Assert.*;

import org.junit.Test;

public class MainTest {
  @Test
  public void testNormalOperation() {
    test("should stay closed unless button is pressed (no buttonpresses)", "..........", "0000000000");
    test("should start opening on buttonpress", "P..", "123");
    test("should open completely and stay open", "P....", "12345");
  }

  @Test
  public void testPause() {
    test("should start opening and pause on second buttonpress", "P.P..", "12222");
    test("→ should resume closing on third buttonpress", ".....P......P.P..P....", "0000012345554333321000");
  }

  @Test
  public void testObstacles() {
    test("should reverse while opening", "P.O....", "1210000");
    test("→ should reverse while closing", "P.O....", "12345554345555");
  }

  @Test
  public void testObstaclePlusPause () {
    test("→ should reverse while opening (and allow pause)", "P..OP..P..", "1232222100");
  }

  @Test
  public void testExample() {
    test("should start opening and reverse when obstacle", "..P...O.....", "001234321000");
  }

  private void test(String description, String events, String result) {
    assertEquals(description ,result, Door.run(events));
  }
}

Я также прочитал:

Как мы можем реализовать дверь гаража, отслеживая то, что было ее последним направление движения?

Ответы [ 2 ]

1 голос
/ 09 февраля 2020

Ты почти там. Поэтому, чтобы сохранить ваш код в целости и сохранности, я попытался внести как можно меньше изменений.

Однако перед продолжением необходимо внести одно исправление - один из ваших тестовых примеров неправильный, 2-й тестовый случай в testObstacles - нет допустимо.

Было:

test("→ should reverse while closing", "P.O....", "12345554345555");

// in the above the first 'P' would start it "12" and the first "O" should
// immediately reverse it (which would be reversing while opening) "1000"

Если предположить, что текст правильный и ожидаемый результат верный, то ввод должен быть:

test("→ should reverse while closing", "P.......P.O....", "12345554345555");

Short ответ:

Введите логическое значение, которое записывает предыдущее движение вверх или вниз:

boolean lastUp = true;  // default to true since we always assume initially CLOSED

Остальные изменения относятся к случаям 'P' и 'O' с использованием lastUp :

else if (movingUp)
{
  movingUp = false;
  lastUp = true;              // ADDED
  result.append (count);
}

...

else if (movingDown)
{
  movingDown = false;
  lastUp = false;            // ADDED
  result.append (count);
}

... и обновить регистр по умолчанию

else 
{
    movingUp = lastUp;         // CHANGED
    movingDown = !lastUp;      // ADDED
    result.append(movingUp ? ++count : --count); // CHANGED
}

И в случае 'O':

{
    movingUp = !movingUp;     // CHANGED
    movingDown = !movingDown; // CHANGED
    lastUp = !lastUp;         // ADDED
    result.append(movingUp ? ++count : --count); // CHANGED
}

Длинный ответ

Ну, в длинном ответе используется другой подход, и это, по сути, ответ @grodzi (состояние, затем ввод), который имеет отличное объяснение, но поскольку я так долго, чтобы ввести его, вопрос спорный.

Я добавлю это, поскольку это на языке о F на ваш выбор .:

public static class Door
  {
    enum DoorStates { OPEN, CLOSED, MOVINGUP, MOVINGDOWN, PAUSED }

    public static String run (String events)
    {
      int count = 0;
      StringBuilder result = new StringBuilder ();
      DoorStates currentState = DoorStates.CLOSED;
      DoorStates lastMovingState = DoorStates.MOVINGUP;

      for (char c:events.toCharArray ())
    {


      switch (currentState)
        {
        case OPEN:
            if (c == 'P') {
              currentState = DoorStates.MOVINGDOWN;
              count--;
            }
            // do nothing for O and .
          break;

        case CLOSED:
            if (c == 'P') {
                currentState = DoorStates.MOVINGUP;
                count = 1;
            }
            // do nothing for O and .
            break;

        case MOVINGUP: // movingup
            if (c == 'P') {
                currentState = DoorStates.PAUSED; // paused
            } else if (c == 'O') {
                currentState = DoorStates.MOVINGDOWN; // movingdown
                count--;
            } else {
                if (count < 5) {
                    count++;
                }
                if (count == 5) {
                    currentState = DoorStates.OPEN; // open
                }
            }
            break;


        case MOVINGDOWN:
            if (c == 'P') {
                currentState = DoorStates.PAUSED; // paused
            } else if (c == 'O') {
                currentState = DoorStates.MOVINGUP; // movingup
                count++;
            } else {
                if (count > 0) {
                    count--;
                }
                if (count == 0) {
                    currentState = DoorStates.CLOSED;
                }
            }
            break;

        case PAUSED:
            if (c == 'P') {
                currentState = lastMovingState;
                count = (currentState == DoorStates.MOVINGUP ? count+1 : count-1);
            }
            // do nothing for O and .
            break;

        } // end switch


        if (currentState == DoorStates.MOVINGUP || 
            currentState == DoorStates.MOVINGDOWN) {
            lastMovingState = currentState;
        }

        result.append (count);
    } // endfor

    return result.toString();
  } // end run

} // door class
0 голосов
/ 09 февраля 2020

Подход с использованием состояний.

Мы можем перечислить два тривиальных: закрытый и открытый.

Я назову их соответственно idleUp и idleDown.

  • переходы для idleUp:

    • P (пользователь нажал кнопку): теперь вы находитесь на activeUp
  • переходы для activeUp:

    • P (пользователь нажал в другой раз): idleUp (сохранить направление, но ничего не делать)
    • O: activeDown
    • End (дверь полностью открыта): idleDown
  • переходы для activeDown:

    • P: idleDown
    • O: activeUp
    • End: idleUp
  • переходов для idleDown:

    • P (пользователь нажал кнопку): теперь вы activeDown

Что-то как на графике ниже.

  +----------+   P   +--------+
  | activeUp +<----->+ idleUp |
  +----------+       +--------+
       |    ^    O        ^
       |End +---------+   |End
       v              v   |
  +----------+   P   +------------+
  | idleDown +<----->+ activeDown |
  +----------+       +------------+

Обратите внимание на (как-то изящно) симметрию между activeUp-idleUp и activeDown-idleDown.

В вашем подходе вы сначала char, а затем State. В приведенном ниже коде мы сначала указываем состояние, затем char.

Вместо того, чтобы рассматривать четыре состояния, мы только «кодируем» два: активное и простое (но под капотом у нас действительно есть четыре состояния, просто обусловленные через переменную вместо классы)

Вы, конечно, можете использовать состояние шаблона, но чувствует себя немного утомительно.

function simu (str) {
  let direction = 1 // 1 for up, -1 for down
  let pos = 0 // initially closed
  let active = false
  let out = '' //  the return string
  for (let c of str) {
    if (active) {
      if (c === '.') {
        pos += 1 * direction
        if (pos <= 0 || pos >= 5) {// End
          active = false
          direction *= -1
        }
      }
      else if (c === 'O') {
        direction *= -1
        pos += 1 * direction // instant change of direction
      }
      else if (c === 'P') {
        active = false
      }
    } else { // idle
      if (c === 'P') {
        active = true
        pos += 1 * direction // instant change too
      }
    }
    out += pos
  }
  return out
}
function test(libel, s, exp) {
  const out = simu(s)
  if (out != exp) {
    throw 'failed '+libel+' got '+out+' expect '+exp
  }
  console.log('OK', s, '->', out)
}
test("should stay closed unless button is pressed (no buttonpresses)", "..........", "0000000000")
test("should start opening on buttonpress", "P..", "123")
test("should open completely and stay open", "P....", "12345")
test("should start opening and pause on second buttonpress", "P.P..", "12222")
test("→ should resume closing on third buttonpress", ".....P......P.P..P....", "0000012345554333321000")
test("should reverse while opening", "P.O....", "1210000")
test("→ should reverse while closing", "P.......P.O....", "123455554345555") //thx Andy
test("→ should reverse while opening (and allow pause)", "P..OP..P..", "1232222100")
test("should start opening and reverse when obstacle", "..P...O.....", "001234321000")
...