Có một chức năng trong lodash để thay thế mục phù hợp


133

Tôi tự hỏi nếu có một phương pháp đơn giản hơn trong lodash để thay thế một mục trong bộ sưu tập JavaScript? (Có thể trùng lặp nhưng tôi không hiểu câu trả lời ở đó :)

Tôi đã xem tài liệu của họ nhưng không tìm thấy gì

Mã của tôi là:

var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
// Can following code be reduced to something like _.XX(arr, {id:1}, {id:1, name: "New Name"});
_.each(arr, function(a, idx){
  if(a.id === 1){
    arr[idx] = {id:1, name: "Person New Name"};
    return false;
  }
});

_.each(arr, function(a){
  document.write(a.name);
});

Cập nhật: Đối tượng tôi đang cố gắng thay thế có nhiều thuộc tính như

{id: 1, Prop1: ..., Prop2: ..., v.v.

Giải pháp:

Cảm ơn dfsq nhưng tôi đã tìm thấy một giải pháp thích hợp trong lodash có vẻ hoạt động tốt và khá gọn gàng và tôi cũng đưa nó vào một mixin vì tôi yêu cầu này ở nhiều nơi. Mã não

var update = function(arr, key, newval) {
  var match = _.find(arr, key);
  if(match)
    _.merge(match, newval);
  else
    arr.push(newval);    
};

_.mixin({ '$update': update });

var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];

_.$update(arr, {id:1}, {id:1, name: "New Val"});


document.write(JSON.stringify(arr));

Giải pháp nhanh hơn Như được chỉ ra bởi @dfsq, sau đây là cách nhanh hơn

var upsert = function (arr, key, newval) {
    var match = _.find(arr, key);
    if(match){
        var index = _.indexOf(arr, _.find(arr, key));
        arr.splice(index, 1, newval);
    } else {
        arr.push(newval);
    }
};

7
Tôi nghĩ rằng bạn có thể sử dụng kết hợp làm tham số thứ hai cho _.indexOf trên liine 4 của "Giải pháp nhanh hơn" của bạn, không cần phải tính toán lại giá trị đó ở đó, điều này sẽ khiến mọi thứ nhanh hơn một chút.
davertron

2
Thậm chí nhanh hơn: sử dụng _.findIndexcho phù hợp.
Julian K

1
Chỉ cần mở rộng những gì @JulianK và @davertron đã nói, sử dụng _.findIndexthay vì _.findsẽ cho phép bạn bỏ cả thứ hai _.findvà thứ hai _.indexOf. Bạn đang lặp lại qua mảng 3 lần khi tất cả những gì bạn cần là 1.
Justin Morgan

Câu trả lời:


191

Trong trường hợp của bạn, tất cả những gì bạn cần làm là tìm đối tượng trong mảng và sử dụng Array.prototype.splice()phương thức, đọc thêm chi tiết tại đây :

var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];

// Find item index using _.findIndex (thanks @AJ Richardson for comment)
var index = _.findIndex(arr, {id: 1});

// Replace item at index using native splice
arr.splice(index, 1, {id: 100, name: 'New object.'});

// "console.log" result
document.write(JSON.stringify( arr ));
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script>


1
Vâng, giải pháp của bạn sẽ tốn nhiều chi phí hơn về mặt hiệu suất, vì indexOfsẽ rất nhanh (nó sẽ sử dụng trình duyệt riêng Array.prototype.indexOf). Nhưng dù sao, vui mừng bạn tìm thấy một giải pháp phù hợp với bạn.
dfsq

14
Tại sao không sử dụng _.findIndex? Sau đó, bạn không cần phải sử dụng _.indexOf.
AJ Richardson

35

Có vẻ như giải pháp đơn giản nhất sẽ sử dụng ES6's .maphoặc lodash's _.map:

var arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];

// lodash
var newArr = _.map(arr, function(a) {
  return a.id === 1 ? {id: 1, name: "Person New Name"} : a;
});

// ES6
var newArr = arr.map(function(a) {
  return a.id === 1 ? {id: 1, name: "Person New Name"} : a;
});

