, чтобы сохранить свой прогресс
Николай Ланец
20 сент. 2021 г., 20:44

Обсуждение задания "Profile Lookup"

Очень интересный урок, который хотелось бы обсудить... К своему удивлению, я не нашел здесь во всех уроках по Javascript уроков, касающихся методов типа Array.find(), Array.filter(), Array.findIndex() и т.п. При этом это очень полезные методы, используемые чуть ли не каждый день. И на мой взгляд они, во-первых, более короткие, чем конструкции типа for(...), а во-вторых, более логичны в плане восприятия.

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

Сравним код большинства ответов в этой задаче:

function lookUpProfile(name, prop){ // Only change code below this line for (let i = 0; i < contacts.length; i++){ if (contacts[i].firstName === name){ if (contacts[i][prop]){ return contacts[i][prop]; } return "No such property"; } } return "No such contact"; // Only change code above this line }
и одно из моих решений:

function lookUpProfile(name, prop) { // Only change code below this line const item = contacts.find(n => n.firstName === name); if (item) { if (item[prop] === undefined) { return 'No such property'; } return item[prop]; } else { return 'No such contact'; } // Only change code above this line }
Какое решение вам кажется более наглядным?
Как мне кажется, вариант с перечислением for (let i = 0; i < contacts.length; i++) более громоздкий и в нем больше отвлекающих переменных. То есть мы создаем переменную-счетчик i, сравниваем ее со значением длины массива, каждую итерацию увеличиваем ее значение, используем для получения элемента массива и т.п., и все это вместо того, чтобы работать просто с массивом и его элементами.
В моем варианте используется метод массива find(condition). Уточню, что прототипом всех массивов является Array. Соответственно, все методы его наследуются и конечным массивам. Метод find(condition) принимает в качестве параметра функцию, используемую для поиска элемента в текущем массиве (первого, удовлетворяющего условию). То есть массив перечисляет все свои элементы до тех пор, пока применяемая к ним функция не вернет истину (или то, что может быть преобразовано в логическое true). В моем случае это n => n.firstName === name.
Для кого-то данная конструкция может быть не понятна, уточню: это стрелочная функция. Если переводить на более понятный синтаксис, то это так:
function condition (n){ return n.firstName === name; }
Подробней о стрелочных функциях в этом уроке.


Итак, применив метод Array.find() вместо перечисления for(...) я попытался найти нужный мне элемент.
const item = contacts.find(n => n.firstName === name);

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

Далее я уже работаю с проверками на самом элементе
if (item) { if (item[prop] === undefined) { return 'No such property'; } return item[prop]; } else { return 'No such contact'; }
То есть вроде тоже все понятно: если элемент item есть, то в нем проверяю значение, если нет, то возвращаю сообщение об ошибке. И никаких лишних i и т.п.


В погоне за сокращением можно и вовсе вот так написать:
function lookUpProfile(name, prop) { const item = contacts.find(n => n.firstName === name); return !item ? 'No such contact' : item[prop] || 'No such property' }
То есть я сразу проверил есть ли объект и если нет, возвращяю 'No such contact', иначе возвращаю свойство объекта (если оно есть) или сообщение 'No such property'

Да, такой стиль может показаться более сложным и запутанным, но сейчас чаще именно так и пишут, потому что меньше кода - меньше над чем думать приходится.

Если правильно понимаю - и по времени быстрее должно работать?
Совершенно не факт. Нужно тестирование проводить. Хотя вряд ли разница будет ощутимая. Но есть разница в восприятии и объеме кодовой базы.
Николай, сейчас более детально буду изучать предложенный тобой метод, но хотел бы уточнить пока по своему варианту решения - почему я получаю "Unspecified AssertionError" в первой и третьей проверке, не могу увидеть свою ошибку

for (let i=0; i<contacts.length; i++) { if (contacts[i].firstName == name && contacts[i].hasOwnProperty(prop)) { return contacts[i][prop]; } else if (name !== contacts[i].firstName) { return 'No such contact'; } else if (contacts.hasOwnProperty(prop) == false) { return 'No such property'; }

Денис, у тебя здесь главная ошибка в том, что ты при первом же несовпадении контакта возвращаешь 'No such contact'. То есть по сути у тебя логика будет работать только для первого в массиве контакта и только если свойство есть. Но у тебя же и другие контакты проверяются. А как до них дойдет логика? В данном случае никак. То есть тебе надо перечислить все контакты и только если перебрал все и не нашел ни одного по имени, тогда только возвращать ошибку, то есть вынести это условие за пределы цикла. Если в цикле отработает целевое условие и ты выполнишь return - тогда и не дойдет дело до возврата ошибки. А если в цикле ничто не выполнится, тогда уже вернешь ошибку.

Второй момент: ты сразу первым делом проверяешь двойное условие
if (contacts[i].firstName == name && contacts[i].hasOwnProperty(prop)) {
Не надо так. Первое главное условие у тебя: это найден контакт или нет. Если найден, тогда по нему уже все и проверяй далее. Если нет - то пропусти его. То есть оберни все в условие

if(name === contacts[i].firstName) { // Здесь уже все остальные условия пропиши. }
Третий момент: старайся не использовать нестрогое сравнение ==, старайся всегда строгое ===. Иначе не редко будешь ловить логические ошибки.



Николай, огромное спасибо за супер подробный ответ
Согласен на счет метода. Здесь описание по лучше: https://doka.guide/js/array-find/
Почему такой код не работает? Логика - сначало проверить на отсутствие свойства с именем атрибута name. Если иначе - выполняется else if (изначально я вообще хотел там написать просто contacts[i].hasOwnProperty(prop) без && и проверки на наличие свайства с именем атрибута name). В этом коде почему-то работает только return "No such contact" и return "No such property".
function lookUpProfile(name, prop) { for (let i = 0; i < contacts.length; i++) { if (contacts[i].firstName != name) { return "No such contact";         } else if (contacts[i].firstName == name && contacts[i].hasOwnProperty(prop)) { return contacts[i][prop];         } else return "No such property";         }     }

И еще хотел спросить: Так что в итоге находиться тут в переменной item: const item = contacts.find(n => n.firstName === name)? И что подставляется в переменную n? В n - gоочередно каждый объект массива contacts (которых по сути 4)? А item - становится найденным объектом (то есть при name "Akira" как я понял - это будет первый объект нашей базы данных, которая в массиве)? И уже там, внутри первого объекта, который занял переменную item мы начинаем искать prop далее через if'ы? И если это так, то как сделать аналогичную вещь, которая бы искала значение во внутренних массивах? Например если бы name было массивом, то как сделать такой find? const item = contacts.find(n => n.likes === name) - Так что ли?

Вот что я имею ввиду. Но почему-то это не работает.
let x = ["Pizza", "Coding", "Brownie Points"]; function lookUpProfile(name) { var contact = contacts.find(item => item.likes == name); if (contact) { return contact;     } return "Dont work"; } console.log(lookUpProfile(x));

А это работает:
let x = "Akira"; function lookUpProfile(name) { var contact = contacts.find(item => item.firstName == name); if (contact) { return contact;     } return "Dont work"; } console.log(lookUpProfile(x));
> В этом коде почему-то работает только return "No such contact" и return "No such property".
Посмотрите внимательно чужие решения. Они сводятся к тому, чтобы сначала найти контакт по имени в цикле, и только если найден контакт, тогда уже смотреть в нем свойства. А за пределами цикла, если не найден контакт, тогда уже только возвращать конечное решение "Контакт не найден". Вы же делаете иначе - вы сразу смотрите тот это контакт или нет, и если не тот, то возвращаете ошибку. В итоге ваша логика может сработать только если первый контакт в массиве соответствует запрошенному и свойство запрошено корректно. В противном случае вы возвращаете ошибку и далее перебор в цикле уже не выполняется.

По второй части вопроса: советую уже сейчас вам начинать изучать TypeScript, вы тогда начнете быть более внимательным к типам. В целом вы идею правильно поняли с поиском по внутренним массивам, но нельзя сравнивать строку с массивом. Для примера ваше: item.likes == name.

item.likes здесь - массив, например ["Pizza", "Coding", "Brownie Points"], и вы его пытаетесь сравнить со строчным name. Нельзя так. Почти всегда будет ложь, ито, только потому что вы используете нестрогое сравнение ==. Если бы использовали строгое ===, то 100% было бы всегда ложь, потому что типы не совпадают.
В вашем случае, если бы вы хотели найти по элементу во вложенном массиве, то надо еще внутри искать по массиву, а не просто через сравнение. Пример.

var contact = contacts.find(item => item.likes.includes(name));

Только здесь надо понимать, что name здесь - не имя контакта, а именно элемент массива likes, то есть "Pizza", "Coding" или "Brownie Points".

Описание метода Array.includes смотрите здесь https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes

Спасибо за ответ. Довольно сложновато это пока воспринимается. Очень мало-по малу мозг проходит перепрошивку.)))
Не за что!
Просто не останавливайтесь и учитесь дальше. Практика решает.

Добавить комментарий