Привет, друзья помогите разобраться с интересной задачей на js

Написать функцию которая будет возвращать имя счастливчика, того кто чаще всего встречается в массиве

['Женя','Женя', 'Иван', 'Женя', 'Виктор', 'Виктор','Константин', 'Виктор', 'Виктор']

Функция должна возвращать

{lucky: "Виктор", loser:"Константин", other:["Женя","Иван"]}

Моя решение на котором засстрял во вложении введите сюда код

function nameFind(array) {
  let first = []
  let second = []
  let third = []
  let forth = []
  
  for (let i = 0; i < array.length; i++) {
    if (array[i] === 'Женя') {
      first.push(array[i])
    }
    if (array[i] === 'Иван') {
      second.push(array[i])
    }
    if (array[i] === 'Виктор') {
      third.push(array[i])
    }
    if (array[i] === 'Константин') {
      forth.push(array[i])
    }
  }

  console.log(Object.assign({}, [first, second, third, forth]))
}

let names = ['Женя', 'Женя', 'Иван', 'Женя', 'Виктор', 'Виктор', 'Константин', 'Виктор', 'Виктор']

console.log(nameFind(names))


Ответы (4 шт):

Автор решения: ΝNL993

Если всегда только один победитель и один проигравший то вот:

const arr = ['Женя', 'Иван', 'Женя', 'Виктор', 'Виктор', 'Константин', 'Виктор', 'Виктор']

function getWinner(input) {
  let output = []
  let loserIndex = 0
  let winnerIndex = 0

  for (let i of input) {
    if(!output.flat().includes(i)) {
      output.push([i])
    } else {
      for (let j in output) {
        if(output[j][0] === i) {
          output[j].push(i)
          break
        }
      }
    }
  }

  for (let i in output) {
    if(output[i] > output[i-1]) {
      loserIndex = i
    }
    else if(output[i] < output[i-1]) {
      winnerIndex = i
    }
  }

  return {
    lucky: output[winnerIndex][0],
    loser: output[loserIndex][0],
    other: output.filter(e => e[0] !== output[winnerIndex][0] && e[0] !== output[loserIndex][0]).map(e => e[0]),
  }
}

console.log(getWinner(arr))

Не считаю свой ответ оптимизированным т.к. по 100 раз идёт проход по массивам, но всё же лучше чем ничего.

Объяснение

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

→ Ссылка
Автор решения: EzioMercer

Скажу сразу, что с ожидаемым выходом я не согласен т.к.:

  • Не понятно почему лузером стал именно Константин, хотя Иван тоже встречается 1 раз

  • Не понятно что будет, если будут несколько лузеров и счастливчиков

В моём алгоритме логика выхода такова:

  • В массив счастливчиков входят все, у кого частота самая высокая т.е. если 2 имени встречаются по 5 раз и это наибольшая частота, то оба станут счастливчиками

  • В массив лузеров входят все, у кого частота самая низкая т.е. если 2 имени встречаются по 1-му разу и это наименьшая частота, то оба станут лузерами

  • Все остальне попадают в массив остальных

Логика самого алгоритма:

  • Сначала проходимся по массиву и собираем данные в словарик namesCountMap о том, сколько раз каждое имя встречается

  • Проходимся по предыдущему словарику и группируем имена по кол-ву совпадений в словарике countNamesMap

  • Преобразуем данные второго словарика в массив, сортируем и преобразуем элементы

  • В первом элементе этого массива у нас список счасливчиков, забираем его с помощью pop

  • В последнем элементе этого массива у нас список лузеров, забираем его с помощью shift

  • Все оставшиеся элементы - это остальные

  • Если в списке было 1 имя, то возвращаем не массив, а имя

  • Если список был пуст или было больше 1-ого имени, то возращаем массив

const names = ['Женя', 'Женя', 'Иван', 'Женя', 'Виктор', 'Виктор', 'Константин', 'Виктор', 'Виктор'];

const getStringOrArr = (arr) => arr.length === 1 ? arr[0] : arr;