Điều này có tác dụng tốt đẹp của việc tránh làm thay đổi mảng ban đầu.


8
Nhưng bạn đang tạo một mảng mới mỗi lần ... Đáng lưu ý.
kboom

3
Tuy nhiên, sự thay thế duy nhất để không tạo ra một mảng mới là làm thay đổi mảng hiện có. Ngoài ra, việc tạo một mảng mới có thể sẽ không có tác động về mặt hiệu suất. Upvote từ tôi.
Nobita

24

[ES6] Mã này hoạt động với tôi.

let result = array.map(item => item.id === updatedItem.id ? updatedItem : item)

1. bạn đang tạo một thể hiện mới của mảng, vì vậy nó không đúng khi "thay thế" một mục. 2. bạn sẽ mất updatedItemnếu mảng không bao gồm một mục với cùng id.
ác độc

đây là giải pháp cho 'cập nhật' chứ không phải 'upert' (câu hỏi là "có chức năng nào trong lodash để thay thế mục MATCHED") và vâng, nó tạo ra một bản sao của mảng, vì vậy đừng sử dụng nó nếu bạn cần làm việc với cùng một mảng (tôi đã không)
shebik

21
function findAndReplace(arr, find, replace) {
  let i;
  for(i=0; i < arr.length && arr[i].id != find.id; i++) {}
  i < arr.length ? arr[i] = replace : arr.push(replace);
}

Bây giờ hãy kiểm tra hiệu suất cho tất cả các phương pháp:


6
Bị hạ thấp vì tiêu cực với mọi người ("đây là một trò đùa nào đó") khiến họ không học được. Hãy tưởng tượng nếu tôi hoàn thành việc này với "trở thành một người thông minh, tôi mong bạn đừng lười biếng về cảm xúc của mình và nghĩ về điều đó".
MP Aditya

5
Tôi không muốn làm tổn thương bất cứ ai, nhưng tôi tự hỏi làm thế nào giải pháp tồi tệ nhất so với cách tiếp cận của người tạo chủ đề ban đầu lại nhận được nhiều phiếu bầu như vậy. Những quy tắc những người đã bỏ phiếu cho điều đó? Và tôi thất vọng rằng mọi người tin tưởng một cách mù quáng câu trả lời được bình chọn nhiều nhất và không có suy nghĩ phê phán.
ác độc

1
@evilive Điểm hợp lệ, nhưng tôi không thấy những người đó yêu cầu bạn đi qua như thể mọi người trước đây đã cung cấp câu trả lời / phiếu bầu là những kẻ ngốc. Các phần thực tế của câu trả lời này là tuyệt vời, phần còn lại có không khí của một phức hợp ưu việt hầu như không chứa. Điều đó không giúp được ai. Bạn có thể dễ dàng đưa ra quan điểm của mình mà không có phản ứng cảm xúc quá mức.
Thor84no

1
Đáng lưu ý rằng giải pháp của bạn và giải pháp của TC chỉ lọc theo id. Đó là lý do đầu tiên khiến hai người đó chạy nhanh hơn. Hai cái còn lại đang cho phép bạn vượt qua bất kỳ phần nào của đối tượng bạn cần để lọc, có thể thích hợp hơn là một hàm upert.
Aram

10

Bạn cũng có thể sử dụng find Index và chọn để đạt được kết quả tương tự:

  var arr  = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
  var data = {id: 2, name: 'Person 2 (updated)'};
  var index = _.findIndex(arr, _.pick(data, 'id'));
  if( index !== -1) {
    arr.splice(index, 1, data);
  } else {
    arr.push(data);
  }

6

Khi thời gian trôi qua, bạn nên nắm lấy một cách tiếp cận chức năng hơn, trong đó bạn nên tránh đột biến dữ liệu và viết các hàm trách nhiệm nhỏ, đơn lẻ. Với tiêu chuẩn ECMAScript 6, bạn có thể thưởng thức mô hình lập trình chức năng trong JavaScript với cung cấp map, filterreducephương pháp. Bạn không cần một lodash, gạch dưới hoặc những gì khác để làm những điều cơ bản nhất.

