Доступ к вложенным словарям внутри словаря в Javascript

#javascript #arrays #dictionary

Вопрос:

Я хочу создать календарь, который извлекает праздник на основе текущей даты для моей школы. В настоящее время у меня есть данные, структурированные в виде словаря со словарями внутри него.

 const holidays = {
  January: {
    "Martin Luther King Jr. Day": [17]
  },
  February: {},
  March: {
    "Spring Break": [16]
  },
  April: {},
  May: {},
  June: {},
  July: {
    "Fourth of July": [4]
  },
  August: {},
  September: {},
  October: {},
  November: {
    "Thanksgiving": [25, 26]
  },
  December: {
    "Christmas Eve": [23],
    "Christmas": [25],
    "Winter Break": [26, 27, 28, 29, 30, 31]
  }
};
 

Мой текущий подход заключается в том, что он проверяет месяц, а затем массив для даты. Если дата существует в массиве, она будет извлекать ключевое значение, чтобы показать, какой сегодня праздник на веб-странице «Школьные часы». Я пробовал разные способы поиска в массивах дочерних объектов, но ни один из них не увенчался успехом.

Это моя попытка:

 function testCase() {
  for (var k in holidays) {
    if (x) {
      let y = holidays[x];
      for (var j in y) {
        if (date == y[j]) {
          return a.innerHTML = Object.keys(j[x]);
        } else {
          return a.innerHTML = "NO FIND";
        }
      }
    }
  }
}
```
 

Комментарии:

1. используйте этот includes() метод для поиска во вложенном массиве.

2. Если ваш ввод в функцию поиска принимает дату в качестве ввода, то я не уверен, что это лучшая структура данных для использования. Я бы упростил это, если бы у меня был массив объектов с датой начала и окончания и названием праздника. затем вы можете сравнить данную дату, чтобы увидеть, находится ли она в этом диапазоне, и месяц подразумевается с этой даты.

3. Я долго размышлял над этим

4. Вам это не нужно Object.keys() , так j как это ключ.

5. Вы вернетесь, NO FIND как только найдете неподходящую дату. Это не означает, что в массиве нет совпадающей даты позже.

Ответ №1:

Получите название и дату текущего месяца от Date объекта.

Вы можете использовать название месяца в качестве индекса holidays объекта. Затем вы можете выполнить цикл над ключами этого объекта, используя includes() для проверки, находится ли текущая дата в этом массиве, и вернуть название праздника.

Если вы никогда не возвращаетесь в цикле, вы возвращаете значение по умолчанию в конце.

 const monthNames = ["January", "February", "March", "April", "May", "June",
  "July", "August", "September", "October", "November", "December"];

function testCase() {
  const today = new Date();
  const curMonth = monthNames[today.getMonth()];
  const curDate = today.getDate();
  let holidayMonth = holidays[curMonth];
  for (let holiday in holidayMonth) {
    if (holidays[holiday].includes(curDate)) {
      return holiday;
    }
  }
  return "No holiday found";
}
 

Комментарии:

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

2. Я бы рекомендовал переименовать testCase метод, потому что он означает все, что угодно. Я думаю, что что-то подобное getCurrentHoliday было бы лучше.

3. @KrystianSztadhaus-это просто мое имя-наполнитель. Когда я пересматриваю, редактирую и добавляю комментарии, я возвращаюсь и переименовываю функции из своих прототипов в вещи, которые имеют больше смысла для тех, кто найдет код позже.

Ответ №2:

Я рискну и скажу, что ваша структура данных немного сложна для того, что вам, вероятно, нужно, и если вы контролируете это, то я бы предложил вам начать с упрощения до чего-то подобного:

 const holidays = [
  {name: "Christmas", startFrom: new Date("2021-12-25T00:00:00Z"), endAt: new Date("2021-12-25T23:59:59Z")}
  ... // the other dates in your list
]
 

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

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

Затем вы можете определить функцию, которая принимает ввод даты, и напрямую сравнить даты:

 
function FindHoliday(date) { // a Date Type input parameter
  return holidays.filter(({startFrom, endAt}) => startFrom <= date amp;amp; endAt >= date)
} // returns all holidays that fit in this range.

 

Функция фильтра массива возвращает новый массив со всеми членами, значение которых равно true в данной функции оценщика, поэтому, если вы получаете пустой список, праздников не существует

Отсюда, если вы хотите, просто напишите в дом…

 
a.innerHTML = FindHolidays(new Date("2021-12-25T12:00:00Z")).map(({name}) => name).join(", ")

 

Комментарии:

1. Хорошо! Я попробовал некоторые другие предложения здесь, но с предложенной вами структурой данных я могу добавить дополнительные атрибуты, чтобы консолидировать изменения в расписании (например. Я могу попросить его проверить одно место в течение половины дня, а также полностью закрыть его.) Поскольку мой код будут видеть коллеги с меньшим знанием JS, структура достаточно интуитивно понятна, чтобы им было удобно вносить изменения.

2. Прямо сейчас я также переделываю код вокруг него, чтобы сделать его чистым.

3. Возможно, вы захотите рассмотреть возможность экспорта данных в a . файл yaml или .json, который вы импортируете, вы можете хранить даты в виде строк UTC и анализировать их на даты при приеме внутрь.

4. Я также забыл упомянуть, что некоторые праздники перекрываются (в зависимости от того, насколько широка ваша сфера охвата) — вышеперечисленное с радостью вернет несколько праздников, которые выпадают на один и тот же день

Ответ №3:

Обновить
Я добавил индикатор даты в свое решение ниже, чтобы вы могли переключать дату и видеть, как реагирует функция.

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

Еще одно важное замечание заключается в том, что простые объекты JS не соблюдают и не сохраняют порядок вставки, поэтому было бы лучше использовать массив объектов, по одному объекту в месяц. Использование массива объектов не только учитывает порядок вставки, но и использует нулевую индексацию (это означает, что первый элемент имеет индекс 0). Именно так работает метод даты .getMonth() , где метод возвращает 0 , если текущий месяц-январь, а 11 если текущий месяц-декабрь. Это также позволяет нам легко определять многопраздничные дни с помощью массивов.

Имея это в виду, мы все еще можем использовать комментарии JS для ссылок на названия месяцев. В приведенном ниже примере я установил значение по умолчанию для ввода даты на Рождество 2021-12-25 и включил в этот день как Рождественские, так и зимние каникулы, чтобы вы могли видеть, что функция работает для нескольких праздников.

 const holidayStatus = document.getElementById('holiday-status'),
      dateInput = document.querySelector('input[type="date"]');

const holidays = [
  {   // January
     1: "New Years Day",
    17: "Martin Luther King Jr. Day"
  },
  {}, // February
  {   // March
    16: "Spring Break"
  },
  {}, // April
  {}, // May
  {}, // June
  {   // July
     4: "Fourth of July"
  },
  {}, // August
  {}, // September
  {}, // October
  {   // November
    25: "Thanksgiving",
    26: "Thanksgiving"
  },
  {   // December
    24: ["Christmas Eve", "Winter Break"],
    25: ["Christmas", "Winter Break"],
    26: "Winter Break",
    27: "Winter Break",
    28: "Winter Break",
    29: "Winter Break",
    30: "Winter Break",
    31: ["New Years Eve", "Winter Break"]
  },
];

const combineStrings = strs => (strs = [].concat(strs), strs.length === 1 ? strs[0] : (strs.length === 2 ? strs.join(' and ') : strs.map((str, i) => i === strs.length - 1 ? 'and '   str : str).join(', ')));

const getDate = () => new Date(dateInput.value   ' 12:00');

const printHolidays = date => {
  const todaysHolidays = holidays[date.getMonth()][date.getDate()];
  holidayStatus.textContent = todaysHolidays ? (`This day's ${typeof todaysHolidays === 'string' || todaysHolidays.length === 1 ? 'holiday is' : 'holdays are'} ${combineStrings(todaysHolidays)}.`) : 'This day has no holidays.';
};
printHolidays(getDate());

dateInput.addEventListener('change', () => {
  printHolidays(getDate());
}); 
 #holiday-status {
  display: inline-block;
  margin-top: 5px;
  padding: 5px 10px;
  border: 2px solid #f00;
  box-sizing: border-box;
} 
 <div>Using the date: <input type="date" value="2021-12-25"></div><span id="holiday-status"></span> 

Добавленная combineStrings мной вспомогательная функция динамически объединяет несколько строк, если существует массив, и возвращает переданную строку, если была передана простая строка.

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

 combineStrings("Holiday 1")
// -> "Holiday 1"
combineStrings(["Holiday 1", "Holiday 2"])
// -> "Holiday 1 and Holiday 2"
combineStrings(["Holiday 1", "Holiday 2", "Holiday 3"])
// -> "Holiday 1, Holiday 2, and Holiday 3"