const nameFind = (array) => {
  const namesCountMap = new Map();
  const countNamesMap = new Map();
  
  for (const el of array) {
    namesCountMap.set(el, (namesCountMap.get(el) || 0) + 1);
  }

  for (const [name, count] of namesCountMap) {
    if (countNamesMap.has(count)) {
      countNamesMap.get(count).push(name); 
    } else {
      countNamesMap.set(count, [name]); 
    }
  }

  const sortedArr = [...countNamesMap]
    .sort((a, b) => b[0] - a[0])
    .map(x => x[1]);
  
  const winners = sortedArr.shift() || [];
  const loosers = sortedArr.pop() || [];
  const others = sortedArr.flat();
  
  return {
    lucky: getStringOrArr(winners),
    loser: getStringOrArr(loosers),
    other: getStringOrArr(others),
  }
}

console.log(1, nameFind([]));
console.log(2, nameFind(['a']));
console.log(3, nameFind(['a', 'b']));
console.log(4, nameFind(['a', 'b', 'a']));
console.log(5, nameFind(['a', 'b', 'a', 'b', 'a', 'c', 'd']));
console.log(6, nameFind(['a', 'b', 'a', 'b', 'a', 'c', 'd', 'e', 'e', 'e']));
console.log(7, nameFind(['a', 'b', 'a', 'b', 'a', 'c', 'd', 'e', 'e', 'e', 'f', 'f']));

console.log('names', nameFind(names));

→ Ссылка
Автор решения: Rudi

Ещё один вариант , покорче..

let m = ['Женя','Женя', 'Иван', 'Женя', 'Виктор', 'Виктор','Константин', 'Виктор', 'Виктор'];

function sorting (m) {
  m = m.sort();
  let name = m[0];
  let res  = []
  for(let i = 0; i <= m.length; i++){
    if(name != m[i]) {
      name = m[i];
      res.push(m[i-1])
    }
  }
  return {lucky: res[0], loser:res[res.length-1], other:res.slice(1,-1)}
}
console.log(sorting(m))

Как правильно заметил @EzioMercer в комментариях под моим ответом

Добавьте всего 1 дополнительную строку 'A' и сразу счастилвчиком станет она.

Немного исправил предыдущий вариант..

let m = ['A', 'Женя', 'Женя', 'Иван', 'Женя', 'Виктор', 'Виктор', 'Константин', 'Виктор', 'Виктор'];
let j = 1;

function sorting(m) {
  m = m.sort();
  let name = m[0];
  let obj = [];

  for (let i = 0; i <= m.length; i++) {
    if (i != 0 && name == m[i]) j++;
    if (name != m[i]) {
      obj.push( {name: m[i - 1], count: j} );
      name = m[i];
      j = 1;
    }
  }
  
  let res = obj.sort(function(a, b) {
    return b.count - a.count;
  }).map(e => {
    return e.name;
  });
  
  return { lucky: res[0], loser: res[res.length - 1], other: res.slice(1, -1) }
}

console.log(sorting(m))

→ Ссылка
Автор решения: Daniil Shelest

Вот написал такое. Возможно можно как-то сократить.

function nameFind(array) {
      const countDuplicates = array.reduce((acc, cur) => {
        acc[cur] = (acc[cur] || 0) + 1;
        return acc // отдаст {Женя: 3, Иван: 1, Виктор: 4, Константин: 1}
      }, {})
      const sortedValues = Object.entries(countDuplicates).sort(([aKey, aValue], [bKey, bValue]) => bValue - aValue).map(i => i[0]) // сортировка по количеству и возврат только имен
      return {
        lucky: sortedValues[0],
        loser: sortedValues[sortedValues.length - 1],
        others: sortedValues.slice(1, sortedValues.length - 1)
      }
    }
    
    let names = ['Женя', 'Женя', 'Иван', 'Женя', 'Виктор', 'Виктор', 'Константин', 'Виктор', 'Виктор']
    
    console.log(nameFind(names))
→ Ссылка