Dưới đây tôi đã bao gồm một số giải pháp được đề xuất cho vấn đề này để chỉ ra cách giải quyết vấn đề này bằng các tính năng ngôn ngữ khác nhau:

Sử dụng bản đồ ES6:

const replace = predicate => replacement => element =>
  predicate(element) ? replacement : element
 
const arr = [ { id: 1, name: "Person 1" }, { id:2, name:"Person 2" } ];
const predicate = element => element.id === 1
const replacement = { id: 100, name: 'New object.' }

const result = arr.map(replace (predicate) (replacement))
console.log(result)


Phiên bản đệ quy - tương đương với ánh xạ:

Yêu cầu phá hủytrải rộng mảng .

const replace = predicate => replacement =>
{
  const traverse = ([head, ...tail]) =>
    head
    ? [predicate(head) ? replacement : head, ...tail]
    : []
  return traverse
}
 
const arr = [ { id: 1, name: "Person 1" }, { id:2, name:"Person 2" } ];
const predicate = element => element.id === 1
const replacement = { id: 100, name: 'New object.' }

const result = replace (predicate) (replacement) (arr)
console.log(result)


Khi trật tự các mảng thức là không quan trọng, bạn có thể sử dụng một objectnhư một HashMap cấu trúc dữ liệu. Rất tiện dụng nếu bạn đã có bộ sưu tập khóa dưới dạng object- nếu không bạn phải thay đổi đại diện của mình trước.

Yêu cầu phần còn lại trải rộng , tên thuộc tính được tính toánObject.entries .

const replace = key => ({id, ...values}) => hashMap =>
({
  ...hashMap,       //original HashMap
  [key]: undefined, //delete the replaced value
  [id]: values      //assign replacement
})

// HashMap <-> array conversion
const toHashMapById = array =>
  array.reduce(
    (acc, { id, ...values }) => 
    ({ ...acc, [id]: values })
  , {})
  
const toArrayById = hashMap =>
  Object.entries(hashMap)
  .filter( // filter out undefined values
    ([_, value]) => value 
  ) 
  .map(
    ([id, values]) => ({ id, ...values })
  )

const arr = [ { id: 1, name: "Person 1" }, { id:2, name:"Person 2" } ];
const replaceKey = 1
const replacement = { id: 100, name: 'New object.' }

// Create a HashMap from the array, treating id properties as keys
const hashMap = toHashMapById(arr)
console.log(hashMap)

// Result of replacement - notice an undefined value for replaced key
const resultHashMap = replace (replaceKey) (replacement) (hashMap)
console.log(resultHashMap)

// Final result of conversion from the HashMap to an array
const result = toArrayById (resultHashMap)
console.log(result)


5

Nếu bạn chỉ đang cố gắng thay thế một tài sản, hãy lưu lại _.find_.setnên là đủ:

var arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];

_.set(_.find(arr, {id: 1}), 'name', 'New Person');

1

Nếu điểm chèn của đối tượng mới không cần khớp với chỉ mục của đối tượng trước đó thì cách đơn giản nhất để thực hiện điều này với lodash là sử dụng _.rejectvà sau đó đẩy các giá trị mới vào mảng:

var arr = [
  { id: 1, name: "Person 1" }, 
  { id: 2, name: "Person 2" }
];

arr = _.reject(arr, { id: 1 });
arr.push({ id: 1, name: "New Val" });

// result will be: [{ id: 2, name: "Person 2" }, { id: 1, name: "New Val" }]

Nếu bạn có nhiều giá trị mà bạn muốn thay thế trong một lần, bạn có thể thực hiện các thao tác sau (được viết bằng định dạng không phải ES6):

var arr = [
  { id: 1, name: "Person 1" }, 
  { id: 2, name: "Person 2" }, 
  { id: 3, name: "Person 3" }
];

idsToReplace = [2, 3];
arr = _.reject(arr, function(o) { return idsToReplace.indexOf(o.id) > -1; });
arr.push({ id: 3, name: "New Person 3" });
arr.push({ id: 2, name: "New Person 2" });


// result will be: [{ id: 1, name: "Person 1" }, { id: 3, name: "New Person 3" }, { id: 2, name: "New Person 2" }]

phương pháp này thay đổi cách sắp xếp mảng
sospedra

1

Sử dụng hàm lodash unionWith, bạn có thể thực hiện một lệnh nâng cấp đơn giản cho một đối tượng. Các tài liệu nói rằng nếu có một trận đấu, nó sẽ sử dụng mảng đầu tiên. Gói đối tượng được cập nhật của bạn trong [] (mảng) và đặt nó làm mảng đầu tiên của hàm union. Đơn giản chỉ cần xác định logic phù hợp của bạn và nếu tìm thấy nó sẽ thay thế nó và nếu không, nó sẽ thêm nó

Thí dụ:

let contacts = [
     {type: 'email', desc: 'work', primary: true, value: 'email prim'}, 
     {type: 'phone', desc: 'cell', primary: true, value:'phone prim'},
     {type: 'phone', desc: 'cell', primary: false,value:'phone secondary'},
     {type: 'email', desc: 'cell', primary: false,value:'email secondary'}
]

// Update contacts because found a match
_.unionWith([{type: 'email', desc: 'work', primary: true, value: 'email updated'}], contacts, (l, r) => l.type == r.type && l.primary == r.primary)

// Add to contacts - no match found
_.unionWith([{type: 'fax', desc: 'work', primary: true, value: 'fax added'}], contacts, (l, r) => l.type == r.type && l.primary == r.primary)

1

Biến thể không tệ quá)

var arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];

var id = 1; //id to find

arr[_.find(arr, {id: id})].name = 'New Person';

1
var arr= [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
var index = _.findIndex(arr, {id: 1});
arr[index] = {id: 100, name: 'xyz'}

0

Nếu bạn đang tìm cách thay đổi bộ sưu tập một cách bất biến (như tôi đã tìm thấy câu hỏi của bạn), bạn có thể xem xét người trợ giúp bất biến , một thư viện rẽ nhánh từ sử dụng React ban đầu. Trong trường hợp của bạn, bạn sẽ thực hiện những gì bạn đã đề cập qua:

var update = require('immutability-helper')
var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}]
var newArray = update(arr, { 0: { name: { $set: 'New Name' } } })
//=> [{id: 1, name: "New Name"}, {id:2, name:"Person 2"}]

0

Bạn có thể làm điều đó mà không cần sử dụng lodash.

let arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];
let newObj = {id: 1, name: "new Person"}

/*Add new prototype function on Array class*/
Array.prototype._replaceObj = function(newObj, key) {
  return this.map(obj => (obj[key] === newObj[key] ? newObj : obj));
};

/*return [{id: 1, name: "new Person"}, {id: 2, name: "Person 2"}]*/
arr._replaceObj(newObj, "id") 

0

Bất biến , thích hợp cho ReactJS:

Giả định:

cosnt arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];

Mục cập nhật là mục thứ hai và tên được đổi thành Special Person:

const updatedItem = {id:2, name:"Special Person"};

Gợi ý : lodash có các công cụ hữu ích nhưng bây giờ chúng tôi có một số trong số chúng trên Ecmascript6 +, vì vậy tôi chỉ sử dụngmapchức năng tồn tại trên cả hailodashecmascript6+:

const newArr = arr.map(item => item.id === 2 ? updatedItem : item);

0

Đã đi qua điều này là tốt và làm nó chỉ đơn giản như vậy.

const persons = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
const updatedPerson = {id: 1, name: "new Person Name"}
const updatedPersons = persons.map(person => (
  person.id === updated.id
    ? updatedPerson
    : person
))

Nếu muốn chúng ta có thể khái quát nó

const replaceWhere = (list, predicate, replacement) => {
  return list.map(item => predicate(item) ? replacement : item)
}

replaceWhere(persons, person => person.id === updatedPerson.id, updatedPerson)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.